thinkphp 6.0反序列化漏洞本质是可控入口(如file驱动read、model::__destruct)触发pop链,通过__destruct→save→updatedata→db→__tostring逐级跳转,最终实现任意代码执行;关键在于lazysave、exists、data、table等属性可控,且不依赖外部扩展。

ThinkPHP 6.0 的反序列化漏洞不是“有没有”的问题,而是“在哪触发、怎么绕过、哪些类能链上”的实操问题。框架本身大量使用魔术方法(__destruct、__wakeup、__toString),加上容器、模型、会话等组件间深度耦合,一旦入口点可控(比如 unserialize() 被用于处理用户传入的 session_id、缓存键、日志上下文等),POP 链就极容易成形。
怎么快速定位反序列化入口点
别从头读源码。先盯住所有可能调用 unserialize() 的地方,尤其是那些参数明显由用户控制的位置:
-
vendor/topthink/framework/src/think/session/driver/File.php中的read()方法:它直接对$id拼接路径后file_get_contents(),再unserialize()—— 如果$id可被污染(如通过伪造 session_id 或修改 Cookie),这就是高危入口 -
vendor/topthink/think-orm/src/Model.php的__destruct():只要$this->lazySave === true就会触发save(),而save()会走到updateData(),最终在db()中把$this->table当字符串用 —— 若该属性是对象,就会触发其__toString() - 自定义控制器里显式调用
unserialize($_GET['data'])或类似逻辑(常见于调试接口、旧版兼容路由),这类代码往往藏在app/controller/下非标准命名的类中,比如Test.php、Debug.php
为什么 Model::__destruct 是经典链起点
因为它的可控性高、链路清晰、且不依赖外部扩展:
-
$this->lazySave是 public 属性,反序列化时可直接赋值为true -
$this->exists控制是否走 update 分支,设为true即可绕过 insert 判断 -
$this->data不能为空(empty($this->data)会失败),但只要填个非空数组(如["x"=>1])就能过isEmpty() - 最关键的是
$this->table:它在db()中被强制转为字符串,若设为一个带__toString()的对象(比如think\model\concern\Conversion实例),链就续上了
这个链不依赖日志、缓存或第三方驱动,只靠框架核心 ORM 组件,复现稳定。
绕过 __wakeup() 限制和类型检查的实操细节
ThinkPHP 6.0.12+ 对部分类加了 __wakeup() 抛异常的防护,但很多老版本或自定义类没补全。实战中要注意:
- 某些类(如
think\cache\driver\File)的__wakeup()会清空$this->options,导致后续链断裂;此时应换用未加固的类,或找不走__wakeup()的入口(比如直接触发__destruct()) -
__toString()的目标类必须满足:方法内调用的函数能进一步触发危险操作(如toJson()→toArray()→__call()或__get())。think\model\concern\Conversion的__toString()调用toJson(),而toJson()又调用toArray(),后者对不存在的属性会触发__get()—— 这就是链延伸的关键跳板 - 注意 PHP 版本差异:
__destruct()在反序列化后立即触发,但若对象在反序列化中途被 GC 回收(如引用计数归零),可能不执行;确保 payload 中至少有一个变量持有该对象引用
Session ID 注入是最容易被忽略的利用场景
很多人只盯着显式的 unserialize() 调用,却忘了 ThinkPHP 的 Session 文件驱动本身就是反序列化载体:
- 默认
File驱动将 session 数据以serialize()格式写入文件,文件名即 session_id - 如果应用允许用户控制 session_id(比如通过 Cookie 设置、URL 传参、或登录态伪造),攻击者可构造恶意 session_id(如
../../../../var/log/apache2/access.log),让read()去读取并反序列化任意文件内容 - 更隐蔽的是:某些中间件或钩子会在 session 启动前解析
PHPSESSID,若解析逻辑存在正则绕过或类型混淆(如把十六进制字符串误判为合法 id),也可能导致非法 session_id 被接受 - 修复不是简单加
ctype_alnum(),而是要确认 session_id 生成、传输、校验全流程是否都强制约束为安全字符集,且不参与任何文件路径拼接
真实攻防中,90% 的 TP6 反序列化利用不是靠手写长链,而是靠污染 session_id + 文件驱动读取日志/临时文件,再用其中已存在的 gadget 触发执行 —— 这类利用链短、稳定、难检测。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











