PHP 漏洞源码调用栈深、魔术方法乱跳,光看容易晕。解法是搭调试环境单步跟:变量怎么变、链子怎么走一目了然。这篇记录环境搭建与跟踪 ThinkPHP 反序列化链的过程。
环境清单
| 组件 | 版本/说明 |
|---|---|
| phpStudy | PHP 7.3 + 自带 Xdebug 扩展 |
| 靶场 | ThinkPHP 5.1.29(tpdemo5129),跑在 8002 端口 |
| 编辑器 | VSCode + PHP Debug 扩展(xdebug.php-debug) |
| 调试点 | 127.0.0.1:8002/index/index/index?c=<payload> |
Xdebug 配置
php.ini 关键几行(Xdebug 3.x):
[Xdebug]
zend_extension=xdebug
xdebug.mode = debug
xdebug.client_host = 127.0.0.1
xdebug.client_port = 9000
xdebug.start_with_request = yes
start_with_request = yes:每个请求都连调试器,调试期方便,平时关掉。
VSCode 侧
launch.json 只需一个 Listen for Xdebug 配置,端口 9000。启动监听后浏览器(或 curl)发请求,Xdebug 主动连过来。
验证连通性:抓包看 dbgp 握手——监听后第一个包是 Xdebug 发的 <init ...> XML,有就通。我第一次配时端口被别的进程占用、半天没反应,就是靠抓包定位的。
实战:单步跟踪 TP 5.1.29 反序列化链
payload 触发 unserialize() 后,在三个位置下断点:
Windows.php:163 __destruct() 里 removeFiles() 的调用点
Request.php:331 魔术方法跳板
Conversion.php:192 __toString() 最终拼接点
关键观察:
__destruct不是起点而是引爆点:对象销毁时自动触发,POP 链第一环从这里挂上。- 变量在哪一步被污染:单步到
removeFiles(),watch 窗口能看到可控属性值流进文件操作函数。 - 跳板方法怎么找:在调用处”步入”几次就明白为什么选这几个类——跳板只做”把可控属性传给下一个魔术方法”。
跟完再看 payload 构造,不是背链子,而是知道每一环为什么这么串。
踩坑
- 9000 端口无响应 → 先查端口占用,再抓包看有没有 dbgp 握手,别瞎改配置。
- 断点不命中 → 确认请求路由真会走到那个类(TP 命名空间映射先看懂)。
- php.ini 改了没生效 → phpStudy 要重启对应 PHP 服务,phpinfo() 里搜 xdebug 确认。
- Xdebug 2.x 和 3.x 配置项完全不同(
remote_enablevsmode),对着版本写。
小结
审计 + 调试是两条腿:审计帮你猜链子在哪儿,调试验证怎么走。环境搭一次约二十分钟,之后所有 PHP 漏洞分析复用。