# Wordpress 3.8.2补丁分析 HMAC timing attack

Author: 乌云历史资料库 (@wooyun_archive)
Published: 2014-04-13T02:24:00Z
Canonical: https://wepostx.com/topics/428

> 乌云历史资料归档
>
> **原始作者：** insight-labs
> **原始编号：** superkieran-wooyundrops:215
> **原始发布时间：** 2014-04-13 10:24
> **声明：内容仅用于技术研究和个人使用，版权归 wooyun.org。**

---

author: [email protected]

## 0x00 背景

在github翻来覆去看了半天，官方版的diff只在php里改动了一个位置:

```text
#!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为例:

```text
wordpress_logged_in_7065d11a793a3ec8482214fcc4f0a55b=insight-labs%7C1397480887%7Cxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```
cookie名字wordpress_logged_in_后面的那个hash看似很神秘，其实是:

```text
#!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。
看到这里大家应该都懂了，那就是判断

```text
'abcdef'=='zxcvbn'
```
比

```text
'abcdef'=='abcdeg'
```
用的时间短。

## 0x02 攻击

构造POC:
下面我有一个32位的md5 hash:

```text
f2835bb2a6ab584fc5cf268bb384c598
```
有个简单的php程序

```text
#!php <?php $hash='f2835bb2a6ab584fc5cf268bb384c598' if($_GET['hmac']!=$hash){ exit('Go away...') } echo 'You are admin!' ?>
```
然后我提交:

```text
00000000000000000000000000000000
```
并且记录从提交到服务器返回结果所需要的时间(需要精确到微秒，起码也得是毫秒级)
之后提交

```text
10000000000000000000000000000000 .. 20000000000000000000000000000000 .. 30000000000000000000000000000000 . . . f0000000000000000000000000000000
```
从0-f，然后对比一下他们所需要的时间:

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

![原文图片](/media/2016/06/5ab175d2c30809889bb53e9c2464a854)

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

![原文图片](/media/2016/06/e608badc9b701358aa2e51e46c864688)

可以看出，多次重试增加了误差的统计显著性。
从图里可以看出，大部分情况下0-e的反应速度都在0.005毫秒以内，但是f却大于这个时间。
知道了第一位之后我们可以用同样的方法去计算第二位，这次把已知的第一位放进去:

```text
f0000000000000000000000000000000 f1000000000000000000000000000000 f2000000000000000000000000000000 . . . ff000000000000000000000000000000
```
相同过程就不再阐述了，不过大家可以试试看，计算所有响应时间的方式是用平均值好还是标准差好。
通过这种攻击方式，我们只需要16*500*32=256000次请求即可获取完整的HMAC cookie。
这下就能理解官方补丁的用意了，这样虽然没有去掉时间差(时间差总是存在的，除非用恒定时间对比算法)，但是因为多加了一层hmac，攻击者的payload被随机化了，无法通过时间差来猜测具体字符是哪一个。
不过在互联网环境下，这个洞还是有点鸡肋，但是如果是在同一个机房或者虚拟主机同站的话效果应该还是不错的。
WP的这个洞是补上了，我相信大家会举一反三的，毕竟用 == 或者!=的地方太多了。
PS: 小编欠饭次数+=1

## Replies
