保护动态 SQL 免受 SQL 注入的影响

Securing Dynamic SQL from SQL Injection

提问人:Nathan L 提问时间:12/10/2019 最后编辑:Dale KNathan L 更新时间:12/10/2019 访问量:91

问:

我有一个动态脚本在数据库中的所有对象上运行,并将每个对象的架构名称从 [dbo] 更改为数据库名称。

脚本运行良好,我想知道我是否可以做得更好,以保护它免受SQL注入的影响?

BEGIN TRANSACTION

/* Change schema to all objects in database (from dbo)*/

DECLARE @SchemaName SYSNAME = db_name();
DECLARE @SQL NVARCHAR(MAX) = N'IF Not Exists (select 1 from sys.schemas where schema_id = SCHEMA_ID(@NewSchemaName))
        EXEC(''CREATE SCHEMA ''+@NewSchemaName+'''')' + NCHAR(13) + NCHAR(10);

SELECT @SQL = @SQL + N'EXEC(''ALTER SCHEMA ''+@NewSchemaName+'' TRANSFER [' + SysSchemas.Name + '].[' + DbObjects.Name + ']'');' + NCHAR(13) + NCHAR(10)
FROM sys.Objects DbObjects
INNER JOIN sys.Schemas SysSchemas
    ON DbObjects.schema_id = SysSchemas.schema_id
WHERE SysSchemas.Name = 'dbo'
    AND (DbObjects.Type IN ('U', 'P', 'V'))

EXECUTE sp_executesql @sql, N'@NewSchemaName sysname', @NewSchemaName = @SchemaName

ROLLBACK

为了保护这一点,我使用了 Thom Andrews 的这篇精彩文章:动态 SQL 的注意事项

这就是我开始的地方:github.com/NathanLifshes

sql-server t-sql sql 注入

评论

1赞 Thom A 12/10/2019
我在你上面链接的文章中涵盖了很多问题。关键的事情是你要正确地引用,使用 .而不是你想要的.对于您的语句,您还将注入(使用 ),因为不能接受变量作为输入,它需要一个文字。QUOTENAME'...[' + SysSchemas.Name + ']...''...' + QUOTENAME(SysSchemas.Name) + '...'ALTER SCHEMA@NewSchemaNameQUOTENAMEALTER
0赞 Nathan L 12/10/2019
谢谢你的评论。为什么需要对从数据库中选择的表列使用 QUOTENAME?
0赞 Nathan L 12/10/2019
在 Alter 中,除了 sysname 之外,我无法使用任何其他对象作为输入(我尝试使用 QUOTENAME 但没有运气)
2赞 Thom A 12/10/2019
真的有 2 个原因。有人可以(尽管不太可能)创建一个要注入的对象,但它也会正确地引用不需要分隔标识的对象名称。人们确实(愚蠢地)拥有带有字符的对象名称,如果你这样做了,它们将需要被正确转义。你的和那个包裹会覆盖任何东西,比如空白,但最好确保没有任何东西可以溜走。]'['']'
2赞 Thom A 12/10/2019
“正在使用 EXEC 命令”为什么要在动态 SQL 中创建动态 SQL?

答:

0赞 Guy Glantser 12/10/2019 #1

在这种情况下,我没有看到 SQL 注入的实际风险,因为用户没有提供任何值。该脚本仅将数据库名称作为输入。利用 SQL 注入的唯一选择是将命令注入数据库名称。当然,这是可能的。为了防止出现此选项,应使用 QUOTENAME 函数在动态脚本中正确引用架构名称。

评论

0赞 Nathan L 12/10/2019
假设我们想使用此代码并从用户那里获取新的架构名称:-)
1赞 Eitan Blumin 12/10/2019 #2

下面的脚本应该更安全。 请注意脚本开头的 QUOTENAME 函数的使用。 这将起作用,因为如果在 EXEC 命令中使用 QUOTENAME 函数“内联”,则可能会出现语法错误。因此,您需要在早期阶段应用它。 幸运的是,当您初始化 @SchemaName 变量时,您有一个“更早”的阶段:

BEGIN TRANSACTION

/* Change schema to all objects in database (from dbo)*/

DECLARE @SchemaName SYSNAME = QUOTENAME(db_name());
DECLARE @SQL NVARCHAR(MAX) = N'IF Not Exists (select 1 from sys.schemas where schema_id = SCHEMA_ID(@NewSchemaName))
        EXEC(''CREATE SCHEMA ''+@NewSchemaName+'''')' + NCHAR(13) + NCHAR(10);

SELECT @SQL = @SQL + N'EXEC(''ALTER SCHEMA ''+@NewSchemaName+'' TRANSFER ' + QUOTENAME(SysSchemas.Name) + '.' + QUOTENAME(DbObjects.Name) + ''');' + NCHAR(13) + NCHAR(10)
FROM sys.Objects DbObjects
INNER JOIN sys.Schemas SysSchemas
    ON DbObjects.schema_id = SysSchemas.schema_id
WHERE SysSchemas.Name = 'dbo'
    AND (DbObjects.Type IN ('U', 'P', 'V'))

PRINT @SQL

EXECUTE sp_executesql @sql, N'@NewSchemaName sysname', @NewSchemaName = @SchemaName

ROLLBACK