Hash 长度扩展攻击

Hash 长度扩展攻击

区块链课程 消息摘要算法-长度扩展攻击 笔记


length extension attack

假设有一段服务器代码(逻辑):

$auth = false;
if (isset($_COOKIE["auth"])) {
   $auth = unserialize($_COOKIE["auth"]);
   $hsh = $_COOKIE["hsh"];
   if ($hsh !== md5($SECRET . strrev($_COOKIE["auth"]))) {    //$SECRET is a 8-bit salt
     $auth = false;
   }
} else {
  $auth = false;
  $s = serialize($auth);
  setcookie("auth", $s);
  setcookie("hsh", md5($SECRET . strrev($s)));
}

代码获取用户的密码输入以及之前的签名(之前生成的md5值),对服务器 secret_key 与 用户输入的字符串拼接,并进行进行hash运算,将摘要与用户输入的哈希进行比较。

访问该页面会返回一个cookie

Cookie:{
    auth="b%3A0%3B",
    hsh="32efdc967fcaebc6853b75cacfb80c5f"
}

如何通过其他 passwd其他 hash_val 来成功通过服务器的验证?

Hash 算法

以MD5为例

首先:将消息分解成512-bit大小的块

image-20210919095744650

关于短的输入如何补足也有一些细节,在输入末尾进行填充,使输入长度在对512取模以后的余数是448

  • 填充的具体过程:先补第一个比特为1,然后都补0,直到长度满足对512取模后余数是448

  • 需要注意的是,信息必须进行填充,也就是说,即使长度已经满足对512取模后余数是448,补位也必须要进行,这时要填充512个比特。因此,填充是至少补一位,最多补512位。

  • 为什么余数是448呢: 因为,还有需要有\(64\)位的数据表示原始输入的长度(位),加上这64bit的长度信息448+64=512,刚好512位。

    : MD4MD5RIPEMD-160 是little-endian,而 SHA 系列和 WHIRLPOOL 是big-endian。

    :比如字符串“Acker” 十六进制0x41636b6572这里与448模512不同余,需补位满足二进制长度位512的倍数,补位后的数据如下:

    0x61646d696e|800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000|2800000000000000
    
    • 以十六进制表示一共是128个字符,十六进制每个字符能够转换成4位二进制,128*4=512这就是一组,正好是512bit。
    • 8是因为补位时二进制第一位要补1,那么0b1000转换成16进制就是8.后面都补上0.
    • Acker长度为5*8=40bit,又因为0x28转换为10进制40所以16进制显示为28.

假设消息M可以被分解为\(n\)个块,于是整个算法需要做的就是完成\(n\)次迭代,\(n\)次迭代的结果就是最终的哈希值。

image-20200630110517445

长度扩展攻击

已知 secret_key 长度passwd="data" 下的哈希值。

已知secret长度为5(方便说明),“data” 为之前验证成功所使用的passwd字符串,获取的用户哈希签名为 hash_known

hash_known = hash(secret+data) = "6036708eba0d11f6ef52ad44e8b74d5b"

由于hash算法的比特填充机制使得以下两个字符串 轮hash函数 f(注意不是被整个消息摘要算法处理) 处理的结果完全相同:

对于字符串 A1(共10个字节):

0000 [73 65 63 72 65]74 64 61 74 61                    [secret]data

通过填充后(在之前10个字节的基础上补充54个字节,使字符串总长度达到64字节(512位)A1 变为:

0000 [73 65 63 72 65]74 64 61 74 61|80 00 00 00 00 00  [secret]data....
0010  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0020  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0030  00 00 00 00 00 00 00 00 50 00 00 00 00 00 00 00  ........P.......

与字符串 A2

0000 [73 65 63 72 65]74 64 61 74 61|80 00 00 00 00 00  [secret]data....
0010  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0020  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0030  00 00 00 00 00 00 00 00 50 00 00 00 00 00 00 00  ........P.......

经过填充后 A2 变为:

0000 [73 65 63 72 65]74 64 61 74 61|80 00 00 00 00 00  [secret]data....
0010  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0020  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0030  00 00 00 00 00 00 00 00 50 00 00 00 00 00 00 00  ........P.......
-----------------------------------------------------
0040  80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0050  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0060  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0070  00 00 00 00 00 00 00 00|A0 00 00 00 00 00 00 00  ................

实际上不难发现经过填充后的 A1A2 的第一块(512bit) 数据经过第一轮hash_f函数的结果是完全一样的

f(A1[0:512]) = f(A2[0:512]) = "6036708eba0d11f6ef52ad44e8b74d5b" = hash_known
运算

此时构造字符串 B = data...............append

字符串 B :

0000 [              ]74 64 61 74 61 80 00 00 00 00 00  [      ]data....
0010  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0020  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0030  00 00 00 00 00 00 00 00 50 00 00 00 00 00 00 00  ........P.......
0040  61 70 70 65 6e 64                                append

随便附加一个字符串"append"即可

在服务器上 hash(secret+B) 需要分两轮运算:

  1. 第一轮参与运算的block为 [secret]data...............省略...(前64个字节),其第一轮 hash_f 的输出与 hash_known 相同
  2. 第二轮的输入向量为第一轮的输出,即 hash_known,分块数据为 append 以及填充数据;

此时在客户端本地中hash_known 作为初始输入向量,参与block为 append 以及填充数据的运算:

:本地运算

#include <stdio.h>
#include <openssl/md5.h>

int main(int argc, const char *argv[])
{
    int i;
    unsigned char buffer[MD5_DIGEST_LENGTH];
    MD5_CTX c;

    MD5_Init(&c);
    MD5_Update(&c, "anycode", 64);

    // 由于md5采用小端存储,需要对每个hash输入分块进行处理
    c.A = htonl(0x6036708e); /* <-- This is the hash we already had */
    c.B = htonl(0xba0d11f6);
    c.C = htonl(0xef52ad44);
    c.D = htonl(0xe8b74d5b);

    // 处理第二个block
    MD5_Update(&c, "append", 6); /* This is the appended data. */
    
    // 输出最终hash值
    MD5_Final(buffer, &c);
    for (i = 0; i < 16; i++) {
        printf("%02x", buffer[i]);
    }
    printf("\n");
    return 0;
}

==> Hash_B = 6ee582a1669ce442f3719c47430dadee

之后需要向服务器发送字符串 B Hash_B 即可验证成功。

hashpump

hashpump 就是一个实现了上述步骤的一个工具,usage

➜  hashpump --help
HashPump [-h help] [-t test] [-s signature] [-d data] [-a additional] [-k keylength]
     HashPump generates strings to exploit signatures vulnerable to the Hash Length Extension Attack.
     -h --help          Display this message.
     -t --test          Run tests to verify each algorithm is operating properly.
     -s --signature     The signature from known message.
     -d --data          The data from the known message.
     -a --additional    The information you would like to add to the known message.
     -k --keylength     The length in bytes of the key being used to sign the original message with.
     Version 1.2.0 with CRC32, MD5, SHA1, SHA256 and SHA512 support.
     <Developed by bwall(@botnet_hunter)>

:通过hashpump实现上述例子

➜  hashpump -s 6036708eba0d11f6ef52ad44e8b74d5b -k 5 -d 'data' -a append
6ee582a1669ce442f3719c47430dadee
data\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00H\x00\x00\x00\x00\x00\x00\x00append

参考

posted @ 2022-03-30 00:42  NIShoushun  阅读(276)  评论(0编辑  收藏  举报