PHP 未授权 RCE 应急响应:CVE-2026-109618 / CVE-2026-109619 排查与加固实战
2026-10-21,PHP 官方凌晨发布带外安全版本,修复两个 CVSS v4.0 评分 9.2 的未认证远程代码执行漏洞,且技术细节在发布前数小时已泄露、社区已出现利用迹象。本文是一份从"自查 → 临时缓解 → 验证 → 升级排期"的完整 SOP,适合直接照做。

一、漏洞背景:发生了什么
2026-10-21,PHP 官方发布 8.5.12 / 8.4.27 / 8.3.36 / 8.2.35,修复两个漏洞:
| CVE | 类型 | 原理(一句话) |
|---|---|---|
| CVE-2026-109618 | OOM 内存损坏 RCE | 内存耗尽使 PHP 中断时请求数据只初始化了一半,请求清理阶段把"无效数据"当有效数据处理 |
| CVE-2026-109619 | 残留全局状态 RCE | 上一个请求的内部状态在内存释放后仍残留,同一 worker 的后续请求用到脏状态(已确认路径指向 session.upload_progress 特性) |
三个关键事实,先对齐认知:
- 影响面:所有处理 Web 请求的 PHP(PHP-FPM、mod_php、php-cgi、FrankenPHP、LiteSpeed)都受影响,其中 mod_php 风险最高,已有"未认证请求在应用代码执行前即可触发"的利用演示;
- "我的站没有文件上传功能"≠ 不受影响:触发面在 PHP 引擎内部,应用代码根本没参与,
session.upload_progress是 PHP 5.4 起自带且默认开启的特性,POST 一个同名字段即可触发; - 官方原话:"There are no complete mitigations, updating is the only safe choice."——下文所有配置修改都只是临时缓解,不是修复。
二、第一步:自查环境
1. 确认 PHP 版本
php -v
升级目标:8.5.12 / 8.4.27 / 8.3.36 / 8.2.35;8.1 及以下已停止维护,需找发行版/LTS 回移植补丁或官方 7.4 / 8.0 / 8.1 源码回移植分支。
2. 确认到底加载的是哪个 php.ini
php --ini
看 Loaded Configuration File 和 Scan for additional .ini files in 两行。两个经典坑:
- CLI 和 FPM 用不同 php.ini:Debian/Ubuntu 上是
/etc/php8.x/cli/php.ini与/etc/php8.x/fpm/php.ini两套独立文件,改错文件白改; - 官方 Docker PHP 镜像默认没有 php.ini:输出是
Loaded Configuration File: (none),只加载conf.d/目录。此时所有参数走编译内置默认值,需要自建配置(见第三节)。
三、第二步:临时缓解(升级窗口期内执行)
配置必须写在 php.ini / FPM pool / 服务器配置(请求解析前生效)层。官方明确:不要依赖
ini_set()或.user.ini,对本漏洞不可靠。
1. 针对 CVE-2026-109619
session.upload_progress.enabled = Off
session.use_strict_mode = 1
- 前一条是官方点名的缓解:堵住已确认的 session 攻击路径。注意官方措辞是"blocks the confirmed session paths but not other variants";
- 后一条是纵深防御:严格模式拒绝客户端传入的、服务端从未创建过的会话 ID,防会话固定/会话伪造注入。它不针对本次 CVE,但是值得长期保留的加固项。
2. 针对 CVE-2026-109618
memory_limit = -1
post_max_size = 8M ; 有文件上传需求则设 32M
upload_max_filesize = 8M ; 必须保持在 post_max_size 之下
memory_limit=-1去掉"可控的"内存限制失败,但官方提示底层分配失败仍可能进入漏洞路径,所以是缓解不是免疫;post_max_size是请求体上限,在解析层直接砍掉大请求的攻击空间;memory_limit是 PERDIR 项,应用代码ini_set()和.user.ini都能覆盖它。要做到"应用改不了",必须放进 FPM pool:
; /etc/php8.x/fpm/pool.d/www.conf(各发行版路径略有差异)
php_admin_value[memory_limit] = -1
3. Web 服务器 / 代理层限流
client_max_body_size 8m;
同时在网关层限制请求体大小、表单字段数、multipart 分片数、header 数量与嵌套深度——官方特别强调这些限制要覆盖没有 Content-Length 的请求。
4. Docker 环境完整示例
官方镜像默认无 php.ini,往 conf.d 加文件最干净:
cat > /usr/local/etc/php/conf.d/99-session-hardening.ini <<'EOF'
session.upload_progress.enabled = Off
session.use_strict_mode = 1
memory_limit = -1
post_max_size = 8M
upload_max_filesize = 8M
EOF
容器重建会丢配置,务必固化进 Dockerfile:
COPY session-hardening.ini /usr/local/etc/php/conf.d/99-session-hardening.ini
5. 硬性要求:重启全部 worker
不要用 reload。CVE-2026-109619 的脏状态在 worker 内存里,reload(HUP)保留旧 worker,清不干净:
systemctl restart php8.3-fpm # 或:docker restart <容器>
四、第三步:验证
php -r 'var_dump(ini_get("session.upload_progress.enabled"), ini_get("session.use_strict_mode"), ini_get("memory_limit"), ini_get("post_max_size"));'
期望输出:
string(1) "0"
string(1) "1"
string(2) "-1"
string(2) "8M"
两个实战小坑:
-
若
upload_progress.enabled返回空串而不是"0",说明加载的某个配置文件里有一行值写空的指令(如session.upload_progress.enabled =)。用grep -rn "upload_progress" /usr/local/etc/php/定位后整体重写,必要时cat -A检查 CRLF/BOM 等不可见字符; -
CLI 验证通过后,还要在 Web 端放一个
info.php(<?php phpinfo();)确认 FPM 实际加载的值,确保改的是对外服务的那个 SAPI。

