# PHP 两个 RCE 漏洞的引擎根因与旧版补丁实践
PHP 的 RCE 安全公告出来后,很多人会先去检查 CMS 插件、框架版本和接口参数。这次问题的落点更靠下:两个漏洞都在 PHP 引擎处理请求状态的路径里,业务层多加几条校验不能代替引擎修复。
2026 年 10 月 21 日,PHP 官方发布了 CVE-2026-109618 和 CVE-2026-109619 的修复版本。官方要求所有对外处理 Web 请求的 PHP 部署核对更新,包括 PHP-FPM、Apache `mod_php`、`php-cgi`、FrankenPHP 和 LiteSpeed。不同 SAPI 的实际可达路径仍有差异,不能把某一种环境的演示直接套到所有部署上。
处理顺序很明确:先确认线上实际运行的 PHP,再使用官方修复版或供应商验证过的回移包。旧分支静态源码里看到相似路径,只能说明需要继续分析,不能写成官方确认受影响;补丁能应用也不等于漏洞已经修复。

一、从一次 OOM 异常,追到两个引擎漏洞
PHP Foundation 的公开复盘提到,2024 年外部安全审计期间,PHP 核心开发者在处理 OOM(内存不足)相关问题时,已经注意到请求状态可能在异常退出后留下隐患。当时还没有找到明确的远程利用路径。
到了 2026 年,Calif 团队在 OpenAI 的 Patch the Planet 项目中借助 GPT-5.6 Sol 找到了关键缺陷。OpenAI 也独立报告了同根因问题,并补充了攻击路径;Wiz 则独立报告了两项漏洞。之后由 PHP 维护者主导修复和发布。
这里需要把归因说准确:不是“AI 单独发现并修复了 PHP 的两个漏洞”。公开资料确认的是研究团队、OpenAI、Wiz 与 PHP 维护者分别参与了发现、验证、披露协调和修复。AI 帮助研究人员缩短了代码审查和路径分析时间,最终修复仍由 PHP 维护者完成。
二、两个根因,用请求生命周期解释
CVE-2026-109618:内存不够时,PHP 还在清理半成品
PHP 处理请求时,会逐步初始化表单、上传文件和内部请求状态。如果内存分配恰好在初始化过程中失败,PHP 会通过 bailout 跳出当前流程;但某些路径上的请求清理仍可能继续,把没有初始化完整的数据当成正常对象处理。
可以把它想成:一张表格才填到一半,程序突然中断,后续清理却按“整张表已经填好”来读取。错误的状态处理可能造成内存破坏,并在特定条件下进一步导致代码执行。
官方公告列举的 OOM 触发因素包括 multipart 请求结构复杂、单个 part 的头部过多,以及空文件控件仍产生元数据等。风险不只取决于请求体总大小;经过解析后,内部对象占用的内存可能明显大于线上数据本身。
CVE-2026-109619:上一个请求留下的状态,被下一个请求读到了
PHP-FPM 等长驻 Worker 会连续处理多个请求。请求结束时,请求专属内存会释放;如果某个全局状态仍然指向这块内存,下一个请求可能在正常初始化清空它之前读到旧指针。
这类似于上一位用户用完房间后,房间已经清空,但门牌簿上仍留着旧地址。下一位用户按门牌簿走过去,拿到的却是已经失效的东西。
利用通常需要“前一个请求留下状态”和“后一个请求在初始化前读取状态”两段条件配合。PHP 官方通告说明了上传进度等具体路径;PHP 8.5 的 fatal error backtrace 也涉及一条特定路径。不能简单地把它概括成“所有 FPM 请求都会被同一种方式利用”。
三、影响版本、CVE 与证据边界
先区分三种证据:PHP 官方公告列出的受影响范围、PHP 官方为旧维护分支提供的回移补丁,以及我对历史源码样本做的静态比对。它们不是同一层级的结论。
| 版本范围或源码样本 | CVE-2026-109618 | CVE-2026-109619 | 建议处理 |
|---|---|---|---|
| PHP 8.2.x,低于 8.2.35 | 官方公告影响范围 | 官方公告影响范围 | 升级到 8.2.35 或该分支更新的修复版 |
| PHP 8.3.x,低于 8.3.36 | 官方公告影响范围 | 官方公告影响范围 | 升级到 8.3.36 或该分支更新的修复版 |
| PHP 8.4.x,低于 8.4.27 | 官方公告影响范围 | 官方公告影响范围 | 升级到 8.4.27 或该分支更新的修复版 |
| PHP 8.5.x,低于 8.5.12 | 官方公告影响范围 | 官方公告影响范围 | 升级到 8.5.12 或该分支更新的修复版 |
| PHP 7.4、8.0、8.1 | PHP 官方发布了对应源码回移补丁 | PHP 官方发布了对应源码回移补丁 | 优先使用发行版、云厂商或维护方已验证的补丁包;否则严格按目标源码审查和测试 |
| PHP 5.4–7.3 的本次指定末版样本 | 静态检查发现相同类别的 OOM 路径 | 静态检查发现上传进度等相关状态路径 | 研究报告中的补丁是本地候选缓解,不是 PHP 官方回移,也未编译或运行验证 |
| PHP 5.3.0、5.3.29 | Apache 2 `mod_php` 源码存在同类 OOM bailout 后继续清理的路径 | 本次样本未证实对应的前请求生产者与后请求消费者组合 | 不能写成官方确认受影响,也不能据此断言安全;5.3.0 本身没有内置 FPM |
表中 5.3–7.3 的内容来自本次指定源码版本的静态分析。它说明这些源码里存在需要继续处理的路径,不代表 PHP 官方逐个版本确认了 CVE,也不代表已经实测出 RCE。PHP 5.3.0 的 Apache2handler 路径与 CVE-2026-109618 同类;CVE-2026-109619 在 PHP 5.3 样本中尚未证实。
PHP 官方公告还指出,PHP 8.1 及更早版本已经不再获得 PHP 项目的常规更新;部分 Linux 发行版、LTS 或托管服务商可能提供自己的回移包。不要只看版本号,最好同时核对包的发行方、更新说明和实际运行构建。
四、Apache、Nginx、FPM 改变的是路径,不是根因
漏洞根因在 PHP 引擎,不在 Apache、Nginx、WordPress、框架或业务代码。升级业务代码、加输入校验、隐藏错误信息,都不能替代 PHP 补丁。
但这不等于 SAPI 完全无关。PHP 官方将处理 Web 请求的多种 SAPI 都列为需要关注的部署;官方复盘指出,风险最高且展示出未认证请求在应用代码运行前触发路径的是 Apache `mod_php`。PHP-FPM 也存在需要处理的路径,部分路径依赖具体应用流程、请求状态和同一个 Worker 后续处理请求的条件。
所以更准确的说法是:漏洞属于 PHP 软件层,不由业务代码写得好不好决定;实际可达路径和利用条件会受 SAPI、模块、配置与应用流程影响。切换 Nginx、FPM 或其他 Web 架构,不等于修复。

