yii反序列化漏洞
Yii框架
Yii 是一个适用于开发 Web2.0 应用程序的高性能PHP 框架。
Yii 是一个通用的 Web 编程框架,即可以用于开发各种用 PHP 构建的 Web 应用。 因为基于组件的框架结构和设计精巧的缓存支持,它特别适合开发大型应用, 如门户网站、社区、内容管理系统(CMS)、 电子商务项目和 RESTful Web 服务等。
Yii 当前有两个主要版本:1.1 和 2.0。 1.1 版是上代的老版本,现在处于维护状态。 2.0 版是一个完全重写的版本,采用了最新的技术和协议,包括依赖包管理器 Composer、PHP 代码规范 PSR、命名空间、Traits(特质)等等。 2.0 版代表新一代框架,是未来几年中我们的主要开发版本。
Yii 2.0 还使用了 PHP 的最新特性, 例如命名空间 和Trait(特质)
漏洞描述
yii2.2.0.38之前的版本存在反序列化漏洞,程序在调用unserialize时,攻击者可以通过构造特定的恶意请求执行RCE,CVE编号是CVE-2020-15148.
2.0.38已修复该漏洞,官方的修复方法:
在yii\db\BatchQueryResult类增加了一个__wakeup()函数,函数内容为:当BatchQueryResult类被反序列化时直接报错,wakeup()方法在类被反序列化会自动调用,这样也就避免了反序列化的发生,避免了漏洞。
环境复现
本地使用phpstudy搭建,将Yii的demo下载下来:
https://github.com/yiisoft/yii2/releases/tag/2.0.37
修改/config/web.php里的cookieValidationKey为任何值,不然会报错:
进入目录,执行 php yii serve
进入http:localhost:8080
前置知识点
namespace:
php中命名空间(namespace)的作用和使用_古语静水流深-优快云博客
PHP命名空间(Namespace)的使用详解 - 酷越 - 博客园
[Yii2.0 路由(Route)的实现原理 2.0 版本 ]
所谓路由是指URL中用于标识用于处理用户请求的module, controller, action的部分,一般情况下由 r 查询参数来指定。
如 http://www.digpage.com/index.php?r=post/view&id=100 ,表示这个请求将由PostController 的 actionView来处理。(主要首字母要大写)__construct():当对象创建(new)时会自动调用。但在unserialize()时是不会自动调用的。
__destruct():当对象被销毁时会自动调用。
__call():是在对象上下文中调用不可访问的方法时触发
由于是反序列化利用链,我们需要一个入口点,在controllers目录下创建一个Controller:
controllers/TestController.php:
url:http:localhost:8080/?r=test/test&data=xxx
<?php
namespace app\controllers;
class TestController extends \yii\web\Controller
{
public function actionTest($data)
{
return unserialize(base64_decode($data));
}
}
漏洞分析
漏洞触发点在\yii-2.0.37\vendor\yiisoft\yii2\db\BatchQueryResult.php
文件中
/**
* Destructor.
*/
public function __destruct()
{
// make sure cursor is closed
$this->reset();
}
/**
* Resets the batch query.
* This method will clean up the existing batch query so that a new batch query can be performed.
*/
public function reset()
{
if ($this->_dataReader !== null) {
$this->_dataReader->close();
}
$this->_dataReader = null;
$this->_batch = null;
$this->_value = null;
$this->_key = null;
}
__destruct
()跟进reset()
,继续跟进close()
,发现close()
里没有利用点。但是这里的 _dataRender
是可控的,可以触发__call
方法进行利用。
当一个对象调用不可访问的close
方法或者类中没有close
方法,即可触发 __call
。全局搜索一下 __call
方法,在\vendor\fzaninotto\faker\src\Faker\Generator.php
中找到了合适的方法:
/**
* @param string $method
* @param array $attributes
*
* @return mixed
*/
public function __call($method, $attributes)
{
return $this->format($method, $attributes);
}
这里的$method
为close
函数,$attributes
为空,因为close
函数形参为空。继续跟进format
方法
public function format($formatter, $arguments = array())
{
return call_user_func_array($this->getFormatter($formatter), $arguments);
}
发现了call_user_func_array()
函数,看到了一丝希望。在这里解释一下call_user_func_array()
函数:
call_user_func_array
:调用回调函数,并把一个数组参数作为回调函数的参数
使用方法:
call_user_func_array(callable $callback, array $param_arr): mixed
callback
被调用的回调函数。
param_arr
要被传入回调函数的数组,这个数组得是索引数组。
示例:
<?php
namespace Foobar;
class Foo {
static public function test($name) {
print "Hello {
$name}!\n";
}
}
// As of PHP 5.3.0
call_user_func_array(__NAMESPACE__ .'\Foo::test', array('Hannes'));
//输出:Hello Hannes
// As of PHP 5.3.0
call_user_func_array(array(__NAMESPACE__ .'\F