引言
近日,PHP 官方安全公告披露了两个 CVSS 评分高达 9.2 的严重安全漏洞(CVE-2026-109618 与 CVE-2026-109619)。这两个漏洞直指 PHP 底层内存管理与 Worker 进程模型,在特定条件下可组合实现未经身份验证的远程代码执行(RCE)。
由于该漏洞波及广泛,且 WordPress 官方 Docker 镜像绝大多数基于经典 FPM 或 Apache 构建,本文记录本次漏洞的核心机制、在 Docker 镜像体系中的修复情况,以及针对线上运行站点的全流程排查、升级与加固实践。
一、漏洞机理深层拆解
这两个 CVE 并不是孤立存在的,它们在攻击链上展现出了极高的“协同性”。
1. CVE-2026-109618:OOM 异常打断引发的未初始化陷阱
- 机制问题:PHP 历史机制中遇到内存耗尽(OOM)时,底层会直接调用
longjmp()强制跳转中断上下文。 - 漏洞产生:如果在请求刚启动或数据刚分配、内部结构尚未完全初始化完毕时发生了 OOM,
longjmp()会跳过常规阶段清理,留下处于“半成品”状态的无效内存。随后执行的 Request Shutdown 清理程序会误将这份无效数据当作有效指针进行遍历与释放,引发野指针解引用或执行流劫持。 - 触发途径:攻击者通过构造特殊的 multipart 报文(如超大请求头、多层嵌套数组等),可以在完全不超出前端
post_max_size限制的情况下,在 PHP 内部成倍放大内存消耗,精确击穿memory_limit。
2. CVE-2026-109619:跨请求的 Worker 全局悬挂指针(Cross-Request UAF)
- 机制问题:在常驻 Worker 进程(如 PHP-FPM、mod_php)架构中,某些全局状态(如错误回溯、Session 上传进度解析状态)在请求执行期间会记录指向该请求堆内存的指针。
- 漏洞产生:
- Producer(生产者):当请求 A 因 OOM 或 Fatal Error 意外中断时,PHP 释放了该请求占用的私有堆内存,但常驻 Worker 的全局指针并未被重置为 NULL。
- Consumer(消费者):同一个 Worker 进程接手下一个请求 B 时,在
php_request_startup()正常完成之前,某些提前运行的逻辑(如解析 INI 产生的诊断日志,或默认开启的session.upload_progress)会误读上述残留的悬挂指针。
- 致命结合:攻击者先发一个畸形请求(利用 109618 的 OOM 中断)制造残留状态,随后立即向同一 Worker 发送后续请求,即可达成典型的堆释放后重用(Use-After-Free),进而实现 RCE。
二、官方修复版本与 Docker 标签机制
官方在以下版本中针对该漏洞进行了彻底修复:
- PHP 8.2:>= 8.2.35
- PHP 8.3:>= 8.3.36
- PHP 8.4:>= 8.4.27
- PHP 8.5:>= 8.5.12
在 Docker Hub 上,WordPress 官方镜像标签(如 wordpress:latest、wordpress:php8.3、wordpress:php8.3-fpm)采用浮动标签(Floating Tags),不会细分标注 PHP 的三位小版本号。因此,仅在服务器上执行 docker compose restart 或 --force-recreate 是无法应用补丁的,必须显式触发 docker compose pull 从远端拉取重新编译打标后的最新镜像层。
三、排查与实战升级过程
1. 强制重新拉取并重建容器
进入项目所在目录,拉取最新镜像并重新生成容器:
# 拉取最新远端镜像
docker compose pull
# 强制基于最新镜像重建容器
docker compose up -d --force-recreate
2. 验证修复后的 PHP 版本
使用 Compose 执行命令检查当前容器内的 PHP 运行时版本(需指定具体的 service 名称,如 wordpress):
docker compose exec wordpress php -v
终端输出如下,确认已成功升级至已修复的 PHP 8.3.36:
PHP 8.3.36 (cli) (built: Oct 21 2026 15:57:18) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.3.36, Copyright (c) Zend Technologies
with Zend OPcache v8.3.36, Copyright (c), by Zend Technologies
四、纵深防御:关闭无用高危特性(Session Upload Progress)
官方在安全公告中特别强调:即使升级了修复版本,若无业务强依赖,主动关闭 session.upload_progress 也是极其稳妥的防守实践。该功能在现代 Web 架构中鲜少使用,却屡次成为攻击链的前置切入点。
1. 检查当前状态
docker compose exec wordpress php -r "echo 'Upload Progress: ' . ini_get('session.upload_progress.enabled') . PHP_EOL;"
若输出为 Upload Progress: 1,说明仍处于默认开启状态。
2. 持久化挂载自定义安全配置
为了防止后续镜像更新或容器重建导致配置覆盖,推荐通过宿主机挂载目录注入自定义 .ini。在站点根目录下创建配置目录并写入禁用参数:
mkdir -p php-conf.d
echo "session.upload_progress.enabled = 0" > php-conf.d/security.ini
编辑 docker-compose.yml,在 wordpress 服务下的 volumes 中添加只读挂载:
services:
wordpress:
image: wordpress:latest
restart: always
volumes:
- ./php-conf.d/security.ini:/usr/local/etc/php/conf.d/security.ini:ro
- wordpress_data:/var/www/html
# ... 其余配置保持不变
3. 重新加载并校验
# 重建容器加载挂载
docker compose up -d
# 再次验证配置项
docker compose exec wordpress php -r "echo 'Upload Progress: ' . ini_get('session.upload_progress.enabled') . PHP_EOL;"
输出为 Upload Progress: 0 或 Upload Progress:(空值在 PHP ini 中即代表 0/Off),表明加固已生效。
4. 清理旧镜像层
确认站点运转正常后,清理宿主机残留的废弃旧镜像层释放空间:
docker image prune -f
结语
在现代容器化部署中,浮动标签带来了极大的部署便利,但也容易在安全更新时造成“重启即更新”的错觉。面对底层内存管理与进程模型级别的严重漏洞,养成显式 Pull 最新摘要、进入容器校验真实 Runtime 版本、以及主动精简关闭高危历史遗留特性的习惯,才是保障业务稳定与防御纵深的关键。



