ThinkPHP 6.1.3 反序列化POP链审计需从可控unserialize入口出发,沿__destruct/__wakeup溯源至__toString/__call等魔术方法,严格验证属性可写性、自动触发性及危险操作,最终通过本地实测闭环验证链有效性。

ThinkPHP 6.1.3 的反序列化 POP 链审计,核心是定位可控入口、追踪魔术方法调用链、验证属性可写性。它不是靠扫描函数名,而是顺着对象生命周期中自动触发的“钩子”一步步推演。
找反序列化入口点
先确认框架是否存在未过滤的 unserialize() 调用。重点关注以下位置:
-
think\cache\driver\File.php中的get()方法,若缓存键可控且启用文件缓存,可能反序列化内容 -
think\Session.php的init()或read(),尤其当 session 存储为 file 或 redis 且数据未净化时 - 自定义控制器中显式调用
unserialize(input('data'))、unserialize(cookie('payload'))等逻辑(常见于调试接口或旧版兼容代码) - Phar 文件触发点:若应用允许上传 Phar 并通过
file_get_contents('phar://xxx')或include 'phar://xxx'加载,会自动触发反序列化
从 __destruct 和 __wakeup 开始溯源
POP 链通常始于对象销毁或反序列化重建阶段。在 ThinkPHP 6.1.3 中重点审计:
-
think\process\pipes\Windows.php的__destruct()→removeFiles()→file_exists($this->files):若$this->files是对象,会强制类型转换触发__toString() -
think\model\Pivot.php(继承think\Model)的__destruct()→save()→updateData()→checkAllowFields():该方法存在字符串拼接,可将$table或$suffix控制为对象,诱导进入__toString() -
think\cache\driver\Psr6Cache.php的__destruct()→save(),其中$this->pool和$this->key若可控,可导向危险操作
跟踪 toString / call / get / invoke 等跳转节点
中间环节依赖魔术方法接力。需逐层检查是否满足“可控属性 + 自动调用 + 危险操作”三要素:
立即学习“PHP免费学习笔记(深入)”;
-
__toString()常见于think\model\concern\Conversion.php,调用toJson()→toArray()→getAttr()→getValue();若$this->data或$this->withAttr可控,可能进入用户定义的回调 -
__call()出现在模型调用不存在方法时,若参数被直接传入call_user_func_array($method, $args),且$method可控(如设为'system'),则构成命令执行 - 注意属性可见性:私有属性序列化格式为
s:15:"\0ClassName\0attr",构造 payload 时必须严格匹配,否则反序列化失败
验证链路是否真正可用
静态分析后必须实测闭环。建议步骤:
- 用
php --ri serialize确认 PHP 未禁用unserialize(某些生产环境会通过unserialize_callback_func或allowed_classes限制) - 在本地搭建相同版本 ThinkPHP 6.1.3,复现疑似链路,用
var_dump()插桩确认每一步魔术方法是否被调用 - 构造最小 payload,例如让
__toString()返回phpinfo()或写入临时文件,观察是否生效 - 检查 autoloader 是否加载了所有链上类——缺失任意一环(如某个 trait 或抽象基类未被引入),链即中断



















