使用带参数的 sp_executesql 如何防止 SQL 注入?

T. *_*ter 8 sql-server sql-injection dynamic-sql

以下是使用sp_executesql的动态过滤解决方案

IF OBJECT_ID(N'dbo.GetOrders', N'P') IS NOT NULL DROP PROC dbo.GetOrders;
GO
CREATE PROC dbo.GetOrders
 @orderid AS INT = NULL,
 @custid AS INT = NULL,
 @empid AS INT = NULL,
 @orderdate AS DATE = NULL
AS
DECLARE @sql AS NVARCHAR(1000);
SET @sql = 
 N'SELECT orderid, custid, empid, orderdate, filler'
 + N' /* 27702431-107C-478C-8157-6DFCECC148DD */'
 + N' FROM dbo.Orders'
 + N' WHERE 1 = 1'
 + CASE WHEN @orderid IS NOT NULL THEN
 N' AND orderid = @oid' ELSE N'' END
 + CASE WHEN @custid IS NOT NULL THEN
 N' AND custid = @cid' ELSE N'' END
 + CASE WHEN @empid IS NOT NULL THEN
 N' AND empid = @eid' ELSE N'' END
 + CASE WHEN @orderdate IS NOT NULL THEN
 N' AND orderdate = @dt' ELSE N'' END;
EXEC sp_executesql
 @stmt = @sql,
 @params = N'@oid AS INT, @cid AS INT, @eid AS INT, @dt AS DATE',
 @oid = @orderid,
 @cid = @custid,
 @eid = @empid,
 @dt = @orderdate;
GO
Run Code Online (Sandbox Code Playgroud)

在T-SQL 查询的第 541 页上,它说

由于动态代码使用参数而不是将常量注入到代码中,因此不会受到 SQL 注入攻击。

sp_executesql 中参数的使用如何防止 SQL 注入?

谢谢

Eri*_*ing 13

双倍

\n

要回答您的问题,您需要尝试使用sp_executesql参数的替代方案:

