PHP 两个 RCE 漏洞的引擎根因与旧版补丁实践

# 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,再使用官方修复版或供应商验证过的回移包。旧分支静态源码里看到相似路径,只能说明需要继续分析,不能写成官方确认受影响;补丁能应用也不等于漏洞已经修复。

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 架构,不等于修复。

Nginx 通过 FastCGI 把 PHP 请求交给 PHP-FPM

五、优先升级;无法马上升级时分层缓解

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 实测;不能将其表述为已验证的生产修复。

This entry was posted in ai and tagged , , , , . Bookmark the permalink.

发表评论