我可以通过转义单引号和用单引号引起来的用户输入来防止SQL注入吗?


139

我意识到,在构建包含用户输入的查询时,参数化SQL查询是清理用户输入的最佳方式,但是我想知道使用用户输入并转义任何单引号并将整个字符串都用单引号引起的问题是什么。这是代码:

sSanitizedInput = "'" & Replace(sInput, "'", "''") & "'"

用户输入的任何单引号都将替换为双单引号,这消除了用户结束字符串的能力,因此他们可能键入的其他任何内容(例如分号,百分号等)都将成为字符串的一部分,并且实际上并未作为命令的一部分执行。

我们使用的是Microsoft SQL Server 2000,我认为单引号是唯一的字符串定界符,也是逃避字符串定界符的唯一方法,因此无法执行用户键入的任何内容。

我没有发现针对此问题发起SQL注入攻击的任何方法,但我意识到,如果这在我看来像防弹一样,那么其他人可能已经想到了,并且这将是普遍的做法。

此代码有什么问题?有没有办法使SQL注入攻击超越这种清理技术?利用此技术的样本用户输入将非常有帮助。


更新:

我仍然不知道有什么方法可以有效地对此代码发起SQL注入攻击。一些人建议反斜杠可以转义一个单引号,而让另一个反引号结束该字符串,以便该字符串的其余部分将作为SQL命令的一部分执行,并且我意识到该方法可以将SQL注入到一个MySQL数据库,但是在SQL Server 2000中,唯一能够逃脱单引号的方法是使用另一个单引号。反斜杠不会这样做。

并且,除非有一种方法可以停止转义单引号,否则将不会执行其余的用户输入,因为所有这些都将被视为一个连续的字符串。

我知道有更好的方法可以清除输入,但是我真的更感兴趣于了解为什么我上面提供的方法行不通。如果有人知道针对此清理方法进行SQL注入攻击的任何特定方式,我很乐意看到它。


17
@BryanH承认不了解普遍接受的智慧如何适用于特定案例,并要求提供有关此类特定案例的示例并不是自负,而是谦虚。当有人要求举一个为什么为什么通常公认的智慧是正确的例子时,就会很生气。通过具体示例进行推理通常是一种很好的调查和学习方式。OP对这个疑问的处理方式对我理解该主题非常有用,尤其是当他解释了所找到的答案时。
SantiBailors,2015年

@patrik在我处理同一段代码时遇到了这个问题,但是试图转义字符串并嵌套查询。你有没有想过?
3therk1ll

1
@ 3therk1ll最好不要尝试,最好使用参数化SQL:blog.codinghorror.com/…–
Patrick

@Patrick,我正从攻击者的角度出发!
3therk1ll

Answers:


87

首先,这只是不好的做法。输入验证始终是必需的,但也总是很困难。
更糟糕的是,黑名单验证始终是有问题的,最好明确定义严格接受的值/格式。诚然,这并非总是可能的-但一定程度上必须始终做到这一点。
有关此主题的一些研究论文:

重点是,您所做的任何黑名单(以及过于宽松的白名单)都可以绕开。我论文的最后一个链接显示了甚至可以绕过引号转义的情况。

即使这些情况不适用于您,这仍然不是一个好主意。而且,除非您的应用很小,否则您将不得不处理维护工作,也许还要进行一定量的管理:如何确保始终无所不在地确保其正确执行?

正确的方法是:

  • 白名单验证:类型,长度,格式或可接受的值
  • 如果您想将其列入黑名单,请继续。引号转义是好的,但是要考虑其他缓解措施。
  • 使用命令和参数对象进行准备和验证
  • 仅调用参数化查询。
  • 更好的是,仅使用存储过程。
  • 避免使用动态SQL,并且不要使用字符串连接来构建查询。
  • 如果使用SP,还可以将数据库中的权限限制为仅执行所需的SP,而不能直接访问表。
  • 您还可以轻松地验证整个代码库仅通过SP访问数据库...

2
如果正确使用,则动态SQL和字符串连接可以安全地用于参数化查询(即,使用sp_executesql代替EXEC)。也就是说,只要没有串联的文本来自用户,就可以动态生成SQL语句。这也具有性能优势;sp_executesql支持缓存。
Brian

2
@Brian,好吧:)。但是实际上,您看到程序员多久这样做一次?而且,在“需要”动态SQL的典型情况下,需要用户输入作为查询的一部分(假定)。如果可以使用sp_executesql,则通常不需要使用动态sql。
AviD 2010年

