如何在SQL Server中清理动态SQL - 防止SQL注入

时间:2010-11-04 23:37:58

标签: sql-server validation stored-procedures sql-injection dynamic-sql

我们有大量依赖动态SQL的SQL Server存储过程。

存储过程的参数用于动态SQL语句。

我们需要在这些存储过程中使用标准验证函数来验证这些参数并防止SQL注入。

假设我们有这些限制:

  1. 我们无法重写不使用Dynamic SQL的程序

  2. 我们不能使用sp_OACreate等来使用正则表达式进行验证。

  3. 在将参数传递给存储过程之前,我们无法修改调用存储过程的应用程序来验证参数。

  4. 我们是否可以过滤掉一组字符以确保我们不会受到SQL注入的影响?

8 个答案:

答案 0 :(得分:10)

我相信您有三种不同的情况需要担心:

  • 字符串(需要引号的任何内容):'''' + replace(@string, '''', '''''') + ''''
  • 姓名(不允许引用的任何内容):quotename(@string)
  • 无法引用的内容:这需要列入白名单

注意一切字符串变量(charvarcharncharnvarchar等。)来自用户控制的来源必须使用上述方法之一。这意味着即使你想要成为数字的东西,如果它们存储在字符串变量中也会被引用。

有关详细信息,请参阅 Microsoft Magazine (过时链接:2016-10-19)

以下是使用所有三种方法的示例:

