# CVE-2026-111497 — Brotli 修复代理

**CVE-2026-111497** 的确定性检测器、修复器与验证器 —— 该漏洞是 Google Brotli 解码器中的越界
写入（`c/dec/decode.c`，CWE-787）。三个 shell 脚本执行完整审计并返回紧凑的判定结论；语言模型
负责编排，只读取一行输出，而不必手工探查主机。

<!-- md-i18n:switcher -->
**其他语言：** [English](README.md) · [Deutsch](README.de.md) · 中文 · [हिन्दी](README.hi.md) · [Español](README.es.md) · [Русский](README.ru.md)

[![CI](https://github.com/paulmann/CVE-2026-111497/actions/workflows/ci.yml/badge.svg)](https://github.com/paulmann/CVE-2026-111497/actions/workflows/ci.yml)
[![Release](https://img.shields.io/github/v/release/paulmann/CVE-2026-111497)](https://github.com/paulmann/CVE-2026-111497/releases)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![Shell: bash](https://img.shields.io/badge/shell-bash-4EAA25.svg)](#环境要求)
[![Platform: Debian/Ubuntu](https://img.shields.io/badge/platform-Debian%20%7C%20Ubuntu-A80030.svg)](#环境要求)

![CVE-2026-111497 — Brotli 修复代理](assets/social-preview.svg)

## 目录

- [为什么需要它](#为什么需要它)
- [暴露模型](#暴露模型)
- [快速开始](#快速开始)
- [判定与退出码契约](#判定与退出码契约)
- [审计覆盖范围](#审计覆盖范围)
- [仓库结构](#仓库结构)
- [环境要求](#环境要求)
- [局限](#局限)
- [文档](#文档)
- [语言](#语言)
- [参与贡献](#参与贡献)
- [许可证](#许可证)

[Wiki](https://github.com/paulmann/CVE-2026-111497/wiki) · [Changelog](CHANGELOG.md) · [Security](SECURITY.md) · [English](README.md)

## 为什么需要它

完整的 Brotli 审计并不是一次版本检查。它要求对每台主机回答四个问题：
装的是哪个软件包，正在运行的进程是否仍在映射存有漏洞的解码器，
哪些应用路径真正解码不受信任的 Brotli，以及发行版回溯移植是否是真修复，而非一次无关的重建。

若靠人工回答，这些问题在每台主机上要消耗数十次工具调用，并且产出无法复核的文字描述。
在此，它们由带固定退出码契约的脚本回答：

| 命令 | 对主机的影响 | 给代理的输出 |
|---|---|---|
| `detect.sh` | 只读 | `VERDICT: SAFE\|VULNERABLE\|NOT_INSTALLED` |
| `remediate.sh` | 唯一的变更点 | `REMEDIATED: <old> -> <new>` |
| `verify.sh` | 只读 | `VERIFY: PASS\|FAIL` |

## 暴露模型

存有漏洞的代码是**解码器**，不是编码器。只用 Brotli 压缩响应的服务不构成攻击面；
解压不受信任 Brotli 数据的服务才构成攻击面。

| 可达的解码路径 | 典型载体 |
|---|---|
| HTTP 请求体 | 发送到应用的 `Content-Encoding: br` |
| 出站 HTTP 客户端 | `fetch()`、把 `CURLOPT_ENCODING` 置空（接受全部编码）的 libcurl |
| 来自代理的入站响应 | Envoy/Istio HTTP 解压器 |
| 上传或下载的归档文件 | 应用层的 Brotli 流式处理 |
| 内置运行时 | Node.js、Bun、Python `Brotli`/`brotlicffi`、.NET 请求解压 |

存在漏洞的上游版本为 **1.0.3 至 1.2.0**。修复版本为 **1.0.10、1.1.1 和 1.2.1**
（2026 年 10 月 21 日发布），以及在打包变更日志中引用该 CVE 的所有发行版回溯移植。

## 快速开始

```bash
# 1. Deliver the scripts once (host is an SSH alias from ~/.ssh/config)
scp src/common.sh src/detect.sh src/remediate.sh src/verify.sh <host>:/root/cve-2026-111497/

# 2. Audit — read-only
ssh <host> 'bash /root/cve-2026-111497/detect.sh'          # exit 0/1/3/10

# 3. Remediate — only when the verdict is VULNERABLE
ssh <host> 'bash /root/cve-2026-111497/remediate.sh'

# 4. Verify the actual effect
ssh <host> 'bash /root/cve-2026-111497/verify.sh'          # VERIFY: PASS|FAIL
```

## 判定与退出码契约

| 代码 | 判定 | 含义 | 下一步 |
|---:|---|---|---|
| 0 | `SAFE` | 已安装版本位于受影响范围之外，或发行版回溯移植已应用 | 记录并结案 |
| 1 | `VULNERABLE` | 已安装的上游版本位于受影响范围之内，且没有回溯移植 | 运行 `remediate.sh` |
| 3 | `NOT_INSTALLED` | 主机上没有 `libbrotli1` | 记录；若此后安装了 Brotli 则复查 |
| 10 | `ERROR` | 前置条件或不变量被违反 | 阅读消息，上报，不得强行推进 |
| 1（`verify.sh`） | `FAIL` | 修复未生效 | 在宣布结案之前调查 |

当 apt 候选版本等于已安装版本，或候选版本的变更日志未引用 `CVE-2026-111497` 时，
`remediate.sh` 会在接触系统之前以代码 10 停止。

## 审计覆盖范围

| 检查项 | 机制 | 为什么重要 |
|---|---|---|
| 已安装版本与上游版本 | `dpkg-query`，剥离 epoch 与修订号 | 发行版修订号会掩盖上游版本 |
| 已应用的回溯移植 | 打包的 `changelog.Debian.gz` 中提及该 CVE | 权威信号，而非版本字符串 |
| apt 候选版本 | 先 `apt-get update`，再 `apt-cache policy` | 决定究竟是否存在受支持的修复 |
| 活跃消费者 | 扫描 `/proc/<pid>/maps` 中的 `libbrotlidec` | 打过补丁的文件并不能修补正在运行的进程 |
| 解码面 | libcurl 的 Brotli 特性、Node/Bun/Python/PHP 模块、`ngx_brotli`、`mod_brotli` | 把解码路径与仅做压缩的路径区分开 |
| 应用解码原语 | 统计含 `CURLOPT_ENCODING` / `CURLOPT_ACCEPT_ENCODING` 的 PHP 文件数 | WordPress、SimplePie 与 Guzzle 接受所有编码，包括 `br` |
| 补丁前的 inode 快照 | 对真实 `.so` 执行 `find -type f -printf '%i'` | 符号链接的 inode 是虚假锚点 |

## 仓库结构

```text
src/
  common.sh      logging, version parsing, classification matrix, state_get
  detect.sh      read-only audit: verdict, JSON report, before-state
  remediate.sh   backup, authenticity gate, dry-run, install, restart, inode check
  verify.sh      version, CVE reference, stale mappings, failed units, round-trip
docs/
  THREAT-MODEL.md  decode-path exposure model and attack surface
  PLAYBOOK.md      phase-by-phase procedure for a human auditor
  FIELD-REPORT.md  anonymised results from three production hosts
wiki/             operator documentation, field cases, advisory digest
SKILL.md          agent skill: the minimal orchestration playbook
AGENTS.md         repository rules and invariants
```

## 环境要求

- 目标主机：Debian 12、Ubuntu 24.04 或 Ubuntu 26.04，具备 `apt`，通过 SSH 密钥取得 root。
- `bash` 5.x、`dpkg`、`apt-get`、`systemd`；`curl` 与 `brotli` 为可选，只用于扩大功能性往返测试。
- 除发行版软件源之外，工具不访问网络。
- 输出状态：`/var/lib/cve-2026-111497/`（`report.json`、`state.before`、`state.after`）。
- 备份：`/root/cve-2026-111497-fix-<timestamp>/`，内含旧 `.deb`、当前 `.so` 与一份 `dpkg` 快照。

## 局限

- **仅支持 apt。** 基于 rpm 的主机、Alpine 与容器基础镜像不在范围内；分类矩阵可移植，修复路径不可移植。
- **不检测静态链接。** 编译进二进制或容器镜像的 Brotli 副本对 `dpkg` 不可见。检测器只报告它能读到的运行时版本，仅此而已。
- **不含编排层。** Envoy、Istio、.NET、Bun 与 Deno 在威胁模型中有描述，但脚本不对其执行修复。
- **桌面与基础设施消费者是延迟处理的，不是重启的。** 字体与 GUI 栈通过 freetype 映射 `libbrotlidec`；重启它们会中断会话。它们被报告为 `deferred`，并在下次登录或重启时加载已打补丁的库。
- **`needrestart` 可能先行生效。** 在 Ubuntu 24.04 上，软件包钩子会在安装期间重启服务器服务，因此重启环节可能确实已无剩余工作。
- **现场数据仅来自三台主机。** 该报告是样本，不是整个机群的普查。

## 文档

| 文档 | 内容 |
|---|---|
| [`docs/THREAT-MODEL.zh.md`](docs/THREAT-MODEL.zh.md) | 暴露模型、解码路径清单、严重性论证 |
| [`docs/PLAYBOOK.zh.md`](docs/PLAYBOOK.zh.md) | 脚本所实现的操作流程，供人工审计员使用 |
| [`docs/FIELD-REPORT.zh.md`](docs/FIELD-REPORT.zh.md) | 三台主机、现场发现的九个缺陷、验证证据 |
| [Wiki](https://github.com/paulmann/CVE-2026-111497/wiki) | 含根因的现场案例、安全公告摘要、运维手册 |

## 语言

英语为规范语言：不带语言后缀的文档即事实来源。每个译文都带有其语言后缀，并链接回规范文件。

| 语言 | 文件 |
|---|---|
| 英语（规范） | `README.md`、`AGENTS.md`、`SKILL.md`、`docs/*.md` |
| 俄语 | `*.ru.md` |
| 德语 | `*.de.md` |
| 西班牙语 | `*.es.md` |
| 中文 | `*.zh.md` |
| 印地语 | `*.hi.md` |

## 参与贡献

欢迎提交修复与新的发行版回溯移植。请先阅读 [`CONTRIBUTING.md`](CONTRIBUTING.md)：退出码契约、
变更前备份规则与真实性闸门均不可协商。安全报告遵循 [`SECURITY.md`](SECURITY.md)。

## 许可证

MIT。该工具是防御性的；它不含凭据、基础设施标识符与目标地址。