我最终遇到了一种情况,使我意识到可以使用unicode来偷偷替换字符串。输入的文本被键入到Word中,该词将单引号从直截了当的版本更改为“卷曲”单引号(看起来更像逗号),它不受字符串替换的影响,但被SQL视为字符串定界符服务器。感谢您回答AviD(以及其他所有人)!
Patrick

1
@ElRonnoco可以肯定,但是我否认这一点,因为我在野外看到它的次数比您想象的要多...
AviD 2013年

1
@AviD我已将您写的SQL走私PDF的链接更新为我可以在网上找到的唯一版本……如果您的论文还有其他位置,请告知我们。
Michael Fredrickson 2014年

41

好的,此答复将与问题的更新有关:

“如果有人知道针对这种清理方法发起SQL注入攻击的任何特定方式,我很乐意看到它。”

现在,除了转义MySQL反斜杠-考虑到我们实际上是在谈论MSSQL,实际上还有3种可能的方式来使SQL仍然注入代码

sSanitizedInput =“'”&替换(sInput,“'”,“”“)&”'“

考虑到这些并非始终都有效,并且非常依赖于您周围的实际代码:

  1. 二阶SQL注入-如果根据转义后从数据库中检索到的数据重建SQL查询,则数据将不经转义而级联,并且可以间接进行SQL注入。看到
  2. 字符串截断-(稍微复杂一点)-方案是您有两个字段,例如用户名和密码,SQL将这两个字段串联在一起。并且这两个字段(或仅第一个字段)都对长度有严格的限制。例如,用户名限制为20个字符。假设您有以下代码:
username = left(Replace(sInput, "'", "''"), 20)

然后,您得到的-是用户名,先进行转义,然后修剪为20个字符。这里的问题-我将引号括在第20个字符(例如19个字符之后),而转义的引号将被删节(在第21个字符中)。然后是SQL

sSQL = "select * from USERS where username = '" + username + "'  and password = '" + password + "'"

加上上述格式错误的用户名,将导致密码已经在引号之外,并且将直接包含有效负载。
3. Unicode的走私-在某些情况下,可以通过一个高层次的Unicode字符是看起来像一个报价,但不是 -直到它获取到数据库中,突然。由于验证时它不是报价,因此很容易...请参阅我以前的答复以获取更多详细信息,并链接到原始研究。


28

简而言之:永远不要查询逃脱自己。您一定会出错。相反,请使用参数化查询,或者,如果由于某种原因而无法执行此操作,请使用现有的库为您执行此操作。没有理由自己做。


2
如果您必须处理“ Google Fusion Tables”之类的问题,而afaik没有可用的支持其方言的抽象库呢?你有什么建议?
systempuntoout 2012年

20

我知道在问了问题之后已经很长时间了,但是..

对“ quote the arguments”过程发起攻击的一种方法是使用字符串截断。根据MSDN,在SQL Server 2000 SP4(和SQL Server 2005 SP1)中,太长的字符串将被安静地截断。

当您引用字符串时,字符串的大小会增加。每个撇号都重复。然后可以将其用于将SQL的某些部分推送到缓冲区之外。因此,您可以有效地修剪where子句的一部分。

这可能在“用户管理”页面方案中最有用,在这种情况下,您可能会滥用“ update”语句而不执行应做的所有检查。

因此,如果您决定引用所有参数,请确保您知道字符串大小在做什么,并确保不会被截断。

我建议使用参数。总是。只希望我可以在数据库中强制执行该操作。而且,由于更多的语句看起来相同,因此您更有可能获得更好的缓存命中率。(在Oracle 8上肯定是这样)


1
发布后,我决定AviD的帖子将对此进行更详细的介绍。希望我的帖子对某人仍然有帮助。
约恩·詹森,2009年

10

我在处理“高级搜索”功能时曾使用过这种技术,其中从头开始构建查询是唯一可行的答案。(示例:允许用户基于对产品属性的无限限制集搜索产品,将列及其允许的值显示为GUI控件,以降低用户的学习阈值。)

本身就是安全的AFAIK。但是,正如另一个回答者指出的那样,您可能还需要处理退格转义(尽管至少在使用ADO或ADO.NET将查询传递给SQL Server时不是这样-不能担保所有数据库或技术)。

