一、MD5哈希算法
1.1 MD5简介
MD5(Message Digest Algorithm 5)是一种广泛使用的加密散列函数,它可以产生一个128位(16字节)的散列值(hash value),通常用一个32位的十六进制字符串表示,且具有不可逆性(即理论上从散列值无法推导出明文)。MD5由Ron Rivest在1991年设计,最初被用来作为一种安全的密码散列算法。它接收任意长度的输入,并产生一个固定长度的输出,这个输出被认为是输入数据的“指纹”。然而,由于后来发现MD5存在安全漏洞,它不再被推荐用于密码存储等安全敏感的应用。尽管如此,MD5仍然被用于一些非安全相关的场合,比如文件完整性检查。
1.1.1 php 中md5函数的用法示例
<?php
echo md5("test string");
?>
= php5.6 index.php
6f8db599de986fab7a21625b7916589c
1.1.2 哈希函数与加密函数的区别
哈希将目标转换为具有相同长度的、不可逆的杂凑字符串;
加密则是将目标转化为不同长度的、可逆的密文,长度一般随明文增长而增加;
1.1.3 常见的哈希算法介绍
当前最常用的哈希算法有MD5
、SHA-1
、SHA-2(SHA-224、SHA-256、SHA-384,和SHA-512并称为SHA-2)
等。
1.2 md5哈希算法流程详解
1.2.0 步骤概览
MD5算法的详细流程可以概括为以下几个步骤:
1)明文填充
2)初始化变量
3)分块处理
4)循环压缩函数处理
5)更新寄存器
6)生成哈希值
具体的数据处理流程如图所示:
1.2.1 明文填充
MD5以512bit为分块处理输入信息,每个分块又被分为16*32的子分组,最终输出为4*32的分组,即32位的16进制字符串。
- 填充
输入信息的长度(bit)对512求余不等于448时,使用OneAndZeroes对输入信息进行填充使得对512求余448。
假设对”abc”进行MD5计算,填充步骤:
- 记录信息长度
填充完成后,用64bit存储信息的长度,”abc”共有24bit,即0x18bit,记录的消息长度为:
1.2.2 初始化变量
MD5的哈希结果长度为128位,按每32位分成一组共4组。这4组结果是由4个初始值A、B、C、D经过不断演变得到。算法初始化四个32位的寄存器A、B、C、D,这些寄存器同时用于存储中间计算结果
MD5的官方实现中,A、B、C、D的初始值如下(16进制):
A=0x01234567
B=0x89ABCDEF
C=0xFEDCBA98
D=0x76543210
1.2.3 分块处理
将填充后的数据按照512位(64字节)一块进行处理,每块包含16个32位字。
1.2.4 循环压缩函数处理
对每个512位的数据块进行四轮循环压缩函数处理,每轮处理包括四个步骤:F函数、G函数、H函数和I函数。这些非线性函数在循环压缩函数处理中起着重要作用。
主要流程是以512位的分块为单位,每一分块经过4轮循环,每轮循环16次迭代,输出128位的结果,存放在缓冲区中,作为下一轮循环缓冲区的输入。
4轮循环的逻辑如图所示:
从缓冲区输入128位,从消息分组输入512位,输出结果128位,要注意结果是由循环的结果加上缓冲区的值得到的(加法为模$2^32$加法)。A,B,C,D就是哈希值的四个分组。每一次循环都会让旧的ABCD产生新的ABCD。一共进行多少次循环呢?由原文长度决定。假设处理后的原文长度是M,主循环次数 = M / 512,每个主循环中包含 (512/32) * 4 = 64 次子循环。上面这张图所表达的是单次子循环的流程。
每轮循环中单次迭代的逻辑如图所示:
每轮循环迭代运算的逻辑:
(1)对A迭代:a <—— b+((a+g(b,c,d)+X[k]+T[i])<<<s)
(2)缓冲区(A,B,C,D)作循环轮换:(B,C,D,A) <——(A,B,C,D)
- a,b,c,d是缓冲区的当前值
- g是4个轮函数之一,输入输出都是
32bit
,进行不同的逻辑运算 <<<s
(CLS(s))是指把bit32
循环左移s
位,s
可查表得到X[k]
是当前处理消息分组的第k
个32bit
(一共512/32=16个字),在每一轮循环中都由不同的公式计算出来T[i]
通过查表得到,32bit字- 所有的加法都是模$2^32$加法
4个轮函数逻辑如图所示:
每轮循环中X[k]
所取的k
的计算方法为:
取j
为当前迭代轮次
- 第一轮循环:
k = j
- 第二轮循环:
k = (1 + 5 * j) % 16
- 第三轮循环:
k = (5 + 3 * j) % 16
- 第四轮循环:
k = (7 * j) % 16
1.2.5 更新寄存器
根据每轮的计算结果更新寄存器A、B、C、D的值。
1.3.6 生成哈希值
最后一轮得到的结果经过高低位互换后就是最终的结果。
6f8db599de986fab7a21625b7916589c
A=0x99b58d6f
B=0xab6f98de
C=0x5b62217a
D=0x9c581679
A B C D 分别按字节反转后拼接构成最后的哈希值。
二、哈希长度扩展攻击原理
2.1 攻击场景
2.1.1 攻击场景1-文件下载权限验证
Message Authentication Codes (MACs)是用于验证信息真实性的算法。最简单的MAC算法是这样的:服务器把key和message连接到一起,然后用摘要算法如MD5
或SHA1
取出摘要。
例如有一个网站,在用户下载文件之前需验证下载权限。
这个网站会用如下的算法产生一个关于文件名的MAC:
def Create_MAC(key, filename)
return Digest::MD5.hexdigest(key + filename)
end
key对于攻击者来说是未知的。
用户请求下载test.pdf文件时提交如下请求:
http://www.example.com/download?file=test.pdf&mac=ca21cf672b66a5ee6fa7fc7c1c314ff3
当用户发起请求要下载一个文件时,会执行下面这个函数:
def verify_mac(key, filename, userMAC)
validMAC = create_MAC(key, filename)
if (validMAC == userMAC) do
initiateDownload()
else
displayError()
end
end
服务端根据key和用户提交的文件名生成一个哈希值,跟用户提交的哈希值做比对,比对成功才会允许下载文件,这种情况下要求文件名和用户提供的mac值都是合法的。
本意是通过key的保密性来验证身份,因为正常情况下只有服务端和合法客户端才掌握key。
这样,只有当用户没有擅自更改文件名时服务器才会执行initiateDownload()
开始下载。
但是这种生成MAC的方式,会给攻击者在文件名后添加自定义的字符串留下隐患。
这种方法就是哈希长度拓展攻击。
攻击结果:不知道key、不知道合法文件名,但是能通过验证。
2.1.2 攻击场景2
在一道web题目中遇到了以下判断: if ($COOKIE["md5hash"] === md5($secret . $input))
在该题目中我们可以掌握的参数有md5hash
、input
的值,secret
的md5值和长度,我们需要想办法让这个判断通过。
难点在于:不知道$secret的情况下,传递一个哈希值和input,使得哈希值===md5($secret.$input)。
2.2 哈希长度扩展攻击简介
哈希长度扩展攻击(Hash Length Extension Attacks)是一种针对某些加密散列函数的攻击手段,特别适用于那些基于Merkle–Damgård结构的算法,如MD5和SHA-1。这类攻击的核心在于,如果你知道一个消息(message)和密钥(key)的组合的哈希值,即使不知道密钥的具体值,只要知道密钥的长度,你就能在这个消息后面添加额外的信息,并计算出新的哈希值。
md5(xxxxx+"plainText")=cdf1ea..
md5(xxxxx+"plainText"+"abcdfafa")=ade24242..
攻击条件:
- 消息可控已知
- 密钥长度已知
- 使用MD5加密且结果可知
2.3 攻击步骤
2.3.1 以某CTF赛题为例子
- 代码如下:
2.3.2 条件分析
从代码中已知$this->sess=md5($this->token.$this->username)
,在不传递任何参数的情况下,$sess
为token(20个未知字符)与”admin”组成的字符串的md5值,并且会在cookie中返回。
$username=$_COOKIE['username'];
$sess=$_COOKIE['session'];
从上面的代码可知,$sess和$username的值是用户提交的。
获取flag的条件是:提交一个伪造的非”admin”的username,并且提交该username与token拼接之后的md5值(也就是$sess),但是由于toekn不为我们所知晓,我们也无从计算出正确的md5值。
我们目前掌握的信息:
1)????????????????????admin 的哈希值 (?表示的内容为未知token),记为H1。
2)未知token的长度为20。
3)username和sess的值是可控的。
2.3.3 核心要点
巧妙利用md5哈希算法的分组运算机制。
将H1当作某个明文(这个明文我们需要伪造)中的第一个数据块(512bit)的散列值。根据md5算法以每512bit为数据块计算散列值的原理,当计算第二个数据块的散列值时,会以H1作为ABCD寄存器的缓存值。
我们如果能把字符串扩展到512bit以上,并保证第一段的md5运算结果不改变,那么我们在不知道第一段的加密内容的情况下仍然能够求得整体的md5值。此所谓”扩展”的含义。
2.3.4 攻击实施
1)模仿md5算法的填充
所以我们首先要做的就是”模仿”md5算法的补位方式来进行补位。
????????????????????admin的长度=20+5=25个byte=25*8=200bit
表示成16进制即为:0xC8=12*16+8=200
所以填充的结果如下(称为M1):
【????????????????????admin】【8000000....000000】【c800000000000000】
【(????????????????????admin)】为消息内容。
【8000000….000000】为填充值。
【c800000000000000】为长度填充。
如此一来,M1经过md5算法计算的结果,跟md5(????????????????????admin)计算的结果是一致的。我们只是作了md5算法本身就该做的事情。
md5(M1)===md5(????????????????????admin)
如前文所述,实际上在本实例中上述值我们是知道的,eg:ae8b63d93b14eadd1adb347c9e26595a(H1)
2)将已知的H1值作为作为ABCD寄存器的缓存值
将ae8b63d93b14eadd1adb347c9e26595a
分成8字节唯一组的四组,ae8b63d9,3b14eadd,1adb347c,9e26595a
由于md5是小端存储,进行相应的调整:
A=0xd9638bae
B=0xddea143b
C=0x7c34db1a
D=0x5a59269e
3)拼接第二段内容
M1+fakeadmin
将我们求出来的ABCD序列作为初始序列,利用md5算法对fakeadmin
这个字符串进行md5加密。得到md5值:
比如是:bdbe1c6fb9d921e4ba3d9d4072b702f7(H2)
这个H2===md5(M1+fakeadmin)
2.3.5 修改cookie获取flag
username=M1+fakeadmin
session=bdbe1c6fb9d921e4ba3d9d4072b702f7
三、攻击工具
3.1 hash_extender
https://github.com/iagox86/hash_extender 【hash_extender】
-d 被扩展的明文
-a 附加的到原来hash的padding
-l 盐的长度
-f 加密方式
-s 带盐加密的hash值
--out-data-format 输出格式
--quiet 仅输出必要的值
3.2 hashpump
3.2.1 安装
1)Mac
brew install hashpump
报错:
Error: hashpump has been disabled because it has a removed upstream repository! It was disabled on 2024-09-16.
2)kali
sudo apt-get update
sudo apt-get install libssl-dev
//git clone https://github.com/bwall/HashPump.git 这个地址已经不存在了
git clone https://github.com/2H-K/hashpumpy_changed
cd hashpumpy_changed
make
make install
3.2.2 使用
-s
或--signature
:从已知消息中获取的签名(哈希值)。-d
或--data
:已知消息的数据。-a
或--additional
:你想要添加到已知消息中的信息。-k
或--keylength
:用于签署原始消息的密钥的字节长度。
3.2.2 实战使用
见本站《web-buuoj-([De1CTF 2019]SSRF Me)》一文。
┌──(root💀kali)-[~]
└─# hashpump
Input Signature: 8100319869013029db5beab17bfa9ba9
Input Data: scan
Input Key Length: 24
Input Data to Add: read
677ae112e086b6faf3c0f2088773371c
scan\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\xe0\x00\x00\x00\x00\x00\x00\x00read