我能否通过转义单引号并用单引号括住用户输入来防止 SQL 注入?

Can I protect against SQL injection by escaping single-quote and surrounding user input with single-quotes?

提问人:Patrick 提问时间:9/26/2008 最后编辑:Peter MortensenPatrick 更新时间:12/14/2022 访问量:91369

问:

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

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

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

我们使用的是 Microsoft SQL Server 2000,我相信单引号是唯一的字符串分隔符,也是转义字符串分隔符的唯一方法,因此无法执行用户键入的任何内容。

我看不出有任何方法可以对此发起SQL注入攻击,但我意识到,如果这在我看来是防弹的,那么其他人已经想到了,这将是常见的做法。

这段代码有什么问题?有没有办法让SQL注入攻击通过这种清理技术?利用此技术的示例用户输入将非常有用。


更新:

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

除非有办法阻止单引号的转义,否则其余的用户输入都不会被执行,因为它将全部被视为一个连续的字符串。

我知道有更好的方法来清理输入,但我真的更感兴趣的是了解为什么我上面提供的方法不起作用。如果有人知道任何针对这种清理方法进行 SQL 注入攻击的具体方法,我很想看到它。

安全性 SQL-Server-2000 SQL 注入 清理

评论

21赞 SantiBailors 6/14/2015
@BryanH 承认不理解普遍接受的智慧如何适用于特定案例,并要求就此类特定案例举例,这不是傲慢,而是谦卑。另一方面,当有人要求举例说明为什么普遍接受的智慧是正确的时,会感到恼火,这可能会让人觉得很傲慢。通过具体的例子进行推理通常是调查和学习的好方法。OP处理这个疑问的方式对我理解这个主题非常有用,尤其是当他解释他找到的答案时。
0赞 3therk1ll 9/20/2019
@patrik 刚刚遇到这个问题,因为我正在处理同一段代码,但试图转义字符串并嵌套查询。你有没有想过?
1赞 Patrick 9/21/2019
@3therk1ll最好不要尝试,最好使用参数化 SQL:blog.codinghorror.com/...
0赞 3therk1ll 9/21/2019
@Patrick,我是从攻击者的角度来看待它的!

答:

-2赞 Rob 9/26/2008 #1

它可能会起作用,但对我来说似乎有点滑稽。我建议通过针对正则表达式测试每个字符串来验证它是否有效。

-1赞 Joseph Daigle 9/26/2008 #2

