对于满足以下条件的散列函数,都可以作为攻击对象:
① 加密前将待加密的明文按一定规则填充到固定长度(例如512或1024比特)的倍数;
② 按照该固定长度,将明文分块加密,并用前一个块的加密结果,作为下一块加密的初始向量(Initial Vector)。
满足上述要求的散列函数称为Merkle–Damgård散列函数(Merkle–Damgård hash function),下列散列函数都属于Merkle–Damgård散列函数:MD4、MD5、RIPEMD-160、SHA-0、SHA-1、SHA-256、SHA-512、WHIRLPOOL
这里有两种类型的长度扩展攻击。符号||表示链接。
类型一:如果Digest(Msg1)=Digest(Msg2),并且Len(Msg1)= Len(Msg2),消 息 Msg1 ¹ Msg2,则 Digest(Msg1 ‖ Sufx)= Digest(Msg2‖Sufx)。
类型二:假设一个原始消息Msg,攻击者不知道消息的内 容,给定 Len(Msg),填充消息 X,Digest(Msg),对于一个适当 的后缀,攻击者可以计算出Digest(Msg‖Sufx)。
通过消息的填充可以抵抗第一类型的攻击;第二类的攻 击可以通过大的内部状态来预防。链接变量循环的Hash结构 中使用了消息填充,可以抵抗第一类型的攻击;链接变量长度 n 远大于Hash值长度 l(n > 2l) ,也就是说Hash值长度小于链接 变量长度,使得计算 Msg‖Sufx 的 Hash 值时,无法连接。因 此,可以抵抗第二类长度扩展攻击。
对于H(salt+data)形式的加密,在以下条件满足的情况下,攻击者可以通过该方法获取H(salt+一定规则构造的data):
① 知道密文的加密算法且该算法满足Merkle–Damgård散列函数特征;
② 不知道salt,但知道salt的长度,并可控制data的值;
③ 可以得到一个H(salt+data)的值。
下面以MD5算法为例,讲述该攻击方式如何进行攻击,具体过程如下:
① 填充
拿到明文后,MD5现将明文转为二进制文件,然后将二进制文件的长度除以512比特(即64字节),如果余数等于448比特(即64-8字节),那么直接在后面加上八个字节的长度标识,使之成为512比特的倍数。否则则在明文后填一个1,再填充0直至其长度除以512等于448,再加上8位的长度标识。长度是使用大端序(big Endian)来存储,即低字节放在高地址位上。
比如加密的明文是admin,其二进制文件以16进制表示是0x61646d696e,长度是40比特(5字节),那么需要补充408比特(51字节)的填充符,填充内容第一位是1,其余全部是0。16进制表示是0x800000000000000000000000000000000000000000000000000。然后再添加8个字节的长度标识,admin长度为40比特,16进制是0x28,这个28要放在高位,就是0x280000000000000
② 分块运算
填充完毕后,函数就将填充后的明文以512比特的长度分块,进行运算。在运算中会用到四个初始向量(MD5中称作链变量,Chaining Variable),分别是A=0x67452301,B=0xefcdab89,C=0x98badcfe,D=0x10325476。经过一系列复杂的数学运算,函数会得到第一块的MD5值,然后将该MD5值分成四块,以大端序形成新的链变量,投入到第二块的运算,形成新的MD5值……以此类推,直到算出最后一块的MD5值,就是整个数据块的MD5值。
例如加密的明文是adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin,首先根据①的描述的算法填充后,分成两块进行运算,第一块是adminadminadminadminadminadminadminadminadminadminadminadminadmi,其MD5码是e7d6ca05773d038378f5e2674850be25,分成四块并以大端序存储,则A=0x05cad6e7,B=0x83033d77,C=0x76e2f578,D=0x25be5048。将这四个变量作为链变量投入运算,再将第二部分加密,得到最终的MD5值是9ea2d490481dbcdadf61e7e404b99585。
二、案例
在MD5中,首先算法将消息以512-bit(64bytes)的长度分为了n组。而最后一组必然不足512-bit,这时候算法就会自动往最后一组中填充字节,这个过程被称为Padding。Padding的规则是,在最末一个字节之后补充0x80,其余的部分填充为0x00,而Padding最后的8字节用来表示需要哈希的消息长度。
0x01从源码中可以提取到重要的信息
首先,明文长度为 $secret.urldecode("admin"."admin")(在PHP中“.”是连接符),前面给出$secret的长度为15位,也就是该脚本会下发给浏览器一个cookie,字段名为“sample-hash”,值为该明文的md5加密值(所给值为571580b26c65f306376d4f64e53cb5c7)。
那么我们可知,flag获取的要求是:传进一个cookie,字段名为“getmein”,使其值等于$secret.urldecode($username. $password)。MD5加密后的结果且$password不能为admin。
0x02综上,我们可以尝试构造payload_1
首先是$secret的15个未知位,在这里笔者直接用问号代替表示,之后按照源码的提示,跟上字符串“adminadmin”
接下来便是对其进行字节填充,在末尾加入0x80,之后为0x00,在最后的8个字节,要表明哈希的长度,总长度为15+10=25,转为16进制,即为0xC
这时我们已经填充到了64bytes,完成了Padding,接下来MD5算法开始依次对每组消息进行压缩,经过64轮数学变换。
注意,在这个过程中,一开始会有定义好的初始化向量,为4个中间值,初始化向量不是随机生成的,而是标准里定义死的。
0x03实施Length Extension Attack
在这里要将原哈希值(571580b26c65f306376d4f64e53cb5c7)拆分为四组,即为:571580b2;6c65f306;376d4f64;e53cb5c7
在此将四组数值按照小端规则反序,可得如下四组
A=0xb2801557;
B=0x06f3656c;
C=0x644f6d37;
D=0xc7b53ce5;
参见0x02,此时我们有已经填充好的欲进行加密运算的字符串,若向补位后的消息再追加一条消息字符串“Hacker”,则会对这个新字符串进行补位,再利用上一个运算算出的值作为初始向量进行函数运算,最终得到的MD5值为6df6547d222c30afcb9fd3809dc04c13。(这里用的是在网上找的MD5的cpp实现,笔者在文末会贴出度盘链接)
0x04构造payload_2
按照源码提示,直接构造
并且不要忘记0x01,设置好cookie,字段名为“getmein”,值为前边完成构造的MD5