为什么 SQL 注入不会发生在存储过程中的这个查询上?

Rav*_*avi 18 sql-server-2008 security sql-server sql-injection dynamic-sql

我做了以下存储过程:

ALTER PROCEDURE usp_actorBirthdays (@nameString nvarchar(100), @actorgender nvarchar(100))
AS
SELECT ActorDOB, ActorName FROM tblActor
WHERE ActorName LIKE '%' + @nameString + '%'
AND ActorGender = @actorgender
Run Code Online (Sandbox Code Playgroud)

现在,我尝试做这样的事情。也许我做错了,但我想确保这样的过程可以防止任何 SQL 注入:

EXEC usp_actorBirthdays 'Tom', 'Male; DROP TABLE tblActor'
Run Code Online (Sandbox Code Playgroud)

下图显示了在 SSMS 中执行的上述 SQL 并且结果显示正确而不是错误:

在此处输入图片说明

顺便说一句,我在查询完成后在分号后面添加了那部分。然后我再次执行它,但是当我检查表 tblActor 是否存在时,它仍然存在。难道我做错了什么?或者这真的是防注射的吗?我想我在这里也想问的是,这是一个像这样安全的存储过程吗?谢谢你。

Sol*_*zky 38

此代码工作正常,因为它是:

  1. 参数化,和
  2. 没有做任何动态SQL