虽然你可能会找到一个适用于字符串的解决方案,但对于数字谓词,你还需要确保它们只传入数字(简单的检查是它可以解析为整数/双精度/十进制吗?

这是很多额外的工作。

6赞 WW. 9/26/2008 #3

无论如何,这似乎是一个坏主意,正如你所知道的。

像这样转义字符串中的引号怎么样:\'

您的替换将导致:\''

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

评论

3赞 Patrick 9/26/2008
感谢您的回复!我知道这种攻击对mySQL数据库有效,但我很确定MS SQL Server不会接受反斜杠作为转义字符(我试过了)。几次谷歌搜索都没有显示任何其他逃生角色,这真的让我想知道为什么这不起作用。
1赞 JeeBee 9/26/2008 #4

对用户输入进行审查的代码将是多么丑陋!然后是 SQL 语句的笨拙的 StringBuilder。准备好的语句方法可以生成更简洁的代码,并且 SQL 注入的好处是一个非常好的补充。

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

1赞 Kevin Fairchild 9/26/2008 #5

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

无论哪种方式,这都有点笨拙......尤其是当您合法地拥有可能使用单引号的东西(例如名称)时......

注意:您的方法还假设在应用程序上工作的每个人都始终记得在输入到达数据库之前对其进行清理,这在大多数情况下可能并不现实。

评论

0赞 andrewf 7/21/2016
投了反对票,因为答案没有解决问题。问题是关于在 SQL 中转义字符串。当你转义一个任意字符串时(正如提问者试图做的那样,为了处理未经审查的数据),你不能只是用任意的其他字符替换有问题的字符;这会损坏数据。(此外,单引号是撇号(至少在 ASCII 中)。
4赞 Kev 9/26/2008 #6

如果您有可用的参数化查询,则应始终使用它们。只需一个查询就会从网上溜走,您的数据库就会面临风险。

9赞 tom.dietrich 9/26/2008 #7

投入卫生不是你想半途而废的事情。用你的整个屁股。对文本字段使用正则表达式。尝试将数值转换为正确的数值类型,如果不起作用,则报告验证错误。在输入中搜索攻击模式非常容易,例如 “ --.假设用户的所有输入都是敌对的。

评论

4赞 BryanH 11/18/2012
当你在一个输入上错过了一个案例时,你就是pwnd。
4赞 MickeyfAgain_BeforeExitOfSO 12/6/2013
“有些人在遇到问题时会想'我知道,我会使用正则表达式。现在他们有两个问题。
1赞 tom.dietrich 12/8/2013
@mickeyf我知道这是一种常见的情绪,但老实说,一旦你理解了正则表达式,它们就非常棒。
0赞 SantiBailors 6/15/2015
@tom.dietrich:这总是取决于现实生活的情况。例如,regexpr 语法不是标准的,因此一般来说,我建议不要在集成不同系统以协同工作的上下文中使用 regexpr。这是因为不同的正则表达式引擎对正则表达式的评估不同,更重要的是,这个硬事实通常会被淡化或忽略,这可能导致开发人员在被咬之前不关心这些不兼容问题。有很多这样的不兼容性;见 F.Ex.regular-expressions.info/shorthand.html(在该页面中搜索)。flavors
28赞 Nick Johnson 9/26/2008 #8

简而言之:永远不要对自己进行查询转义。你一定会出错的。请改用参数化查询,或者如果由于某种原因无法执行此操作,请使用现有库来执行此操作。没有理由自己动手。

评论

3赞 systempuntoout 7/17/2012
如果你必须处理像“Google Fusion表”这样的东西,而afaik,没有任何抽象库支持它的方言,该怎么办?你有什么建议?
1赞 Scott Smith 1/5/2021
问题不在于哪个更明智,而在于特定解决方案实际上是如何失败的。如果你不知道,那么你就没有这个问题的答案。
12赞 Pontus Gagge 9/26/2008 #9

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

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

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

但是,在大多数情况下,参数绑定是要走的路 - 它只是更简单。

评论

2赞 Nick Johnson 9/26/2008
即使您正在构建自己的查询,您仍然可以使用参数替换。
1赞 JeeBee 9/26/2008
您应该从头开始构建 SQL 语句字符串,但仍使用参数替换。
0赞 AviD 9/28/2008
不,永远不要从头开始构建 SQL 语句。
92赞 AviD 9/26/2008 #10

首先,这只是不好的做法。输入验证始终是必要的,但也总是不稳定的。
更糟糕的是,黑名单验证总是有问题的,最好明确和严格地定义你接受的值/格式。诚然,这并不总是可能的——但在某种程度上,它必须始终做到。
关于该主题的一些研究论文:

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

即使这些情况不适用于您,这仍然是一个坏主意。此外,除非你的应用程序非常小,否则你将不得不处理维护,也许还有一定程度的治理:你如何确保它始终在任何地方都正确完成?

正确的方法:

  • 白名单验证:类型、长度、格式或接受的值
  • 如果您想列入黑名单,请继续。引号转义是好的,但在其他缓解措施的上下文中。
  • 使用 Command 和 Parameter 对象进行预分析和验证
  • 仅调用参数化查询。
  • 更好的是,只使用存储过程。
  • 避免使用动态 SQL,也不要使用字符串连接来生成查询。
  • 如果使用 SP,还可以将数据库中的权限限制为仅执行所需的 SP,而不能直接访问表。
  • 您还可以轻松验证整个代码库是否仅通过 SP 访问数据库...

评论

3赞 Brian 7/17/2010
如果使用得当,动态 SQL 和字符串串联可以安全地与参数化查询一起使用(即用代替)。也就是说,只要没有串联的文本来自用户,就可以动态生成 SQL 语句。这也具有性能优势; 支持缓存。sp_executesqlEXECsp_executesql
2赞 AviD 7/18/2010
@Brian,嗯,:)。但实际上,你多久看到一次程序员这样做?此外,“需要”动态 SQL 的典型场景需要用户输入作为查询的一部分(据说)。如果你能做sp_executesql,你(通常)一开始就不需要动态sql。
1赞 Patrick 10/31/2012
我终于遇到了一个情况,让我意识到可以使用unicode来偷偷溜过字符串替换。输入文本键入到 Word 中,将撇号从直下版本更改为“卷曲”撇号(看起来更像逗号),该撇号不受字符串替换的影响,但被 SQL Server 视为字符串分隔符。感谢 AviD(和其他所有人)的回答!
1赞 AviD 1/21/2013
@ElRonnoco当然,但我不低估这一点,因为我在野外看到它的次数比你想象的要多......
1赞 Michael Fredrickson 12/18/2014
@AviD,我将您编写的 SQL 走私 PDF 的链接更新为我在网上可以找到的唯一版本......如果您的论文有其他位置,请告诉我们。
6赞 Invalid Character #11

简单的回答:它有时会起作用,但不会一直起作用。 你想对你所做的一切使用白名单验证,但我意识到这并不总是可行的,所以你被迫使用最佳猜测黑名单。同样,您希望在所有内容中使用参数化存储过程,但同样,这并不总是可行的,因此您被迫使用带有参数的sp_execute。

您可以想出任何可用的黑名单(以及一些白名单)的方法。

这里有一篇体面的文章:http://www.owasp.org/index.php/Top_10_2007-A2

如果您需要这样做作为快速修复,以便有时间获得真正的解决方案,那就去做吧。但不要认为你是安全的。

6赞 olle 9/30/2008 #12

有两种方法可以做到这一点,没有例外,可以避免SQL注入;预准备语句或实用存储过程。

3赞 Mark Brackett 10/21/2008 #13

是的,这应该一直有效,直到有人运行 SET QUOTED_IDENTIFIER OFF 并在您身上使用双引号。

编辑:这并不像不允许恶意用户关闭带引号的标识符那么简单:

SQL Server Native Client ODBC 驱动程序和用于 SQL Server 的 SQL Server Native Client 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在你不一定知道的情况下关闭它。诚然,这不是你要找的吸烟枪漏洞,但它是一个相当大的攻击面。当然,如果你也省略了双引号 - 那么我们又回到了我们开始的地方。;)