EXEC 'SELECT * FROM Employee WHERE Salary > ''' +
     REPLACE(@salary, '''', '''''') +   -- replacing quotes even for numeric data
     ''' ORDER BY ' + QUOTENAME(@sort_col) + ' ' +  -- quoting a name
     CASE @sort_dir WHEN 'DESC' THEN 'DESC' END     -- whitelisting

另请注意,通过在EXEC语句中内联执行所有字符串操作,不会出现截断问题。如果将中间结果分配给变量,则必须确保变量足够大以保存结果。如果您执行SET @result = QUOTENAME(@name),则应定义@result以保留至少258(2 * 128 + 2)个字符。如果您执行SET @result = REPLACE(@str, '''', ''''''),则应将@result定义为@str的两倍(假设@str中的每个字符都可以作为引用)。当然,保存最终SQL语句的字符串变量必须足够大,以容纳所有静态SQL和所有结果变量。

答案 1 :(得分:6)

可以通过QUOTENAME和REPLACE:

修复琐碎的案例
set @sql = N'SELECT ' + QUOTENAME(@column) + 
   N' FROM Table WHERE Name = ' + REPLACE(@name, '''', '''''');

虽然也可以在文字上使用QUOTENAME来添加单引号并用双引号替换单引号,因为它不会将输入截断为128个字符。

但这只是冰山一角。您需要妥善处理多部分名称(dbo.table):引用多部分名称会导致标识符[dbo.table]无效,必须对其进行解析和拆分(使用PARSENAME),然后适当引用[dbo].[table]

另一个问题是截断攻击,即使你对文字做了普通的REPLACE也可能发生,请参阅New SQL Truncation Attacks And How To Avoid Them

SQL注入问题永远无法通过在每个过程中放置​​一个魔术函数来解决。这就像问'我想要一个能让我的代码运行得更快的功能'。防止注入攻击是端到端游戏,需要编码规则一直通过,它不能简单地添加为事后的想法。您最好的机会是检查每个程序并分析T-SQL代码逐行,并密切关注漏洞,然后在找到问题时解决问题。

答案 2 :(得分:4)

有了这些限制,你就会搞砸了。

以下两个选项可能会为您指明方向:

  1. 使用白名单验证器/解析器,它只接受格式以及预期的关键字和表格的查询。这可能只适用于非常好的SQL解析器,它真正理解语法。

  2. 在受限制的环境中执行查询。例如,使用权限非常有限的用户帐户。例如,仅允许(读取)访问永远不会返回敏感数据的某些视图,并禁止访问所有其他视图,所有存储过程,函数和表。更安全的是在另一个数据库服务器上执行这些查询。另外,请不要忘记禁用OPENROWSET命令。

  3. 请注意以下事项:

    1. 当您接受除具有无效关键字的查询以外的所有查询时,您肯定会失败,因为黑名单始终失败。特别是使用SQL这样复杂的语言。

    2. 不要忘记,即使您使用这些提示,从最纯粹的意义上允许来自您不能信任的来源的动态SQL也是邪恶的,因为偶尔会发现可能被滥用的bugs {{3}}通过将特制的SQL发送到服务器。因此,即使您应用这些提示,风险仍然存在。

    3. 当您决定使用允许动态SQL的解决方案时。请不要认为您可以提出安全的解决方案,尤其是在您尝试保护敏感的业务数据时。聘请数据库服务器安全专家来帮助您。

答案 3 :(得分:3)

这是一个非常讨厌的问题,它你想要解决的问题,但是这是一个有效的小案例,(评论者,如果我错过了一个案例,请告诉我,这个附带保证)

create proc Bad 
  @param nvarchar(500) 
as 

exec (N'select ''' + @param + N'''') 

go

-- oops injected
exec Bad 'help'' select ''0wned!'' select ''' 

go 

create proc NotAsBad
   @param nvarchar(500) 
as 

declare @safish nvarchar(1000), @sql nvarchar(2000) 
set @safish = replace(@param, '''', '''''')

set @sql = N'select ''' + @safish  + N''''

exec (@sql) 

go 

-- this kind of works, but I have not tested everything
exec NotAsBad 'help'' select ''0wned!'' select ''' 

答案 4 :(得分:2)

OWASP有关于此策略的一些信息。它应该始终是最后的选择(如我正在链接的文章中所解释的那样),但如果这是你唯一的选择......

http://www.owasp.org/index.php/SQL_Injection_Prevention_Cheat_Sheet

关于它是最后选择的文章的引用

  

然而,这种方法很脆弱   与使用参数化相比   查询。这种技术应该只是   谨慎使用改造遗产   以经济有效的方式编写代码。   从头开始构建的应用程序,或   需要低风险的应用   应该建立公差或   使用参数化重写   查询。

从本质上讲,反对这种方法的论据即使你确实逃脱了所有已知的不良输入,也不能保证有人在未来不会想出办法绕过它。

但是,要具体回答你的问题......

要转义的字符列表在我上面链接的文章中。

修改如上所述,该文章未提供非常好的链接。但是,对于SQL Server,这个执行:http://msdn.microsoft.com/en-us/library/ms161953.aspx

请注意,您需要转义的字符列表会因数据库平台而异,但看起来您使用的是SQL Server,因此这应该是相关的。

引用下面的文章:

通过删除转义字符,过滤输入也可能有助于防止SQL注入。但是,由于可能造成问题的大量字符,这不是一个可靠的防御。以下示例搜索字符串分隔符。

private string SafeSqlLiteral(string inputSQL)
{
  return inputSQL.Replace("'", "''");
}

LIKE条款

请注意,如果您使用LIKE子句,则仍必须转义通配符:

s = s.Replace("[", "[[]");
s = s.Replace("%", "[%]");
s = s.Replace("_", "[_]");

答案 5 :(得分:2)

  

我们是否可以过滤掉一组字符以确保我们不会受到SQL注入的影响?

NO

SQL注入不被称为"某些字符集注入",并且出于某种原因。过滤掉某些特征可能会使特定漏洞复杂化,但并不会阻止SQL注入本身。要利用SQL注入,必须编写SQL。 SQL不仅限于少数特殊字符。

答案 6 :(得分:0)

你能否获得SQL CLR是非常有用的 - 你至少可以使用它来编写比使用T-SQL更多,更有效的清理。在perfact世界中,您可以使用参数化语句和其他更强大的结构完全替换存储过程。

答案 7 :(得分:-1)

还有另一种可能可能有效的方法,尽管它取决于存储过程参数中允许的字符数。而不是转义可用于SQL注入的麻烦字符,而是删除字符。例如,如果你有这个SP:

create procedure dbo.MYSP(@p1 varchar(100))
as begin
  set @p1 = Replace(@p1, '''',' '); -- Convert single quotes to spaces
  set @p1 = Replace(@p1, ';', ' ');
  set @p1 = Replace(@p1, '--', ' ');      
  set @p1 = Replace(@p1, '/*', ' ');      
  set @p1 = Replace(@p1, '*/', ' ');      
  set @p1 = Replace(@p1, 'xp_', ' ');      
  ...
end;

您可以用空格或空字符串替换任何单引号。这种方法也可以用来替换注释字符,例如/ * * / - 通过使用更多的替换命令(如上所示)。但请注意,只有在正常输入中不会指望这些字符时,此方法才有效,这取决于您的应用程序。

请注意,替换字符集基于https://msdn.microsoft.com/en-us/library/ms161953(SQL.105).aspx