9

我们拥有大量依赖动态SQL的SQL Server存储过程。如何清理SQL Server中的动态SQL - 防止SQL注入

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

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

假设我们有这些约束:

  1. 我们不能改写程序不使用动态SQL

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

  3. 我们不能修改调用存储过程的应用程序在传递给存储过程之前验证参数。

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

+0

ouch。通常它是3)应该被修改以防止SQL注入。请记住,它是“SQL注入”,而不是“SQL拒绝”。一旦它到达DB,它应该已经被清理。但是,如果你说你不能更改应用程序,那么我想你不能。有兴趣看到答案。 – RPM1984 2010-11-04 23:55:15

回答

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和所有结果变量。

  • +0

    我同意这里,它完全取决于什么SQL正在构建 – 2010-11-05 01:40:12

    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("_", "[_]"); 
    
    +1

    -1:该文章没有说明MS SQL Server要逃脱什么字符。它只是链接到另一篇文章,并没有明确哪些字符可以逃脱。 – Gabe 2010-11-05 01:21:41

    +0

    你说得对。编辑我的答案。 – David 2010-11-05 02:15:07

    3

    这是一个非常讨厌的问题,其问题你想解决,但这里是一个微不足道的案件,(审查,请让我知道,如果我错过了一个案件,这与NO保证)

    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 ''' 
    
    +0

    +1,我从来没有看到任何暗示这不起作用的东西。 – Gabe 2010-11-05 01:15:03

    +1

    在我看来,使用除sp_executesql之外的所有值作为参数传递的任何动态SQL都只是纯粹的弊端。 – 2010-11-05 13:00:39

    +0

    仍然脆弱。假设NotAsBad的主体包含以下内容:set @sql = N'select * from'+ @ safish ....如果用户可以猜出他们可以提交的表的名字@param ='tablename; drop database xyz; - ' – frankadelic 2010-11-05 21:52:22

    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代码逐行,着眼于漏洞,然后解决发现问题。

    +0

    我建议*不*使用'PARSENAME',因为它的目的是用于已经引用的名称。如果你的用户告诉你他想从'secret..table'获取数据,你想查询'[secret..table]'并且得到一个错误。你不希望他能够查询'[secret] .. [table]'! – Gabe 2010-11-05 04:35:55

    +0

    在我看来,使用除sp_executesql以外的所有值作为参数传递的任何动态SQL都只是纯粹的弊端。 – 2010-11-05 13:00:03

    0

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

    +0

    不幸的是,由于数据库管理员的限制,我无法使用CLR – frankadelic 2010-11-05 16:25:48

    4

    有了这些制约因素,

    这里有可能给你一些方向上的两个选项:

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

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

    请注意以下事项:

    1. 当您接受,除了那些有无效的关键字的所有查询,你肯定会失败,因为黑色的房源总是失败。尤其是对于像SQL这样复杂的语言。

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

    3. 当您决定使用允许动态SQL的解决方案时。请不要以为你可以自己提出一个安全的解决方案,特别是如果你想保护敏感的业务数据。雇用数据库服务器安全专家来帮助你。

    -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

    +0

    SQL注入不称为“单引用注入”。因为某种原因。 – 2017-10-25 09:16:30

    +0

    我对'单引号注入'不熟悉,我刚刚描述的技术是防范SQL注入的一种方法,它基于上面引用的Microsoft文章。我不清楚你为什么低估了这个答案。 – Ubercoder 2017-10-25 09:33:26

    +0

    然后让自己熟悉安全基础知识,并了解为什么任何基于黑名单的方法故意有缺陷 – 2017-10-25 09:35:33

    2

    有一组我们可以过滤掉,以确保我们不容易受到SQL注入的字符?

    NO

    SQL注入不叫“某些字符集合注射液”,并且是有原因的。过滤掉某些字符可能会使特定的漏洞复杂化,但不会阻止SQL注入本身。要利用SQL注入,必须编写SQL。而SQL不限于少数特殊字符。