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大小的块
关于短的输入如何补足也有一些细节,在输入末尾进行填充,使输入长度在对512取模以后的余数是448。
-
填充的具体过程:先补第一个比特为1,然后都补0,直到长度满足对512取模后余数是448。
-
需要注意的是,信息必须进行填充,也就是说,即使长度已经满足对512取模后余数是448,补位也必须要进行,这时要填充512个比特。因此,填充是至少补一位,最多补512位。
-
为什么余数是448呢: 因为,还有需要有\(64\)位的数据表示原始输入的长度(位),加上这64bit的长度信息448+64=512,刚好512位。
注:
MD4
、MD5
和RIPEMD-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\)次迭代的结果就是最终的哈希值。
长度扩展攻击
已知
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 ................
实际上不难发现经过填充后的 A1
与 A2
的第一块(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)
需要分两轮运算:
- 第一轮参与运算的block为
[secret]data...............省略...
(前64个字节),其第一轮hash_f
的输出与hash_known
相同; - 第二轮的输入向量为第一轮的输出,即
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
参考: