PHP2Shell 深扒:PHP 两个 9.2 分 RCE 漏洞(CVE-2026-109618/109619)全解读

2026 年 10 月 21 日 22:00(北京时间),PHP 官方发布 8.5.12、8.4.27、8.3.36、8.2.35 四个安全版本,修复两个 CVSS v4.0 评分 9.2 的远程代码执行漏洞:CVE-2026-109618 与 CVE-2026-109619。安全社区给这组漏洞起了个名字:php2shell。

补丁比原定计划提前了一天,攻击却比补丁更早。Cloudflare 在 10 月 21 日 15:43(北京时间,07:43 UTC)观测到利用流量,距官方发布还有六个多小时。watchTowr 的全球蜜罐网络在当天确认了在野利用。所有向 Web 提供服务的 PHP 运行方式都在风险范围内:PHP-FPM、Apache mod_php、php-cgi、FrankenPHP、LiteSpeed。其中 mod_php 已被完整验证:一个未认证请求,可以在任何应用代码运行之前触发攻击。

PHP 官方头图

时间线:从首次发现到补丁发布共 81 天

时间(UTC)事件
2026-08-01Calif 研究员 Ryan Austin 发现多条 RCE 路径
2026-08-11Calif 向 PHP 报告第一个漏洞,协调披露启动
2026-08-19Wiz Research 用 AI 工具链独立发现同类问题
2026-08-20 / 08-21Wiz 确认对 Drupal、WordPress 的未认证 RCE
2026-08-24Wiz 向 PHP 安全团队报告
2026-09-21PHP 确认报告,开始开发协调修复
2026-10-20技术细节提前泄露;PHP 发预告,定档 10-21 14:00 UTC
2026-10-21 07:43Cloudflare 观测到补丁发布前的早期攻击
2026-10-21 14:00修复版本发布,Calif、Wiz、OpenAI 与 PHP 同步披露

原定披露时间是 10 月 22 日 18:00(欧洲中部时间)。10 月 20 日,一个 Hacker News 帖子提前泄露了两个 CVE 编号,帖子很快被删,截图已经在社交网络上传开。PHP 安全团队当天发布预告,把发布提前到 10 月 21 日,并保留了「细节公开或出现野外利用就提前」的条款。提前了一天,攻击还是抢在了补丁前面。

两个漏洞分别是什么

PHP 的 Web 运行模式是常驻 worker 接连处理请求。这组漏洞的共同根源都在「请求异常退出」之后的清理逻辑:一次本该干净的退出,留下了跨请求存活的坏状态。

CVE-2026-109618:OOM 打断请求留下的坏状态

PHP 用 longjmp() 从内存耗尽(OOM)错误中跳出当前上下文。某些场景下,请求内存已经可达但尚未完成初始化;此时任何一次分配失败,都会让请求带着非法状态进入关闭流程。关闭流程会读取这段内存、按它做分支,攻击者布置好堆布局就能把它发展成代码执行。

可复现的 OOM 路径集中在 multipart 表单解析(浏览器上传文件用的那种请求):

  • 声明的 Content-Length 不可用的 multipart 请求,可能绕过 post_max_size 的初步检查,继续投递解码后的数据
  • 单个 multipart part 可以携带数量不受限制的 header,这些 header 在 PHP 内部表示占用的内存远大于线上字节,请求在 post_max_size 之内就能耗尽 memory_limit
  • 空文件控件不计入 max_file_uploads,却照样生成完整的文件元数据数组;嵌套字段名会成倍放大数组操作

SAPI(PHP 的服务器抽象层)之间的差异很关键。Apache mod_php 即使请求启动失败也会无条件执行关闭流程,整条攻击链发生在任何应用代码之前,PHP 官方已在默认配置、多 worker 的 prefork 部署上完整验证。PHP-FPM 在启动失败时直接终止子进程,避开了这条最短路径;官方源码分析仍判定 FPM 易感,但针对该 SAPI 的完整利用尚未演示。FrankenPHP 与 mod_php 行为同源,判定为大概率易感。

这个 CVE 还附带一个 Heartbleed 式的内存泄露:上传进度处理器可以把已释放的 worker 内存,通过响应里的 PHPSESSID Cookie 原样回显给攻击者。泄露的内存可能包含其他请求的密码、会话令牌、API 密钥甚至 TLS 私钥。应用代码从始至终没有调用 session_start() 也拦不住它,因为出事的代码跑在应用之前。

CVE-2026-109619:跨请求存活的过期全局状态

worker 是常驻的,它的部分全局状态可能指向某个请求拥有的内存。一次致命错误(包括 OOM)可以让操作在执行匹配的重置之前中断;甚至一条非致命的诊断信息,也可能在关闭阶段的后期把新的请求级错误信息重新写回全局状态。请求堆随后被释放,全局变量仍指着它。同一个 worker 处理下一个请求时,如果某个操作赶在正常初始化之前碰了这些指针,就是一个跨请求的释放后使用(use-after-free),布置得当可以发展成 RCE。

已确认的「消费者」(读取过期状态的操作)有两类。其一是请求初始化前的 INI 诊断:mod_php 下 .htaccess 和 php_value 等目录级配置在请求初始化之前生效,触发一条警告就够;FPM 只在前端发送 PHP_VALUE / PHP_ADMIN_VALUE 且恰好产生诊断时暴露,pool 配置在 worker 启动时应用,.user.ini 在初始化之后应用,都构不成早期消费者。其二是 session upload progress(上传进度):它默认开启,不需要应用调用 session_start(),可以在正常会话初始化之前触达会话处理逻辑。

PHP 官方公告确认了两个具体案例。PHP 8.5 的 fatal_error_backtraces 特性会把最近一次致命错误的回溯存进 EG(last_fatal_error_backtrace),关闭流程永远漏掉对它的重置,这是已验证 mod_php 利用链的基础。上传进度的生命周期问题是第二个确认案例:mod_php 上「发布状态加重致命错误」可以全部发生在 multipart 启动阶段;FPM 上验证过的路径需要在应用执行期发布状态、随后一次致命错误、再由上传进度充当早期消费者,普通动态 pool 即可,但对应用里有合适的操作有依赖。Wiz 另外演示了一个归入此 CVE 的 OPcache 类型混淆:脚本持久化过程中进程本地指针 frontier 先行推进、共享状态滞后发布,中间一次分配失败就能造成跨 worker 类型混淆,构成针对 WordPress 的 RCE 链。

四个 bug,两个 CVE

CVE对应 bug已演示能力
CVE-2026-109618上传进度反序列化 UAF未认证 RCE(WordPress、Drupal、Joomla)
CVE-2026-109618远程内存泄露(PS(id) 悬挂指针)应用无关的 worker 内存回显
CVE-2026-109619致命错误回溯 UAF(EG(last_fatal_error_backtrace))未认证 RCE(Drupal、WordPress)
CVE-2026-109619OPcache 类型混淆(map_ptr frontier)跨 worker 共享状态破坏加 RCE

Wiz 强调这些演示目标只起代表作用:漏洞在 PHP 引擎本身,哪怕应用只是一个 Hello World,跑在受影响版本上就危险。补丁从引擎层面修掉整类问题,官方确认也覆盖了更多已知与未知的变体。

版本对照与升级路径

分支修复版本说明
8.58.5.12受支持分支,官方修复
8.48.4.27受支持分支,官方修复
8.38.3.36仅安全修复,官方修复
8.28.2.352026-12-31 到期,官方修复
8.1 / 8.0 / 7.4无官方版本已 EOL;PHP 团队提供最小源码回移分支,供自编译用户使用
7.3 及更早无已 EOL 且无回移,只能依赖发行版或升级

几个升级路径上的坑,官方公告写得很具体:

  • Linux 发行版和 LTS 厂商提前拿到了补丁,用发行版包的用户直接等发行版更新即可
  • Remi 仓库(rpms.remirepo.net,RHEL 系常用)当天不会发布更新,官方建议切换到发行版包或自行编译
  • PHP 8.1 及更早分支不再收到 PHP 官方更新,只能找发行版回移或升级

影响面有多大

W3Techs 统计,在服务端语言已知的网站里 69.3% 用 PHP。Shodan 上能搜到超过 190 万台暴露在公网、运行 PHP 的主机(安全研究员 Henk Poley 的统计),Silas Cutler 估算潜在受影响主机超过 130 万台。

Calif 用一个合并了两个漏洞的利用工具,在 11 个当时的最新版本应用上验证了未认证 RCE:Magento 2.4.9、WordPress 7.1.2、Drupal 11.4.3、Joomla 6.1.4、MediaWiki 1.46.2、Roundcube 1.7.4、phpMyAdmin 5.2.3、Nextcloud 35.0.1、Laravel 13.11、Symfony 8.1 和 WHMCS。这些应用自身毫无问题,出事的是下面的 PHP 引擎。受影响代码可以追溯到 2012 年发布的 PHP 5.4.0,默认配置即受影响。

补丁发布的同时,Calif、Wiz、OpenAI 的技术文章和利用工具 php2shell 已全部公开。Sansec 在补丁发布前预测过「补丁发布 15 分钟内开始大规模攻击」,实际走向比预测更早:发布前六个多小时攻击流量已经出现,发布当天 watchTowr 蜜罐确认在野利用,Huntress 截至 10:00(美东)还没在自己的客户基里观测到。荷兰多家网站(Tweakers、荷兰国会、NCSC、Security.NL)选择在发布当天预防性下线。CISA 已知被利用漏洞目录(KEV)截至本文写作还未收录这两个 CVE。

缓解配置实操:争取时间,不是替代升级

