Wordpress 3.8.2补丁分析 HMAC timing attack

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的时间最长,这尼玛不是……

enter image description here

但是统计学告诉我们,任何微小的差异在重复多次后都会放大,所以这次我们把每个请求轮流跑500遍,然后都记录下来,注意,要轮流跑,如果先把0跑500遍再把1跑500遍,第二次对比相同内容的时候服务器返回都飞快,似乎有某种缓存机制。

把第一位的0-f轮流跑了500次之后,把记录的时间差做成图:

enter image description here

可以看出,多次重试增加了误差的统计显著性。

从图里可以看出,大部分情况下0-e的反应速度都在0.005毫秒以内,但是f却大于这个时间。

知道了第一位之后我们可以用同样的方法去计算第二位,这次把已知的第一位放进去:

f0000000000000000000000000000000  
f1000000000000000000000000000000  
f2000000000000000000000000000000  
.  
.  
.  
ff000000000000000000000000000000  

相同过程就不再阐述了,不过大家可以试试看,计算所有响应时间的方式是用平均值好还是标准差好。

通过这种攻击方式,我们只需要16*500*32=256000次请求即可获取完整的HMAC cookie。

这下就能理解官方补丁的用意了,这样虽然没有去掉时间差(时间差总是存在的,除非用恒定时间对比算法),但是因为多加了一层hmac,攻击者的payload被随机化了,无法通过时间差来猜测具体字符是哪一个。

不过在互联网环境下,这个洞还是有点鸡肋,但是如果是在同一个机房或者虚拟主机同站的话效果应该还是不错的。

WP的这个洞是补上了,我相信大家会举一反三的,毕竟用 == 或者!=的地方太多了。

PS: 小编欠饭次数+=1

©乌云知识库版权所有 未经许可 禁止转载


30
昌维 2015-06-28 11:25:57

6666666666666666

30
小贱人 2014-05-07 11:59:00

mark

30
depycode 2014-04-15 13:19:53

这种思路 真是太牛逼了,深深的受教了!

30
HackBraid 2014-04-15 10:06:38

给力!

30
核攻击 2014-04-15 09:24:13

牛叉!

30
匿名用户 2014-04-14 23:41:46

测试了发现,多次统计发现,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
)

30
N1ghtBird 2014-04-14 23:36:35

@insight-labs 你好,可以申请转载到我的个人博客吗?

30
ding 2014-04-14 16:30:58

有点太理想化了, 这样很多if( a == b)的判断都能破解

30
lucky 2014-04-14 11:16:46

不错,学习了思路

30
齐迹 2014-04-14 09:16:48

尼玛 这已经可以上升为语言本身漏洞了!

30
donwa 2014-04-13 22:44:10

刚写了一篇,发现和freebuf是一样的 。。。。

30
ksc 2014-04-13 22:35:52

思路赞

30
xDo 2014-04-13 22:13:14

看到freebuf那篇,原来同时修复了两个漏洞哎。

30
test 2014-04-13 21:19:38

时间到微秒级别的话网速就是问题了

30
我是壮丁 2014-04-13 21:09:39

晕了。。。。。。

30
insight-labs 2014-04-13 21:08:39

另外那个洞的我也看了,可能一次补两个洞,不然没必要用double hmac,把!=换成!==就行了

30
livers 2014-04-13 20:43:11

你确定修复是你这个吗!?

30
廷廷 2014-04-13 20:23:05

膜拜 这个方式 这个思路 好奇特

30
queen787 2014-04-13 18:58:08

点3个赞

30
Matt 2014-04-13 17:01:33

太理想化了。。投入实战难度有点大。。

30
xsser 2014-04-13 17:00:03

表示美帝太意淫了

30
insight-labs 2014-04-13 16:46:54

刚才又玩了下,其实每次对比一个字符需要多少时间是和cpu频率有关的。所以如果知道目标的系统cpu频率的话,可以针对误差做出修正,过滤掉偏差值太大的数据,可以不用跑500次,可能50-100次就能找到正确的值。

30
xiaoL 2014-04-13 16:41:23

思路高端...
赞!

30
肉肉 2014-04-13 16:30:13

思路很赞呐

30
insight-labs 2014-04-13 16:19:21

补丁那块有点像ASLR,你发送过去payload再经过一层HMAC后(不知道key的话无法预测hmac后的内容) ,比如你发送00000000000000000000000000000000,经过hmac后变成03ac9b8ce112c99de723b77225403286,发送10000000000000000000000000000000,经过hmac后变成了
5436449fc8bd1610cafeb1289743ad5c,由于key未知,无法掌握你发送的数据和被加密后数据的映射关系,所以无法再通过时间差来判断hmac的值了

30
梧桐雨 2014-04-13 16:14:48

赞!

30
Newbie 2014-04-13 16:07:44

思路太牛了,不得不赞一个!

30
My5t3ry 2014-04-13 15:54:44

怒赞!

30
乌帽子 2014-04-13 15:33:23

好高端哦!

30
lxj616 2014-04-13 13:31:57

真是太强大了

30
kilobps 2014-04-13 13:16:24

这里的文章的确很有味道,本人不懂代码,但还是看懂了思路。
利用时间+统计学,这种方法太有创造性了,赞。
另外,关于WordPress的补丁那块,通俗些讲是什么原理?

感谢知乎授权页面模版