基于 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';
我正在创建一个 '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';