返回

2026/10/26

HTTP2Shell(CVE-2026-111497):更新二进制文件并不能修补正在运行的进程

HTTP2Shell 背后的 Brotli 漏洞波及 Node.js、Bun 和 Python。本文介绍如何确认运行中进程的真实暴露面,并安全重启 AI 代理。

2026 年 10 月 21 日,Node.js 发布了 CVE-2026-111497 的修复。该漏洞是 Google Brotli 解码器中的越界写入。随后几天内,Bun、Python 的 brotli 与 brotlicffi 包、Envoy 和 Istio 相继发布安全公告,安全媒体先将其称为 Node2Shell,后来又称为 HTTP2Shell。尽管名字如此,它并不是 HTTP/2 协议的缺陷,而是一个解压缩漏洞:凡是软件会解码攻击者可影响的 Brotli 数据的地方,都需要关注。

本文梳理哪些已经证实、哪些只是可能,以及如何检查自己的系统。同时分享我们自身处置过程中的一条教训:安装已修复的二进制文件,并不能修复仍在运行旧版本的进程。在我们的环境中,已安装的 Claude Code 更新之后,长时间运行的 Claude Code 会话仍在使用旧的内嵌运行时。

要点

  • 受影响版本:上游 Brotli 1.0.3 至 1.2.0。已修复版本:1.0.10、1.1.1 和 1.2.1。捆绑 Brotli 的产品需要各自发布修复版本。
  • 暴露需要一条真实路径:攻击者可影响的 Brotli 数据到达存在漏洞的解码器。仅凭产品名称或网络可达,并不能证明存在暴露。
  • 要检查运行中的进程,而不仅仅是已安装的软件包。进程在重启之前,会一直使用其启动时的运行时。
  • 有状态的 AI 代理要逐个重启,并先交接其事件、审批和工作状态。

1. 漏洞是什么

解码器在遇到无效的块切换时,返回的是“需要更多输入”,而不是永久性的格式错误。于是流式调用方会继续送入字节,解码器把它们追加到一个 8 字节的内部缓冲区中,写出了缓冲区末尾。Node.js 公告指出,能够提供由应用通过 node:zlib 解压的 Brotli 数据的攻击者,“可以破坏进程内存,从而可能导致崩溃、进程内存泄露或远程代码执行”。

各方的严重性评级不一。Node.js 评为 High。GitHub 针对 Python 包的公告和 Envoy 公告评为 Critical,CVSS 4.0 评分 9.2。Ubuntu 目前将其列为 Medium 优先级。截至 10 月 26 日,CVE 记录中 CISA 的评估将利用情况列为“none”,该 CVE 也未被列入 CISA 的已知被利用漏洞(KEV)目录。但 Node.js 公告仍将远程代码执行列为可能后果,因此不要把“尚无攻击报告”当作可以等待的理由。

2. 前提条件:什么情况下才真正暴露

以下三个条件必须同时满足:

  1. 进程中加载了存在漏洞的解码器:捆绑的或系统的 Brotli 库处于受影响版本范围内,且未修复或未回移补丁。
  2. 有代码调用它:显式解压,或自动解码 Content-Encoding: br。
  3. 输入可被攻击者影响:来自任意 URL 的响应、上传的文件、传入的请求体,或敌意网络上的明文 HTTP 流量。

自动解码是团队最常忽略的路径。Bun 公告指出,fetch 默认发送 Accept-Encoding: br 并解码响应,因此任何获取不可信 URL 的 Bun 程序都存在这条路径。在 Python 中,一旦安装了 brotli 或 brotlicffi,urllib3 就会自动解码 Content-Encoding: br 响应,requests 也会在请求中声明支持 br。即使你自己的代码从未提及 Brotli,也会如此。Envoy 和 Istio 只有在配置了 Brotli 解压器时才会暴露。

3. 已确认受影响与仅可能受影响

状态截至 2026 年 10 月 26 日。情况变化很快,请查看链接中的公告。

