我可以配置 MySQL 的类型转换来考虑 0 != 'foo' 吗?

Nat*_*ong 3 mysql security configuration

本文指出 MySQL 存在以下安全问题:

USERS
id | email            | password_reset_token
1  | one@example.com  | QTlXww)uV!Hdg3U6aGwKV2FPvAqzVgPx
2  | two@example.com  | CZor5t7WbX#LTeqiG3v@6f3@z#)BfK*n
Run Code Online (Sandbox Code Playgroud)

在这里,我们有很好的随机令牌,用户必须证明他们拥有才能重置他们的帐户。但是用户设法提交了一个重置​​令牌0,所以我们运行这个查询:

SELECT * FROM `users` WHERE `email` = 'one@example.com' AND `password_reset_token` = 0
Run Code Online (Sandbox Code Playgroud)

MySQL将令牌转换VARCHAR为 anINT以进行比较。它认为一个VARCHAR不以数字开头的 a等于 0。因此,攻击者可以匹配任何非数字字符串并接管帐户

即使重置令牌开始一些数字,情况也好不到哪里去。MySQL 在进行此比较时忽略除初始数字之外的所有字符,因此它考虑12blahblah3blah4等于12,这使得猜测更容易。

我可以将 MySQL 配置为不进行这种类型转换吗?例如,如果它投射INTVARCHAR而不是反之,则此攻击将不起作用。

笔记

如果使用'0'而不是运行查询0,这将不起作用。本文根据 Ruby on Rails 对 XML 的接受程度来讨论此漏洞,其中type=integer属性说服 Rails 在查询中发送一个实际整数。

该错误已在 Rails 中修复;它现在将所有请求参数转换为字符串,因此它永远不会使用整数构建查询。但我仍然认为 MySQL 应该是可配置的以防止这种情况发生。

Mic*_*bot 11

但是,问题在于您提供的是一个未加引号的数值,因此 MySQL 尝试将比较的另一侧转换为 a DOUBLE,将其与您提供的数字参数进行比较......这给您留下了 0这相当于 0。

mysql> select 'foo' = 0;
+-----------+
| 'foo' = 0 |
+-----------+
|         1 |
+-----------+
1 row in set, 1 warning (0.00 sec)

mysql> show warnings;
+---------+------+-----------------------------------------+
| Level   | Code | Message                                 |
+---------+------+-----------------------------------------+
| Warning | 1292 | Truncated incorrect DOUBLE value: 'foo' |
+---------+------+-----------------------------------------+
1 row in set (0.00 sec)
Run Code Online (Sandbox Code Playgroud)

如果在字符串上下文中提供 0,则计算结果为 false。

mysql> select 'foo' = '0';
+-------------+
| 'foo' = '0' |
+-------------+
|           0 |
+-------------+
1 row in set (0.00 sec)
Run Code Online (Sandbox Code Playgroud)

因此,您可以用来避免这种行为的一个选项是将您的输入值转换为CHAR...,大多数情况下,它已经是...但如果不是,它会按照您的预期进行评估。

mysql> select 'foo' = cast(0 as char);
+-------------------------+
| 'foo' = cast(0 as char) |
+-------------------------+
|                       0 |
+-------------------------+
1 row in set (0.00 sec)
Run Code Online (Sandbox Code Playgroud)

update,更全面地回答这个问题:

我可以将 MySQL 配置为不进行这种类型转换吗?

不,似乎没有任何方法可以通过配置来避免这种情况 - 只能通过显式转换您的可能看起来像数字的字符串 - 到一个字符串,您已经说过无法完成,因为您是使用 ORM。最有可能的地方,如果存在这样的东西,将是SQL Server Mode,当涉及到比较时,这些选项都不会修改这种行为。

Bug #63112 中有一些关于隐式转换的讨论:

选择橙色>香蕉;

这是对还是错?嗯.. 通常香蕉比橙子长。因此,如果长度是标准,则上述陈述是错误的。但大多数情况下,橙子比香蕉重,因此如果以重量为标准,则上述陈述是正确的。

在比较不同类型(橙色与香蕉,字符串与整数)时,必须使用一些“标准”或“规则”。当 MySQL 比较不同的数据类型时,它们在比较之前 [两者] 在内部都被强制转换为 DOUBLE。整数“1”转换为双精度“1”——字符串“a”或“UUID()”转换为双精度“0”。[...]

请注意,此错误报告的范围并不严格限于在上下文中隐式转换,UUID()这里的提及有点UUID()让人分心,因为可以返回以数字开头的字符串并转换为其他数字......但我认为它确实用于确认没有替代退出,因为除了“如果您想避免强制转换,请不要使用不同类型进行比较”之外没有任何内容,我们已经知道。它继续:

避免强制转换为DOUBLE内部确保比较的值是相同的数据类型。在您的情况下,id列是字符串,您与之比较的值也应该是字符串(不是 1 而是 '1' - 不是 2 而是 '2' 等)或在您的语句中使用cast()/ convert()[...]

关于你的建议,如果要进行隐式转换,那么数字应该变成一个字符串而不是字符串变成一个数字......我可以看到你的观点,在某种程度上,对于严格的相等比较......但是有很多除了=使用该逻辑的其他运算符之外,还会出现一系列全新的问题......

mysql> SELECT CAST(100 AS CHAR) < '2';
+-------------------------+
| CAST(100 AS CHAR) < '2' |
+-------------------------+
|                       1 |
+-------------------------+
1 row in set (0.00 sec)
Run Code Online (Sandbox Code Playgroud)

正确的!字符串“100”在字母意义上“小于”字符串“2”……就像字符串“AXX”“小于”字符串“B”一样。所以这个替代方案肯定有它的缺点。

然而,还有第三种选择,这似乎是最准确的逻辑(尽管我欢迎建设性的反对意见):如果以非数字字符开头的字符串被强制转换为 a DOUBLE(或任何其他数字类型,就此而言) ),看起来最正确的结果NULL

不幸的是,这不是 MySQL 的方法。似乎这可以在源代码中修复,可能在sql/field.cc其中 my_strntod() 返回的“0”似乎在发生错误时抛出警告后简单地返回给调用者,但尝试在源代码中进行如此深入的破解超出了我的专业知识。