该代码可以正常工作,因为它是:
- 参数化
- 没有做任何动态SQL
为了使SQL注入能够正常工作,您必须构建一个查询字符串(不执行此操作),并且不将单个撇号('
)转换为转义的撇号(''
)(通过输入参数对它们进行转义)。
在您尝试传递“妥协”值时,'Male; DROP TABLE tblActor'
字符串就是这样,即纯字符串。
现在,如果您正在执行以下操作:
DECLARE @SQL NVARCHAR(MAX);
SET @SQL = N'SELECT fields FROM table WHERE field23 = '
+ @InputParam;
EXEC(@SQL);
然后,由于该查询不在当前的预先解析的上下文中,因此容易受到SQL注入的影响;该查询目前只是另一个字符串。因此,of的值@InputParam
可能为'2015-10-31'; SELECT * FROM PlainTextCreditCardInfo;
并且可能会出现问题,因为该查询将按以下方式呈现和执行:
SELECT fields FROM table WHERE field23 = '2015-10-31'; SELECT * FROM PlainTextCreditCardInfo;
这是使用存储过程的(几个)主要原因之一:本质上更安全(只要您不通过构建如上所示的查询而不验证所使用的任何参数的值来规避安全性)。虽然如果您需要构建Dynamic 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;
使用这种方法,有人试图在传递'2015-10-31'; SELECT * FROM PlainTextCreditCardInfo;
一个DATETIME
执行存储过程时,输入参数会得到一个错误。或者,即使存储过程接受@InputParameter
了NVARCHAR(100)
,它也必须转换为DATETIME
才能传递给该sp_executesql
调用。并且即使Dynamic SQL中的参数是字符串类型,首先进入存储过程的任何单个撇号也会自动转义为双撇号。
有一种鲜为人知的攻击类型,其中,攻击者尝试用撇号填充输入字段,以使存储过程内部的一个字符串(该字符串将用于构造动态SQL,但声明为太小)无法容纳所有内容并推出结尾的撇号,并以某种正确的撇号结束,从而不再在字符串中“转义”。这称为SQL截断,在Bala Neerumalla的MSDN杂志文章“新的SQL截断攻击和如何避免它们”中进行了讨论,但该文章不再在线。包含本文的问题(2006年11月版的MSDN Magazine)仅作为Windows帮助文件提供(在.chm中格式)。如果下载它,由于默认的安全设置,它可能无法打开。如果发生这种情况,请右键单击MSDNMagazineNovember2006en-us.chm文件,然后选择“属性”。在这些标签中的一个中,将有一个“信任此类型的文件”(或类似名称)选项,需要选中/启用。单击“确定”按钮,然后尝试再次打开.chm文件。
截断攻击的另一个变体是,假设使用局部变量来存储用户提供的“安全”值,因为它会将任何单引号加倍以进行转义,以便逃脱,以填充该局部变量并放置单引号在末尾。这里的想法是,如果局部变量的大小不正确,则第二个单引号的结尾将没有足够的空间,请将该变量以单个单引号结尾,然后将其与结束Dynamic SQL中的文字值,将结束的单引号转换为嵌入式转义的单引号,然后Dynamic 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];
现在,要执行的动态SQL现在是:
UPDATE dbo.TableName SET [Password] = N'Any Value ....'' WHERE [TableNameID] = 37 AND [Password] = N';Injected SQL--';
更具可读性的相同的Dynamic SQL是:
UPDATE dbo.TableName SET [Password] = N'Any Value ....'' WHERE [TableNameID] = 37 AND [Password] = N';
Injected SQL--';
解决这个问题很容易。只需执行以下操作之一:
- 除非绝对必要,否则不要使用动态SQL !(我将其列为第一,因为它实际上应该是首先要考虑的)。
- 适当调整局部变量的大小(即,应为输入参数大小的两倍,以防万一传入的所有字符都是单引号。
不要使用局部变量来存储“固定”值;只需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];
动态SQL不再受到威胁:
UPDATE dbo.TableName SET [Password] = N'Any Value ....''' WHERE [TableNameID] = 37 AND [Password] = N';Injected SQL--';
有关上述截断示例的注意事项:
- 是的,这是一个非常人为的例子。仅用15个字符即可插入的内容很少。当然,这可能具有
DELETE tableName
破坏性,但不太可能添加后门用户或更改管理员密码。
- 这种类型的攻击可能需要了解代码,表名等信息。不太可能由随机的陌生人/脚本小子来完成,但是我确实在一个受到一个相当不高兴的前员工了解的漏洞的地方工作在一个没有其他人知道的特定网页中。这意味着,有时攻击者确实对系统有深入的了解。
- 当然,重置每个人的密码很可能会受到调查,这可能会提示公司发生了攻击,但它仍可能提供足够的时间来注入后门用户,或者获取一些辅助信息以供以后使用/利用。
- 即使这种情况大部分是学术性的(即在现实世界中不太可能发生),但这仍然不是没有可能。
有关与SQL注入相关的更多详细信息(涵盖各种RDBMS和方案),请参阅Open Web Application Security Project(OWASP)中的以下内容:
测试SQL注入
有关SQL注入和SQL截断的相关堆栈溢出答案:
替换'转义符后,T-SQL的安全性如何?
EXEC usp_actorBirthdays 'Tom', 'Male''; DROP TABLE tblActor'