组件状态修复版本
上游 Brotli(C)已确认:1.0.3 至 1.2.01.0.10、1.1.1、1.2.1
Node.js 22、24、26已确认22.23.4、24.22.1、26.12.1
Node.js 20 及更早版本(已停止支持)未评估;Node.js 将停止支持的版本线一律视为受影响迁移到受支持的版本线
Bun(包括编译后的可执行文件)已确认:1.0.21 至 1.4.31.4.4,并重新构建编译后的应用
Python brotli / brotlicffi已确认:自 1.0.3 起1.2.1 / 1.2.1.0
Envoy(已配置 Brotli 解压器)已确认1.40.0、1.39.3、1.38.6、1.37.8(不受支持的 1.36 分支为 1.36.12)
Istio(已启用 Brotli 解压)已确认1.31.2、1.30.6、1.29.9(1.29 的最终版本)
Ubuntu brotli 软件包待评估(所有版本)尚无修复软件包
Ubuntu nodejs 软件包20.04 上存在漏洞(捆绑 Brotli)。22.04 及更高版本使用系统 Brotli,因此情况与上一行相同参见 Ubuntu 跟踪页面
Debian brotli 软件包bookworm 和 trixie 已修复;forky 尚未修复1.0.9-2+deb12u1、1.1.0-2+deb13u1
Chrome、Electron 及 Electron 应用未知:未找到厂商声明关注厂商发布说明

关于最后一行:Chrome 和 Electron 自带各自的 Brotli 副本,而 Electron 应用还内嵌了 Node.js 运行时。截至撰写本文时,最新的 Electron 稳定版(45.0.0、44.9.3 和 43.8.3,均发布于 10 月 21 日)所列 Node.js 版本为 24.22.0,而非 24.22.1,其发布说明也未提及此 CVE。10 月 23 日的 Electron 预发布版已包含 Node.js 24.22.1,因此稳定版修复很可能即将到来,但目前尚未发布。这意味着基于 Electron 的桌面应用可能暴露,而非已确认存在漏洞。聊天客户端和 AI 桌面工具同理:在判定其受影响或安全之前,请先查看各厂商自己的声明。

还有一个陷阱:报告的库版本号无论如何都不能作为证据。已修复的 Node.js 22.23.4 报告的 process.versions.brotli 仍是 1.1.0,因为修复是以回移补丁方式加入的。各发行版也是如此。应依据产品的修复版本或发行版的安全状态来判断,而不是 Brotli 的版本字符串。

4. 我们在自身环境中的发现

在做任何变更之前,我们先进行了只读盘点。以下是经过匿名化处理的观察结果,已确认的发现与无法验证的风险分开列出。

已确认的观察

  • 已安装的 Claude Code 早已更新,但有十几个长时间运行的会话仍在运行数周前启动的旧版本,从未重启过。
  • 我们第一次统计时漏掉了其中三个。它们运行在另一个辅助进程之下,所以按命令行搜索没有匹配到。按可执行文件搜索才找到它们。
  • 旧版本的文件已被更新程序从磁盘上删除,因此无法读取这些进程内嵌的 Bun 版本。
  • 一个通过包管理器安装、遗留下来的旧版 Claude Code 内嵌了 Bun 1.4.0。正常运行中没有任何东西使用它,但某条回退路径可能会用到。在证明没有任何依赖之后,我们将其删除。
  • 一台服务器上仍安装着一个未使用、已停止支持的 Node.js 运行时。我们以同样的方式将其删除。
  • 我们升级到的当前 Claude Code 版本(2.1.310)包含 Bun 1.4.4。我们通过检查二进制文件确认了这一点。Anthropic 的更新日志并未提及此 CVE,因此请用同样的方法检查你自己的版本。

未经验证,按风险处理

  • 旧会话是否确实存在漏洞。它们的 Bun 版本无法读取。已修复的 Bun 1.4.4 大约在 10 月 22 日才发布,晚于这些版本,因此我们将其视为受影响。
  • 攻击者能否通过这些会话触达解码器,例如让其获取恶意 URL。根据 Bun 文档中描述的 fetch 行为,这条路径是合理可能的,但我们并未证实。
  • 其他自带 Brotli 副本的软件是否受影响。在没有厂商声明的情况下,我们对照厂商的发布版本跟踪该产品,而不是假定其安全或存在漏洞。

我们没有发现任何被利用的证据。但我们也没有把这当作安全的证明:修复的方法是消除暴露,而不是靠论证把它解释掉。

5. 检测与修复清单

找出依赖

不仅要在作为软件包安装的地方寻找 Brotli,还要在它被捆绑的地方寻找。检查 SBOM 和容器镜像中的 Node.js、Bun、Python 包、Envoy 以及静态链接的 Brotli。检查 Python 的传递依赖,因为 Brotli 经常作为可选附加项被引入。

