两个 CVSS 4.0 9.2 的未授权 RCE:一个在请求解析收尾时就能触发,一个靠上一个请求留下的脏状态喂给下一个请求。
10 月 21 日,PHP 安全团队一次发了四个修复版本:8.5.12、8.4.27、8.3.36、8.2.35。修的是 CVE-2026-109618 和 CVE-2026-109619,两个都能未授权远程执行代码。公告原话:没有完整的缓解措施,升级是唯一安全的选择。配置只能砍掉部分触发路径,完整修复只有换版本。

图 1 · 封面:两个 9.2 分,四个修复版本
洞一:内存被打爆,也可能变成 RCE
CVE-2026-109618,破坏点在 OOM 处理上。
PHP 处理内存耗尽用的是 longjmp():就地跳出当前上下文、中止这个请求。有些时机请求内存已经能被访问,却还没初始化完。这时只要有分配失败------失败的那次分配甚至不需要和这块内存有关系------PHP 就带着半初始化的状态退出去,请求收尾阶段再把这份脏数据当成有效数据读。
公告列了三条「在限额之内打爆内存」的路子:
- multipart 请求不带可用的 Content-Length,能绕过 post_max_size 的初检继续灌数据
- 单个 part 的 header 数量没有上限,它在内存里的表示(数组加字符串头)比线上字节大得多,post_max_size 之内的请求就能顶爆 memory_limit
- 空文件控件不计入 max_file_uploads,照样生成完整的上传元数据数组,字段名再嵌套几层,数组开销继续翻倍

图 2 · 洞一:从内存耗尽到远程代码执行
补丁把 longjmp() 换成两段式:在下一个 VM 安全点触发延迟的软 OOM,内存再冲到第二个、更高的阈值就直接 _exit() 硬退出。shutdown 阶段不会再看到无效状态,memory_limit 该有的限制一点没少。
洞二:上一个请求的垃圾,喂给下一个请求
CVE-2026-109619 是另一条路:跨请求的陈旧全局状态。这类问题要凑齐两半才成立------写脏的一侧(生产者)和读脏的一侧(消费者)。
worker 长期持有 error、session、object 和扩展状态,这些字段指向的内存属于某一次请求。正常流程里,请求内存释放前会把它们重置掉。但只要中途撞上致命错误,解析失败、未捕获异常、require 失败、执行超时、内存耗尽都算,操作就被丢在半路,配套的重置没执行。一条普通 warning 也可能在清理之后,把新的请求内存重新写回全局字段。这就是生产者。
堆释放了,worker 里的指针还指着它。同一个 worker 接下个请求时,正常初始化之前就有操作去读、去释放、拿它做分支判断。这是消费者。堆布局可控时,use-after-free 直接变成 RCE。
公告点名了两个已确认的消费者:请求初始化之前触发的无效/废弃 INI 告警,以及 session upload progress 在常规 session 初始化之前碰到 session 状态。生产者侧已确认的案例同样是两个:PHP 8.5 的 fatal_error_backtraces,和 upload progress 的生命周期。把 error_reporting 调低、加个 @、关掉 display_errors 都拦不住------全局状态在 PHP 决定要不要报告之前,就已经被读过了。
攻击成立需要生产者和消费者落在同一个 worker 上。多开 worker、勤回收能降低命中率,条件本身一直都在。

