在野利用比补丁早跑4天:PHP2Shell打穿升级窗口

在野利用比补丁早跑4天:PHP2Shell打穿升级窗口

PHP安全漏洞RCECVE-2026-109618CVE-2026-109619运维安全

数据源:Datadog Security Labs + PHP 官方公告 + AWS 威胁情报

漏洞提前泄漏:在野利用比补丁早跑了 4 天

2026 年 10 月 21 日,PHP 官方发布两个远程代码执行漏洞的修复:CVE-2026-109618 与 CVE-2026-109619。两个漏洞在 CVSS v4.0 下均为 9.2。修复版本覆盖 PHP 8.2.35、8.3.36、8.4.27 与 8.5.12。针对停更的 7.4、8.0 与 8.1 分支,安全团队破例提供了源码级回移补丁。

常规的协同披露假定补丁先于外部利用。但对 PHP2Shell 而言,这套机制失效了。补丁发布之前,安全团队 Huntress 便以《Leaked Critical PHP Vulnerabilities》为题公开预警,直指漏洞细节已在暗处泄漏。PHP 维护团队罕见地在 php.announce 发布第 509 号公告,呼吁全网运维提前准备。Ars Technica 报道了这一异常举动,维护团队提前吹哨,表明事态脱离了受控轨道。

威胁并未等待官方补丁。Datadog Security Labs 在披露前就独立构建出可用的 exploit。AWS 威胁情报团队在 MadPot 蜜罐中证实,早在 10 月 17 日便观测到真实利用尝试。在野攻击比官方披露早到了 4 天。防守方尚未收到预警,公网节点就已经承受探测。

安全厂商 Sansec 随即发出预警:补丁发布后 15 分钟内就会出现全网规模的自动化攻击。截至 10 月 21 日,Datadog 引述 Patchstack 监测确认,多起在野利用与无差别扫描已经在公网蔓延。攻击者与防御方在同一时间拿到补丁代码。防守方原本赖以排查的升级窗口直接归零。

Datadog Security Labs 对 PHP2Shell 漏洞的研究总览图 图:Datadog Security Labs 公布的 PHP2Shell 核心影响范围与攻击链总览。来源:Datadog Security Labs

请求未进业务代码:解析器清理阶段直接交出 Shell

大部分应用安全体系将重心放在网关认证、路由鉴权与业务代码审计上。但在 Datadog Security Labs 的实测中,PHP2Shell 绕过了所有应用层逻辑。测试环境采用 PHP 8.5.11、Apache prefork 与 mod_php,配置参数为 memory_limit=16M 和 post_max_size=64M。攻击者仅需发送一个未认证的特制 HTTP POST 请求,就能直接劫持主机。

触发逻辑发生在 PHP 运行时的 C 语言层面。恶意请求抵达服务器后直接触发了解析失败。此时控制流尚未移交给任何业务脚本。PHP 在处理失败请求的清理阶段,直接执行了攻击载荷注入的命令。哪怕服务器上只有一个输出固定文本的最简脚本,整台主机同样会在解析瞬间失守。

这种击穿覆盖了当下几乎所有主流部署形态。根据 Huntress 报告与 Wikipedia 词条记录,从 PHP 5 到 PHP 8 均受波及。受影响的运行时涵盖 Apache mod_php、php-cgi、高并发主力 PHP-FPM、新一代 FrankenPHP 以及 LiteSpeed。应用层鉴权在解析阶段根本无法筑起防线。

PHP2Shell 触发请求解析阶段执行命令的攻击调用流 图:攻击请求在解析阶段触发清理例程执行系统命令的调用流。来源:Datadog Security Labs

计数器早于内存分配:深挖双 CVE 状态残留

两个漏洞分别切中了请求周期的不同缺陷。CVE-2026-109618 的问题,在于内存分配失败后的清理例程错误信任了无效数据。触发点位于 Zend 引擎核心函数 _zend_hash_str_add_or_update_i()。该函数向哈希表写入键值时,会先递增计数器,再去为键名申请内存。

