MD5长度扩展攻击原理

      MD5长度扩展攻击原理无评论

0x00 漏洞简述

前不久在乌云上phpwind被提交了一个有趣的漏洞《phpwindv9某前台某padding可getshell漏洞 》,漏洞在代码层面的原因和简单,但是内在原理我之前没有接触过,所以深入的去了解了下MD5长度扩展攻击的原理。在这里把学习到的内容分享给大家,用作相互讨论和学习:)。

0x01 关于MD5

MD5计算流程大致如下:

  1. 将消息内容按64字节分组
  2. 最后一组的长度模512(64字节)小于448(56字节)的使用空字节填充,空字节开始处使用0x80标识,若大于等于448(56字节)则填充本组至64字节后,再向下填充一个分组(64字节)。结尾8字节用于填写整个消息的长度。
  3. 每个分组进行64轮数学计算,上一组的计算结果作为下一组计算的初始输入,最开始的输入为硬编码的一套数字。

0x02 漏洞原理

从rfc1321文档中的示例代码,我们可以看到MD5字符串计算主要是三个步骤:MDInit、MDUpdate和MDFinal,代码如下:

/* Digests a string and prints the result.
 */
static void MDString (string)
char *string;
{
  MD_CTX context;
  unsigned char digest[16];
  unsigned int len = strlen (string);

  MDInit (&context);
  MDUpdate (&context, string, len);
  MDFinal (digest, &context);

  printf ("MD%d (\"%s\") = ", MD, string);
  MDPrint (digest);
  printf ("\n");
}

MDInit的作用用以初始化MD5计算所需要的前置变量(主要是4个被硬编码的初始计算值),MDUpdate用于计算64字节的每组内容,直至遇到不满足64字节的一组数据停止,MDFinal则处理最后的那组不满足的64字节数据,前文所说的padding就是在这个函数中进行的。MD5的长度扩展攻击中,我们需要关注的主要是MDFinal这个函数。

通过上述的前置知识,我们来构建长度扩展攻击的攻击流程。首先我们假设我们已知md5(md5(‘secret’)+’tang3’)的值,其中tang3是我们的可控内容。现在我想要在tang3后面加一些其他的内容,并且让添加内容后的字符串MD5值仍和原来的一样。

md5(‘secret’)+’tang3’因为只有32+5(加号为字符拼接操作符)个字符,所以按照之前我们所说的padding规则,参与MD5计算的字符串其实是:

md5('secret') + 'tang3' + '\x80' + '\x00' * 16 + '\x28' + '\x01' + '\x00' * 6

这个计算内容的结果我们是已知的,那么如果我们还原他,并且加入要追加的内容,比如just4fun,这样一来我们期望的MD5过程就变成了下面这样:

第一组64字节数据:md5('secret') + 'tang3' + '\x80' + '\x00' * 18 + '\x28' + '\x01' + '\x00' * 6
第二组64字节数据:'just4fun' + '\x80' + '\x00' *47 + '\x40' + '\x00' * 7

而根据MD5的规则,上一组数字的计算结果会作为下一组数字计算的初始值。这样的话,我们发现第一组数据虽然我们不知道原文内容,但是它的MD5计算结果我们可以直接拿来用做第二组计算的初始值。如此这般,我们就可以和远端服务器同步得到一个相同的MD5值了。因为tang3这个字符串是我们可控,这里我们来演示下向服务端提交数据的样子来达到获取相同MD5值得目的:

tang3%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%28%01%00%00%00%00%00%00just4fun

使用正常的MD5计算过程来看待这个字符串加上md5(‘secret’)的计算,会发现和我们所期望的MD5计算是一样的两组64字节数据计算,所以它最终的计算结果是我们可知的。

0x03 实际案例

Jannock在乌云提交了一个phpwind的长度扩展攻击漏洞http://www.wooyun.org/bugs/wooyun-2016-0210850,phithon也在他的blog针对这个漏洞进行了分析。

这里我们简单针对这个漏洞的利用来展现MD5长度扩展攻击的实际攻击场景。漏洞原理很简单,服务端使用了下面这种方式对用户行为进行hash,来防止一些越权或csrf操作。time和str都是通过GET或POST请求提交的,hash时GET在POST前。

md5(md5($apiId.'||'.$secretkey).$time.$str)

我们构造如下请求:

http://192.168.3.106/windid/index.php?windidkey=8e759a35c269562a95b648ab7e961e1f&adoAvatarcavatarmapitypeflashuid5uidundefined=%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%B8%02%00%00%00%00%00%00&time=1466476894

post:{'a': 'editUser', 'c': 'user', 'password': 'tang3', 'm': 'api', 'uid': '3'}

可以通过MD5长度扩展攻击绕过操作hash校验,完成重置用户密码操作。漏洞详细信息可以参考本节开始提到的两篇文章,这里就不多说了。

0x04 漏洞总结

这是算法本身层面的问题,很难从实现层面来解决,引用道哥所09年的blog的一句话:

很多哈希算法都存在Length Extension攻击,这是因为这些哈希算法都使用了Merkle–Damgård construction进行数据的压缩,流行算法比如MD5、SHA-1等都受影响。

因为这个漏洞的说明文章对一些细节的内容没有做讲解,所以不得不开启人生第一次RFC阅读。阅读过程中发现很有趣的一个地方,就是RFC上提供的实现步骤描述和样例中的实现不太一样。文档描述中的实现步骤是这样的:

3.1 Step 1. Append Padding Bits

3.2 Step 2. Append Length

3.3 Step 3. Initialize MD Buffer

而实现却是在最后的MDfinal函数中才进行padding,这种风格是不是会出现问题啊?第一次阅读RFC,不懂,求有经验人士解答。

0x05 参考内容

  1. The MD5 Message-Digest Algorithm
  2. github hash_extender项目
  3. 维基百科——Length extension attack
  4. Understanding MD5 Length Extension Attack
  5. phpwind 利用哈希长度扩展攻击进行getshell
  6. phpwindv9某前台某padding可getshell漏洞

发表评论