148

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

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

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

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

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

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


更新:

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

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

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

4

18 回答 18

91

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

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

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

正确的做法:

  • 白名单验证:类型、长度、格式或接受的值
  • 如果您想加入黑名单,请继续。引用转义很好,但在其他缓解措施的范围内。
  • 使用命令和参数对象,预解析和验证
  • 仅调用参数化查询。
  • 更好的是,只使用存储过程。
  • 避免使用动态 SQL,并且不要使用字符串连接来构建查询。
  • 如果使用 SP,您还可以将数据库中的权限限制为仅执行所需的 SP,而不是直接访问表。
  • 您还可以轻松验证整个代码库是否仅通过 SP 访问数据库...
于 2008-09-26T14:18:45.813 回答
41

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

“如果有人知道针对这种清理方法发起 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 字符——直到它到达数据库时,它突然出现。由于验证时它不是引用,因此它会很容易...有关更多详细信息,请参阅我以前的回复,并链接到原始研究。

于 2008-12-18T02:03:54.007 回答
28

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

于 2008-09-26T12:51:17.427 回答
21

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

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

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

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

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

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

于 2009-02-19T10:57:55.770 回答
11

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

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

问题是你真的必须确定哪些字符串包含用户输入(总是潜在的恶意),哪些字符串是有效的 SQL 查询。陷阱之一是如果您使用数据库中的值——这些值最初是用户提供的吗?如果是这样,他们也必须逃脱。我的回答是在构建 SQL 查询时尽可能晚地进行清理(但不能更晚!​​)。

然而,在大多数情况下,参数绑定是可行的方法——它更简单。

于 2008-09-26T12:51:34.853 回答
9

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

于 2008-09-26T12:51:02.310 回答
6

正如你所知道的那样,无论如何这都是个坏主意。

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

您的替换将导致:\''

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

于 2008-09-26T12:45:33.307 回答
6

简单的答案:它有时会起作用,但并非一直有效。你想对你所做的每件事都使用白名单验证,但我意识到这并不总是可能的,所以你不得不选择最好的猜测黑名单。同样,您希望在Everything中使用参数化的存储过程,但同样,这并不总是可行的,因此您不得不使用带参数的 sp_execute。

您可以通过多种方法解决任何可用的黑名单(以及一些白名单)。

一篇不错的文章在这里:http ://www.owasp.org/index.php/Top_10_2007-A2

如果您需要将此作为快速解决方案,以便让您有时间获得一个真正的到位,那就去做吧。但不要认为你是安全的。

于 2008-09-26T19:32:42.750 回答
6

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

于 2008-09-29T18:50:27.977 回答
4

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

于 2008-09-26T12:49:21.770 回答
4

帕特里克,您是否在所有输入周围添加单引号,甚至是数字输入?如果您有数字输入,但没有在其周围加上单引号,那么您就有了曝光。

于 2008-11-14T20:01:32.143 回答
3

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

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

SQL Server Native Client ODBC 驱动程序和用于 SQL Server 的 SQL Server Native Client OLE DB Provider 在连接时自动将 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 有很多方法可以在您不知道的情况下关闭。诚然 - 这不是您正在寻找的确凿证据,但它是一个相当大的攻击面。当然,如果您还转义了双引号 - 那么我们又回到了开始的地方。;)

于 2008-10-20T21:22:33.273 回答
3

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

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

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

于 2008-10-22T13:53:16.970 回答
1

对用户输入进行清理的所有代码将是多么丑陋!然后是用于 SQL 语句的笨重的 StringBuilder。准备好的语句方法导致代码更简洁,SQL 注入的好处是一个非常好的补充。

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

于 2008-09-26T12:45:48.517 回答
1

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

无论哪种方式,这都有点杂乱无章...尤其是当您合法拥有可能使用单引号的事物(例如名称)时...

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

于 2008-09-26T12:48:17.793 回答
-1

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

这是很多额外的工作。

于 2008-09-26T12:45:29.813 回答
-2

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

于 2008-09-26T12:45:14.960 回答
-3

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

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

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

    • SQL 语句难以验证
  2. 您要么使用nvarchar/nchar列(并使用前缀字符串文字N)或将进入varchar/char列的值限制为仅 ASCII 字符(例如,在创建 SQL 语句时抛出异常)

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

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

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

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

于 2016-02-20T22:05:06.030 回答