长度扩展攻击详解

这几天在看密码学的长度扩展攻击,看了不少文章,感觉都说得不够清楚,自己弄清楚之后想写一篇,做一个记录。


1. 简介

       长度扩展攻击(length extension attack),是指针对某些允许包含额外信息的加密散列函数的攻击手段。对于满足以下条件的散列函数,都可以作为攻击对象:

       ① 加密前将待加密的明文按一定规则填充到固定长度(例如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
       对于H(salt+data)形式的加密,在以下条件满足的情况下,攻击者可以通过该方法获取H(salt+一定规则构造的data):

       ① 知道密文的加密算法且该算法满足Merkle–Damgård散列函数特征;

       ② 不知道salt,但知道salt的长度,并可控制data的值;

       ③ 可以得到一个H(salt+data)的值。


2. 攻击方法详解

         下面以MD5算法为例,讲述该攻击方式如何进行攻击。

         百度百科中详细阐述了MD5算法的实现过程https://baike.baidu.com/item/MD5/212708?fr=aladdin,我们并不需要知道MD5具体的算法是怎么回事,只需要知道它的实现是满足上面所说的Merkle–Damgård散列函数的两个条件的,具体过程是这样的:

        ① 填充

        拿到明文后,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(salt+data)的值并可控制data的值,攻击者可以设定data为与data+padding+append等长的任意字符串,然后计算MD5(str+append)。我们知道,MD5需要先填充再运算,攻击者可以在程序计算append所在块之前,将MD5(salt+data)的值直接替换掉初始的链变量,就能够算出MD5(salt+data+padding+append)的值了。设MD5(CV,data)表示以链变量CV计算data的MD5值,那么(为简便起见,这里设append的长度不超过448比特,超过的原理也类似)

MD5(IV, salt+data+padding+append) = MD5(MD5(IV, salt+data), append)。

         攻击原理如下:


         之所以要知道salt的长度,是为了确保salt+data+padding+append和攻击者输入的data拥有相同的填充(即padding2相等),以确保最后一步的运算得到相同的结果。


3. 示例

         以实验吧一道CTF题为例,展示长度扩展攻击的过程。

         这道题给出了页面的代码:

<?php 

$flag = "flag{flag is here}";
$secret = "xxxxxxxxxxxxxxxx"; // This secret is 15 characters long for security!

$username = $_POST["username"];
$password = $_POST["password"];

if (!empty($_COOKIE["getmein"])) {
    if (urldecode($username) === "admin" && urldecode($password) != "admin") {
        if ($_COOKIE["getmein"] === md5($secret . urldecode($username . $password))) {
            echo "Congratulations! You are a registered user.\n";
            die ("The flag is ". $flag);
        }
        else {
            die ("Your cookies don't match up! STOP HACKING THIS SITE.");
        }
    }
    else {
        die ("You are not an admin! LEAVE.");
    }
}

setcookie("sample-hash", md5($secret . urldecode("admin" . "admin")), time() + (60 * 60 * 24 * 7));

if (empty($_COOKIE["source"])) {
    setcookie("source", 0, time() + (60 * 60 * 24 * 7));
}
else {
    if ($_COOKIE["source"] != 0) {
        echo ""; // This source code is outputted here
    }
}
?>

       根据题意,我们得到以下信息:

       ①程序使用MD5算法进行加密;

       ②$secret值未知,但是知道$secret长度是15位,且知道MD5($secret+"adminadmin")的值;

       ③$password可控,需要计算MD5($secret+"admin"+$password),且$password不等于admin。

       首先设定$password="admin"+padding+append,这里padding按照填充规则是0x8000000000000000000000000000000000000000000000000000000000000c800000000000000,append也设定为admin,总共是69个字节(64+5)。我从百度百科上下载了MD5的JAVA实现算法,将getMD5()替换如下:

private void setChainingVariable(int A,int B,int C,int D){
    this.Atemp = A;
    this.Btemp = B;
    this.Ctemp = C;
    this.Dtemp = D;
}
public String getMD5(String source,int A,int B,int C,int D){
    init();
    int strByte[]=add(source);
    for(int i=0;i<strByte.length/16;i++){
        int num[]=new int[16];
        for(int j=0;j<16;j++){
            num[j]=strByte[i*16+j];
        }
        if(i == strByte.length/16 - 1){
           setChainingVariable(A, B, C, D);
        }
        MainLoop(num);
    }
    return changeHex(Atemp)+changeHex(Btemp)+changeHex(Ctemp)+changeHex(Dtemp);
}

       接着以随意填充字符代替$secret+"adminadmin"+padding,我填充了64个A,然后运行程序计算AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAadmin的md5值。通过抓包,我们得到MD5($secret+"adminadmin")=e2c25a7f7fd42f0f03194d7258fbcdb6,那么替换的链变量A=0x7f5ac2e2,B=0x0f2fd47f,C=0x724d1903,D=0xb6cdfb58。计算得出值是c7b48fd99a4387ad46bd6f37da840c52。最后构造POST请求,将参数改为

username=admin&password=%61%64%6d%69%6e%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%c8%00%00%00%00%00%00%00%61%64%6d%69%6e

然后在COOKIE中添加getmein=c7b48fd99a4387ad46bd6f37da840c52,显示成功:


       至此,长度扩展攻击宣告成功。


       最后介绍一个进行长度扩展攻击的工具:hash_extender,提供了简单的命令来进行攻击,详情可参见页面当中的说明。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章