基于 json 个字段的动态 SQL select 多列,不会暴露 SQL 注入风险

Dynamic SQL select multiple columns based on json field(s) without exposing SQL Injection risks

我正在创建一个 'select colA, colB, colC, ...' 存储过程,我希望根据 JSON 输入选择 return 的列。

最基本的实现方式是

DECLARE @jsonField nvarchar(max) = 'col1, col2, col3' --will get this from input json
DECLARE @sql nvarchar(max) = N'Select '+@jsonField +'from tblFoo'
exec sys.sp_executesql @sql

但是执行上述操作会为 SQL 注入创建一个太大的安全漏洞。我能想到的安全方法是:

DECLARE @jsonUseField1 bit = 0
DECLARE @jsonUseField2 bit = 1--again would be getting this from json in the actual proc
--would also have to declare jsonField3, 4, 5, ...
DECLARE @sql nvarchar(max) = N'Select idFieldName'
if(@jsonUseField1 = 1)
begin
    set @sql = @sql + ', field1Name'
end
if(@jsonUseField2 = 1)
begin
    set @sql = @sql + ', field2Name'
end
set @sql = @sql +'from tblFoo'
exec sys.sp_executesql @sql

上述方法的缺点是,我必须向每个要查询的字段的 proc 添加 5 行额外的行。这会使存储过程变长且难以维护。每次我想要一个新字段时,我还必须向构成 json 的 class 添加一个新字段。

有没有办法实现这种行为而不必显式编码每个可查询字段,同时仍然防止注入?例如,有没有办法改变第一个例子,用某种定界符分割 json 字段,然后在对每个段应用 QUOTENAME() 后重新加入?

我正在使用 SQL 服务器 2019

我想我会分享我最终 运行 合并 Gareth 的回答和 Charlie 的评论的解决方案,并添加一个明确的 table 参考(因为我在动态 table 中做了连接和其他事情 SQL) 如果它对任何人都有帮助。

DECLARE @ColumnNames NVARCHAR(MAX) = 
    (
        SELECT  STRING_AGG('tblFooTable.'+QUOTENAME(c.name), ',')
        FROM    sys.columns AS c
        WHERE   object_id =  OBJECT_ID(N'dbo.tblFooTable', 'U')
        AND  c.name IN (SELECT TRIM(value) FROM OPENJSON(@jsonData, '$.footblColumnNames'))
    );
DECLARE @sql2 nvarchar(max) = N'Select '+@ColumnNames

您可以通过检查它们是否是有效的列名来验证传递的列,例如

DECLARE @ColumnNames NVARCHAR(MAX) = 
    (
        SELECT  STRING_AGG(QUOTENAME(c.name), ',')
        FROM    sys.columns AS c
        WHERE   object_id =  OBJECT_ID(N'dbo.Foo', 'U')
        AND     EXISTS
                (   SELECT  1
                    FROM    STRING_SPLIT(@jsonField, ',') AS ss
                    WHERE   TRIM(ss.value) = c.name
                )
            );

这会使用 STRING_SPLIT(), then uses this to filter the list of actual column names in the table (from sys.columns), then rebuilds a single string using STRING_AGG()

将您的字符串拆分为单独的列名称

任何与有效列名不相关的解析都不会被使用,并且所有确实存在的列名都将使用 QUOTENAME().

正确转义

然后您可以在动态语句中使用这个新变量,并确保它只包含有效的列名。

DECLARE @sql nvarchar(max) = N'SELECT '+@ColumnNames +' FROM tblFoo';

Working Demo on SQL Fiddle