评论

2赞 Patrick 10/21/2008
这可行,但同样,当所有用户输入都用单引号括起来时,他们如何让代码执行?能够将 SQL 注入上述代码的特定代码行将非常有帮助。谢谢!
3赞 AJ. 10/22/2008 #14

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

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

(在后一种情况下,它必须是在您完成替换后才扩展的东西)

评论

0赞 Joshua 11/20/2021
无法重现。
4赞 Rob Kraft 11/15/2008 #15

帕特里克,您是否在所有输入周围添加单引号,甚至是数字输入?如果你有数字输入,但没有用单引号括起来,那么你就有风险。

44赞 AviD 12/18/2008 #16

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

“如果有人知道任何针对这种清理方法进行SQL注入攻击的具体方法,我很想看到它。

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

sSanitizedInput = “'” & Replace(sInput, “'”, “''”) & “'”

考虑到这些并非在所有时候都有效,并且非常依赖于围绕它的实际代码:

  1. 二阶 SQL 注入 - 如果在转义后根据从数据库中检索到的数据重新生成 SQL 查询,则数据将以未转义的方式连接,并可能间接注入 SQL。看
  2. 字符串截断 - (稍微复杂一点) - 场景是你有两个字段,比如用户名和密码,SQL将它们连接起来。两个字段(或仅第一个字段)对长度都有硬性限制。例如,用户名限制为 20 个字符。假设你有这个代码:
username = left(Replace(sInput, "'", "''"), 20)

