2026 年 10 月 21 日 13:30 UTC(北京时间 21:30 左右),PHP 官方发布 8.5.12 / 8.4.27 / 8.3.36 / 8.2.35,修复两个未认证远程代码执行漏洞:CVE-2026-109618(GHSA-w8hw-w9fx-j79w)与 CVE-2026-109619(GHSA-6wmf-4rv5-p3rp),社区代号 PHP2SHELL,CVSS v4.0 均为 9.2。
官方公告里有一句话值得先记住:「There are no complete mitigations, updating is the only safe choice.」——没有完整的缓解方案,升级是唯一安全选择。所以下面所有配置类手段,定位都是「降低暴露面」,不是「修好了」。
时间线:从细节泄露到补丁,不到 24 小时
| 时间 | 事件 |
|---|---|
| 10 月 20 日 | PHP 项目预告将发布安全版本;漏洞细节在补丁前已在网上流传,部分报道称官方被迫提前发布 |
| 10 月 21 日 13:30 UTC | 8.5.12 / 8.4.27 / 8.3.36 / 8.2.35 发布,同时公开两个 GHSA 技术公告 |
| 10 月 21 日当天下半夜 | 补丁发布数小时内出现 PoC 与大规模扫描;CERT-EU、ENISA 等机构陆续发布公告与联合声明 |
| 10 月 22 日 | 国内 CNVD 收录为 CNVD-2026-41674、CNVD-2026-41675,多家云厂商与高校信息化部门发布风险通告 |
这个节奏意味着:不要用「等官方出利用分析再动手」的惯性处理这次事件。补丁与 PoC 几乎同日出现,暴露窗口以小时计。
两个漏洞,分别坏在哪一环
CVE-2026-109618(GHSA-w8hw-w9fx-j79w):OOM 中断留下的「半初始化状态」
PHP 在内存分配失败(OOM)时会用 longjmp() 提前跳出,放弃继续处理当前请求。问题在于:中断发生时,内部请求数据可能只初始化了一部分,而这段内存已经可以被引用。随后请求清理阶段会把这批无效数据当成正常数据处理,于是内存被破坏。
触发方式很「便宜」:攻击者构造一个 multipart 请求,用大量头部、深层嵌套的字段名或空文件控件,在看起来没超过 post_max_size 的情况下把 memory_limit 耗光。
CVE-2026-109619(GHSA-6wmf-4rv5-p3rp):跨请求残留的「过期状态」
PHP-FPM、mod_php 这类常驻进程会复用 worker。请求私有的内部状态(错误、会话、对象、扩展状态)在底层内存释放后仍然存活;同一个 worker 处理的下一个请求,可能在正常初始化重置之前就读到了这个过期状态,形成释放后使用(use-after-free)。
利用方式是双请求协作:第一个请求当「生产者」,把堆布局摆成特定形状;第二个请求当「消费者」,去访问已经失效的内存。整个过程不需要登录,也不需要诱导任何人点击链接。
谁能真的被打穿:mod_php 已被实证,FPM 还没有完整链
这一点常被二手文章写混,直接影响处置优先级:
- Apache + mod_php:已在 HTTP 场景下实证未认证 RCE,而且发生在应用代码执行之前——也就是说,框架、路由、中间件都还没有机会运行;
- PHP-FPM:源码分析表明同样存在易感性,但公开资料里还没有完整可用的利用链;
- php-cgi、FrankenPHP、LiteSpeed 同属受影响范围,处置上按 FPM 一类对待。
所以「FPM 还没被打穿」不等于安全:这只是当前公开研究的进度,不是架构上的免疫。
为什么业务层防不住
- 触发点在解释器内部的错误处理路径,WAF 看到的只是一个格式合法的 multipart 上传;
- 不经过任何业务代码判断,
if (!isAdmin())之类的防护完全无效; - 内存破坏发生在请求生命周期里,与应用是 Laravel、ThinkPHP 还是 WordPress 无关。
先判断你在哪一档
| 分支 | 受影响版本 | 修复版本 |
|---|---|---|
| PHP 8.5 | < 8.5.12 | 8.5.12 |
| PHP 8.4 | < 8.4.27 | 8.4.27 |
| PHP 8.3 | < 8.3.36 | 8.3.36 |
| PHP 8.2 | < 8.2.35 | 8.2.35(该分支 2026-12-31 EOL) |
| 8.1 及更早 | 全部,官方不再维护 | 仅有发行版回溯包,或官方为 7.4 / 8.0 / 8.1 提供的最小源码补丁 |
按风险从高到低排,资源有限时按这个顺序推:
- 最高:Apache + mod_php 且对外暴露 80/443。能切 PHP-FPM 就先切,切之前优先升级;
- 高:PHP-FPM 且存在匿名上传、评论、投稿、导入等入口的站点——攻击者本来就有合法途径发 multipart;
- 中:FPM 部署、严格限制上传接口、前置有 CDN 或 WAF;
- 低但仍需处理:纯内网或只跑 CLI 的 PHP。CLI 不经过 HTTP 请求生命周期,但不能以此无限期拖延。
自查时注意多版本共存的面板环境,别只看 php -v 的输出了事:
php -v
# 面板通常装了多个版本,确认 FPM / Apache 实际加载的是哪一个二进制
ls -l /www/server/php/*/bin/php
ps aux | grep -E 'php-fpm|httpd|apache2' | grep -v grep
# 发行版包:核对包管理器里的实际版本
dpkg -l | grep php | head -20
rpm -qa | grep -E '^php' | head -20
别忘了两类容易漏的资产:容器镜像里的 PHP,以及后台依赖 PHP 的商业软件 / 开源产品。ENISA 的公告里点名了 NCSC-NL 维护的 php2shell 清单,专门用来比对哪些第三方产品内嵌了受影响版本的 PHP。
升级:唯一完整修复,但有四个坑
- 选对包源:优先用发行版或面板的安全更新;
rpms.remirepo.net这类志愿者仓库在发布当天不会有更新,官方直接建议改用发行版包或自行编译; - 重启所有 worker:升级后不重启 FPM / Apache,内存里跑的仍是旧代码。队列 worker、常驻的定时进程、swoole / roadrunner 这类常驻进程也要一并重启;
- 验证运行时版本:看
phpinfo()或 FPM 实际加载的二进制,而不是看安装包版本号; - 回归业务:上传、图片处理、数据库连接、缓存、队列消费逐项过一遍,尤其是上传路径——它正是这次漏洞的触发面。
如果还在 PHP 8.2,注意它 2026-12-31 就该 EOL 了。这次升级顺手挪到 8.3 或 8.4,比两个月后再来一轮要划算。
不能立刻升级时的官方缓解
官方原文说得很清楚:以下措施只降低特定攻击路径的暴露面,不能使安装变安全。按漏洞分开看。
针对 CVE-2026-109618(OOM 路径)
- 取消可控的内存上限:设置
memory_limit=-1,并让它无法被应用覆盖——用 FPM pool 的php_admin_value或 Apache 配置下发,然后确认没有任何 pool、目录或应用级设置把有限值重新写回去。注意这只消除了「可控的 PHP 内存上限失败」,底层分配失败仍可能触达漏洞路径。 - 收紧请求体:不需要上传的站点
post_max_size = 8M,需要上传的32M,并保持upload_max_filesize小于post_max_size。 - 在 Web 服务器或代理层限制:解码后的请求体大小、输入字段数量、multipart part 数量、header 数量、嵌套深度。关键是这些限制要覆盖没有 Content-Length 的请求(chunked 传输),否则攻击者可以绕开。
- 用 cgroup 做可用性边界:策略应尽量只终止受影响的 worker。不要用
RLIMIT_AS/RLIMIT_DATA替代——它们本身就会制造漏洞所依赖的分配失败,等于帮攻击者触发条件。
; FPM pool 里下发,应用无法用 ini_set 覆盖
php_admin_value[memory_limit] = -1
; php.ini / pool / Apache 配置均可
post_max_size = 8M
upload_max_filesize = 4M
针对 CVE-2026-109619(过期状态路径)
- 关掉上传进度:
session.upload_progress.enabled = 0,改完重启所有 worker。必须在 php.ini、FPM pool 或服务器配置里于请求解析之前生效;不要依赖ini_set()或 FPM 的.user.ini。它只阻断已确认的 session 路径,不覆盖其他变体。 - PHP 8.5 额外一步:
fatal_error_backtraces = 0,同样要重启 worker。 - 清理初始化前会产生诊断的配置:移除无效或已弃用的按请求设置。用
error_reporting、display_errors或@把报错藏起来是无效的——诊断信息本身已经在初始化前产生了。 - 修复可被触达的致命错误与超时:回调、上传 / 导入处理、清理阶段的失败都要收敛。
session.upload_progress.enabled = 0
fatal_error_backtraces = 0 ; 仅 PHP 8.5 需要
兜底
- 实在升不了的互联网暴露系统,先从公网隔离(下线、或收进访问控制之后),这是 ENISA 联合声明里给的保守做法;
- WAF 规则能挡已知变种,挡不住绕过 CDN 直连源站的流量,也挡不住改写后的请求;
session.use_strict_mode = 1属于常规加固,顺手打开没有坏处,但别把它当成这次漏洞的解法。
升级之外:怎么判断有没有被打
细节在补丁前就泄露了,所以你看到的异常可能早于你打的补丁。升级不能替代排查:
- PHP-FPM / Apache 的 worker 是否出现异常崩溃与频繁重启;
- OOM 类致命错误、warning 与 deprecation 是否突然增多,尤其是伴随上传 / multipart 请求时;
- PHP 进程树里是否冒出
sh、bash、curl、wget、python等异常子进程; - Web 目录近期新增 / 修改的
.php文件、非预期的文件写入; crontab -l、/etc/cron.d/与 systemd 里是否有陌生任务;- 访问日志里密集的 multipart POST:头部异常多、字段名超长、带空文件控件、缺失 Content-Length。
确认被入侵的机器,优先重装系统并重建服务。只删文件几乎一定复发——webshell 通常是多点布置的。
几个常见误区
- 「框架新就没事」:漏洞在解释器层,Laravel、Symfony、WordPress、Drupal 一样中招。
- 「升级了就等于修好了」:没重启 worker 等于没升级。
- 「改了 php.ini 就生效」:
session.upload_progress不支持ini_set,.user.ini也不算数。 - 「把报错关掉就看不见了」:诊断信息在初始化前已生成,隐藏不解决问题。
- 「用 RLIMIT 限内存更安全」:正好相反,它会制造漏洞需要的分配失败。
- 「FPM 还没被打穿,可以等等」:这是研究进度,不是架构免疫;公网暴露面优先处理。
- 「EOL 版本没人打」:长期未升级的存量站点恰恰是自动化扫描的首选目标。
可以直接抄的落地清单
- 定优先级和时限:互联网暴露系统 24–48 小时内完成升级,内网 7 天内;确实修不了就先隔离(这个时限是我们的内部 SLA,官方只说了「立即」);
- 建 PHP 版本台账:主机、容器镜像、面板多版本、第三方产品一个都不能漏;
- 把「升级 + 重启 + 验版本 + 回归」固化成一个原子流程,写成脚本或检查单;
- 复核四组配置:
memory_limit、post_max_size/upload_max_filesize、session.upload_progress.enabled、fatal_error_backtraces(8.5); - 把 multipart 入口列成清单:匿名上传、评论带图、导入接口,逐个确认大小与字段数限制。
参考(官方优先)
- PHP 官方公告与下载:php.net/archive/2026.php
- CVE-2026-109618:GHSA-w8hw-w9fx-j79w
- CVE-2026-109619:GHSA-6wmf-4rv5-p3rp
- CERT-EU Security Advisory 2026-018(缓解措施细节最全)
- ENISA 等机构关于 PHP 严重漏洞的联合声明(2026-10-21)
- NCSC-NL
php2shell清单:排查内嵌 PHP 的第三方产品 - CNVD-2026-41674 / CNVD-2026-41675
本文的版本号、触发条件与缓解参数来自 php.net 公告、两个 GHSA 与 CERT-EU 公告整理。实际处置前请再对照一次官方原文与你所用发行版的安全通告——尤其是 EOL 分支,能拿到什么补丁取决于发行版而不是 PHP 项目。