本文指出了 MySQL 的以下安全问题:
USERS
id | email | password_reset_token
1 | [email protected] | QTlXww)uV!Hdg3U6aGwKV2FPvAqzVgPx
2 | [email protected] | CZor5t7WbX#LTeqiG3v@6f3@z#)BfK*n
这里我们有很好的随机令牌,用户必须证明他们拥有这些令牌才能重置他们的帐户。但是用户设法提交了重置令牌0
,因此我们运行此查询:
SELECT * FROM `users` WHERE `email` = '[email protected]' AND `password_reset_token` = 0
MySQL将令牌转换VARCHAR
为 anINT
以便进行比较。它将VARCHAR
不以数字开头的 a 视为等于 0。因此,攻击者可以匹配任何非数字字符串并接管该帐户。
即使重置令牌开始一些数字,情况也好不了多少。进行此比较时,MySQL 会忽略除初始数字以外的所有字符,因此它认为12blahblah3blah4
等于12
,这使得猜测更容易成倍增加。
我可以配置 MySQL 不进行这种类型转换吗?例如,如果它强制INT
转换为VARCHAR
而不是相反,则此攻击将无效。
笔记
如果使用'0'
而不是运行查询0
,则这不起作用。本文从 Ruby on Rails 接受 XML 的角度讨论了这个漏洞,其中一个type=integer
属性说服 Rails 在查询中发送一个实际的整数。
该错误已在 Rails 中修补;它现在将所有请求参数转换为字符串,因此它永远不会使用整数构建查询。但我仍然认为 MySQL 应该是可配置的以防止这种情况发生。
但是,问题是您提供的是一个不带引号的数值,因此 MySQL 会尝试将比较的另一端转换为 a
DOUBLE
,以将其与您提供的数字参数进行比较……这会给您留下 0那相当于0。如果您在字符串上下文中提供 0,则其计算结果为 false。
因此,您可以用来避免这种行为的一种选择是将您的输入值转换为
CHAR
...,大多数情况下,它已经是 ... 但如果不是,它的计算结果与您预期的一样。更新,更全面地回答问题:
不,似乎没有任何方法可以通过配置来避免这种情况——只能通过将你的潜在数字字符串显式转换为一个字符串,你说过这是无法完成的,因为你是使用 ORM。如果存在类似的东西,最有可能的地方是SQL Server Mode,并且在比较时,这些选项都不会修改此行为。
在Bug #63112中有一些像这样的隐式转换的讨论:
请注意,此错误报告的范围并不严格限于在 out 上下文中隐式转换,
UUID()
这里的提及有点让人分心,因为UUID()
可以返回以数字开头的字符串并转换为其他数字......但我认为它确实用于确认没有其他选择存在,因为除了我们已经知道的“如果你想避免强制转换,不要使用不同的类型进行比较”之外,没有其他内容。它继续:关于你的建议,如果要进行隐式转换,那么数字应该变成一个字符串而不是字符串变成一个数字......我明白你的观点,在某种程度上,对于严格的相等比较......但是有很多除了其他运算符
=
并使用该逻辑之外,还会出现一系列全新的问题......正确的!字符串“100”在字母意义上“小于”字符串“2”……就像字符串“AXX”“小于”字符串“B”一样。因此,这种替代方案肯定有其缺点。
然而,还有第三种选择,这似乎是最准确的逻辑(尽管我欢迎建设性的反对意见):如果将以非数字字符开头的字符串转换为
DOUBLE
(或任何其他数字类型,就此而言) ),看起来该转换的最正确结果是NULL。不幸的是,这不是 MySQL 的方法。似乎这可以在源代码中修复,可能是在
sql/field.cc
my_strntod() 返回的“0”似乎只是在发生错误时抛出警告后返回给调用者的地方,但是在源代码的深处尝试这样的破解超出了我的专业范围。首先,如果您的最终查询是这样生成的,这似乎意味着您直接获取用户输入并将其粘贴到查询模板中,这本身就是 sql 注入的问题。此外,即使您这样做,查询模板也需要用引号引起来才能使 charish 输入的合法请求在语法上有效。
无论您的应用程序使用何种语言,ruby 或其他语言,请查看它们如何执行参数化查询和准备好的语句。API 将为您处理所有正确的转义。