问题在于,您确实必须确定哪些字符串包含用户输入(总是可能是恶意的),以及哪些字符串是有效的SQL查询。陷阱之一是,如果您使用数据库中的值-这些值最初是用户提供的吗?如果是这样,它们也必须逃脱。我的答案是在构造SQL查询时尝试尽早进行清理(但不要晚于此!)。

但是,在大多数情况下,参数绑定是必经之路-只是更简单。


2
即使您正在构建自己的查询,您仍然可以使用参数替换。
尼克·约翰逊

1
您应该从头开始构建SQL语句字符串,但仍要使用参数替换。
JeeBee

不,永远不要从头开始构建SQL语句。
AviD

8

输入卫生不是您想要的事。用你的屁股。在文本字段上使用正则表达式。尝试将您的数字转换为正确的数字类型,如果不起作用,请报告验证错误。在输入中搜索攻击模式非常容易,例如'-。假设来自用户的所有输入都是敌对的。


4
而当您在一个输入上错过一个案例时,您就被压住了。
BryanH 2012年

4
“有些人遇到问题时会想:“我知道,我将使用正则表达式。”现在,他们有两个问题。”
MickeyfAgain_BeforeExitOfSO

1
@mickeyf我知道这是一种普遍的看法,但是老实说,一旦您将它们表示为正则表达式就非常了不起。
tom.dietrich 2013年

@ tom.dietrich它始终取决于实际生活情况。F.ex. regexpr语法不是标准的,因此总的来说,我建议不要在集成了不同系统的环境中使用regexpr。这是因为不同的regexpr引擎对regexpr的评估方式不同,更重要的是,通常会轻描淡写或忽略这个困难的事实,这可能导致开发人员在被咬之前不关心这些不兼容性。有很多这样的不兼容性;见f.ex. normal-expressions.info/shorthand.htmlflavors在该页面中搜索)。
SantiBailors,2015年

6

您似乎知道这是个坏主意。

像这样在字符串中转义引号怎么办?

您的替换将导致:\''

如果反斜杠转义了第一个引号,则第二个引号已结束字符串。


3
感谢您的回复!我知道这种攻击对mySQL数据库有效,但是我很确定MS SQL Server不会接受反斜杠作为转义字符(我尝试过)。几次Google搜索都没有发现任何其他转义字符,这真的让我怀疑为什么这行不通。
帕特里克

6

简单的回答:有时会起作用,但并非总是如此。您想对所有操作使用白名单验证,但是我知道这并非总是可能的,因此您不得不选择最佳猜测黑名单。同样,您想在所有内容中使用参数化的存储过程,但并非总是如此,因此您不得不将sp_execute与参数一起使用。

您可以通过各种方法找到可用的黑名单(还有一些白名单)。

体面的文章在这里: http

如果您需要快速解决此问题,以便有时间让自己有一个真正的解决方案,请执行此操作。但是不要以为你很安全。


6

有两种方法可以做到这一点,无一例外,可以防止SQL注入。准备好的语句或实用的存储过程。


4

如果有可用的参数化查询,则应始终使用它们。只需要一个查询就可以遍历整个网络,您的数据库就处于危险之中。


4

是的,直到有人运行SET QUOTED_IDENTIFIER OFF并对您使用双引号之前,它应该可以正常工作。

编辑:这不像不让恶意用户关闭引用的标识符那样简单:

连接时,SQL Server的SQL Server本机客户端ODBC驱动程序和SQL Server的SQL Server本机客户端OLE DB提供程序会自动将QUOTED_IDENTIFIER设置为ON。可以在ODBC数据源,ODBC连接属性或OLE DB连接属性中进行配置。对于来自DB-Library应用程序的连接,SET QUOTED_IDENTIFIER的默认值为OFF。

创建存储过程时,将捕获SET QUOTED_IDENTIFIER和SET ANSI_NULLS设置,并将其用于该存储过程的后续调用

SET QUOTED_IDENTIFIER也对应于ALTER DATABASE的QUOTED_IDENTIFER设置。

SET QUOTED_IDENTIFIER 在解析时设置。在分析时进行设置意味着,如果批处理或存储过程中存在SET语句,则无论代码执行是否实际到达该点,该语句都将生效。并且SET语句在执行任何语句之前生效。

