2026 年 10 月 26 日,PHP 官方发布了一则不太引人注目的 Windows 构建更新公告:四个受支持分支(PHP 8.5、8.4、8.3、8.2)的 Windows 安装包被打上了一个 -1 后缀——PHP 8.5.12-1、8.4.27-1、8.3.36-1、8.2.35-1。运行时版本号没变,源码也是同一批,变化的只是打包进去的一个依赖库——Brotli,从 1.2.0 升到了 1.2.1,用来封堵刚刚爆出的 CVE-2026-111497。

这不是一个只影响 PHP 的漏洞。就在前一周,Node.js 社区也在同一波 Brotli 安全修复中同步升级了打包依赖,10 月 13 日的 Node.js 安全公告里已经把它列为高优先级修复项。Brotli 作为 Google 出品的开源无损压缩库,被广泛嵌入到服务器语言运行时、反向代理(Nginx、Apache)、Web 客户端甚至移动浏览器中,影响面比单一语言漏洞要广得多。

漏洞技术细节:越界写入出在什么地方

根据 NVD 和 Brotli 官方 v1.2.1 Release Notes 的描述,问题出在解码器 c/dec/decode.c 文件的 DecodeBlockTypeAndLength 函数对"块切换(block-switch)"信号的处理上。构造恶意 Brotli 压缩流时,可以在流中途注入一个无效的块切换指令,而旧版本解码器没有对这条指令做充分校验就继续执行,导致后续写入操作越过了分配给解码器状态机的内存边界。

这是一个典型的 C/C++ 内存安全问题:缓冲区越界写入。攻击者通过精心编排的 brotli 压缩数据,可以在目标进程内存中覆写相邻位置的控制信息,后果从轻则崩溃(DoS)到重则泄露敏感内存内容或获得远程代码执行(RCE)权限。CVSS v3.1 给这个漏洞打了 8.8 分(高危),CVSS v4.0 评分类似。

受影响的 Brotli 版本范围很宽:1.0.3 到 1.2.0,覆盖了 2021 年至今的全部稳定发布版本。好消息是,Google 在 10 月 21 日就出了 1.2.1 修复版,补丁非常聚焦,只改动了解码器里几行错误传播逻辑。

PHP 场景下的实际攻击面有多大

这可能是很多 PHP 开发者第一个会问的问题。PHP 官方的公告里专门强调了一点:受影响的只有 Windows 构建。原因是 PHP Windows 官方包默认会把 Brotli 以静态链接的方式嵌入 cURL 扩展,而 Linux 和 macOS 上的 PHP 通常链接系统发行包自带的 Brotli 动态库,由发行版独立负责更新。

而且,触发路径也有一个默认关闭的安全开关。漏洞要被利用,攻击者需要通过 HTTP 请求让目标 PHP 应用返回 brotli 压缩的响应体,同时客户端或 cURL 扩展启用了 CURLOPT_AUTOENC 自动解压。cURL 默认是禁用自动解压的,但以下场景会让开关打开:

  • 代码里显式调用 curl_setopt($ch, CURLOPT_AUTOENC, true) 或设置 encoding 相关选项;
  • 使用 Guzzle、Symfony HTTP Client 等封装库时,某些配置组合下会自动启用;
  • PHP 的 HTTP 流式处理场景中,对 brotli 编码的响应做读取操作。

换句话说,如果你写的 PHP 应用没有主动开 brotli 解压功能,风险其实很低。但"低"不代表"零"——攻击面往往就藏在第三方依赖的配置里,尤其是那些你以为"默认安全"但实际上会在某个条件分支里偷偷打开的选项。

和两周前的 PHP RCE 漏洞有什么关系

有意思的是,就在 Brotli 公告发出前五天(10 月 21 日),PHP 官方刚发过一次紧急安全更新,修复了两个 CVSS 9.2 评分的远程代码执行漏洞——GHSA-6wmf-4rv5-p3rp(CVE-2026-109618)和 session upload progress 相关的 CVE-2026-109619。

那两个漏洞的根因在 PHP 内核层,影响全平台、全分支、全 PHP 版本,威胁等级比 Brotli 这个组件漏洞高得多。Brotli 公告是后续跟进的 Windows 依赖更新,属于"顺手打补丁"级别的维护。所以如果你两周前已经跟进了 PHP 8.5.12 / 8.4.27 / 8.3.36 / 8.2.35 的升级,那这次只需要在 Windows 环境里把安装包换成带 -1 后缀的新文件即可,PHP 源码和运行时本身没有任何改动。

这里有个容易踩的坑:如果你的 Windows 环境是"源码编译 + 动态链接系统 Brotli"而不是用官方预编译包,那你需要自己去升级系统里的 Brotli 库(libbrotlidec),而不是等 PHP 包更新。两种路线的修复动作不一样,别搞混了。

修复动作清单

按你的环境对号入座:

环境 修复方式 验证命令
Windows + PHP 官方安装包 下载 8.5.12-1 / 8.4.27-1 / 8.3.36-1 / 8.2.35-1 替换旧包 php -v && php -m | findstr curl
Windows + 自行编译 PHP 升级 Brotli 源码到 v1.2.1 后重新编译 PHP php -i | findstr brotli
Linux / macOS + 发行版 PHP 等待发行版推送 brotli 库更新,或手动编译升级 ldconfig -p | grep brotli(Linux)
Docker 环境 重新拉取 PHP 基础镜像,确认依赖已经升级 docker run php:8.5 bash -c "ldconfig -p | grep brotli"

另外,作为临时缓解措施,你可以在启动脚本或 php.ini 里加一行:curl.cainfo=/dev/null 其实没用——真正的缓解是确保代码里不要主动开启 brotli 自动解压,或者在 HTTP 客户端里拒绝 brotli 编码的响应体。但比起临时打补丁,直接升级 Brotli 才是正解。

写在最后:组件安全是一条长期供应链

Brotli 这次的故事,其实是在重演过去几年软件安全圈的一个老模式:基础组件的安全漏洞,往往比应用层漏洞影响面更广、修复链条更长。Brotli 被 PHP、Node.js、Nginx、Chrome、Edge、Android WebView、Flutter……几十上百种主流软件嵌入,每一种软件的升级节奏和依赖管理方式都不一样。Google 10 月 21 日出修复补丁,到今天(10 月 26 日)PHP 才发 Windows 包更新,中间差了 5 天——这已经算快的了。

对于 PHP 开发者来说,这件事最值得记住的不是"某个具体漏洞要怎么修",而是养成关注"语言运行时打包的第三方库"这种边缘信息的习惯。每次 PHP 官方更新的 changelog 里,那些不起眼的"更新了 XX 库到 YY 版本"记录,可能藏着下一个 CVE 的影子。特别是在 Windows 上用官方预编译包的环境——你以为你只在升级 PHP,实际上你同时在升级十几个静态链接进去的底层依赖。

好消息是这次 Brotli 漏洞的修复链条很短,补丁很小,验证也简单。坏消息是下一个类似的漏洞还会来。供应链安全没有一劳永逸的解决方案,保持敏感、快速响应,是目前最靠谱的做法。

参考链接