phpwind index.php?m=design&c=api,phpwind 利用哈希长度扩展攻击进行getshell

一哥新发的漏洞,还是蛮屌的:

今晚基本没睡觉,一直在调试这个漏洞,虽说不知道一哥后续getshell用的是什么方法,但我这个文章基本把该漏洞的原理讲清楚了。至于getshell的话,等漏洞公开了再看看吧。0x01 漏洞点分析

phpwind逻辑太冗杂了,一看就是java程序员开发的。

补丁文件修补了src/windid/service/base/WindidUtility.php的appKey函数。之前的appKey函数如下:[size=1em][size=1em]01public static function appKey($apiId, $time, $secretkey, $get, $post) {

[size=1em]02// 注意这里需要加上__data,因为下面的buildRequest()里加了。

[size=1em]03$array = array('windidkey', 'clientid', 'time', '_json', 'jcallback', 'csrf_token',

[size=1em]04'Filename', 'Upload', 'token', '__data');

[size=1em]05$str = '';

[size=1em]06ksort($get);

[size=1em]07ksort($post);

[size=1em]08foreach ($get AS $k=>$v) {

[size=1em]09if (in_array($k, $array)) continue;

[size=1em]10$str .=$k.$v;

[size=1em]11}

[size=1em]12foreach ($post AS $k=>$v) {

[size=1em]13if (in_array($k, $array)) continue;

[size=1em]14$str .=$k.$v;

[size=1em]15}

[size=1em]16return md5(md5($apiId.'||'.$secretkey).$time.$str);

[size=1em]17}

可见,这里其实是一个『签名』函数,将GET、POST变量使用secrectkey进行签名,但签名的算法比较古老,直接用md5。

其中详尽的说明了哈希长度扩展攻击的原理及利用方法(所以我文章就不细说了),总结起来其实就一句话:

当知道 MD5(secret) 时,在不知道secret的情况下,可以很轻易的推算出 MD5(secret||padding||m')

当前phpwind的签名算法刚好符合上述的格式 md5(md5($apiId.'||'.$secretkey).$time.$str)。我们虽然不知道md5($apiId.'||'.$secretkey),但$time和$str是可控的,可以进行哈希长度扩展攻击。0x02 寻找已知哈希

根据哈希长度扩展攻击的原理,也就是 MD5(secret) --> MD5(secret||padding||m'),我们就需要先找到一个已知的secret。

src/windid/service/user/srv/WindidUserService.php showFlash函数:[size=1em][size=1em]1public function showFlash($uid, $appId, $appKey, $getHtml = 1) {

[size=1em]2$time = Pw::getTime();

[size=1em]3$key = WindidUtility::appKey($appId, $time, $appKey, array('uid'=>$uid, 'type'=>'flash', 'm'=>'api', 'a'=>'doAvatar', 'c'=>'avatar'), array('uid'=>'undefined'));

[size=1em]4$key2 = WindidUtility::appKey($appId, $time, $appKey, array('uid'=>$uid, 'type'=>'normal', 'm'=>'api', 'a'=>'doAvatar', 'c'=>'avatar'), array());

[size=1em]5

[size=1em]6$postUrl = "postAction=ra_postAction&redirectURL=/&requestURL=" . urlencode(Wekit::app('windid')->url->base . "/index.php?m=api&c=avatar&a=doAvatar&uid=" . $uid .'&windidkey=' . $key . '&time=' . $time . '&clientid=' . $appId . '&type=flash') .'&avatar=' . urlencode($this->getAvatar($uid, 'big') . '?r=' . rand(1,99999));

[size=1em]7...

[size=1em]8}

这里调用了WindidUtility::appKey生成key,并将这个key放入了url中。其实这个功能是前台用户头像上传,我们来到

8f3ff2fbfdd7c7c8339951a6542705fa.gif

1.jpg (55.79 KB, 下载次数: 53)

2016-10-17 17:08 上传

这个key,实际上是通过如下计算获得:[size=1em][size=1em]1$get = some_sort(array('uid'=>$uid, 'type'=>'flash', 'm'=>'api', 'a'=>'doAvatar','c'=>'avatar'));

[size=1em]2$post = some_sort(array('uid'=>'undefined'));

[size=1em]3md5( md5($apiId.'||'.$secretkey) . time() . $get . $post )

也就是说,此时的secret其实是 md5($apiId.'||'.$secretkey) . time() . $get . $post,大概类似md5(...) + 1464044063adoAvatarcavatarmapitypeflashuid2uidundefined

在哈希长度扩展攻击中,我们是不需要知道这个secret的值的,只需要知道它的长度,上面这个字符串的长度很好算,md5固定32位,time时间戳10位,get和post两个数组也是固定的。

所以,经过计算,uid=2的用户获得的secret长度为55位。0x03 进行扩展攻击

公式: MD5(secret) --> MD5(secret||padding||m'),我们已经知道了MD5(secret)和secret的长度(55),现在可以推MD5(secret||padding||m')了。

因为secret的值为md5(...) + 1464044063adoAvatarcavatarmapitypeflashuid2uidundefined,所以下次增加的padding和m'必须放在1464044063adoAvatarcavatarmapitypeflashuid2uidundefined后面。

我们需要推导并绕过的是如下函数 src/applications/windidserver/api/controller/OpenBaseController.php beforeAction函数:[size=1em][size=1em]01public  function beforeAction($handlerAdapter) {

[size=1em]02parent::beforeAction($handlerAdapter);

[size=1em]03$charset = 'utf-8';

[size=1em]04$_windidkey = $this->getInput('windidkey', 'get');

[size=1em]05$_time = (int)$this->getInput('time', 'get');

[size=1em]06$_clientid = (int)$this->getInput('clientid', 'get');

[size=1em]07if (!$_time || !$_clientid) $this->output(WindidError::FAIL);

[size=1em]08$clent = $this->_getAppDs()->getApp($_clientid);

[size=1em]09if (!$clent) $this->output(WindidError::FAIL);

[size=1em]10if (WindidUtility::appKey($clent['id'], $_time, $clent['secretkey'], $this->getRequest()->getGet(null), $this->getRequest()->getPost()) != $_windidkey)  $this->output(WindidError::FAIL);

[size=1em]11

[size=1em]12$time = Pw::getTime();

[size=1em]13if ($time - $_time > 1200) $this->output(WindidError::TIMEOUT);

[size=1em]14$this->appid = $_clientid;

[size=1em]15}

我们看到,这里计算出 WindidUtility::appKey($clent['id'], $_time, $clent['secretkey'], $this->getRequest()->getGet(null), $this->getRequest()->getPost()) 的结果,与传入的windidkey进行比较,如果不相等,则函数报错。

我们需要先构造出1464044063adoAvatarcavatarmapitypeflashuid2uidundefined,才能够在后面增加我们需要的GET和POST值。

所以,我传入time=1464044063,而adoAvatarcavatarmapitypeflashuid2uidundefined作为一个GET数组的键传入。

这样,在appKey函数构造的过程中,就可以人为构造出secret,然后后面的内容就是可以利用的padding,如下图(s是未知的key,t是获取md5时的时间戳,R就是adoAvatarcavatarmapitypeflashuid2uidundefined):

8f3ff2fbfdd7c7c8339951a6542705fa.gif

2.jpg (58.61 KB, 下载次数: 34)

2016-10-17 17:08 上传

所以,我们构造出类似如下的数据包:

8f3ff2fbfdd7c7c8339951a6542705fa.gif

3.jpg (39.09 KB, 下载次数: 35)

2016-10-17 17:08 上传

此时后端进行的加密如下:MD5( key + 1464048076 + adoAvatarcavatarmapitypeflashuid2uidundefined + %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%B8%02%00%00%00%00%00%00 + sort(a=get&c=app&m=api&id=1) )

我之前获取的secret如下:MD5( key + 1464048076 + adoAvatarcavatarmapitypeflashuid2uidundefined )

正好满足哈希长度扩展攻击的条件,成功绕过beforeAction中的验证。0x04 获取敏感信息 + 修改管理员密码 + getshell

我绕过了beforeAction的验证,其实效果类似于绕过了discuz中UC_KEY的验证过程。拿到了UC_KEY,就可以构造为任意用户做其权限(甚至是管理员权限)下的很多事情。

比如,获取敏感信息:

8f3ff2fbfdd7c7c8339951a6542705fa.gif

4.jpg (29.5 KB, 下载次数: 41)

2016-10-17 17:08 上传

获得所有系统配置信息(包括cookie加密的密钥):

8f3ff2fbfdd7c7c8339951a6542705fa.gif

5.jpg (29.21 KB, 下载次数: 38)

2016-10-17 17:08 上传

我写了一个脚本用来生成payload,我就不公开了。看懂的人自然会写,看不懂的人好好琢磨琢磨:

8f3ff2fbfdd7c7c8339951a6542705fa.gif

6.jpg (19.13 KB, 下载次数: 30)

2016-10-17 17:08 上传

另外,利用该方法可以修改所有系统配置、增删改APP、增删改查用户、修改用户密码等等。phpwind有个奇怪的逻辑,其管理员分为『创始人』和『管理员』,而创始人如果要登录后台,需要一个保存在文件中的账号密码,而管理员登录后台需要的是数据库中的账号密码。

这个漏洞只能修改数据库中的账号密码,所以无法修改创始人的后台账号,但管理员权限也就够了,配合我之前发的phpwind后台getshell(

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值