图 3 · 洞二:写脏的和读脏的,必须落在同一个 worker 上
谁在射程内
所有对外处理 web 请求的 PHP:PHP-FPM、Apache mod_php、php-cgi、FrankenPHP、LiteSpeed。公告没提在野利用,但 mod_php 上的完整利用链已经跑通,并做了 HTTP 验证。
mod_php 风险靠前:它不管请求启动成功还是失败,都会跑一遍 shutdown 流程,所以 startup 阶段打爆内存,就能在应用代码起来之前走完整个链条。FPM 稍好一点,startup 失败直接退出子进程,脏状态没机会被消费;但应用执行阶段撞上 fatal、再配合 upload progress 的那条路,已经在 FPM 上验证通过。FrankenPHP 与 mod_php 同类,LiteSpeed 与 FPM 同类,这两个只做了源码分析。
常驻进程框架(Swoole、Workerman、RoadRunner 之类)不在公告评估的 SAPI 名单里,官方没给结论。按最坏情况处理,一样升级。
升到哪个版本:
- PHP 8.5 → 8.5.12
- PHP 8.4 → 8.4.27
- PHP 8.3 → 8.3.36
- PHP 8.2 → 8.2.35。这个分支 2026 年 12 月 31 日 EOL,之后不再有安全修复,能顺手排到 8.3 或 8.4 就一起排
- PHP 8.1 及更早:项目本身不再发版,先问发行版和 LTS 厂商有没有 backport;官方也为 7.4、8.0、8.1 准备了最小源码 backport 分支
公告还提了一句社区维护的 rpms.remirepo.net:那个仓库当天不会有更新,官方建议换成发行版包,或者自己从源码构建。
升级之前,先把能关的门关上
下面这些都不等于安全,只是缩小具体路径的暴露面。
先确认自己站在哪个版本上。别只查 CLI,对外服务的是 FPM:
perl
php -v
php-fpm -v
php -i | grep -E 'memory_limit|session.upload_progress.enabled|fatal_error_backtraces'
配置长这样(fatal_error_backtraces 只有 8.5 需要):
css
; /etc/php/8.4/fpm/pool.d/www.confphp_admin_value[memory_limit] = -1php_admin_value[session.upload_progress.enabled] = 0php_admin_flag[fatal_error_backtraces] = 0pm.max_requests = 1 ; 可选,见下文代价
挡 OOM 那条:
- memory_limit 设成 -1,写在 FPM pool 的 php_admin_value 或 Apache 配置里,让应用改不回来。改完重启全部 worker,再逐个确认没有 pool、目录或应用把它设回有限值。底层分配真失败了,仍然可能走到那条路径,所以这只是缩小面。裸机上用这招要配外部边界(systemd 的 MemoryHigh/MemoryMax,或容器 cgroup),否则一个死循环就能把整台机器连同数据库一起拖死。
- post_max_size 收紧:不用上传就 8M,要上传就 32M,upload_max_filesize 记得比它小。代理层能限的是字节数和 header 长度(client_max_body_size、large_client_header_buffers),part 数、字段数、嵌套深度得在 PHP 侧(max_file_uploads、max_input_vars、max_input_nesting_level)或 WAF 上做。没有 Content-Length 的请求也要算进去。
- 拿容器或 cgroup 的内存上限当可用性边界,最好只杀受影响的 worker。别用 RLIMIT_AS、RLIMIT_DATA 顶替,它们会直接造成那次致命的分配失败。
挡陈旧状态那条:
- session.upload_progress.enabled=0,重启全部 worker。这条必须在请求解析之前生效,写进 php.ini、FPM pool 或服务器配置;ini_set() 和 .user.ini 都太晚。
- PHP 8.5 再加一条 fatal_error_backtraces=0,同样要重启。
- 把请求级的无效、废弃 INI 清干净。用 error_reporting、display_errors 或 @ 把诊断藏起来没有用。
- 修掉攻击者能摸到的致命错误和执行超时,包括 callback、上传、导入、清理阶段的失败。
两个可选项:FPM 的 pm.max_requests=1 让 worker 干完一单就换人(mod_php 对应 MaxConnectionsPerChild),代价是每次请求冷启动、opcache 失效;以及开 log_errors=On 加 E_ALL 做一轮配置和日志审计,注意日志里没报错不等于安全。
还有一件事容易被忽略:打完补丁,OOM 的表现会变。软 OOM 延迟到安全点才生效,硬 OOM 直接 _exit(),worker 会当场消失,PHP 错误日志里可能什么都没留下。监控上看得到的可能只是一次莫名单个 worker 掉线,或者 502 抖一下。这不是新故障,是补丁的正常行为。
升级做完,把临时措施撤回去:memory_limit 调回合理值,业务真需要 upload progress 就开回来。

图 4 · 对照表:四个分支、四个版本号
出处
- PHP 安全公告原文
- CVE-2026-109618 / GHSA-w8hw-w9fx-j79w
- CVE-2026-109619 / GHSA-6wmf-4rv5-p3rp
- 源码与 Windows 二进制下载
以上信息核对于 2026-10-22。
php -v 跑一下,版本号自己心里有数就行。要是还停在 8.1 或更早,说说卡在哪一步------发行版没跟上、框架版本锁死,还是共享主机动不了。公告会过期,留在旧版本上的机器不会自己变安全。