PHP 核心开发者 Gina Peter Banyard 的原话是「唯一的缓解措施就是打补丁」。以下配置全部来自官方公告和 Sansec 的指引,它们只能压缩特定攻击路径,任何一条都不能让系统变得安全。Cloudflare 已为已知攻击向量部署 WAF 规则,免费计划同样可用,官方称这只是止血措施。能升级就升级,升级不了才用这些买时间。

第一步,在 Web 服务器层面拦截 multipart 请求。目前所有已知触发路径都是 multipart/form-data。

nginx,在每个转发 PHP 的 server 块里加:

nginx
if ($http_content_type ~* "multipart/form-data") {
    return 403;
}

Apache,在每个提供 PHP 服务的 VirtualHost 里加:

apache
RewriteEngine On
RewriteCond %{HTTP:Content-Type} multipart/form-data [NC]
RewriteRule ^ - [F]

这条规则会同时弄坏文件上传(比如后台传产品图),部分商店的购物车、结算表单也走 multipart,加了规则记得下一单测试。升级完 PHP 后移除。

第二步,改 PHP 配置。Calif 公开的两项部分缓解是关掉上传进度和取消内存上限。

mod_php 写进 php.ini(或 conf.d 下的 drop-in 文件)并重启 Apache:

ini
session.upload_progress.enabled = 0
memory_limit = -1
post_max_size = 8M

PHP-FPM 读的是自己的 pool 配置,改命令行的 php.ini 没有用。每个 pool(例如 /etc/php/8.3/fpm/pool.d/www.conf)里加:

ini
php_admin_flag[session.upload_progress.enabled] = off
php_admin_value[memory_limit] = -1

改完 reload worker(systemctl reload php8.3-fpm,版本号按实际替换),并且通过 Web 请求验证配置生效,命令行 php -i 看到的值不代表 FPM。ini_set() 和 .user.ini 都救不了这个场景,配置必须在请求解析之前生效。

PHP 8.5 用户另有专属开关:fatal_error_backtraces=0,同样要重启所有 worker,它移除的是已验证利用链里的状态生产者。

memory_limit = -1 的代价要想清楚:单个请求可以吃掉全部服务器内存。盯紧内存曲线,升级后立刻恢复限制。官方还特别警告,别用 RLIMIT_AS 或 RLIMIT_DATA 顶替,它们引起的 mmap() 失败恰好会触发漏洞路径;容器或 cgroup 限制可以作为可用性边界,策略上让被杀的只是出事的那个 worker。需要上传的应用把 post_max_size 放宽到 32M,并保持 upload_max_filesize 小于 post_max_size。

第三步,修应用层:攻击者可达路径上的致命错误和执行超时都是「状态生产者」,上传、导入、回调、清理、析构里的报错都算数。FPM 用户如果性能允许,pm.max_requests = 1 让每个 worker 只处理一个请求,也能切断跨请求的状态复用。

Sansec php2shell 专题图

攻击检测信号

Wiz 给防守方的观测建议可以直接抄:应用日志在这类攻击里提供的上下文很有限(代码跑在应用之前),重点看基础设施层的信号。异常的 multipart 请求后跟内存超限报错或连续 HTTP 500;响应里尺寸或格式异常的 PHPSESSID Cookie;没有解释的 PHP worker 频繁重启。这三类现象出现任何一类,都该按已被攻击处理。

为什么说这是一个 AI 拐点

这组 bug 在代码里存在了 14 年,爆发的时点由利用成本决定。

答案在利用成本里。这组 bug 的代码问题虽然古老,把它变成可用 exploit 却需要串起多个缺陷,还要适配每个目标的 PHP 版本、配置和内存布局,一处假设失配整个链条就断。Wiz 的结论是:AI 出现之前,这种手工适配工作很难完成,更难规模化。用上前沿模型之后,一名研究员一周内完成了过去世界级团队要花数月的工作。

Wiz 在研究里使用了 Wiz Atlas、Gemini 4 Argon 和 GPT-5.6 Sol,其中 Gemini 4 Argon 在漏洞发现、利用开发、攻击检测和补丁验证四个环节都出了力。Calif 的发现路径类似:各大 AI 实验室的自动化扫描都扫过 PHP,都没有扫出来,最后是研究员在 AI 协助下做到了。OpenAI 也参与了披露协作,其 Patch the Planet 项目正把同样的能力推向开源维护。

这对漏洞库存的定价逻辑是个冲击。过去「理论可利用但没人愿意花这个成本」的老 bug,现在都要重新估值:利用开发的边际成本在塌缩。防御方手里的牌是对称的,同样的技术可以用来大规模审计存量代码。武器在两边手里同时变便宜。

回到运维层面,这件事的 checklist 反而很朴素:盘点所有 PHP(包括藏在供应商软件里的),隔离 EOL 版本,升到 8.5.12 / 8.4.27 / 8.3.36 / 8.2.35,暂时升不了的套上缓解配置,等升级窗口。

说明与来源

本文写于 2026 年 10 月 21 日深夜(北京时间),距披露落地数小时,在野利用规模仍在演变中。所有技术细节、版本号、时间线均以下列来源为准: