深入浅出-Hash长度拓展攻击
0x00前言
因为kengwang师傅在新生赛出了这个题目,但是之前没学过www,这就来学学。
0x01 什么是hash长度拓展攻击
hash长度拓展攻击,概括一下就是由于hash的生成机制使得我们可以人为的在原先的明文基础上添加新的拓展字符,从而使得原本的加密链变长,进一步控制加密链的最后一节,使得我们得以控制最终的结果。
这里简单介绍一下hash算法。
hash算法
hash算法又叫做散列算法。是一种把任意长度的字符串加密为固定长度的字符串的加密算法,该算法生成的密文就是散列值。这里拿一个比较典型的MD5来做例子来详细分析一下
0X02MD5加密
MD5 introduction
MD5 消息摘要算法是一种广泛使用的哈希函数,可生成 128位哈希值。 MD5 由Ronald Rivest于 1991年设计,用于取代早期的哈希函数MD4 ,并于 1992 年被指定为 RFC 1321。
MD5 可用作校验和来验证数据完整性,防止意外损坏。历史上它被广泛用作加密哈希函数;然而,它被发现存在广泛的漏洞。它仍然适用于其他非加密目的,例如用于确定分区数据库中特定密钥的分区,并且由于比最新的安全散列算法更低的计算要求,它可能是首选。
MD5 proccess
这里我将MD5加密分为三个步骤,分别是:
- 填充
- 分块
- 多轮压缩 最后输出,这里我一一举例
填充
这里我贴一下墨师傅的脚本
|
|
具体的代码逻辑应该很清晰
- input是0-f
- 将input转换为2进制内容 这里的输出为
|
|
这里便是128位的二进制字符 或者我们可以用010来查看文件,这里举例 这里就是9*8=72个二进制字符
填充的rule
什么叫填充呢,填充的过程就是讲2进制字符个数填充到512比特的整数倍大小,并且需要在最后面预留64位来固定表示原始数据大小,即小端在前格式,中间剩下的比特第一个填1其余的补0
那么对于以上的两个二进制文件,则就可以按照这个填充规则来进行填充。
疑问?
这里提出一个小问题,如果说你输入的数据刚好为512bit呢,那么他是否会进行填充,答案是肯定的,因为没有最后64位固定值来表示原始数据大小,那么就会填充到1024-512=512个数据(当然包含进最后64位固定数据)。 包括在最后不足64位时也需要补齐到下一个512整数倍的数据。 最后补的64位的值就是前面数据长度的大小,例如abcd,则就是4*8=32,转化成二进制就是100000。
分块
那么在前面进行填充后肯定可以进行分块,并且分块肯定可以是512的整数倍。 并且这个时候会给出四组幻数来作为md5的初始值 例如 0x1a8ba2ba 0x18s62abs 0x17ais27a2 0x1a662b1a
多轮压缩
过程:把当前的四组散列值各复制一份,分别用abcd来表示 然后在每一个数据块中进行四轮操作,其中包含与,或,非,和循环位移操作,每次把abcd更新四次。 所以每个大块都可以对abcd的值进行更新16次。
这里我贴一个java实现md5加密的脚本
|
|
MD5攻击
第一原像攻击(First Preimage Attack)
第一原像攻击的目标是从给定的哈希值ℎh中找到相应的消息m,使得当这个消息被输入到哈希函数(例如MD5)时,会产生给定的哈希值ℎ。
在MD5的上下文中,虽然找到第一原像的理论复杂度是O(2^128)(由于MD5生成128位的哈希值),但是由于MD5的安全性问题,实际上的攻击复杂度远低于理论值。例如,研究已经显示,使用现代硬件和算法,可以在几小时或几天内找到MD5的第一原像。
第二原像攻击(Second Preimage Attack)
第二原像攻击的目标是对于给定的输入消息m1,找到另一个不同的消息m2,使得两个消息产生相同的哈希值。这意味着对于哈希值ℎ,存在两个不同的消息m1和m2满足:
MD5(m1)=MD5(m2)=h
同样,理论上,第二原像攻击的复杂度是O(2^128),但由于MD5的弱点,实际攻击的复杂度比理论值低得多。尽管如此,相对于第一原像攻击,第二原像攻击仍然是一个更为困难的问题。
碰撞攻击
碰撞攻击是一种密码学攻击,目的是找到两个不同的输入消息(也称为原文),这两个消息在经过同一哈希函数计算后,会产生相同的哈希值。换句话说,攻击者试图找到两个不同的消息 m1 和 m2,使得:
hash(m1)=hash(m2)
在MD5的上下文中,由于MD5哈希值为128位,理论上的碰撞攻击复杂度为O(2^128)。然而,由于MD5的设计缺陷和计算上的优化,现实中的碰撞攻击比这个理论复杂度要低得多。
example
2004年,Xiaoyun Wang等人发布了一个研究,成功地找到了两个不同的PDF文件,这两个文件在经过MD5哈希后产生了相同的哈希值。这是MD5碰撞攻击的早期和最著名的例子。
具体来说,这项研究找到了两个PDF文件:一个是普通的合同文件,另一个是恶意的PDF文件,其中嵌入了攻击代码。当这两个文件经过MD5哈希后,它们产生了相同的128位哈希值。这意味着,对于MD5哈希值来说,这两个文件是不可区分的,尽管它们的内容截然不同。
这个例子揭示了MD5在实际应用中的严重安全风险。如果攻击者能够找到一个合法文件和一个恶意文件,这两个文件的MD5哈希值相同,那么攻击者就可以轻易地伪装合法文件,诱使用户打开恶意文件,从而进行恶意攻击。
由于MD5的这种弱点,现在强烈建议避免使用MD5进行任何安全相关的应用,而应选择更安全的哈希算法,如SHA-256或SHA-3。这些算法在当前时间(2022年1月之前)被认为是安全的,并且不容易受到碰撞攻击的威胁。
Hash 长度拓展攻击
可能我现在写的东西有点抽象,不过后面我会总结讲一下为什么要这么做以及他的原理 这里定义一些东西
- let
secret = "secret"
- let
data = "data"
- let
H = md5()
- let
signature = hash(secret || data) = 6036708eba0d11f6ef52ad44e8b74d5b
- let
append = "append"
首先这里放出一个简单的example
|
|
代码模式可能看的不是很清晰,这里我贴出来图片 当然这里是利用16进制来表示的,我们用二进制也是可以的,8->1,5->8bit。
ATTACK
这里我们将一个值,就是前面的append加入到字符串中,是这样的
此时,这个哈希值有两种计算方式(破题点,这个后面会好好解释一下可能这里比较抽象)
- 通过将其粘贴在缓冲区中并执行
H(buffer)
- 从第一个块的末尾开始,使用我们已经知道的状态
signature
,并append
从该状态开始进行散列
对于一种(也是在服务器端的计算过程)
其实就是按照正常的生成hash值的过程,最后生成出来的哈希值请我们这里暂定为:
6ee582a1669ce442f3719c47430dadee
这里贴出一下生成其md5的一个脚本
|
|
可以看到这里的签名就是我们之前的数据。
第二种(也就是攻击者)
这里我直接贴脚本出来
|
|
可以看见我们这里对四个幻数(前面的理论有讲到),作为了一个初始参数
let signature = hash(secret || data) = 6036708eba0d11f6ef52ad44e8b74d5b
来进行md5值的加密,其实就是利用了AAAA那群数据块对其进行了再次的多轮压缩环节。
最后生成出来的哈希值
6ee582a1669ce442f3719c47430dadee
其实到此为止,这个攻击就完成了。但是这里要说一下,放在理论上是比较难理解的,这里还是需要举例子可以更加直观的理解(其实在题目中的本质就是将最后面的多轮压缩再对你可控的append进行一次压缩从而你可以知道后面生成的MD5值是什么)
DEMO1
|
|
这里直接放出playload:
?md5=d9671633e3723203bc2a1479c8412307&i=-1&s=}yusa%80%00%00%00%00%00%00%00%00%00%00%00%00%00%50%01%00%00%00%00%00%00a
这里我利用前面的的讲的ATTACK来进行讲解:
例如我们的flag值是DT{axsak1dlsajl1}
那么此时的16进制就是这样的
然后在加了salt之后就是加入yusa
后就是
此时的原始数据长度就是21*8=168bit,转化为16进制就是a8
填充后就是这样
那么进行多轮压缩的时候就是将其作为一个数据块进行压缩。
这里我直接贴出来md5值为:
2b604a43f439b6e02c09dece6a83e503
这个是我们已知的,后面用[YZ]表示
构造
这个时候我们就可以构造一下我们的playload什么的了
我们看到题目
$fl4g = substr_replace($flag, $_GET["s"], $_GET["i"], 1);
是对原本的flag进行了替换,那么此时我们可以想想,因为这个题目只能替换掉一个长度,那么对于定义,在前面的数据块中我们已经知道了其md5值,那么我们可以替换后添加到下一个数据块让他再次进行压缩,这个时候我们就知道他的下一个构造出来的md5是什么了
过程:
这个是完整的一个数据块,那么此时我们如果替换完后把他变为
这样之后那么是不是在服务端就会再次对其进行填充和多轮压缩,也就是这样
(这里的A7是我随便写的,因为懒得算前面的长度再转换了)
这里此时是不是第二个分块压缩就会利用包含a在的数据块再次对数据进行压缩,此时生成出来的md5值就和我们需要的md5值便一样了,
这里随便举一个例子:我们生成的md5是d9671633e3723203bc2a1479c8412307
那么显而易见,在服务端生成的md5值也就一定是这个也就绕过了
$_GET["md5"] === md5($fl4g)
的限制了
demo1小总结
总的来说,我们可以很容易的看出来,其实就是我们可以构造出和原本数据一样的数据块之后自己补到下一个数据块内容来进行获得md5的加密值,也就是将我们的YZ放到上面ATTACK->第二种下面的脚本来进行获得md5值的过程
这个demo1后面再放出一个py脚本
|
|
DEMO2
这个是kengwang师傅出的一道题目
|
|
这里同样也给出了加密前的secret的md5值,我们这里就当做data也就是demo1的yusa是空值即可。 当然因为这里加入了死亡杂糅的内容所以这里我放出wp,其实总的来说还是上面分析的方法。
我们可以使用 hash_extender
这个工具来进行哈希长度扩展攻击, 生成新的密文
首先考虑将secret
写入到一个文件中
执行:
|
|
得到:
|
|
可能需要用 CyberChef 套 From Hex (Auto) - To Base64
发送:
|
|
访问 a.txt
发现secret
是<?php die();
, 接下来是死亡 die 绕过
这里需要用到伪协议的一些技巧:
首先利用 string.strip_tags
的过滤器链吞掉之前的死亡 die, 然后再在后面接上你想要写的内容
这里使用写 .htaccess
的方法来实现rce, 直接加个 auto_prepend_file
即可
构造出 php://filter/write=string.strip_tags/?>php_value auto_prepend_file flag\n#/resource=.htaccess
|
|
最终 payload:
|
|
即可写入
Conclusion
总的来说,这个攻击方法还是很好玩的,只不过发现市面上很多的文章写的不尽人意,看了一天都可能不明白其中的原理,写的过于抽象所以想着写写。 只要我们注意到MD5中加密过程中的填充和多轮压缩,并且利用即可,当然也有很多工具可以生成一些playload,这个就不放出来了。 最后感谢kengwang师傅,x1r0z师傅的指导
参考文章: 1、 https://xz.aliyun.com/t/10602 2、 https://github.com/iagox86/hash_extender