然后你得到的是用户名,转义,然后修剪为 20 个字符。这里的问题是 - 我会将我的引号贴在第 20 个字符中(例如,在 19 个 a 之后),您的转义引号将被修剪(在第 21 个字符中)。然后 SQL

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

与上述格式错误的用户名相结合将导致密码已经在引号之外,并且只会直接包含有效负载。
3. Unicode 走私 - 在某些情况下,可以传递一个看起来像引号但实际上不是的高级 Unicode 字符,直到它到达数据库,它突然出现。由于当您验证它时它不是报价,因此它很容易通过......有关更多详细信息,请参阅我之前的回复,并链接到原始研究。

22赞 Jørn Jensen 2/19/2009 #17

我意识到这是在提出问题后很长一段时间,但是..

对“引用参数”过程发起攻击的一种方法是使用字符串截断。 根据 MSDN 的说法,在 SQL Server 2000 SP4(和 SQL Server 2005 SP1)中,太长的字符串将被悄悄截断。

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

这可能在“用户管理员”页面场景中最有用,在该场景中,您可以滥用“update”语句来不执行它应该执行的所有检查。

因此,如果您决定引用所有参数,请确保您知道字符串大小的情况,并确保不会遇到截断。

我建议使用参数。总是。只是希望我能在数据库中强制执行。作为副作用,您更有可能获得更好的缓存命中,因为更多的语句看起来相同。(这在 Oracle 8 上确实如此)

评论

1赞 Jørn Jensen 2/19/2009
发布后,我决定 AviD 的帖子会更详细地介绍这一点。希望我的帖子仍然对某人有所帮助。
-3赞 miroxlav 2/21/2016 #18

是的,你可以,如果......

在研究了这个主题之后,我认为按照你的建议清理输入是安全的,但只有在这些规则下:

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

    • SQL 语句验证有问题
  2. 您可以使用 / 列(并在字符串文字前加上 )或将 / 列中的值限制为仅 ASCII 字符(例如,在创建 SQL 语句时抛出异常)nvarcharncharNvarcharchar

    • 这样,您将避免从 CHAR(700) 到 CHAR(39) 的自动撇号转换(可能还有其他类似的 Unicode 黑客)
  3. 您始终验证值长度以适合实际列长度(如果更长,则引发异常)

    • SQL Server 中存在一个已知缺陷,允许绕过截断时引发的 SQL 错误(导致静默截断)
  4. 你要确保这总是SET QUOTED_IDENTIFIERON

    • 请注意,它在解析时生效,即即使在无法访问的代码部分

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

评论

1赞 Hogan 2/23/2016
这就像你没有阅读这个 8 年前问题的所有其他答案一样,因为这些答案中的任何一个都指出,如果攻击者只是使用 unicode 字符,他的方法就无法停止注入
0赞 miroxlav 2/23/2016
@Hogan – 我做到了,但我认为我的问题有额外的价值。我写的东西背后有很多经验和测试。我知道使用查询参数更好,但我也完全理解由于各种原因(例如雇主要求保持旧方式)而必须避免使用查询参数的情况。在这种情况下,我认为我的答案非常全面,比说“不要那样做”的答案具有更高的价值,因为它指明了解决问题的方法。在这里向我展示其他答案,这些答案以相同的方式显示,我会考虑删除我的答案。
0赞 Hogan 2/23/2016
好的,当您的系统受到损害时(不是如果),请回来删除此答案......或者,您可以使用参数化查询。
0赞 miroxlav 2/23/2016
@Hogan – 我:)没有问题但目前我声称,如果您遵守我发布的 4 条规则,则没有已知的方法可以解决这个问题。如果你真的认为有办法解决它,那么只需指出在哪里。
0赞 Shayne 4/16/2019
不好的建议,伙计。任何插值都可以被击败。
1赞 AmirHossein Manian 12/14/2022 #19

我不确定你的情况,但我刚刚在Mysql中遇到了一个案例,它不仅不能阻止SQL注入,而且还会导致注入。 如果输入以 结尾,则无需替换即可,但是在替换尾部时,字符串引号的前尾会导致 SQL 错误。Replace(value, "'", "''")\''\