# Node.js: compare with 22.23.4 / 24.22.1 / 26.12.1
node -p process.version
# Python packages: run once per interpreter and virtual environment
python -m pip list 2>/dev/null | grep -iE '^(brotli|brotlicffi) '
pipdeptree -r -p brotli          # what pulled it in
# Debian / Ubuntu system library (then check the distro tracker)
dpkg-query -W -f='${db:Status-Abbrev} ${Package} ${Version}\n' 'libbrotli*'
# Container images, with an SBOM scanner such as Syft
syft <image> | grep -iE 'brotli|node|bun'

找出内嵌运行时

用 bun build --compile 构建的程序自带一份 Bun。Bun 公告指出,在用 1.4.4 重新构建之前,它们一直受影响。在构建机上升级 Bun,并不会改变已经部署的二进制文件。

# The Bun version inside a compiled executable
grep -aoE 'Bun v1\.[0-9]+\.[0-9]+' /path/to/binary | sort -u

找出运行中的进程

检查正在运行的东西,而不是已安装的东西。在 Linux 上,可执行文件已被替换或删除的进程会显示 (deleted)。这强烈表明它仍在运行旧版本。

# Processes still running a deleted (replaced) executable (run as root to see all users)
for p in /proc/[0-9]*; do
  e=$(readlink "$p/exe" 2>/dev/null) || continue
  case "$e" in *"(deleted)") echo "${p#/proc/} $e";; esac
done
# Processes older than the fix
pgrep -af 'node|bun|claude'      # then: ps -o pid,lstart,args -p <pid>

在 macOS 上,lsof -p <pid> 会显示进程加载的可执行文件。请按可执行文件匹配,而不是按进程名匹配,否则会漏掉辅助进程和后台工作进程。

修复、重新构建、重新部署

  • 升级到表中的修复版本。有回移补丁的地方,使用发行版提供的已修补软件包。
  • 重新构建容器镜像和编译后的可执行文件,然后重新部署。已修补的基础镜像对你尚未重新构建的镜像毫无作用。
  • 在证明没有任何服务、作业或脚本依赖之后,删除未使用的运行时和遗留安装。保留回滚手段。
  • 在尚无修复的情况下,在安全可行的地方禁用对不可信输入的 Brotli 解码。对于 Bun,可以按请求使用 fetch(url, { decompress: false })。为每项缓解措施指定负责人和截止日期。

重启并验证

重启后,检查新进程实际使用的可执行文件和运行时版本。然后检查业务输出:队列已清空、计划任务执行成功、监控告警正常送达。升级命令执行成功,只能证明该命令运行过。

6. 安全重启常驻 AI 代理

长时间运行的 AI 代理持有状态:待处理的工具调用、审批、排队的消息以及对工作的认领。一次性全部重启可能丢失这些状态。以下顺序在我们这里行之有效:

  1. 盘点每个会话:可执行文件版本、状态(空闲、等待或忙碌)、最后活动时间、待处理工作、正在等待或持有的审批、未确认的事件以及恢复方式。
  2. 把审批从内存中移出。只存在于会话对话中的审批,重启后就会丢失。重启前,把每项待处理审批(批准了什么、由谁批准、针对哪项具体变更)记录在持久的位置,例如工单或拉取请求中。重启后,确认会话仍能看到它。如有疑问,宁可重新申请审批,也不要想当然。
  3. 先证明替代方案可用。确认运行在已修复版本上的会话已经正常工作,并且有一个独立于旧会话的事件监视器在运行,从而没有任何事件依赖于某个待重启的会话。
  4. 先重启空闲会话,一次一个。每重启一个,都要验证旧进程已消失、新进程运行的是已修复的可执行文件、对话历史完好,且所有远程连接已恢复。然后再处理下一个。
  5. 最繁忙的负责者放在最后,在任务间隙重启。让它写一份交接说明,仅在待处理事项和未确认事件均为零时停止其监视器,然后重启,再确认其监视器已从保存的位置恢复,没有遗漏任何内容。
  6. 预料到重连时会收到排队的输入。我们有一个重启后的会话收到了一条已等待十天的消息,并据此采取了行动。重启前请检查每个会话的收件箱,并告知其负责人。
  7. 停止并上报:如果任何一步无法证明工作已被恢复,就停下来上报。不要退回到全部强制终止的做法。

开始之前先定义“完成”。完成意味着:没有任何运行中的进程使用受影响的版本,每个例外都有记录,并有负责人和复查日期,且业务检查全部通过。“软件包已升级”不等于完成。

参考资料