ThinkPHP反序列化POP链构造本质是利用框架既有类与魔术方法串联触发危险操作,关键在于找准__wakeup/__destruct等入口、通过可控数据触发、经魔术方法自动接力,最终落点于file_exists、call_user_func等敏感函数。

ThinkPHP反序列化POP链的构造,本质是利用框架中已有的类和魔术方法,把它们像“积木”一样串起来,最终触发危险操作。关键不在写新代码,而在读懂已有逻辑、找准可控入口、连通调用路径。
从入口点开始:找谁来触发第一环
POP链必须有个起点,最常见的是__wakeup()或__destruct()。ThinkPHP各版本中,这类方法大量存在于处理请求、日志、缓存、进程管道等组件里。比如:
- v5.1.x 中
think\process\pipes\Windows::__destruct()会调用removeFiles(),再调用file_exists($filename) - v6.0 中容器(
think\Container)和请求对象(think\Request)的魔术方法常被用于参数绑定或反射调用,可能成为链的跳板 - 入口往往藏在用户可控的数据位置:如
unserialize($_GET['data'])、unserialize(file_get_contents($cacheFile)),甚至通过phar://协议触发文件函数间接反序列化
链路衔接:靠魔术方法“自动接力”
POP链不是手动调用,而是依赖PHP在特定场景下自动触发的魔术方法。每一步都得满足“前一个方法能控制下一个对象的属性,且该属性在后续某个魔术方法中被当作对象使用”。典型衔接方式包括:
-
__destruct()→ 调用某方法 → 方法中访问未定义属性 → 触发__get() -
__toString()→ 返回值参与字符串拼接或JSON编码 → 触发toJson()→ 再调用toArray()→ 最终走到getAttr()或getValue() -
__invoke()→ 把对象当函数执行 → 执行时读取自身某个属性作为回调函数名 → 实现任意函数调用
敏感终点:让链落到能干活的地方
链的末端必须抵达可造成实际危害的位置。在ThinkPHP中,常见落点有:
立即学习“PHP免费学习笔记(深入)”;
-
file_exists()、is_dir()等文件系统函数:配合phar://可绕过显式unserialize()调用 -
call_user_func()、call_user_func_array()、eval()(极少见但存在定制类中) - 模型类中的
where()、save()、find()等方法:若参数可控,可能引发SQL注入或命令执行(需结合其他漏洞) - 日志写入、缓存写入等操作:若路径或内容可控,可能导致任意文件写入
实战验证:三步确认链是否可用
构造完一条候选链后,不能只看源码推演,要动手验证:
- 用
serialize(new 类名())生成原始串,检查关键属性是否被序列化(private属性带类名前缀,需注意) - 在本地环境反序列化该串,启用xdebug或var_dump跟踪调用栈,确认是否按预期顺序触发了所有魔术方法
- 替换链中某处属性为恶意值(如伪造文件路径、回调函数名),观察是否真正到达敏感函数并产生预期效果(如报错、文件读取、命令执行)



















