QQ扫一扫联系
两个 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 分,四个修复版本
CVE-2026-109618,破坏点在 OOM 处理上。
PHP 处理内存耗尽用的是 longjmp():就地跳出当前上下文、中止这个请求。有些时机请求内存已经能被访问,却还没初始化完。这时只要有分配失败——失败的那次分配甚至不需要和这块内存有关系——PHP 就带着半初始化的状态退出去,请求收尾阶段再把这份脏数据当成有效数据读。
公告列了三条「在限额之内打爆内存」的路子:
图 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 名单里,官方没给结论。按最坏情况处理,一样升级。
升到哪个版本:
公告还提了一句社区维护的 rpms.remirepo.net:那个仓库当天不会有更新,官方建议换成发行版包,或者自己从源码构建。
下面这些都不等于安全,只是缩小具体路径的暴露面。
先确认自己站在哪个版本上。别只查 CLI,对外服务的是 FPM:
php -v
php-fpm -v
php -i | grep -E 'memory_limit|session.upload_progress.enabled|fatal_error_backtraces'
配置长这样(fatal_error_backtraces 只有 8.5 需要):
; /etc/php/8.4/fpm/pool.d/www.conf
php_admin_value[memory_limit] = -1
php_admin_value[session.upload_progress.enabled] = 0
php_admin_flag[fatal_error_backtraces] = 0
pm.max_requests = 1 ; 可选,见下文代价
挡 OOM 那条:
挡陈旧状态那条:
两个可选项: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 · 对照表:四个分支、四个版本号
以上信息核对于 2026-10-22。
php -v 跑一下,版本号自己心里有数就行。要是还停在 8.1 或更早,说说卡在哪一步——发行版没跟上、框架版本锁死,还是共享主机动不了。公告会过期,留在旧版本上的机器不会自己变安全。