PHP处理0e开头md5哈希字符串(判断比较)缺陷/bug 详解
漏洞描述
PHP在处理哈希字符串时,会利用”!=”或”==”来对哈希值进行比较,它把每一个以”0E”开头的哈希值都解释为0,所以如果两个不同的密码经过哈希以后,其哈希值都是以”0E”开头的,那么PHP将会认为他们相同,都是0。
攻击者可以利用这一漏洞,通过输入一个经过哈希后以”0E”开头的字符串,即会被PHP解释为0,如果数据库中存在这种哈希值以”0E”开头的密码的话,他就可以以这个用户的身份登录进去,尽管并没有真正的密码。
攻击案例:
4月8日,wordpress发布了一个重要更新,在该次更新中,修复了一系列安全漏洞。其中最显眼的就是cookie伪造漏洞(CVE -2014- 0166)。
我们来看修补的代码:
$key = wp_hash($username . $pass_frag . '|' . $expiration, $scheme);
$hash = hash_hmac('md5', $username . '|' . $expiration, $key);
- if ( $hmac != $hash ) {
+ if ( hash_hmac( 'md5', $hmac, $key ) !== hash_hmac( 'md5', $hash, $key ) ) {
前日尝试分析该漏洞,奈何知识匮乏,对php特性不够了解,一连用了几个小时都想不出。想不通为何加了一次hash_hmac就可以修复漏洞。当时的我以为是涉及到了加密算法上的一些问题,就放弃了。
今日突然看到一篇文章,顿悟是comparision operator的问题。其实之前也有注意到 “!=” 与 “!==” 这两个符号的不同,不过大致想了一下没想到利用的可能,就把重点放在了hash_hmac上。也许这是wordpress的障眼法?呵呵,说笑。
我们把全部的关注点放到 “!=” 与 “!==” 上来:
我们知道php比较相等性的运算符有两种,一种是strict,一种是non-strict。php manual中是这样定义的:
PHP 比较运算符
PHP 比较运算符用于比较两个值(数字或字符串):
运算符 | 名称 | 例子 | 结果 |
---|---|---|---|
== | 等于 | $x == $y | 如果 $x 等于 $y,则返回 true。 |
=== | 全等(完全相同) | $x === $y | 如果 $x 等于 $y,且它们类型相同,则返回 true。 |
!= | 不等于 | $x != $y | 如果 $x 不等于 $y,则返回 true。 |
<> | 不等于 | $x <> $y | 如果 $x 不等于 $y,则返回 true。 |
!== | 不全等(完全不同) | $x !== $y | 如果 $x 不等于 $y,或它们类型不相同,则返回 true。 |
> | 大于 | $x > $y | 如果 $x 大于 $y,则返回 true。 |
< | 小于 | $x < $y | 如果 $x 小于 $y,则返回 true。 |
>= | 大于或等于 | $x >= $y | 如果 $x 大于或者等于 $y,则返回 true. |
<= | 小于或等于 | $x <= $y | 如果 $x 小于或者等于 $y,则返回 true。 |
"==="和"!=="即strict比较符,只有在类型相同时才相等。"=="和"!="即non-strict比较符,会在类型转换后进行比较。
再看php manual中给出的例子:
1 2 3 4 5 6 | <?php var_dump(0 == "a"); // 0 == 0 -> true var_dump("1" == "01"); // 1 == 1 -> true var_dump("10" == "1e1"); // 10 == 10 -> true var_dump(100 == "1e2"); // 100 == 100 -> true ?> |
字符串在与数字比较前会自动转换为数字,所以0=="a"了。
另外两个字符串比较,如果两个都是数字形式,则同时转换为数字进行比较,所以"1"=="01"。
这时你要问了,wordpress的代码中是将cookie中的hash和真实hash这两个hash的字符串进行比较,和这个有什麽关系呢?
我们注意上面"10"=="1e1"这个例子,php智能的将科学计数形式的字符串转换为对应数字(1e1 = 1*10^1 = 1)。两个不同的字符居然相等了,不知到这里你是否得到了启示?
好吧,来一个明显的,你一定会恍然大悟:
var_dump("0e123456789012345678901234567890"==="0") //false
var_dump("0e123456789012345678901234567890"=="0") //true
当hash以"0exxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"之类的形式出现时,与"0"相等。
让我们回到wordpress的验证代码上来。先生成根据用户名($username)、密码($pass_frag)、cookie有效期($expiration)、wp-config.php中的key($key)四个信息计算出对应的$hash (算法很简单,不细说), 然后用cookie中取得的$hmac值与之进行比较($hmac != $hash ?),从而验证cookie有效性。
另外提下,cookie的格式是这样的:wordpress_hashofurl=username|expiration|hmac
我们能控制的变量有$username和$expiration,其中$username需要固定。于是我们可以通过控制cookie中的$expiration去改变$hash的值,然后将cookie中的$hmac设置为0
只要不断改变$expiration,直到满足$hash=="0"的$hash出现,就成功伪造了有效的cookie。
当然,通过粗略的数学概率运算即可推知,满足条件的hash出现概率非常之低:
P = Sum(10^n,n=0,30)/16^32 = 3.26526*10^-9
接近三亿个请求才有一个可碰上的概率,呜呜~
有时看人品,人品好的很快就碰到了,人品不好的就如我,几乎五亿了:
怎么样,少年,写一个多线程脚本跑起?
影响范围
影响大量web站点,其中的登录认证,“忘记密码”,二进制校验,cookie等方面,由于都会用到PHP中的哈希函数值比较,因此都存在隐患。
解决方案
分析使用PHP的web站点中哈希比较函数,将其中的”==”,”!=”分别更改为”===”和”!===”。
PHP在处理哈希字符串时,会利用”!=”或”==”来对哈希值进行比较,
它把每一个以”0E”开头的哈希值都解释为0,所以如果两个不同的密码经过哈希以后,
其哈希值都是以”0E”开头的,那么PHP将会认为他们相同,都是0。
0x01 md5(str)
QNKCDZO
0e830400451993494058024219903391
s878926199a
0e545993274517709034328855841020
s155964671a
0e342768416822451524974117254469
s214587387a
0e848240448830537924465865611904
s214587387a
0e848240448830537924465865611904
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s1885207154a
0e509367213418206700842008763514
s1502113478a
0e861580163291561247404381396064
s1885207154a
0e509367213418206700842008763514
s1836677006a
0e481036490867661113260034900752
s155964671a
0e342768416822451524974117254469
s1184209335a
0e072485820392773389523109082030
s1665632922a
0e731198061491163073197128363787
s1502113478a
0e861580163291561247404381396064
s1836677006a
0e481036490867661113260034900752
s1091221200a
0e940624217856561557816327384675
s155964671a
0e342768416822451524974117254469
s1502113478a
0e861580163291561247404381396064
s155964671a
0e342768416822451524974117254469
s1665632922a
0e731198061491163073197128363787
s155964671a
0e342768416822451524974117254469
s1091221200a
0e940624217856561557816327384675
s1836677006a
0e481036490867661113260034900752
s1885207154a
0e509367213418206700842008763514
s532378020a
0e220463095855511507588041205815
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s214587387a
0e848240448830537924465865611904
s1502113478a
0e861580163291561247404381396064
s1091221200a
0e940624217856561557816327384675
s1665632922a
0e731198061491163073197128363787
s1885207154a
0e509367213418206700842008763514
s1836677006a
0e481036490867661113260034900752
s1665632922a
0e731198061491163073197128363787
s878926199a
0e545993274517709034328855841020
0x02 md5(md5())
0x03 md5(md5(str)."SALT")
2
0e774261293712168181959463563504
文章来源:http://www.cnblogs.com/Primzahl/p/6018158.html
https://www.jianshu.com/p/9fd9355bfd59
布施恩德可便相知重
微信扫一扫打赏
支付宝扫一扫打赏