我是否可以通过转义单引号或者用单引号来包裹用户的输入来防止 SQL 注入?

我意识到,当构建包含用户输入的查询时,参数化 SQL 查询是清除用户输入的最佳方式,但我想知道,获取用户输入并转义任何单引号以及用单引号包围整个字符串有什么错。密码是这样的:

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

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

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

我没有看到任何方法来启动一个 SQL 注入攻击,但我意识到,如果这是刀枪不入的,因为它似乎对我来说,别人会已经想到了这一点,这将是常见的做法。

这个代码有什么问题吗?有没有办法让 SQL 注入攻击通过这种消毒技术?利用此技术的示例用户输入将非常有帮助。


更新:

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

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

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

87362 次浏览

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

虽然您可能会找到一个适用于字符串的解决方案,但是对于数字谓词,您还需要确保它们只传递数字(简单的检查是,它可以被解析为 int/double/decal 吗?).

有很多额外的工作。

反正你也知道这不是个好主意。

比如像这样在字符串中转义引用:

你的替换将导致:

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

对用户输入进行消毒会是多么丑陋的代码啊!然后是用于 SQL 语句的笨重的 StringBuilder。准备好的语句方法产生了更清晰的代码,SQL 注入的好处是一个非常好的补充。

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

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

无论哪种方式,这是一个有点拼凑... 尤其是当你合法地有东西(如名字) ,可能使用单引号..。

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

如果您有可用的参数化查询,那么您应该一直使用它们。只要一个查询漏过网络,你的数据库就有危险了。

投入环境卫生可不是你想半途而废的东西。用你的整个屁股。在文本字段上使用正则表达式。尝试将数值转换为适当的数值类型,如果不起作用,则报告验证错误。在输入中搜索攻击模式非常容易,比如“——”。假设来自用户的所有输入都是敌对的。

简而言之: 永远不要质疑逃避自己。你一定会出错的。相反,可以使用参数化查询,或者如果由于某种原因无法这样做,可以使用现有的库来实现这一点。没必要自己动手。

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

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

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

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

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

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

即使这些情况不适用于你,它仍然是一个坏主意。此外,除非您的应用程序非常小,否则您将不得不处理维护,可能还有一定数量的治理: 您如何确保它在任何时间、任何地点都正确运行?

正确的做法是:

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

简单的回答: 它有时会起作用,但不是一直起作用。 你想在 一切上使用白名单验证,但我意识到这并不总是可能的,所以你不得不使用最佳猜测黑名单。同样,您希望在 一切中使用参数化存储进程,但是同样,这并不总是可能的,因此必须使用带参数的 sp _ execute。

有很多方法可以绕过任何可用的黑名单(还有一些白名单)。

一份不错的报告在这里: http://www.owasp.org/index.php/Top_10_2007-A2

如果你需要这样做作为一个快速修复给你时间得到一个真正的地方,这样做。但别以为你很安全。

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

是啊,这应该工作的权利,直到有人运行 设置引用 _ 标识符关闭和使用双引号对你。

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

SQLServer 本机客户端 ODBC 驱动程序和 SQLServer 本机客户端 OLEDB 提供程序在连接时自动将 QUOTED _ IDENTIFIER 设置为 ON。这可以在 ODBC 数据源、 ODBC 连接属性或 OLEDB 连接属性中配置。对于来自 DB-Library 应用程序的连接,SETQUOTED _ IDENTIFIER 的默认值为 OFF。

创建存储过程时,SETQUOTED _ IDENTIFIER 和 SETANSI _ NULLS 设置被捕获并用于该存储过程的后续调用

设置 QUOTED _ IDENTIFIER also 对应于 ALTERDATABASE 的 QUOTED _ IDENTIFER 设置。

SETQUOTED _ IDENTIFIER 是 在分析时设置。分析时设置意味着,如果 SET 语句出现在批处理或存储过程中,则无论代码执行是否实际到达该点,SET 语句都会生效; 而且 SET 语句在执行任何语句之前就会生效。

有很多方法可以在您不必知道的情况下关闭 QUOTED _ IDENTIFIER。不得不承认,这不是你想要的确凿证据,但它是一个相当大的攻击面。当然,如果你也省略了双引号——那么我们又回到了开始的地方。;)

如果:

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

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

帕特里克,你是不是在所有输入,甚至是数字输入前后都加了单引号?如果您有数字输入,但是没有在其周围放置单引号,那么您就有了一个曝光。

好的,这个回答会涉及到问题的更新:

“如果有人知道任何针对这种消毒方法的 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字符,就像一个报价,但是 不是-直到它到达数据库,在那里突然 是的。由于它不是一个报价时,你验证它,它将通过容易... 见我以前的回应更多细节,并链接到原始研究。

我知道这个问题已经过去很久了,但是。

攻击“引用参数”过程的一种方法是使用字符串截断。 根据 MSDN,在 SQLServer2000SP4(和 SQLServer2005SP1)中,过长的字符串将被悄悄地截断。

当你引用一个字符串时,字符串的大小会增加。每个撇号都会重复。 然后,可以使用这个函数将 SQL 的某些部分推到缓冲区之外。所以你可以有效地去掉 where 子句的某些部分。

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

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

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

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

在研究了这个主题之后,我认为像你建议的那样经过消毒的输入是安全的,但是只有在以下规则下:

  1. 永远不要让来自用户的字符串值变成字符串字面值以外的任何东西(例如,避免给出配置选项: “在这里输入其他 SQL 列名/表达式:”)。字符串以外的值类型(数字、日期、 ...) : 将它们转换为其本机数据类型,并为每种数据类型的 SQL 文本提供一个例程。

    • SQL 语句的验证是有问题的
  2. 你要么使用 nvarchar/nchar列(前缀字符串文字与 N)或限制值进入 varchar/char列到 ASCII 字符只(例如,抛出异常时创建 SQL 语句)

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

    • SQLServer 中存在一个已知的缺陷,允许绕过在截断时抛出的 SQL 错误(导致静默截断)
  4. 确保 SET QUOTED_IDENTIFIER始终是 ON

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

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