五、业务影响评估(回归清单)
| 配置 | 潜在业务影响 |
|---|---|
upload_progress.enabled=Off |
若有前后端依赖 $_SESSION['upload_progress_*'] 展示上传进度,会失效;现代前端普遍用 XHR upload.onprogress(浏览器端进度),不受影响 |
use_strict_mode=1 |
"外部传入 PHPSESSID 恢复会话"的场景(深链、App 跳 H5)会掉登录态;多节点 + 本地文件会话未集中存储时,用户会随机掉登录 |
post_max_size=8M |
超过 8M 的上传请求被整体拒绝,有大文件业务的按上面说明提到 32M |
memory_limit=-1 |
取消脚本内存上限,依赖容器 cgroup 限制兜底即可 |
上线前建议:全局搜一遍代码里的 upload_progress 与 ini_set('memory_limit';有条件的话把会话迁移到 Redis/DB 再开严格模式;变更后盯 1–2 天登录成功率与上传错误率。
六、终局:升级版本
| 当前版本 | 动作 |
|---|---|
| 8.2 – 8.5 | 升级到 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12(Docker 直接换基础镜像 tag 重打) |
| 8.1 及以下 | 已 EOL:找发行版/LTS 回移植,或用官方 7.4 / 8.0 / 8.1 源码回移植分支,同时规划迁移到受支持版本 |
| 特别注意 | 8.2 将于 2026-12-31 EOL,之后不再有任何安全修复,建议直接规划跳 8.3+ |
升级完成后,use_strict_mode=1 与 upload_progress=Off 建议保留——它们是好的长期加固,不是临时补丁。
七、一页 Checklist
- [ ] 排查 PHP 版本与 SAPI(FPM / mod_php / CGI),逐个节点
- [ ] 确认对外 SAPI 加载的 php.ini(含 Docker "none" 状态)
- [ ] 改配置,
memory_limit=-1用 poolphp_admin_value锁死防覆盖 - [ ] Nginx / 网关层请求体与字段数限流
- [ ] restart 全部 worker(不是 reload)
- [ ] CLI + Web 双重验证,期望值
0 / 1 / -1 / 8M - [ ] 业务回归:文件上传、登录态保持、搜
upload_progress依赖 - [ ] 配置固化进 Dockerfile / 配置管理
- [ ] 升级版本排期(建议本周内)
八、雷池WAF规则
动作选“黑名单”,勾选“启用观察模式:发现攻击后仅记录攻击事件,不拦截”。
- 条件组 1(组内 AND):
- POST 参数:PHP_SESSION_UPLOAD_PROGRESS,操作符“存在”
- POST 参数:PHPSESSID,操作符“正则”,值 ^[0-9A-Za-z,-]{16,256}$
- 请求头:Content-Type,操作符“包含”,值 multipart/form-data
点“添加或条件”
- 条件组 2(组内 AND):
- POST 参数:PHP_SESSION_UPLOAD_PROGRESS,操作符“存在”
- 请求头:Content-Type,操作符“包含”,值 multipart/form-data
- 请求 Body,操作符“正则”,值:
Content-Disposition:\s*form-data;\s*name="[^"]+"\r\n([^\r\n]+\r\n)*\r\n[\s\S]{0,32}\x00

JSON
{
"name": "PHP Core RCE CVE-2026-109618/109619 - observe",
"action": 1,
"monitor": true,
"log": false,
"is_enabled": true,
"insert_position": "first",
"pattern": [
[
{"k": "post_param", "op": "exist", "v": [], "sub_k": "PHP_SESSION_UPLOAD_PROGRESS"},
{"k": "post_param", "op": "re", "v": ["^[0-9A-Za-z,-]{16,256}$"], "sub_k": "PHPSESSID"},
{"k": "req_header", "op": "has", "v": ["multipart/form-data"], "sub_k": "Content-Type"}
],
[
{"k": "post_param", "op": "exist", "v": [], "sub_k": "PHP_SESSION_UPLOAD_PROGRESS"},
{"k": "req_header", "op": "has", "v": ["multipart/form-data"], "sub_k": "Content-Type"},
{"k": "req_body", "op": "re", "v": ["Content-Disposition:\\s*form-data;\\s*name=\"[^\"]+\"\\r\\n([^\\r\\n]+\\r\\n)*\\r\\n[\\s\\S]{0,32}\\x00"], "sub_k": ""}
]
]
}
参考资料