为了使 SQL 注入起作用,您必须构建一个查询字符串(您没有这样做)并且不能将单个撇号 ( ') 转换为转义的撇号 ( '')(这些是通过输入参数转义的)。

在您尝试传递“妥协”值时,该'Male; DROP TABLE tblActor'字符串就是一个普通的字符串。

现在,如果你正在做一些事情:

DECLARE @SQL NVARCHAR(MAX);

SET @SQL = N'SELECT fields FROM table WHERE field23 = '
          + @InputParam;

EXEC(@SQL);
Run Code Online (Sandbox Code Playgroud)

那么很容易受到 SQL 注入的影响,因为查询不在当前的、预先解析的上下文中;该查询目前只是另一个字符串。因此,的值@InputParam可能是'2015-10-31'; SELECT * FROM PlainTextCreditCardInfo;并且可能会出现问题,因为该查询将被呈现和执行,如下所示:

SELECT fields FROM table WHERE field23 = '2015-10-31'; SELECT * FROM PlainTextCreditCardInfo;
Run Code Online (Sandbox Code Playgroud)

这是使用存储过程的(几个)主要原因之一:本质上更安全(好吧,只要您不通过构建像我上面展示的那样的查询而不验证所使用的任何参数的值来规避该安全性)。虽然如果您需要构建动态 SQL,首选方法是使用sp_executesql以下参数对其进行参数化:

DECLARE @SQL NVARCHAR(MAX);

SET @SQL = N'SELECT fields FROM table WHERE field23 = @SomeDate_tmp';

EXEC sp_executesql
  @SQL,
  N'SomeDate_tmp DATETIME',
  @SomeDate_tmp = @InputParam;
Run Code Online (Sandbox Code Playgroud)

使用这种方法,试图传入'2015-10-31'; SELECT * FROM PlainTextCreditCardInfo;一个DATETIME输入参数的人在执行存储过程时会得到一个错误。或者即使存储过程接受@InputParameteras NVARCHAR(100),它也必须转换为 aDATETIME才能传递给该sp_executesql调用。即使动态 SQL 中的参数是字符串类型,首先进入存储过程的任何单个撇号都会自动转义为双撇号。

有一种鲜为人知的攻击类型,攻击者试图用撇号填充输入字段,这样存储过程中将用于构造动态 SQL 但声明太小的字符串不能容纳所有内容并推出结尾的撇号,并以某种方式以正确数量的撇号结束,以便不再在字符串中“转义”。这称为 SQL 截断,在 MSDN 杂志的一篇题为“新的 SQL 截断攻击以及如何避免它们”的文章中进行了讨论,该文章由 Bala Neerumalla 撰写,但该文章已不再在线。包含本文的问题 — MSDN 杂志 2006 年 11 月版— 仅可作为 Windows 帮助文件(在.chm格式)。如果您下载它,由于默认安全设置,它可能无法打开。如果发生这种情况,请右键单击MSDNMagazineNovember2006en-us.chm文件并选择“属性”。在这些选项卡之一中,将有一个选项“信任这种类型的文件”(或类似的东西)需要检查/启用。单击“确定”按钮,然后再次尝试打开.chm文件。

截断攻击的另一个变体是,假设一个局部变量用于存储“安全”用户提供的值,因为它有任何单引号加倍以进行转义,以填充该局部变量并放置单引号在末尾。这里的想法是,如果局部变量的大小不正确,则在第二个单引号的末尾将没有足够的空间,让变量以单个单引号结尾,然后与单引号组合结束动态 SQL 中的文字值,将该结束单引号转换为嵌入的转义单引号,然后动态 SQL 中的字符串文字以下一个旨在开始下一个字符串文字的单引号结束。例如:

-- Parameters:
DECLARE @UserID      INT = 37,
        @NewPassword NVARCHAR(15) = N'Any Value ....''',
        @OldPassword NVARCHAR(15) = N';Injected SQL--';

-- Stored Proc:
DECLARE @SQL NVARCHAR(MAX),
        @NewPassword_fixed NVARCHAR(15) = REPLACE(@NewPassword, N'''', N''''''),
        @OldPassword_fixed NVARCHAR(15) = REPLACE(@OldPassword, N'''', N'''''');

SELECT @NewPassword AS [@NewPassword],
       REPLACE(@NewPassword, N'''', N'''''') AS [REPLACE output],
       @NewPassword_fixed AS [@NewPassword_fixed];
/*
@NewPassword          REPLACE output          @NewPassword_fixed
Any Value ....'       Any Value ....''        Any Value ....'
*/

SELECT @OldPassword AS [@OldPassword],
       REPLACE(@OldPassword, N'''', N'''''') AS [REPLACE output],
       @OldPassword_fixed AS [@OldPassword_fixed];
/*
@OldPassword          REPLACE output          @OldPassword_fixed
;Injected SQL--       ;Injected SQL--         ;Injected SQL--
*/

SET @SQL = N'UPDATE dbo.TableName SET [Password] = N'''
           + @NewPassword_fixed + N''' WHERE [TableNameID] = '
           + CONVERT(NVARCHAR(10), @UserID) + N' AND [Password] = N'''
           + @OldPassword_fixed + N''';';

SELECT @SQL AS [Injected];
Run Code Online (Sandbox Code Playgroud)

这里,要执行的动态 SQL 现在是:

UPDATE dbo.TableName SET [Password] = N'Any Value ....'' WHERE [TableNameID] = 37 AND [Password] = N';Injected SQL--';
Run Code Online (Sandbox Code Playgroud)

同样的动态 SQL,以更易读的格式,是:

UPDATE dbo.TableName SET [Password] = N'Any Value ....'' WHERE [TableNameID] = 37 AND [Password] = N';

Injected SQL--';
Run Code Online (Sandbox Code Playgroud)

解决这个问题很容易。只需执行以下操作之一:

  1. 除非绝对必要,否则不要使用动态 SQL !(我首先列出它,因为它确实应该是首先要考虑的事情)。
  2. 适当调整局部变量的大小(即应该是输入参数大小的两倍,以防传入的所有字符都是单引号。
  3. 不要使用局部变量来存储“固定”值;只是把REPLACE()直接进入创建动态SQL:

    SET @SQL = N'UPDATE dbo.TableName SET [Password] = N'''
               + REPLACE(@NewPassword, N'''', N'''''') + N''' WHERE [TableNameID] = '
               + CONVERT(NVARCHAR(10), @UserID) + N' AND [Password] = N'''
               + REPLACE(@OldPassword, N'''', N'''''') + N''';';
    
    SELECT @SQL AS [No SQL Injection here];
    
    Run Code Online (Sandbox Code Playgroud)

    动态 SQL 不再受到损害:

    UPDATE dbo.TableName SET [Password] = N'Any Value ....''' WHERE [TableNameID] = 37 AND [Password] = N';Injected SQL--';
    
    Run Code Online (Sandbox Code Playgroud)

关于上述截断示例的注意事项:

  1. 是的,这是一个非常人为的例子。仅注入 15 个字符就无能为力了。当然,可能具有DELETE tableName破坏性,但不太可能添加后门用户或更改管理员密码。
  2. 这种类型的攻击可能需要了解代码、表名等。不太可能由随机陌生人/脚本小子完成,但我确实在一个地方工作过,那里被一位知道漏洞的相当沮丧的前雇员攻击在一个其他人不知道的特定网页中。意思是,有时攻击者确实对系统有深入了解。
  3. 当然,可能会调查重置每个人的密码,这可能会提示公司发生了攻击,但它仍然可能提供足够的时间来注入后门用户,或者可能会获得一些辅助信息以供以后使用/利用。
  4. 即使这种情况主要是学术性的(即在现实世界中不太可能发生),它仍然不是不可能的。

有关 SQL 注入的更多详细信息(涵盖各种 RDBMS 和场景),请参阅开放 Web 应用程序安全项目(OWASP) 中的以下内容:
SQL 注入测试

有关 SQL 注入和 SQL 截断的相关堆栈溢出答案:
替换 ' 转义字符后 T-SQL 的安全性如何?

  • 哦,非常感谢,这是一个很好的答案。我现在知道了。我真的很想看看你在最后提到的技术,如果你能找到的话,攻击者试图用撇号填充输入字段。提前致谢。我将保持打开状态,如果你找不到它,我会选择它作为答案。 (2认同)