两个缓存的 bucket 指针与一棵释放的树,导致 PHP 的 session.uploadprogress 出现堆释放后重用并触及 zendcallfunction。其处理函数 phpsessionrfc1867callback(位于 ext/session/session.c)把两个 zval * bucket 指针 postbytesprocessed 与 currentfilebytesprocessed 缓存进活跃 HashTable,指向数组在请求生命周期中被攻击者释放。默认配置即可触发:session.uploadprogress.enabled=1,默认名称、前缀、freq=1%、minfreq=1。一次快速 multipart POST 武装两处缓存;第二次 update() 重读会话文件,zendhashupdate 经 HashTable 析构函数 zvalptrdtor 销毁先前解码的树。被释放 bucket 随后被回收,悬垂指针落到攻击者构造 zendfcallinfo / zendinternalfunction,调用 zendcall_function,即 system()。以上在真实 php-src 8.4.4 与 8.2.34 验证。
正文
概述
两个被缓存的 bucket 指针,一棵被释放的树。PHP 的 session.upload_progress 机制中存在一个堆释放后重用(use-after-free)漏洞,并能一路触达 zend_call_function。
PHP 的 session.upload_progress 处理程序(php_session_rfc1867_callback,位于 ext/session/session.c)把两个原始 zval * bucket 指针缓存进仍在使用的 HashTable:post_bytes_processed 和 current_file_bytes_processed。这两个指针都指向会在请求生命周期中被释放的数组,而且释放时机可由攻击者掌控。按上方的 [SOURCE] 可查看完整 bundle:触发请求、两条 PoC 利用链、提取 harness,以及已验证的各构建版本符号偏移。
默认配置就足以触发:session.upload_progress.enabled=1,加上默认的名称、前缀、freq=1% 和 min_freq=1。一次快速的 multipart POST 就会准备好这两个缓存;第二次 update() 会重新读取会话文件,zend_hash_update 通过其 HashTable 析构函数 zval_ptr_dtor 销毁前一棵已解码的树。随后被释放的 bucket 被回收,陈旧指针落到一个由攻击者构造的 zend_fcall_info / zend_internal_function 上,进而调用 zend_call_function,也就是 system()。
以下内容均在真实的 php-src(8.4.4 与 8.2.34)上验证过:原始 writeup 中的每一处行号引用、ZVAL_COPY_VALUE 的不增加引用计数(no-addref)移动、重新解码 / track_init 的释放,以及结构体偏移(zend_fcall_info 为 64 字节,param_count 位于 +48;zend_internal_function.handler 位于 +88;IS_STRING_EX 为 0x106;IS_OBJECT_EX 为 0x308)。
该框架内置了按构建版本提取偏移差的功能(--syms),因为 zval_ptr_dtor 与 zend_call_function 之间的偏移会随每次构建而变化。官方 LiteSpeed lsphp82 8.2.34 的调试符号给出了三个发行版子构建的确切值(bookworm 为 0xc930,noble 为 0xcef0,jammy 为 0xd060),这些值已包含在下文。
用法
如何验证、提取并交付该利用链
$ python3 phpupframework.py --selftest # 伪造与 body 布局检查
$ python3 phpupframework.py --syms --binary /path/to/lsphp # 提取 dtor / call / 偏移差
$ python3 phpupframework.py --build --bug 619 --dtor
$ python3 phpupframework.py --lab --bug 618 --dtor
$ python3 phpupframework.py --live
缓解措施(补丁发布前)
在官方公告发布前该做什么。你今晚就可以完成所有这些工作。
1. 清点你的 PHP
弄清每一台主机及其版本(php -v)。任何仍在运行 PHP 7.x 或更旧版本的系统都不会获得修复,因此需要计划升级。
2. 应用临时缓解措施(依据最佳情报、低风险、可回滚)
在 php.ini / .user.ini / .htaccess 中设置:
session.upload_progress.enabled = Off
然后重载 PHP-FPM 或 Web 服务器。我的分析表明,这会封堵两条可能路径中的一条。
3. 在网络边缘限制 multipart 上传
第二条可能路径存在于 multipart 解析器本身,任何 multipart/form-data POST 都会经过它,与上面的设置无关。如果你有 WAF 或反向代理,应加强检查,或对不需要上传的端点限速 multipart POST。这与企业厂商正在悄悄为其付费客户部署的网络层缓解措施是同一做法。
4. 补丁一经发布立即修复
当前稳定版:8.5.11 / 8.4.26 / 8.3.35 / 8.2.34。如果你的主机是托管型,今天就向服务商确认其修复时间表。
下载
获取地址。
来自常用位置的签名发布包:
- wget http://jackalsyn.com/posts/phpupAssets/php-up-2026.tar.gz
- tar -xzf php-up-2026.tar.gz
- hash(256) a29d640e0481ff64a39541faeeeec0f998ecd18fb692816e75a6bcfa0f683017
致谢:
- 作者 @J4ck3LSyN
- 研究 Andrew Dorman (@acd421)
- 研究 Arch&Yuki (@ArchKnight23)
- 验证与武器化(dev)
评论
多数人质疑文章可读性,认为它像 AI 生成甚至 AI 扮演黑客;少数人则冷嘲 PHP 生态,用夸张说法表达对它的不信任。技术派的分歧主轴是漏洞是否就是仍在封锁中的那个、以及该不该在服务器层拒绝 multipart 请求来止血,另有少数人猛烈批评官方先披露后又把修复补丁封锁 24 小时极不负责。
认为文章几乎没法读,像 AI 代笔或 AI 扮演黑客
- 不少读者抱怨文本几乎没法读,怀疑是 AI 生成;也有人嘲讽这像有人用大模型来角色扮演黑客,整体观感尴尬、像脚本小子的中二表演。(较多人)
- > “这东西基本没法读,哪怕只掺一点人工写作都会比现在强。”(@drum55)
认为 PHP 项目和生态天然不让人放心
- 有人用夸张经历表达对 PHP 的长期不信任,称每个 PHP 项目最后都会冒出以 www-data 身份运行的门罗币矿工;也有人直接关掉应用服务器,等这阵“风暴”过去再说。(少数人)
- > “我碰到的每个 PHP 项目,最后机器里都会冒出一个以 www-data 身份运行的门罗币矿工。”(@himata4113)
认为不需要上传的站点应直接在服务器层拒绝 multipart 请求
- 若 PHP 站点并不需要上传,可在 Nginx 或 Apache 层拦掉内容类型里包含 multipart 的请求;Nginx 可对该类请求直接返回 403,Apache 可用 .htaccess 判断并拒绝。这样会误伤很多正常表单,但被认为比被入侵强。(较多人)
- 仅关闭 PHP 的 session.upload_progress.enabled 并不足以覆盖所有触发路径,不能当作唯一缓解手段。(少数人)
- > “用 Nginx 可以临时屏蔽发往 PHP 的 multipart 请求,虽然会误伤很多表单,但总比被黑好。”(@Mooty)
认为先披露漏洞又把修复补丁封锁 24 小时极不负责
- 有人强烈指责 PHP 内部开发组在公开消息后仍把修复补丁封锁 24 小时,认为这是二十年来最不负责任的做法,等同于把用户留在可被攻击的窗口里。(少数人)
- > “先把事情放出来,又把修复补丁封锁 24 小时,这是 PHP 内部开发组有史以来最不负责任的操作。”(@hparadiz)
认为当前说法可能不是真正未公开的那个漏洞
- 有人引述 PHP 开发者说法,称这可能完全是个无关漏洞,在补丁到位前最实际的缓解是上 SELinux 或 AppArmor 做隔离;另有开发者称自己已找到两个相似的释放后使用问题并自行修补,同时镜像流量等待真实攻击样本,因此暂不公开补丁。(少数人)
- > “据一位 PHP 开发者说,这可能完全是个无关漏洞,眼下最靠谱的临时缓解是 SELinux 或 AppArmor。”(@InvisibleUp)
有价值的补充
- > “这是一个释放后使用漏洞,而这里的‘使用’其实就是执行任意代码,确实很糟。”(@synack)
- 有人核对文中给出的下载哈希值,表示自己对不上,希望有人能验证该哈希。(@e12e)
- 有人担心从 Debian 仓库安装的 PHP 多久才会更新,反映出发行版补丁节奏的不确定性。(@TekMol)
- 有人顺带质疑,既然一直说要用 AI 找漏洞,为什么老代码里的漏洞至今还能被翻出来。(@shevy-java)
- > “读这段东西时我感觉眼睛在物理失焦,大脑在潜意识里抵抗 AI 生成的文字,或许它比我意识到的更像一种认知毒药。”(@fwlr)