一旦键名申请超出 memory_limit,Zend 引擎立刻抛出致命错误。此时计数器已经把未初始化的桶节点计入在内。节点内部残留着早期分配留下的脏字节。随后的清理流程将这些脏字节视为合法变量并执行销毁,攻击者得以控制残留数据诱导调用 system()。即使到了 PHP 8.5.12,计数器仍然在分配之前更新;官方修复改的是错误处理流程,分配失败不再留下半初始化条目。

另一个漏洞 CVE-2026-109619 则利用了 Worker 进程跨请求的状态复用。在 PHP 8.5 中,致命错误回溯机制在 mod_php 下可直接促成任意命令执行。在更普遍的文件上传处理中,致命错误遗留的 session 或解码器状态会跨请求传递。默认开启的 session.upload_progress.enabled 无需代码调用 session_start(),就会在后台持续运转。

利用 CVE-2026-109619 需要后续请求连续命中同一个 Worker 进程。在多进程池或大并发架构下,单次利用成功率会被流量摊薄。但这只改变了统计概率,没有降低漏洞本身的危害。

Worker 跨请求复用脏内存的状态流转机制 图:Worker 进程跨请求复用脏内存导致状态流转的机制。来源:Datadog Security Labs

关掉上传进度防不住:配置缓解挡不住内存缺陷

漏洞公布后,社区流传将 session.upload_progress.enabled 设为 0 的临时做法。这一调整存在明显的防护死角。它只能封堵 CVE-2026-109619 的部分上传入口,挡不住 CVE-2026-109618 的内存分配缺陷。保留内存分配缺陷不修,单纯关闭上传进度无法消除风险。

PHP 官方随后给出了严格的临时缓解方案。针对 CVE-2026-109618,官方建议强制设置 memory_limit=-1。同时管理员必须在反向代理限制请求体体积,并在容器层施加 cgroups 限制。运维人员不得使用 RLIMIT_AS 或 RLIMIT_DATA 等进程配额,这类配额恰好会触发漏洞所需的内存分配失败。

针对 CVE-2026-109619,除了关闭上传进度,运行 PHP 8.5 还需设置 fatal_error_backtraces=0。所有这些配置必须在 PHP 解析请求前加载,并逐一重启每个 Worker 进程。CERT-EU 在 2026-018 号公告中建议,无法立即修复的应用应直接隔离。Datadog 则建议在反向代理或其他前置边界拦截异常请求。

偿还 40 年技术债:防御只剩立刻替换运行时

回顾整个事件的发酵过程,PHP2Shell 打穿的不是 PHP 的解析器,是「补丁先行、用户随后从容升级」的默认秩序。漏洞在发布前泄漏,在野攻击提前 4 天涌现,防御方的安全缓冲直接破灭。攻防双方同时拿到补丁,全网攻击在补丁公开后几分钟内便已启动。

IEEE Spectrum 当天把这起漏洞事件概括成一句话:AI 正在逼我们偿还 40 年的技术债。PHP2Shell 正好落在这句话上——先递增计数器、再为键名申请内存,这套写法一直没人动过,连 PHP 8.5.12 的修复改的也是周边错误处理,而不是计数器本身的顺序。

排查潜在受害情况的运维人员可以关注三类典型信号。第一类是请求初始化阶段爆发的内存耗尽报错;第二类是包含异常字段的大型表单提交;第三类是 Web 账户下非预期派生的 shell 子进程。这些信号意味着恶意载荷正在尝试击穿解析层。

面对被压缩归零的响应时间,配置调优只是权宜之计。反向代理拦截可以阻隔部分流量,但无法从内部消除解析隐患。尽快在每一处生产环境中替换受影响的运行时,才是防御方唯一的选择。

参考链接:

  • Datadog Security Labs 漏洞分析报告
  • PHP 官方安全更新公告与 php.announce 第 509 号公告
  • Huntress 威胁情报报告
  • AWS Security Blog 观测记录
  • Ars Technica 专题报道
  • CERT-EU 安全公告 2026-018
  • IEEE Spectrum 专题报道