有很多方法可以关闭QUOTED_IDENTIFIER,而您不一定知道它。诚然-这不是您要寻找的吸烟手段,但这是一个很大的攻击面。当然,如果您还使用了双引号-那么我们又回到了开始的地方。;)


1
这可能行得通,但是当所有用户输入都用单引号引起来时,他们又如何使该代码执行?能够将SQL注入上述代码的特定代码行将非常有帮助。谢谢!
帕特里克

4

如果出现以下情况,您的辩护将失败:

  • 查询需要数字而不是字符串
  • 还有其他表示单引号的方法,包括:
    • 转义序列,例如\ 039
    • Unicode字符

(在后一种情况下,必须替换后才能进行扩展)


4

帕特里克(Patrick),您是否要在所有输入(甚至数字输入)周围添加单引号?如果您有数字输入,但没有将单引号引起来,那么您就有机会了。


1

清除用户输入的所有代码会是多么丑陋的代码!然后是笨拙的StringBuilder用于SQL语句。预准备的语句方法可以使代码更简洁,并且SQL Injection的好处是非常好的补充。

还有为什么要重新发明轮子呢?


1

与其将一个单引号更改为(看起来像)两个单引号,为什么不将其更改为单引号,引号或将其完全删除呢?

无论哪种方式,这都有点麻烦……尤其是当您合法拥有可以使用单引号的内容(例如名称)时……

注意:您的方法还假设,使用您的应用程序的每个人始终记得在输入数据库之前清除输入,这在大多数情况下可能是不现实的。


否决票,因为答案不能解决问题。问题是关于在SQL中转义字符串。当您转义任意字符串时(就像发问者试图这样做的那样,为了处理未经过滤的数据),您不能仅仅用任意其他字符替换有问题的字符。破坏数据。(此外,单引号是撇号(至少是ASCII)。)
andrewf '16

-1

尽管您可能会找到一种适用于字符串的解决方案,但对于数字谓词,您还需要确保它们仅传递数字(是否可以将其简单解析为int / double / decimal?)。

这是很多额外的工作。


-2

它可能有效,但对我来说似乎有点骗人。我建议通过对正则表达式进行测试来验证每个字符串是否有效。


-3

是的,如果...

在研究了该主题之后,我认为按照您的建议进行清理的输入是安全的,但仅在以下规则下:

  1. 您永远不允许来自用户的字符串值变成字符串文字以外的任何内容(即避免提供配置选项:“在此处输入其他SQL列名称/表达式:”)。字符串以外的值类型(数字,日期等):将它们转换为其本机数据类型,并为每种数据类型的SQL文字提供例程。

    • SQL语句难以验证
  2. 您可以使用nvarchar/ nchar列(并在字符串前缀前加上N),或将varchar/ char列中的值限制为仅ASCII字符(例如,在创建SQL语句时引发异常)

    • 这样,您将避免从CHAR(700)到CHAR(39)的自动撇号转换(以及其他类似的Unicode hacks)
  3. 您始终会验证值长度以适合实际的列长度(如果更长则抛出异常)

    • SQL Server中存在一个已知缺陷,可以绕过截断引发的SQL错误(导致静默截断)
  4. 您确保SET QUOTED_IDENTIFIER始终ON

    • 当心,它在解析时即在代码的不可访问部分中生效

遵守这4点,您应该是安全的。如果您违反其中任何一项,则将打开SQL注入的方法。


1
就像您没有读完这个已有8年历史的问题的所有其他答案一样,因为这些答案中有许多都指出,如果攻击者仅使用Unicode字符,他的方法将无法停止注入
霍根

@Hogan –我做到了,但是我认为我的问题还有额外的价值。我写的东西后面有很多经验和测试。我知道使用查询参数会更好,但是我也完全理解有人由于各种原因(例如雇主要求保留旧方法)而必须避免使用查询参数的情况。在这种情况下,我认为我的答案非常全面,并且比说“只是不这样做”的答案具有更高的价值,因为它显示了方法。在这里显示其他答案,以相同的方式显示,我将考虑删除我的答案。
miroxlav '16

好的,当(如果不是)您的系统受到威胁时,请返回并删除此答案...。否则,您可以使用参数化查询。
霍根

@Hogan –我没问题:)但是目前我声称,如果您遵守我发布的4条规则,则没有已知的解决方法。如果您真的认为有解决办法,请指出在哪里。
miroxlav '16

不好的建议。任何插值都可以被击败。
Shayne
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.