PHP2Shell漏洞CVE-2026-109618/109619真实存在且危害严重,但公开PoC声称的单请求zval UAF直接RCE未被独立实验稳定复现;实际可复现路径是核心写入原语与应用层LFI组合利用。

2026 年 10 月 20 日,两个 PHP 核心 CVE 编号在 Hacker News 和社交媒体上开始流传。按原计划,完整技术细节要等到 10 月 22 日才解禁,但泄露已经发生,PHP 安全团队随即把紧急补丁的发布时间提前到了 10 月 21 日 14:00 UTC。被提前“逼出来”的版本,正是 PHP 8.4.27。
这次披露的两个漏洞被统称为 PHP2Shell:CVE-2026-109618 和 CVE-2026-109619,CVSS v4.0 均为 9.2,官方描述为未认证远程代码执行。影响面覆盖所有处理 Web 请求的 PHP 部署——PHP-FPM、Apache mod_php、php-cgi、FrankenPHP、LiteSpeed,其中 mod_php 配置的风险被官方视为最高:单个未授权请求即可在应用代码运行之前触发攻击。8.4 分支的修复版本是 8.4.27,受影响范围是 8.4.0 – 8.4.26。
但“9.2 分 RCE”并不等于“随便一条 curl 就能拿 shell”。围绕 8.4.4 的复现验证,恰恰需要把公开 PoC 声称的东西和实际能在实验室里稳定跑通的东西分开来看。
公开 PoC 的声称:session.upload_progress 里的 zval UAF
在补丁正式发布前和发布当天,GitHub 上出现了大量基于同一叙事逻辑的 PoC。核心机制指向 ext/session/session.c 中的 rfc1867 上传进度处理器:该处理器把裸 zval* 指针缓存进 session HashTable 的 arData,当 multipart 上传的停滞时间超过 session.upload_progress.min_freq(默认 1 秒),后续 FILE_DATA 事件会重新进入 session 初始化流程并释放该 HashTable,缓存指针随之悬空;下一次写入 bytes_processed 时就会通过 stale pointer 完成堆 UAF 写。
立即学习“PHP免费学习笔记(深入)”;
对应到两个 CVE,公开分析给出的区分是:
CVE-2026-109618:
post_bytes_processed相关的 stale pointer;CVE-2026-109619:
current_file_bytes_processed相关的 stale pointer。
syrex1013 的 lab-uaf/ 明确以 php:8.4.4-fpm 作为复现环境,声称可以在 stock PHP-FPM、不带 ASAN、不带 debug build 的条件下复现 stale-pointer write。快速启动方式也很直接:
git clone https://github.com/syrex1013/php2shellcd php2shell/lab-uafdocker compose up -d --buildpython3 scripts/php2shell_poc.py --monitor --rounds 5
预期输出会看到 bytes_processed 被更新,以及 “Cached zval* pointers were written through (stale path reached)”。另一条被广泛引用的验证来自 zhaoboy9692/CVE-2026-109618_,标题直接写着 “Verified End-to-End (PHP 8.4.4, stock php:8.4.4-cli)”,使用 PHP_SESSION_UPLOAD_PROGRESS 字段注入 <?php ... system("id"); ... ?>,再通过应用层 include() 触发,最终回显 uid=0(root)。
8.4.4 上真正能跑通的是什么:PoC 的“声称”与实测的落差
这里需要引入一个关键的、容易被公众号标题掩盖的事实。
2026 年 10 月 22 日,一篇标题为《Investigating the Return of the PHP2Shell Nightmare》的独立调查给出了明确的测试记录:公开的 618/619 PoC 在 PHP 8.4.4 和 8.5.4 的 ASAN 构建上,并没有复现出 zval UAF,也没有复现出 RCE;在关闭 ASLR、使用 poisoned call address 的条件下同样没有成功。作者的结论是:公开 PoC 找错了根因——官方的修复并不是主要针对 “第二次 update 时的 zval corruption”,而是重新设计了 OOM bailout 路径、在 RINIT 之前初始化 session state,并重置上一个请求残留的状态。
但这不意味着漏洞是假的。同一份调查给出了另一条能走通的路径:把 session.upload_progress 的写入原语和应用层 LFI 组合起来,RCE 可以稳定复现。在 cleanup=0 下直接成功;在默认 cleanup=1 下,3/3 次 race 也成功了;即便 enabled=0,通过上传临时文件 include 的路径也仍然存在。换言之,8.4.4 上可验证的攻击链,不是“单个 HTTP 请求在 PHP 核心里凭空完成 RCE”,而是“核心写入原语 + 应用层 include/LFI 汇合”的两层结构。
这也解释了为什么官方 advisories 在描述根因时用的是 “RCE via OOM corruption” 和 “RCE via stale global state” ,而不是“session.upload_progress 单请求 UAF”。PHP 安全团队确认的消费者包括 Session Upload Progress,但根因的准确表述是 OOM 处理中断导致请求数据部分未初始化,以及请求归属的内部状态在支撑内存释放后仍然存活,被同一 worker 的后续请求使用。把 9.2 分归因于一个能在单请求内稳定触发的 zval UAF,是把 PoC 的机制假设当成了已证实的根因。
用 8.4.27 做“修复验证”:比“打 PoC 有没有 shell”更可靠的指标
既然 8.4.4 上“直接 RCE”的 PoC 本身就有争议,复现验证就不应该只盯着 “有没有回显 uid=”。一个更可重复、更少依赖利用链细节的验证思路,是比较未修复版 8.4.24 和修复版 8.4.27 在同一个 OOM/会话异常场景下的状态差异。
1amrahul/php2shell-cve-2026-109618-cve-2026-109619 提供了这样一组可复现的对照数据:
| 测试项 | PHP 8.4.24(未修复) | PHP 8.4.27(已修复) |
|---|---|---|
| 上传进度处理器内触发 OOM | 可达 | 可达 |
| OOM 后的 session lock / fd |
泄漏(lslocks + /proc/fd 验证)
|
正常释放 |
| 对同一 session id 的下一个请求 | 永久阻塞,worker 卡死 | 正常返回 200 |
| 单 worker 服务器被卡死后 | 整站不可用 | 健康 |
| PHP 自带 OOM 回归测试 | 67 通过 / 5 失败 | 72 通过 / 0 失败 |
这个对照的价值在于:它验证的是补丁确实改变了可被未认证请求触达的状态处理行为,而不是一个高度依赖 heap groom、ASLR 偏移和 target-specific 符号的利用链是否在某一台机器上恰好成功。对于公众号读者中的运维和应急人员,这组数据也比“跑 PoC 等 shell”更适合作为“升级是否生效”的判据。
如果确实要在 8.4.4 上观察 UAF 原语本身,syrex1013/php2shell 的 lab-uaf 仍然可以作为原语级验证使用;但需要清楚:原语可达 ≠ 完整 RCE 链在 stock 8.4.4 上无条件成立。完整链能否走通,高度依赖目标是否具备 include/LFI 汇合点、cleanup 配置、worker 复用条件,以及目标二进制的符号和 ASLR 情况。
补丁提前,但防护不能只靠“升级了事”
PHP 官方在公告中写得很直接:“There are no complete mitigations, updating is the only safe choice.” 同时给出的临时缓解措施,也明确标注“只降低特定攻击路径的暴露面,并不使安装变得安全”。
对于 CVE-2026-109618(OOM corruption),可操作的缓解包括:将 memory_limit=-1 以应用无法覆盖的方式设置(如 FPM pool 或 Apache 配置中的 php_admin_value),降低 post_max_size(无上传场景 8M,有上传场景 32M),并在 Web 服务器或代理层限制解码后的请求大小、字段数、multipart part 数和嵌套深度。
对于 CVE-2026-109619(stale global state),最直接的缓解是在请求解析之前将 session.upload_progress.enabled=0 并重启所有 worker;不要依赖 ini_set() 或 FPM .user.ini,因为它们可能来不及在解析阶段生效。
但更重要的是:即使升级到 8.4.27,应用层 LFI 的风险并不自动消失。独立调查已经指出,session.upload_progress 写入原语配合应用自身 include/LFI 的链,在概念上可以独立于核心 CVE 存在。把 PHP 升到 8.4.27 是关闭核心漏洞的必要条件,但它不负责修复应用里任何一处 include($_GET['page'])。两层风险需要分开处理。
一句话收束:PHP2Shell 的 9.2 分是真实的,补丁提前发布也是真实的;但在 8.4.4 的复现验证中,公开 PoC 声称的“单请求 zval UAF 直接 RCE”并没有被独立实验稳定复现,真正可重复的路径是“核心写入原语 + 应用层 LFI”。对防守方而言,8.4.27 必须升,但升级之后,下一件事仍然是去看应用代码里那些能被用户输入喂进去的 include。



















