哈希长度扩展和CBC字节翻转

写这篇文章是因为0CTF的一道Crypto题目,以及以前做过的jarvisoj上的一道题,因为这几道题所以重新梳理了一下CBC和哈希算法的知识,做一个小的总结。

jarvisoj上那道题的核心代码大概是这样的:

这道题应该是以前pctf的原题,但是pctf的题目给了index.php.swp有源码,而jarvisoj上貌似扫不出源码。

想要绕过admin验证的核心点是

这里就要用到哈希长度扩展攻击,然后找到这篇文章:http://www.freebuf.com/articles/web/69264.html

以及这篇文章:https://ricterz.me/posts/%E5%93%88%E5%B8%8C%E9%95%BF%E5%BA%A6%E6%89%A9%E5%B1%95%E6%94%BB%E5%87%BB%E8%A7%A3%E6%9E%90

在之前也有一道差不多的ctf题目:https://blog.skullsecurity.org/2014/plaidctf-web-150-mtpox-hash-extension-attack,作者是用了自己编写的hash_extender.c这个脚本来进行Hash长度扩展攻击,脚本项目地址:https://github.com/iagox86/hash_extender

这些文章将哈希扩展攻击的原理讲的差不多了,这里再简单总结一下Hash的原理:

首先会取得要加密的字符串的长度,并用长度整除64,把余数补位到56,之后加上8个字节的长度描述,最后得到一个长度是64字节倍数的字符串

设原始字符串长度为x,(x % 64) + (第一次补位(0x80 + 0x00 *  n)) = 56,之后第二次补位:(x % 64) + (第一次补位(0x80 + 0x00 *  n)) + (第二次补位(长度描述:x)) = 64,之后将得到的字符串以64字节一组分块,初始的key与第一块字符串加密得到第一块的hash,同时第一块的hash作为第二块的key与第二块得到第二块的hash,以此类推得到最后一块的hash就是整个字符串的hash值,这就可以解释为什么hash加密后的字符串长度是固定的,因为只有最后一块的hash显示出来(只有一块的时候第一个块的hash就是最终的hash值)。

那么怎么进行hash长度扩展攻击,以上面那道题为例,首先要理解的是我们的攻击目的是为了绕过 $role===”admin” &&& $hsh === md5($salt.strrev($_COOKIE[“role”])) 这个表达式,首先是$role===”admin”,这个很简单,只要cookie的role最前面的字符是s%3A5%3A%22admin%22%3b,php就会把他反序列化成admin,其实从这里就可以看出,这道题能够绕过验证的真正点其实是php反序列化机制的处理不当,如果他在setcookie的时候没有进行序列化操作,那么想要突破admin的验证会十分困难。接着要处理这个表达式 $hsh === md5($salt.strrev($_COOKIE[“role”])),这就这道题的关键部分,先说原理:和上面的序列化一样,如果验证这里没有strrev函数,那么我们也是无法突破admin的验证的,因为我们得到的是guest的md5值,但是有strrev函数的存在使得我们可以将第二块字符串设置为 ;”nimda”:5:s 这样role传入的参数就会被反序列化为admin,并且可以用 ;”tseug”:5:s 的md5值得到字符串扩展后的md5值。

明白原理后用工具爆破,我这里使用的是hashpump,项目地址:https://github.com/bwall/HashPump,因为不知道salt的长度所以只能一个一个猜,最后得到salt的长度是12。

从上面这道题可以看到哈希长度扩展的条件还是非常苛刻的,特别是有身份权限验证的情况下,序列化和sttrev在这次攻击中都是必需的函数,然而实战中这样的环境是非常少的。

 

0CTF的那道题是给了PYTHON的源码:

最开始看到AES.MODE_CBC的时候以为是一个普通的CBC字节翻转,但是看到后面的验证md5的时候就觉得不对,因为CBC字节翻转理论上是会破坏前一个块的密文的,如果要把name更改为admin的话就必然会破坏md5的密文导致无法通过验证,所以这道题不是一个普通的字节反转。

先总结一下CBC的原理,详细的可以看这篇文章:http://www.tuicool.com/articles/vEVFZz

首先CBC有一个随机变量iv,类似于hash算法的链接变量,会随着当前块的变化而变化,但是他初始时是随机的。

和hash一样,CBC加密也是将要加密的字符串分为几个块,每个块的字节数相同并且等于iv的长度,如果最后一个块长度不够的时候就要进行补位,CBC的补位有多种模式,当整个字符串的长度刚好是块长度的倍数的时候也要补一个整块。当字符串被分成n个块以后,第一个字符串与iv进行异或,并与一个随机key加密,加密后的密文就会成为第二个块的iv:(明文块1 ^ iv ) + key = 密文块1

(明文块2 ^ 密文块2) + key =密文块2(+代表‘复杂的数学变化’)。以此类推,直到最后 (明文块n ^ 密文块(n-1)) + key =密文块n,生成最后密文的时候与hash不同的是最后的密文是所有的密文块连接起来而不是只取最后一块密文。

可以得到最后解密的时候的一个公式:明文块n = (密文块n – key) ^ 密文块(n-1),至此可以发现通过更改密文块(n-1)的数值可以更改解密后明文块n的数值。,这也是CBC字节翻转的原理,因为(密文块n – key) ^ 密文块(n-1) ^ 明文块n = 0,所以(密文块n – key) ^ 密文块(n-1) ^ 明文块n ^ 更改后的明文块n = 更改后的明文块n,所以令密文块(n-1) = 密文块(n-1) ^ 明文块n ^ 更改后的明文块n,就可以更改明文块n,因为这里的异或是位异或,所以要更改明文块n的第x位,只需要更改密文块(n-1)的第x位即可。需要注意的是这样的修改或破坏密文(n-1)的数值,导致明文(n-1)的数值也被破坏。

回到上面的0CTF题目,因为需要加密的字符串只有两个16字节的块,并且第一个块是第二个块name的md5值,而且解密后需要验证md5值,所以不能通过简单的修改第一个块的密文来更改第二个块的明文从而绕过验证,并且name也限制了长度,不能用简单的哈希长度扩展绕过。这时注意到几个点:name限制的长度是小于32字节而不是小于16字节,md5是16字节表示,在一个块中,可以通过更改iv可以达到修改整个md5的效果,用公式:iv = iv ^ 明文块1 ^ 更改后明文块1。通过这几点可以得出绕过方案:构造长度超过16字节的name,但是登陆的时候只提交第一个16字节块,通过更改iv修改md5的值为第一个块的md5值,接着是如何构造name的前16字节使得==’admin’ 生效,这里通过

就可以绕过。

附上python脚本: