PHPWIND 老版本GBK 任意管理API调用

前言

​ 几年前,PHPWIND出过一个MD5 Padding可实现调用任意管理api方法的问题自己写了个exp, 不过遇到个老gbk(2014)版本没法成功利用, 下载了个GBK版本看了下代码。之前漏洞成因可以查看Reference链接,这里不再细谈。

分析

​ 直接对比一下两个版本关键代码的不同之处。

​ UTF版本 src/windid/service/user/srv/WindidUserService.php

1
2
3
public function showFlash($uid, $appId, $appKey, $getHtml = 1) {
$time = Pw::getTime();
$key = WindidUtility::appKey($appId, $time, $appKey, array('uid'=>$uid, 'type'=>'flash', 'm'=>'api', 'a'=>'doAvatar', 'c'=>'avatar'), array('uid'=>'undefined'));

​ src/windid/service/base/WindidUtility.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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);
}

​ GBK版本 src/windid/service/user/srv/WindidUserService.php

1
2
3
public function showFlash($uid, $appId, $appKey, $getHtml = 1) {
$time = Pw::getTime();
$key = WindidUtility::appKey($appId, $time, $appKey, array('uid'=>$uid, 'type'=>'flash'), array('uid'=>'undefined'));

​ src/windid/service/base/WindidUtility.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static function appKey($apiId, $time, $secretkey, $get, $post) {
$array = array('m', 'c', 'a', 'windidkey', 'clientid', 'time', '_json', 'jcallback', 'csrf_token', 'Filename', 'Upload', 'token');
$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);
}

这里很大的一个区别是在GBK版本中appKey签名方法, 竟然不将m、c、a参数值加入签名中计算, 那么相当于我们能够随意修改这些参数。GBK版本中, showFlash方法中调用appKey方法时也未将m、c、a参数加入到get数组中。

那么相当于我们在访问/index.php?m=profile&c=avatar&_left=avatar

1
<param name="FlashVars" value="postAction=ra_postAction&redirectURL=/&requestURL=http%3A%2F%2Flocalhost%2Fphpwindgbk%2Fwindid%2Findex.php%3Fm%3Dapi%26c%3Davatar%26a%3DdoAvatar%26uid%3D2%26windidkey%3D7d0cff9b85cc0f62fe062421d1caf067%26time%3D1567155029%26clientid%3D1%26type%3Dflash&avatar=http%3A%2F%2Flocalhost%2Fphpwindgbk%2Fwindid%2Fattachment%2Favatar%2F000%2F00%2F00%2F2.jpg%3Fr%3D66455"/>

拿到的windidkey(7d0cff9b85cc0f62fe062421d1caf067)是md5(md5($apiId.’||’.$secretkey).$time.’typeflashuid2uidundefined’)的值, 从FlashVars中可以拿到time和apiId(clintid)所以也就是md5(md5(‘1’.’||’.$secretkey).’1567155029’.’typeflashuid2uidundefined’) uid2这个每个环境都不同,不过uid也输出到了FlashVars中。

接着来看调用管理的api时是如何对签名进行校验的,

src/applications/windidserver/api/controller/OpenBaseController.php

1
2
3
4
5
6
7
8
9
10
11
12
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();

time、clientid都已知, 这里因为m、c、a参数并不会加入签名计算,我们只要构造出typeflashuid2uidundefined就能通过判断进而调用任意Api方法。

1567155842824

如何判断是否为GBK版本

1
2
<meta charset="GBK" />
<title>本站新帖 - phpwind 9.0 - Powered by phpwind</title>

直接查看meta标签的charset属性就能确定。

References

  1. https://mp.weixin.qq.com/s?__biz=MzA5NzQxOTQ1MA==&mid=2247483676&idx=1&sn=161d456328bdcf71b65a03a3376891dc