author: [email protected]
0x00 背景
在github翻来覆去看了半天,官方版的diff只在php里改动了一个位置:
#!diff
- if ( $hmac != $hash ) {
+ if ( hash_hmac( 'md5', $hmac, $key ) !== hash_hmac( 'md5', $hash, $key ) ) {
WP的开发人员也只是含糊的说这个版本修复了一个可以伪造cookie的漏洞。苦思半天翻来覆去看代码之后,甚至顺带挖出了个0day,才发现原来自己又想多了,这个洞的原理其实很简单,那就是边信道攻击。
0x01 细节
边信道攻击我就不具体解释了,这个地方补的是一个关于HMAC的边信道攻击,利用时间差来判断HMAC。
HMAC是一种加密后的hash,比如WP里用的是HMAC-MD5,外观和md5一样,也是长度位32的16进制字符串,但是经过一个key加密,用来防止重放攻击等。
通过时间差攻击(Timing attack),可以获取完整的HMAC从而伪造cookie。
首先我们来看下wordpress登录后的cookie长什么样,拿乌云drops为例:
wordpress_logged_in_7065d11a793a3ec8482214fcc4f0a55b=insight-labs%7C1397480887%7Cxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
cookie名字wordpress_logged_in_
后面的那个hash看似很神秘,其实是:
#!php
if ( !defined( 'COOKIEHASH' ) ) {
$siteurl = get_site_option( 'siteurl' );
if ( $siteurl )
define( 'COOKIEHASH', md5( $siteurl ) );
也就是说其实就是wp网站的url,比如drops的是'http://drops.wooyun.org',md5 一下你就知道了。
但是cookie的内容就没那么简单了,%7c是键盘上的竖线 |
。
wp的cookie用竖线作为分隔符,前面是用户名,中间是cookie过期时间,后面是32位的hmac。
hmac的来源比较复杂,但是如果我们能得到这个hmac,我们就可以登录任意已知用户了(WP并没有在本地记录登录session,全靠cookie)。
下面来看下HMAC timing attack的最基本原理,字符串对比。
即使没有看到php的源码,从理性角度分析,任何程序语言的字符串对比应该都是这样实现的:
如果两个字符串第一个字符不相同,那么后面的即使相同也没有意义了,所以返回 False。
如果第一个字符相同,那么到第二个字符,如果第二个字符也相同,判断第三个字符...直到最后一个字符,如果其中有一个字符不一样,那么就终止后续判断,返回 False,如果全部一样,就返回 True。
看到这里大家应该都懂了,那就是判断
'abcdef'=='zxcvbn'
比
'abcdef'=='abcdeg'
用的时间短。
0x02 攻击
构造POC:
下面我有一个32位的md5 hash:
f2835bb2a6ab584fc5cf268bb384c598
有个简单的php程序
#!php
<?php
$hash='f2835bb2a6ab584fc5cf268bb384c598'
if($_GET['hmac']!=$hash){
exit('Go away...')
}
echo 'You are admin!'
?>
然后我提交:
00000000000000000000000000000000
并且记录从提交到服务器返回结果所需要的时间(需要精确到微秒,起码也得是毫秒级)
之后提交
10000000000000000000000000000000
..
20000000000000000000000000000000
..
30000000000000000000000000000000
.
.
.
f0000000000000000000000000000000
从0-f,然后对比一下他们所需要的时间:
0 0.005450913
1 0.005829198
2 0.004905407
3 0.005286876
4 0.005597611
5 0.004814430
6 0.004969118
7 0.005335884
8 0.004433182
9 0.004440246
a 0.004860263
b 0.004561121
c 0.004463188
d 0.004406799
e 0.004978907
f 0.004887240
等等……这不是差不多么,而且明明第一位是f,但是7的时间最长,这尼玛不是……
但是统计学告诉我们,任何微小的差异在重复多次后都会放大,所以这次我们把每个请求轮流跑500遍,然后都记录下来,注意,要轮流跑,如果先把0跑500遍再把1跑500遍,第二次对比相同内容的时候服务器返回都飞快,似乎有某种缓存机制。
把第一位的0-f轮流跑了500次之后,把记录的时间差做成图:
可以看出,多次重试增加了误差的统计显著性。
从图里可以看出,大部分情况下0-e的反应速度都在0.005毫秒以内,但是f却大于这个时间。
知道了第一位之后我们可以用同样的方法去计算第二位,这次把已知的第一位放进去:
f0000000000000000000000000000000
f1000000000000000000000000000000
f2000000000000000000000000000000
.
.
.
ff000000000000000000000000000000
相同过程就不再阐述了,不过大家可以试试看,计算所有响应时间的方式是用平均值好还是标准差好。
通过这种攻击方式,我们只需要16*500*32=256000
次请求即可获取完整的HMAC cookie。
这下就能理解官方补丁的用意了,这样虽然没有去掉时间差(时间差总是存在的,除非用恒定时间对比算法),但是因为多加了一层hmac,攻击者的payload被随机化了,无法通过时间差来猜测具体字符是哪一个。
不过在互联网环境下,这个洞还是有点鸡肋,但是如果是在同一个机房或者虚拟主机同站的话效果应该还是不错的。
WP的这个洞是补上了,我相信大家会举一反三的,毕竟用 == 或者!=的地方太多了。
PS: 小编欠饭次数+=1
6666666666666666
mark
这种思路 真是太牛逼了,深深的受教了!
给力!
牛叉!
测试了发现,多次统计发现,f这个值太特殊。
我用123的MD5测试,统计发现,f速度最快的次数是最多的,
无论第一位是什么。
只在本地测试了下,不知道和联网是不是有差。
<?php
//得到当前毫秒时间
function microtime_float() {
list($usec, $sec) = explode(" ", microtime());
return ((float) $usec + (float) $sec);
}
//比较$hmac1和$hmac2的值 返回比较所花费的毫秒时间
// 0000000000000000000000000000000
function check_string($hmac1,$hmac2="f2835bb2a6ab584fc5cf268bb384c598") {
$t1 = microtime_float();
if ($hmac1 == $hmac2) {
exit('The END!');
}
$t2 = microtime_float();
return (float) ($t2 - $t1);
}
$time = array();
$min_keys = array();
$hex = array(0,1,2,3,4,5,6,7,8,9,'a','b','c','d','e','f');
for ($s = 0; $s 1540
[6] => 1532
[5] => 1308
[9] => 1155
[4] => 1042
[1] => 876
[3] => 766
[2] => 615
[7] => 368
[0] => 254
[f] => 115
[d] => 95
[e] => 93
[a] => 87
[c][/c] => 86
[b] => 67
)
@insight-labs 你好,可以申请转载到我的个人博客吗?
有点太理想化了, 这样很多if( a == b)的判断都能破解
不错,学习了思路
尼玛 这已经可以上升为语言本身漏洞了!
刚写了一篇,发现和freebuf是一样的 。。。。
思路赞
看到freebuf那篇,原来同时修复了两个漏洞哎。
时间到微秒级别的话网速就是问题了
晕了。。。。。。
另外那个洞的我也看了,可能一次补两个洞,不然没必要用double hmac,把!=换成!==就行了
你确定修复是你这个吗!?
膜拜 这个方式 这个思路 好奇特
点3个赞
太理想化了。。投入实战难度有点大。。
表示美帝太意淫了
刚才又玩了下,其实每次对比一个字符需要多少时间是和cpu频率有关的。所以如果知道目标的系统cpu频率的话,可以针对误差做出修正,过滤掉偏差值太大的数据,可以不用跑500次,可能50-100次就能找到正确的值。
思路高端...
赞!
思路很赞呐
补丁那块有点像ASLR,你发送过去payload再经过一层HMAC后(不知道key的话无法预测hmac后的内容) ,比如你发送00000000000000000000000000000000,经过hmac后变成03ac9b8ce112c99de723b77225403286,发送10000000000000000000000000000000,经过hmac后变成了
5436449fc8bd1610cafeb1289743ad5c,由于key未知,无法掌握你发送的数据和被加密后数据的映射关系,所以无法再通过时间差来判断hmac的值了
赞!
思路太牛了,不得不赞一个!
怒赞!
好高端哦!
真是太强大了
这里的文章的确很有味道,本人不懂代码,但还是看懂了思路。
利用时间+统计学,这种方法太有创造性了,赞。
另外,关于WordPress的补丁那块,通俗些讲是什么原理?