\n
    \n
  • 使用EXEC(不使用sp_executesql
  • \n
  • 使用sp_executesql(不带参数)
  • \n
\n

在适当的情况下,这两种情况都可能导致SQL 注入攻击。

\n

可能值得注意的是,即使完全未参数化,上面的代码风险也相对较低,因为传递的数据类型不是字符串类型,但它仍然是可能的

\n

字符串遭受恶意负载的风险要高得多。

\n

下面的代码示例来自我关于在不同上下文中使用动态 SQL 的演示,但非常适合您的问题。

\n

字符串安全

\n

您可以安全地使用这样的代码,因为用户输入不是执行的字符串的一部分:

\n
DECLARE \n    @SQLString nvarchar(MAX) = N\'\',\n    @TableName sysname = N\'Votes\';\n\nIF @TableName = N\'Votes\'\nBEGIN\n    SET @SQLString += N\'SELECT COUNT_BIG(*) AS records FROM dbo.Votes AS v;\'\nEND\n\nIF @TableName = N\'Posts\'\nBEGIN\n    SET @SQLString += N\'SELECT COUNT_BIG(*) AS records FROM dbo.Posts AS p;\'\nEND\n\nEXEC(@SQLString);\nGO \n
Run Code Online (Sandbox Code Playgroud)\n

不安全字符串

\n

在此示例中,用户输入被连接到要执行的字符串中,并且未参数化。这可能会导致问题:

\n
DECLARE \n    @SQLString nvarchar(MAX) = N\'\',\n    @Filter nvarchar(MAX) = N\'\',\n    @Title nvarchar(250) = N\'\'\' \n  UNION ALL   \n  SELECT \n      t.object_id, t.schema_id, t.name, SCHEMA_NAME(t.schema_id), t.create_date, t.modify_date, NULL \n  FROM sys.tables AS t --\'; \n/* This ends the current statement, and adds in some sneaky code */\n\nSET @SQLString += N\' \n  SELECT TOP (5000) \n      p.OwnerUserId, p.Score, p.Tags, p.Title, p.CreationDate, p.LastActivityDate, p.Body \n  FROM dbo.Posts AS p \n  WHERE p.OwnerUserId = 22656 \';\n\n/* This appends the sneaky code onto our harmless query */\nIF @Title IS NOT NULL\nBEGIN\n    SET @Filter = @Filter + N\' \n  AND p.Title LIKE \'\'\' + N\'%\' + @Title + N\'%\'\'\';\nEND;\n\nIF @Filter IS NOT NULL\nBEGIN\n    SET @SQLString += @Filter;\nEND;\n\nSET @SQLString += N\' \n  ORDER BY p.Score DESC;\';\n\n/* Check the messages tab... */\nRAISERROR(\'%s\', 0, 1, @SQLString) WITH NOWAIT;\n\n/* Check the results -- what\'s that at the end? */\nEXEC (@SQLString);\n
Run Code Online (Sandbox Code Playgroud)\n

最终结果是像这样执行的查询,它在Title列中搜索单个通配符,然后是列出数据库中所有表的附加结果。

\n
  SELECT TOP (5000) \n      p.OwnerUserId, p.Score, p.Tags, p.Title, p.CreationDate, p.LastActivityDate, p.Body \n  FROM dbo.Posts AS p \n  WHERE p.OwnerUserId = 22656  \n  AND p.Title LIKE \'%\' \n  UNION ALL   \n  SELECT \n      t.object_id, t.schema_id, t.name, SCHEMA_NAME(t.schema_id), t.create_date, t.modify_date, NULL \n  FROM sys.tables AS t --%\' \n  ORDER BY p.Score DESC;\n
Run Code Online (Sandbox Code Playgroud)\n

虽然许多人会关注诸如删除表之类的迷因,但动态 SQL 的真正问题通常是数据被盗。这就是钱的所在。

\n

仍然不安全

\n

使用sp_executesql是一个很好的第一步,但仍然需要与参数一起使用。下面的代码仍然会像上面一样受到 SQL 注入。

\n
DECLARE \n    @SQLString nvarchar(MAX) = N\'\',\n    @Filter nvarchar(MAX) = N\'\',\n    @Title nvarchar(250) = N\'\'\' \n  UNION ALL   \n  SELECT \n      t.object_id, t.schema_id, t.name, SCHEMA_NAME(t.schema_id), t.create_date, t.modify_date, NULL \n  FROM sys.tables AS t --\';\n/* This ends the current statement, and adds in some sneaky code */\n\nSET @SQLString += N\' \n  SELECT TOP (5000) \n      p.OwnerUserId, p.Score, p.Tags, p.Title, p.CreationDate, p.LastActivityDate, p.Body \n  FROM dbo.Posts AS p \n  WHERE p.OwnerUserId = 22656 \';\n\n/* This appends the sneaky code onto our harmless query */\nIF @Title IS NOT NULL\nBEGIN\n    SET @Filter = @Filter + N\' \n  AND p.Title LIKE \'\'\' + N\'%\' + @Title + N\'%\'\'\';\nEND;\n\nIF @Filter IS NOT NULL\nBEGIN\n    SET @SQLString += @Filter;\nEND;\n\nSET @SQLString += N\' \n  ORDER BY p.Score DESC;\';\n\n/* Check the messages tab... */\nRAISERROR(\'%s\', 0, 1, @SQLString) WITH NOWAIT;\n/* Check the results -- what\'s that at the end? */\nEXEC sys.sp_executesql \n    @SQLString;\n
Run Code Online (Sandbox Code Playgroud)\n

将执行与上面相同的查询。

\n

回到安全地带

\n

使用与您的示例更一致的代码,我们可以通过将值分配给参数而不是直接将其连接到字符串中来避免 SQL 注入。

\n
DECLARE \n    @SQLString nvarchar(MAX) = N\'\',\n    @Filter nvarchar(MAX) = N\'\',\n    @Title nvarchar(250) = N\'\'\' \n  UNION ALL \n  SELECT \n      t.object_id, t.schema_id, t.name, SCHEMA_NAME(t.schema_id), t.create_date, t.modify_date, NULL \n  FROM sys.tables AS t --\'; \n/* This ends the current statement, and adds in some sneaky code */\n\nSET @SQLString += N\' \n  SELECT TOP (5000) \n      p.OwnerUserId, p.Score, p.Tags, p.Title, p.CreationDate, p.LastActivityDate, p.Body \n  FROM dbo.Posts AS p \n  WHERE p.OwnerUserId = 22656 \';\n\n/* This appends the sneaky code onto our harmless query */\nIF @Title IS NOT NULL\nBEGIN\n    SET @Filter = @Filter + N\' \n  AND p.Title LIKE N\'\'%\'\' + @Title + N\'\'%\'\' \';\nEND;\n\nIF @Filter IS NOT NULL\nBEGIN\n    SET @SQLString += @Filter;\nEND;\n\nSET @SQLString += N\' \n  ORDER BY p.Score DESC;\';\n\n/* Check the messages tab... */\nRAISERROR(\'%s\', 0, 1, @SQLString) WITH NOWAIT;\n\n/* Check the results -- what\'s that at the end now? */\nEXEC sys.sp_executesql \n    @SQLString, \n  N\'@Title NVARCHAR(250)\', \n    @Title;\n
Run Code Online (Sandbox Code Playgroud)\n

这次我们构建字符串的结果有所不同。现在看起来像这样:

\n
  SELECT TOP (5000) \n      p.OwnerUserId, p.Score, p.Tags, p.Title, p.CreationDate, p.LastActivityDate, p.Body \n  FROM dbo.Posts AS p \n  WHERE p.OwnerUserId = 22656  \n  AND p.Title LIKE N\'%\' + @Title + N\'%\'  \n  ORDER BY p.Score DESC;\n
Run Code Online (Sandbox Code Playgroud)\n

我们没有返回包含详细信息的结果,而是sys.tables返回零行,因为参数设置为搜索字符串,并且没有帖子标题与之匹配。

\n

下一个下一个下一个

\n

希望这能让您更好地了解参数化动态 SQL 如何帮助您避免 SQL 注入攻击。

\n

使用它还有其他很好的理由,比如更好的计划缓存,但这超出了这个问题的范围。

\n

欲了解更多信息,请查看我的帖子:

\n\n


Cha*_*ace 8

SQL 注入是获取用于创建查询的参数数据并将其注入到查询中的行为。换句话说,orderid = @oid您不使用参数,而是使用orderid = 'abc'. 这会让您容易遭受恶意或意外注入不正确或非预期的查询。例如,您可能在参数中包含撇号,或者它可能无法正确翻译日期。或者更危险的是,黑客尝试类似的事情 Robert'); DROP TABLE Students;--

上述查询更安全的原因是它本质上是静态的。(这个特定的查询称为厨房水槽查询,并具有其他性能优势。)它仅响应一组有限的条件而变化:是否提供参数。它不会实际数据注入到查询中。


SET @sql = 
 N'SELECT orderid, custid, empid, orderdate, filler'
 + N' /* 27702431-107C-478C-8157-6DFCECC148DD */'
 + N' FROM dbo.Orders'
 + N' WHERE 1 = 1'
Run Code Online (Sandbox Code Playgroud)

这部分是静态的并且始终是查询的一部分


 + CASE WHEN @orderid IS NOT NULL THEN
 N' AND orderid = @oid' ELSE N'' END
Run Code Online (Sandbox Code Playgroud)

这部分是有条件添加的。然而,重要的是,实际数据不是查询的一部分,只有是否提供数据才是变化的部分。

这更容易防范,因为现在唯一的风险是您预先知道的小语法错误(您显然没有获得智能感知)。不同查询的可能范围要小得多:您只有 16 种不同的可能性,并且它们都遵循相同的路线,因此您可以更好地推理结果,并且可以轻松测试所有可能性是否有效。


其结果是查询和数据分离。查询被传递给编译器,并且数据单独绑定到参数,这些参数充当占位符。编译器不可能数据误解为实际代码,它始终保留数据。