提问人:Patrick 提问时间:9/26/2008 最后编辑:Peter MortensenPatrick 更新时间:12/14/2022 访问量:91369
我能否通过转义单引号并用单引号括住用户输入来防止 SQL 注入?
Can I protect against SQL injection by escaping single-quote and surrounding user input with single-quotes?
问:
我意识到参数化 SQL 查询是在构建包含用户输入的查询时清理用户输入的最佳方法,但我想知道接受用户输入并转义任何单引号并用单引号括起来整个字符串有什么问题。代码如下:
sSanitizedInput = "'" & Replace(sInput, "'", "''") & "'"
用户输入的任何单引号都将替换为双单引号,这消除了用户结束字符串的能力,因此他们可能键入的任何其他内容(如分号、百分号等)都将是字符串的一部分,而不是作为命令的一部分实际执行。
我们使用的是 Microsoft SQL Server 2000,我相信单引号是唯一的字符串分隔符,也是转义字符串分隔符的唯一方法,因此无法执行用户键入的任何内容。
我看不出有任何方法可以对此发起SQL注入攻击,但我意识到,如果这在我看来是防弹的,那么其他人已经想到了,这将是常见的做法。
这段代码有什么问题?有没有办法让SQL注入攻击通过这种清理技术?利用此技术的示例用户输入将非常有用。
更新:
我仍然不知道有什么方法可以有效地针对此代码发起SQL注入攻击。一些人建议反斜杠会转义一个单引号,让另一个单引号结束字符串,以便字符串的其余部分将作为 SQL 命令的一部分执行,我意识到这种方法可以将 SQL 注入 MySQL 数据库,但在 SQL Server 2000 中,转义单引号的唯一方法(我能够找到)是使用另一个单引号;反斜杠不会这样做。
除非有办法阻止单引号的转义,否则其余的用户输入都不会被执行,因为它将全部被视为一个连续的字符串。
我知道有更好的方法来清理输入,但我真的更感兴趣的是了解为什么我上面提供的方法不起作用。如果有人知道任何针对这种清理方法进行 SQL 注入攻击的具体方法,我很想看到它。
答:
它可能会起作用,但对我来说似乎有点滑稽。我建议通过针对正则表达式测试每个字符串来验证它是否有效。
虽然你可能会找到一个适用于字符串的解决方案,但对于数字谓词,你还需要确保它们只传入数字(简单的检查是它可以解析为整数/双精度/十进制吗?
这是很多额外的工作。
无论如何,这似乎是一个坏主意,正如你所知道的。
像这样转义字符串中的引号怎么样:\'
您的替换将导致:\''
如果反斜杠对第一个引号进行转义,则第二个引号已结束字符串。
评论
对用户输入进行审查的代码将是多么丑陋!然后是 SQL 语句的笨拙的 StringBuilder。准备好的语句方法可以生成更简洁的代码,并且 SQL 注入的好处是一个非常好的补充。
还有为什么要重新发明轮子?
与其将单个引号更改为(看起来像)两个单引号,为什么不将其更改为撇号、引号或完全删除它呢?
无论哪种方式,这都有点笨拙......尤其是当您合法地拥有可能使用单引号的东西(例如名称)时......
注意:您的方法还假设在应用程序上工作的每个人都始终记得在输入到达数据库之前对其进行清理,这在大多数情况下可能并不现实。
评论
如果您有可用的参数化查询,则应始终使用它们。只需一个查询就会从网上溜走,您的数据库就会面临风险。
投入卫生不是你想半途而废的事情。用你的整个屁股。对文本字段使用正则表达式。尝试将数值转换为正确的数值类型,如果不起作用,则报告验证错误。在输入中搜索攻击模式非常容易,例如 “ --.假设用户的所有输入都是敌对的。
评论
flavors
简而言之:永远不要对自己进行查询转义。你一定会出错的。请改用参数化查询,或者如果由于某种原因无法执行此操作,请使用现有库来执行此操作。没有理由自己动手。
评论
我在处理“高级搜索”功能时使用了这种技术,其中从头开始构建查询是唯一可行的答案。(示例:允许用户根据对产品属性的无限约束集搜索产品,将列及其允许值显示为 GUI 控件,以降低用户的学习阈值。
它本身是安全的AFAIK。但是,正如另一位回答者所指出的,您可能还需要处理退格转义(尽管在使用 ADO 或 ADO.NET 将查询传递给 SQL Server 时不会这样做,但不能保证所有数据库或技术)。
问题在于,您确实必须确定哪些字符串包含用户输入(总是潜在的恶意),以及哪些字符串是有效的 SQL 查询。其中一个陷阱是,如果您使用数据库中的值 - 这些值最初是用户提供的吗?如果是这样,他们也必须逃脱。我的答案是在构造 SQL 查询时尝试尽可能晚(但不要晚!
但是,在大多数情况下,参数绑定是要走的路 - 它只是更简单。
评论
首先,这只是不好的做法。输入验证始终是必要的,但也总是不稳定的。
更糟糕的是,黑名单验证总是有问题的,最好明确和严格地定义你接受的值/格式。诚然,这并不总是可能的——但在某种程度上,它必须始终做到。
关于该主题的一些研究论文:
- http://www.imperva.com/docs/WP_SQL_Injection_Protection_LK.pdf
- http://www.it-docs.net/ddata/4954.pdf(披露,最后一个是我的;))
- https://www.owasp.org/images/d/d4/OWASP_IL_2007_SQL_Smuggling.pdf(基于上一篇论文,该论文已不再可用)
关键是,您所做的任何黑名单(以及过于宽松的白名单)都可以被绕过。我论文的最后一个链接显示了甚至可以绕过引号转义的情况。
即使这些情况不适用于您,这仍然是一个坏主意。此外,除非你的应用程序非常小,否则你将不得不处理维护,也许还有一定程度的治理:你如何确保它始终在任何地方都正确完成?
正确的方法:
- 白名单验证:类型、长度、格式或接受的值
- 如果您想列入黑名单,请继续。引号转义是好的,但在其他缓解措施的上下文中。
- 使用 Command 和 Parameter 对象进行预分析和验证
- 仅调用参数化查询。
- 更好的是,只使用存储过程。
- 避免使用动态 SQL,也不要使用字符串连接来生成查询。
- 如果使用 SP,还可以将数据库中的权限限制为仅执行所需的 SP,而不能直接访问表。
- 您还可以轻松验证整个代码库是否仅通过 SP 访问数据库...
评论
sp_executesql
EXEC
sp_executesql
简单的回答:它有时会起作用,但不会一直起作用。 你想对你所做的一切使用白名单验证,但我意识到这并不总是可行的,所以你被迫使用最佳猜测黑名单。同样,您希望在所有内容中使用参数化存储过程,但同样,这并不总是可行的,因此您被迫使用带有参数的sp_execute。
您可以想出任何可用的黑名单(以及一些白名单)的方法。
这里有一篇体面的文章:http://www.owasp.org/index.php/Top_10_2007-A2
如果您需要这样做作为快速修复,以便有时间获得真正的解决方案,那就去做吧。但不要认为你是安全的。
有两种方法可以做到这一点,没有例外,可以避免SQL注入;预准备语句或实用存储过程。
是的,这应该一直有效,直到有人运行 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在你不一定知道的情况下关闭它。诚然,这不是你要找的吸烟枪漏洞,但它是一个相当大的攻击面。当然,如果你也省略了双引号 - 那么我们又回到了我们开始的地方。;)
评论
如果出现以下情况,您的辩护将失败:
- 查询需要一个数字而不是字符串
- 还有其他方法可以表示单引号,包括:
- 转义序列,例如 \039
- Unicode 字符
(在后一种情况下,它必须是在您完成替换后才扩展的东西)
评论
帕特里克,您是否在所有输入周围添加单引号,甚至是数字输入?如果你有数字输入,但没有用单引号括起来,那么你就有风险。
好的,此回复将与问题的更新有关:
“如果有人知道任何针对这种清理方法进行SQL注入攻击的具体方法,我很想看到它。
现在,除了MySQL反斜杠转义之外 - 考虑到我们实际上是在谈论MSSQL,实际上还有3种可能的方法可以仍然SQL注入你的代码
sSanitizedInput = “'” & Replace(sInput, “'”, “''”) & “'”
考虑到这些并非在所有时候都有效,并且非常依赖于围绕它的实际代码:
- 二阶 SQL 注入 - 如果在转义后根据从数据库中检索到的数据重新生成 SQL 查询,则数据将以未转义的方式连接,并可能间接注入 SQL。看
- 字符串截断 - (稍微复杂一点) - 场景是你有两个字段,比如用户名和密码,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 的说法,在 SQL Server 2000 SP4(和 SQL Server 2005 SP1)中,太长的字符串将被悄悄截断。
当您引用字符串时,字符串的大小会增加。每个撇号都是重复的。 然后,这可用于将 SQL 的某些部分推送到缓冲区之外。因此,您可以有效地剪裁掉 where 子句的部分内容。
这可能在“用户管理员”页面场景中最有用,在该场景中,您可以滥用“update”语句来不执行它应该执行的所有检查。
因此,如果您决定引用所有参数,请确保您知道字符串大小的情况,并确保不会遇到截断。
我建议使用参数。总是。只是希望我能在数据库中强制执行。作为副作用,您更有可能获得更好的缓存命中,因为更多的语句看起来相同。(这在 Oracle 8 上确实如此)
评论
是的,你可以,如果......
在研究了这个主题之后,我认为按照你的建议清理输入是安全的,但只有在这些规则下:
你永远不允许来自用户的字符串值变成字符串文字以外的任何东西(即避免提供配置选项:“在此处输入其他 SQL 列名称/表达式:”)。字符串以外的值类型(数字、日期等):将它们转换为其本机数据类型,并为每种数据类型的 SQL 文本提供例程。
- SQL 语句验证有问题
您可以使用 / 列(并在字符串文字前加上 )或将 / 列中的值限制为仅 ASCII 字符(例如,在创建 SQL 语句时抛出异常)
nvarchar
nchar
N
varchar
char
- 这样,您将避免从 CHAR(700) 到 CHAR(39) 的自动撇号转换(可能还有其他类似的 Unicode 黑客)
您始终验证值长度以适合实际列长度(如果更长,则引发异常)
- SQL Server 中存在一个已知缺陷,允许绕过截断时引发的 SQL 错误(导致静默截断)
你要确保这总是
SET QUOTED_IDENTIFIER
ON
- 请注意,它在解析时生效,即即使在无法访问的代码部分
遵守这 4 点,您应该是安全的。如果违反其中任何一个,则会打开一种 SQL 注入方法。
评论
我不确定你的情况,但我刚刚在Mysql中遇到了一个案例,它不仅不能阻止SQL注入,而且还会导致注入。
如果输入以 结尾,则无需替换即可,但是在替换尾部时,字符串引号的前尾会导致 SQL 错误。Replace(value, "'", "''")
\'
'
\
评论