五、优先升级;无法马上升级时分层缓解
1. 首选官方修复版或供应商补丁
PHP 官方当前给出的修复版本是 8.2.35、8.3.36、8.4.27 和 8.5.12。若使用 Linux 发行版、云主机、容器镜像或面板托管的 PHP,先确认供应商是否已提供对应安全更新。升级后重启相关 PHP Worker,并确认网站实际运行的构建已改变。
`php -v` 查看的是当前命令行调用到的 PHP,不一定是网站正在使用的 FPM 版本。还要盘点 Web 服务器、FPM pool、容器和多个 PHP 安装目录,避免只升级了 CLI,线上仍连着旧 Worker。
2. 只能临时缓解时,按实际版本和业务调整
PHP 官方给出的若干措施只能降低特定路径的风险,不能让未修复构建变成安全版本。下面是一个 PHP-FPM pool 配置示例,不能不看业务就整段照抄:
; 放在现有 FPM pool(例如 [www])中;数值按业务和机器资源调整
php_admin_value[memory_limit] = -1
; 这里按需要上传文件的站点举例;无上传业务时可按官方建议收紧到 8M
php_admin_value[post_max_size] = 32M
php_admin_value[upload_max_filesize] = 30M
php_admin_value[max_input_nesting_level] = 32
; PHP 5.4 及以上;关闭一条上传进度相关路径
php_admin_value[session.upload_progress.enabled] = 0
; PHP 8.0 及以上;限制 multipart part 数,不限制每个 part 的头部数量
php_admin_value[max_multipart_body_parts] = 256
; 仅 PHP 8.5 适用
php_admin_flag[fatal_error_backtraces] = off
; 可选:减少单个 Worker 连续处理请求的数量,性能会受影响
pm.max_requests = 1
需要特别注意:
- `memory_limit = -1` 会取消 PHP 层的内存上限,必须同时评估容器或主机的系统内存边界;不应在没有资源隔离的机器上盲目启用。
- 无上传业务时,可把 `post_max_size` 收紧到 8M;确需上传时再按业务设定请求体上限,并保持 `upload_max_filesize` 小于 `post_max_size`。请求体字节数不等于解析后的内存占用。
- `max_multipart_body_parts` 只限制 part 数,不等于限制每个 part 的头部数量;它也不是完整修复。
- `session.upload_progress.enabled` 只适用于具有该功能的版本;PHP 5.3 没有这项设置。
- `pm.max_requests = 1` 会增加 Worker 回收频率,只是减少跨请求复用机会,挡不住单个请求造成的 OOM。
- 如果服务完全不需要文件上传,可以在 Web 服务器或 WAF 层拒绝 multipart 请求;这样会影响所有依赖 multipart 的表单和 API。保留上传业务时,应先梳理真实需求再限制请求体、part 数、头部数量和嵌套深度。
业务不接收文件:在 Nginx 拒绝 multipart
只有确认站点的 PHP 接口都不依赖文件上传时,才考虑在实际转发到 PHP-FPM 的 `location` 中拒绝 `multipart/form-data`。下面的配置要合并进现有 PHP 入口,不要另建一个重复的 `location`:
location ~ \.php$ {
# 本站不接收文件;拒绝 multipart 表单
client_max_body_size 8m;
if ($http_content_type ~* "^[[:space:]]*multipart/form-data($|[;,[:space:]])") {
return 415;
}
try_files $uri =404;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/php-fpm.sock;
}
把 PHP-FPM socket 换成实际路径,确认没有其他 PHP 转发入口绕过规则,再运行 `nginx -t`。这会让依赖 multipart 的上传表单和 API 失效;它只拦一类请求,不是 PHP 补丁,也不覆盖其他 OOM 或跨请求状态路径。
如果当前公网服务无法及时打补丁或确认供应商修复,可先把受影响入口限制到可信网络,必要时临时下线该服务,等修复、重启和验证完成后再恢复。PHP 补丁发布前,荷兰政府曾预防性关闭部分政府网站;这类下线是应急止损,不是修复本身。现在官方修复版已经发布,优先完成升级。
3. 老版本自研回移:先追源码,再做验证
旧版本不要直接拿最新分支的补丁硬套。每个分支的请求生命周期、SAPI 行为和内存所有权都可能不同。本次研究中的 5.3–7.3 补丁只是静态分析后的候选缓解方案,未编译、未跑 PHPT,也未做 FPM、Nginx 或 Apache 实测,不能直接作为生产级补丁推荐。
相对稳妥的过程是:
- 固定目标 PHP 的精确版本和源码,记录原始编译参数、SAPI、扩展与系统库。
- 分别梳理 OOM、请求初始化/关闭、bailout、multipart、上传进度、Worker 回收和全局状态的生命周期。
- 将官方修复提交与目标分支逐个函数对照,先说明旧代码为何会进入危险清理,再设计最小改动;不能只把 `zend_bailout()` 换成 `exit()` 就当成修复。
- 对补丁做上下文检查和代码审查,确认不会误杀 FPM master、破坏 ABI 或留下新的清理路径。
- 在隔离环境编译,运行 PHP 自带测试,再按目标 SAPI 做连续请求、Worker 重启、上传和扩展兼容性回归。
- 把“补丁能应用”“编译成功”“测试通过”分别记录。只有覆盖目标环境的安全验证完成后,才能讨论上线;单项通过不等于完整安全证明。
六、用 AI 辅助源码分析,先把提示词边界说清楚
如果你也想用 Codex 辅助做 PHP 漏洞源码排查,可以看看我做的云枢 Codex 助手桌面版。这次分析中,我用它辅助梳理请求初始化、OOM bailout 和清理路径,再把线索逐项对照官方公告与修复提交。MotoAgent 则可以把公告核对、历史代码比较和补丁复核拆成不同 Agent 任务。工具提供的是分析线索,结论仍要回到源码证据和目标环境测试。
AI 可以协助追代码、对照补丁和起草差异,但不能只凭新版文件名或网上的补丁生成一个“看起来能用”的 diff。下面这段提示词可以作为旧版防御性回移的起点。把版本和源码路径替换成真实值,并在隔离副本里工作:
你是 PHP 源码安全维护者。请只为 PHP [精确版本] 做防御性补丁研究。
目标源码目录:[绝对路径]
目标部署 SAPI:[Apache mod_php / PHP-FPM / php-cgi / 其他]
原始编译参数、扩展和系统库:[逐项填写]
官方资料:
- https://www.php.net/archive/2026.php#2026-10-21-5
- https://github.com/php/php-src/security/advisories/GHSA-w8hw-w9fx-j79w
- https://github.com/php/php-src/security/advisories/GHSA-6wmf-4rv5-p3rp
- https://github.com/php/php-src/commit/31c0ea7d7ff1d1fccb6492e52dab544011628065
请遵守以下要求:
1. 先检查目标版本的真实源码,不得套用新版行号。逐项追踪 OOM 处理、请求启动与关闭、SAPI 生命周期、FPM worker 退出、multipart 解析、session 上传进度、fatal/error 状态和相关全局状态;列出文件、函数和代码位置。
2. 分开分析 CVE-2026-109618 与 CVE-2026-109619。官方未确认或源码证据不足的路径标记为“未证实”,不得把静态路径推断写成已验证 RCE。
3. 先解释目标版本为何进入危险状态,再提出最小改动。遵守该版本的内存所有权、ABI 和 SAPI 约定;不得只把 zend_bailout() 改成 exit/_exit,也不得误杀 FPM master。
4. 只有在完整阅读相关源码后,才输出能对应目标版本的 unified diff。每个 hunk 都必须能在目标源码中定位。若无法证明改动安全,请说明无法产出生产级补丁,不要猜代码。
5. 逐条解释补丁如何改变状态生命周期、功能变化、Worker 重启或拒绝服务影响、ABI 风险,以及仍未覆盖的路径。
6. 提供补丁应用检查、编译、PHP 自带回归测试,以及目标 SAPI 连续请求和 Worker 回收的验证计划。不得生成攻击 payload、shellcode 或针对真实目标的攻击步骤。
7. 明确区分“补丁可以应用”“编译成功”“测试通过”和“漏洞已修复”。不要把任何单项检查说成生产安全证明;列出仍需人工代码审查、完整回归和供应商安全审查的事项。
AI 给出的差异仍要由熟悉 PHP 内核的人审查。尤其对停止维护的分支,补丁还可能带来兼容性、崩溃或新的拒绝服务风险。
七、结论
问题不在 WordPress 插件、框架校验或某段业务代码,而在 PHP 引擎管理请求状态的方式。先确认网站实际运行的 PHP 构建,再按官方修复版或供应商回移包处理;旧版本则把源码分析、补丁应用、编译和目标 SAPI 回归分开验证。
如果你维护的是 PHP 服务,建议先记下四项:实际版本、补丁来源、Web SAPI、所有 Worker 是否已重启。 这比只看一次 `php -v` 更接近真实风险。
参考资料
- 作者对 PHP 历史版本源码的静态比对(非 PHP 官方逐版本影响确认,未编译或运行验证)
证据边界: 历史版本部分是对本地指定源码样本的静态检查。研究报告记录的补丁通过了 `git apply –check`,但未编译 PHP、未运行 PHPT、PoC 或目标 Web SAPI 实测;不能将其表述为已验证的生产修复。