MD5长度扩展攻击


MD5原理

1.MD5加密过程中512比特(64字节)为一组,属于分组加密,而且在运算的过程中,将512比特分为32bit*16块,分块运算
2.我们关键利用的是MD5的填充,对加密的字符串进行填充(比特第一位为1其余比特为0),使之(二进制)补到448模512同余,即长度为512的倍数减64,最后的64位在补充为原来字符串的长度,这样刚好补满512位的倍数,如果当前明文正好是512bit倍数则再加上一个512bit的一组。
3.MD5不管怎么加密,每一块加密得到的密文作为下一次加密的初始向量IV,这一点很关键!!!

1.填充+增加长度

将数据进行填充,首先添加0×80,接着添加0×00使得(新字符串字节数+8)%64=0,8个字节存的是原始字符串长度

len(secret) + len("test.pdf") + len(padding) + 8 == 64

举例:

字符串:admin,16进制:0x61646d696e

二进制:1111 1111

16进制:F F

ASCII:1字符=1字节(Byte)==8位(bit)

第一步:

0x61646d696e后面补二进制1000 0000 0000 000000000…

所以第一个数的16进制数为8

一直补到bit数=448,目的是**(bit mod 512)=(448 mod 512)**

448位(bit)=56字节(Byte)=112个十六进制数

admin:0x61646d696e
补:
800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

第二步:

补需要加密明文的长度,admin长度为5,也就是5*8=40位=0x28h

MD5中存储的都是小端方式,0x12345678存储的时候是78 56 34 12

所以后面的64位=8字节=16个十六进制数为

2800000000000000

合一下,就是:

0x61646d696e8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002800000000000000

2.轮次变换

以64字节=512位为1组进行轮次变换,这一组中以4字节为1个单位分成16子分组。

然后准备ABCD四个32位的字符,这几个是固定的MD5算法特征值。

物理顺序:A=0x01234567,B=0x89ABCDEF,C=0xFEDCBA98,D=0x76543210

MD5以小端存储:A=0×67452301, B=0xEFCDAB89, C=0x98BADCFE, D=0×10325476

将ABCD作为种子,与16个子分组进行一种复杂的运算,运算结果为A1、B1、C1、D1,

以A1、B1、C1、D1为种子然后重复这个过程计算最后得到An、Bn、Cn、Dn。

5b5d9ca7bf5e2

扩展攻击

如何在不知道salt/key/secret的情况下,计算出一个文件名的合法hash值。

举例

假设有这样一个文件下载的接口

/download?name=test.pdf&secret=6543109bb53887f7bb46fe424f26e24a

很明显test.pdf的md5值不是654310,这是加了salt的。

判断代码按如下:

if ($md5 === md5($secret.$name))

M1=md5($secret+'test.pdf')

已知$secret的长度为10,M1=6543109bb53887f7bb46fe424f26e24a

求任意文件名的合法md5。

解析

前面说到填充,就是长度扩展攻击的关键所在。

于是,先填充,将其补充到64Byte。

len(secret) + len("test.pdf") + len(padding) + 8 == 64

10+8+x+8=64,x=38,也就是说填充的长度为38Byte:0x80+0x00*37。

secret + "test.pdf" + "\x80" + "\x00"*37 + "\x90\x00\x00\x00\x00\x00\x00\x00"

这样的话,长度就为64Byte,满足第一轮的计算块,后续再加内容就是新的计算块。

当一个块计算完成后,ABCD四个寄存器中的值就是刚计算完的 hash 值,如果后面还有数据块,那么会在已有的ABCD四个寄存器中的值上进行更新

构造文件名为

"test.pdf" + "\x80" + "\x00"*37 + "\x90\x00\x00\x00\x00\x00\x00\x00"+'flag.txt'

于是,第一轮计算块,结果就是M1,这是一个合法值,将M1放入下一轮计算块的ABCD寄存器。去计算flag.txt,合法的ABCD得出也是合法的hash,于是就算出了M2

利用

用C语言复现下其操作就是

#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);
  //64个A无所谓是什么,只是让它完成第一个计算块
  MD5_Update(&c, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 64);
  //M1=0x6543109bb53887f7bb46fe424f26e24a,注意大小端
  c.A = 0x9b104365;
  c.B = 0xf78738b5;
  c.C = 0x42fe46bb;
  c.D = 0x4ae2264f;
  MD5_Update(&c, "/../../../../etc/passwd", 23);

  MD5_Final(buffer, &c);
  for (i = 0; i < 16; i++) {
    printf("%02x", buffer[i]);
  }
  printf("\n");
  return 0;
}

例题

<?php

$flag = "flag{flag is here}"; //未知
$secret = "aaaaabbbbbccccc"; // 长度15,具体未知
//md5($secret . urldecode("admin" . "admin"))=e2c25a7f7fd42f0f03194d7258fbcdb6
@$username = $_POST["username"];
@$password = $_POST["password"];
@$md5 = $_POST['md5'];
if (urldecode($username) === "admin" && urldecode($password) != "admin") {
    if ($md5 === md5($secret . urldecode($username . $password))) {
        die ("The flag is " . $flag);
    } else {
        die ("no");
    }
}

已知md5($secret+'admin'+'admin')e2c25a7f7fd42f0f03194d7258fbcdb6,且secret长度为15

md5($secret+'admin'+任意至少1个字符)

这里key length就不是15而是20,因为是$secret+'admin',还得加上一个admin的长度

%00这个字符要进行特别关注,具体见%00发包要点

注意,在这里不能直接浏览器发包,可以用burpsuite,但记得把\x80改为%80

username=admin&password=admin%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%001&md5=479706f076e8c16e5bb7252d4f042e13

用python发包

import requests

url = "http://127.0.0.1:80/3.php"
headers = {"Content-Type": "application/x-www-form-urlencoded", "Connection": "close"}
data = {"username": "admin",
        "password": "admin%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%001",
        "md5": "479706f076e8c16e5bb7252d4f042e13"}
res = requests.post(url, headers=headers, data=data)
print(res.text)

注意:这算出来的只是$secret+'admin'+填充字符+'1'的md5值,而非$secret+'admin'+'1'的md5值。这只是验证MD5是否合法,并非是在不知道$secret的情况下算出$secret+任意字符

2021年11月18日17:24:12更新

今天软测碰到个MD5长度扩展攻击,折磨了几个小时没做出来,才明白我根本没理解怎么用。于是复盘重新整理下思路。

为了做不出来?

1.笔记写的不好,看上去就以为能算出$secret+任意字符,结果试了半天MD5对不上,开始怀疑人生。

2.魔怔了。哪怕不懂长度扩展攻击,想想看都知道不同字符串相同MD5的情况非常非常稀少到几乎不存在,难得有一个字符串都长的不得了。哪怕想起来这个都不会继续魔怔下去。

奇文共赏

哈希长度扩展攻击


文章作者: 巡璃
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 巡璃 !
评论
  目录