nginx:1.25-bookworm 的源构建即插即用替换一个源构建的 nginx 1.25.5 容器镜像,它修复了官方 nginx:1.25-bookworm 镜像中存在的两个 CVE,同时保留了上游运行时行为。
| CVE | 组件 | 严重程度 | 技术 | 验证模型 |
|---|---|---|---|---|
| CVE-2024-6119 | OpenSSL / libssl3 | 高 | 依赖版本升级 | 扫描器可验证:libssl3 3.0.20 在 dpkg 数据库中可见 |
| CVE-2026-42945 | nginx ngx_http_rewrite_module | 严重 | 向后移植源补丁 | 来源可验证:补丁推导 + 回归测试 + 构建证明 + VEX |
这两种代表了两种不同的修复模型:
此镜像的 nginx -V 配置参数匹配 nginx:1.25-bookworm(在规范化 -ffile-prefix-map 构建路径后逐字符比较;在 test/compat.py::test_nginx_version 中验证)。测试套件(make test)针对实时上游镜像验证了 89 个断言,涵盖镜像元数据、动态模块、文件系统布局、入口点行为、dpkg 打包和 HTTP 请求处理。
与上游的已知差异(未经验证为相同):
nginx -V 中的 built with OpenSSL X.X.X 行反映构建者的 libssl-dev 版本,可能不同于上游编译时的 OpenSSL。debian:bookworm-slim,而非官方镜像继承的包树。libssl3 版本是 bookworm 当前提供的版本(撰写时为 3.0.20),而非上游镜像中固定的版本。make image
make test
make test-cve
make verify-patch
make scan
一个命令用于构建、测试和扫描:```bash
make all
build/ Dockerfile.build Builder image (debian:bookworm-slim + compilation deps) build.sh Fetch → verify → patch → compile → package nginx generate-vex.sh Generate OpenVEX document for backported CVE verify-patch.sh Re-derive patch from upstream tarballs (audit tool) patches/ CVE-2026-42945.patch Backported one-line fix from nginx 1.30.1 CVE-2026-42945.provenance.json Machine-readable patch provenance and derivation metadata
test/ compat.py 89-assertion compatibility test suite (runs against live upstream) test_cve_2026_42945.py CVE-specific regression test (exercises vulnerable code path)
artifacts/
patch-attestation.json Build-time patch attestation (tracked)
nginx_*.deb Compiled package (gitignored - rebuilt via make build-source)
nginx Compiled binary (gitignored)
Containerfile Final runtime image definition
Makefile Orchestrates build → test → scan pipeline
vex.json Generated OpenVEX v0.2.0 document
baseline-trivy.txt Point-in-time Trivy scan of nginx:1.25-bookworm
baseline-grype.txt Point-in-time Grype scan of nginx:1.25-bookworm
fixed-trivy.txt Trivy scan of the fixed image
fixed-grype.txt Grype scan of the fixed image (without VEX)
fixed-grype-vex.txt Grype scan of the fixed image (with VEX applied)
---
## 构建过程
### 架构```
debian:bookworm-slim (builder)
└─ build.sh
├─ curl nginx-1.25.5.tar.gz (SHA256-verified)
├─ curl njs-0.8.4 from github.com/nginx/njs
├─ patch -p1 < CVE-2026-42945.patch
├─ ./configure (flags identical to upstream nginx -V)
├─ make: release binary, debug binary, 4 dynamic module families (×2 release/debug)
├─ make: NJS modules (×2 release/debug) + njs CLI binary
└─ dpkg-deb → nginx_1.25.5-1~bookworm+echo1_<arch>.deb
debian:bookworm-slim (runtime)
├─ apt-get install runtime deps (libssl3 ≥ 3.0.14 enforced)
├─ dpkg -i nginx_*.deb
└─ COPY --from=upstream /docker-entrypoint.sh + /docker-entrypoint.d/
The Containerfile does NOT copy /etc/nginx from upstream. All configuration
files are shipped inside the .deb and tracked by dpkg's conffile mechanism.
This is required for the 10-listen-on-ipv6-by-default.sh entrypoint script,
which uses dpkg-query to detect whether default.conf has been user-modified.
./configure && make。无上游二进制文件,不使用 apt install nginx。build.sh 仅获取固定的源代码归档。构建过程_基本_可重现,但并非完全封闭:
Dockerfile.build 中列举,但未固定版本。为提高可重现性,请固定基础镜像摘要:```bash docker pull debian:bookworm-slim docker inspect debian:bookworm-slim --format='{{index .RepoDigests 0}}'
---
## CVE 修复详情
### CVE-2024-6119 - OpenSSL 版本升级
| 字段 | 值 |
| -------------------- | ---------------------------------------------------- |
| **组件** | OpenSSL / libssl3 |
| **严重性** | 高 (CVSS 7.5) |
| **类型** | 通过 X.509 名称检查导致的拒绝服务 |
| **基线版本** | 3.0.11-1~deb12u2 |
| **修复版本** | 3.0.14-1~deb12u2 (或更高) |
| **我们的版本** | 3.0.20-1~deb12u1 |
| **NVD** | https://nvd.nist.gov/vuln/detail/CVE-2024-6119 |
| **公告** | https://openssl-library.org/news/secadv/20240903.txt |
**修复工作原理:**
`.deb` 包声明了 `Depends: libssl3 (>= 3.0.14)`,这强制 `apt-get install` 拉取包含修复的 OpenSSL 版本。当前 Debian bookworm 仓库提供了 3.0.20,它修复了 CVE-2024-6119 以及从基线版本起的数十个其他 OpenSSL CVE(CVE-2024-2511, CVE-2024-5535, CVE-2024-4741, CVE-2023-5678, CVE-2023-6129, CVE-2023-6237, CVE-2024-9143, CVE-2025-15467, CVE-2025-69420)。
**扫描器行为:** Grype 和 Trivy 会在 dpkg 数据库中看到 `libssl3 3.0.20`,并识别到 3.0.20 ≥ 3.0.14,因此不再报告 CVE-2024-6119。无需 VEX——版本升级是不言自明的。
**验证:**```bash
grep "CVE-2024-6119" baseline-grype.txt # present
grep "CVE-2024-6119" fixed-grype.txt # absent
漏洞:
src/http/ngx_http_script.c 中的 ngx_http_script_regex_end_code() 在处理重写正则表达式结果时,未能重置 e->is_args。如果 is_args 被前一个脚本引擎操作设置,则后续重定向/重写路径中的缓冲区长度计算将不正确,导致通过精心构造的请求 URI 造成攻击者可控制大小的堆内存缓冲区溢出。
修复(一行):```c // Added before the existing e->quote = 0; at line 1205 e->is_args = 0;
**补丁来源:**
| 证据 | 位置 / 值 |
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| 上游提交 | [`2046b45aa0c6e712c216b9075886f3f26e9b4ca9`](https://github.com/nginx/nginx/commit/2046b45aa0c6e712c216b9075886f3f26e9b4ca9) |
| 上游作者 | Roman Arutyunyan (arut),由 Sergey Kandaurov (pluknet) 提交,2026-05-13 |
| 上游 PR | [nginx/nginx#1350](https://github.com/nginx/nginx/pull/1350) |
| 上游标签 | `release-1.31.0`,`release-1.30.1` |
| 相关之前的修复 | [`74d9399`](https://github.com/nginx/nginx/commit/74d939974d430a2c2e71b0134114d2a7b04dcc20)(2012 年,类似的 `is_args` 转义问题) |
| 补丁文件 | `build/patches/CVE-2026-42945.patch` |
| 来源元数据 | `build/patches/CVE-2026-42945.provenance.json` |
| 源压缩包(存在漏洞) | `https://nginx.org/download/nginx-1.25.5.tar.gz` SHA256:`2fe2294f8af4144e7e842eaea884182a84ee7970e...` |
| 源压缩包(已修复) | `https://nginx.org/download/nginx-1.30.1.tar.gz` SHA256:`99765000d974896b31ca5882d8c279ce3fe7ef6f...` |
| 存在漏洞文件的 SHA256 | `c4b10c95b00e43d731a61f2380d406155e037f3cfb40a3bf58711ecefddc799a` |
| 已修复文件的 SHA256 | `a6182175a22d88f66b9a61ae051bdcfece10d1bb2e643f7f175eb07589143403` |
| 派生方法 | 两个压缩包之间的 `diff src/http/ngx_http_script.c`(确认与上游提交一致) |
| 变更行数 | 1(仅添加) |
| 补丁干净应用 | 已验证:`patch --dry-run -p1 < build/patches/CVE-2026-42945.patch` 返回 0 |
| 构建时验证 | `build.sh` 在打补丁后验证 `e->is_args = 0` 存在于源码中,若不存在则中止构建 |
| 构建时证明 | 构建期间写入 `artifacts/patch-attestation.json` |
| 回归测试 | `test/test_cve_2026_42945.py` - 运行易受攻击的代码路径 |
| 补丁验证脚本 | `make verify-patch` - 从上游压缩包独立重新派生补丁 |
**补丁创建方式:**
1. 下载 `nginx-1.25.5.tar.gz`(SHA256 已根据固定哈希验证)
2. 下载 `nginx-1.30.1.tar.gz`(SHA256 已验证:`99765000d974896b31ca5882d8c279ce3fe7ef6f5c6f9f0a967ed7fd3407f9cc`)
3. 在两个树之间运行 `diff src/http/ngx_http_script.c`
4. 生成的差异恰好包含一行——添加了 `e->is_args = 0;`
5. 提取为带有上下文行的统一差异补丁
6. 验证补丁可干净地应用于 nginx 1.25.5
7. 两个源代码树均保留在 `research/` 中,用于独立验证
**回归测试:**
测试套件 `test/test_cve_2026_42945.py` 通过发送经过重写规则(操纵查询字符串状态 `is_args`)的请求来运行易受攻击的代码路径。它验证:
- 带有尾随 `?`(查询剥离)的重写重定向生成格式正确的响应
- 切换 `is_args` 状态的链式重写不会导致工作进程崩溃
- 长查询字符串(4KB+)不会引起缓冲区溢出痕迹(空字节、过长的 Location 头部)
- 100 个快速的混合请求不会导致工作进程崩溃
- 工作进程在所有测试用例中存活,没有 SIGSEGV 或堆损坏
运行:`make test-cve`
**扫描器行为:**
由于软件包版本仍为 `1.25.5`,因此当公告存在于其数据库中时,扫描器将标记 CVE-2026-42945。这是预期的——扫描器根据 `(软件包名,上游版本)` 匹配,无法检查源代码是否已被修补。VEX 文档通过机器可读的理由抑制此发现。扫描器抑制是一种通信机制,而非修复的主要证据。主要证据是上述的补丁来源链和回归测试。
---
## VEX(漏洞可利用性交换)
### 为何需要 VEX
扫描器根据 dpkg 数据库中的 `(名称, 版本)` 识别软件包。我们的软件包是 `nginx 1.25.5-1~bookworm+echo1`——扫描器将其解析为上游版本 1.25.5,并标记所有影响 nginx ≤ 1.25.5 的 CVE。
对于向后移植的修复,扫描器无法确定易受攻击的代码已被修补。为了保持软件包兼容性,版本故意保持为 1.25.5。VEX 提供机器可读的证明,表明 CVE 已得到处理,并链接到可验证的证据。
### VEX 可信度链
针对 CVE-2026-42945 的 VEX 声明并非单纯的书面文件。它有以下支持:
1. **补丁来源** - `build/patches/CVE-2026-42945.provenance.json` 记录了确切的上游来源、派生方法和文件哈希
2. **构建时验证** - `build.sh` 断言修复标记存在于编译后的源代码中,并在失败时中止构建
3. **构建证明** - `artifacts/patch-attestation.json` 记录了构建期间生成的补丁前/后文件哈希
4. **回归测试** - `test/test_cve_2026_42945.py` 运行易受攻击的代码路径,无崩溃或损坏
5. **独立重新派生** - `make verify-patch` 下载两个上游压缩包,并证明提交的补丁是最小安全差异
### VEX 涵盖的内容
| CVE | 状态 | 理由 | 证据 |
| -------------- | ------- | ------------------------------ | ------------------------------------------------------------ |
| CVE-2026-42945 | `fixed` | `vulnerable_code_not_present` | 向后移植补丁 + 来源 + 回归测试 + 构建门控 |
VEX 范围故意狭窄:仅涵盖本项目使用可验证证据主动修复的 CVE。未抑制那些仅“预期被发布版本缓解”的发现——这避免了将 VEX 用作 blanket 抑制工具。
### 工作原理
`build/generate-vex.sh` 脚本生成 OpenVEX v0.2.0 文档。应用它的扫描器命令为:```bash
# Without VEX - CVE is reported (version-based match):
grype nginx-fixed > fixed-grype.txt
# With VEX - CVE is suppressed with justification:
grype nginx-fixed --vex vex.json > fixed-grype-vex.txt
VEX 抑制在保存的扫描产物中是可观察的。请注意,扫描器数据库会定期重建,CVE 的存在可能因运行而异。保存的 baseline-grype.txt 和 fixed-grype.txt 文件是时间点证据。VEX 文档的有效性不依赖于扫描器数据库状态——它可以通过上述溯源链独立验证。
该包名为 nginx(而不是 nginx-custom)。这一点至关重要,因为:
nginx 使其能够满足依赖关系,并由标准的 Debian 工具进行管理。扫描器公告数据库会定期重建。某个 CVE 本周存在,下周可能就不存在了。本项目不依赖扫描器输出作为修复的主要证据。证据链如下:
保存的扫描文件仅为时间点证据。
| Package | Version | CVE-2024-6119 | CVE-2026-42945 |
|---|---|---|---|
| libssl3 | 3.0.20-1~deb12u1 | 未报告(版本已修复) | 不适用 |
| nginx | 1.25.5-1~bookworm+echo1 | 不适用 | 数据库中报告(反向移植对扫描器不可见) |
使用 --vex vex.json,CVE-2026-42945 发现(如果存在于扫描器数据库中)会被抑制。VEX 文档的有效性由溯源链(补丁派生、构建证明、回归测试)建立,而非仅凭扫描器输出。
测试套件(test/compat.py)将 nginx:1.25-bookworm 和 nginx-fixed 作为 Docker 容器启动,并比较行为。每个断言直接测试运行中的修复镜像与运行中的上游镜像。
89 个断言。全部通过。
此外,test/test_cve_2026_42945.py 提供了约 20 个断言,专门针对反向移植的 CVE 修复(参见上文“回归测试”)。
--with-http_v3_module 标志的存在;未发送 UDP/QUIC 流量).so 文件之外,较少使用的动态模块(GeoIP、image_filter、XSLT)的行为运行:```bash make test
---
## 镜像大小
| 镜像 | 大小(arm64,`docker images`) |
| ------------------------ | ------------------------------ |
| `nginx:1.25-bookworm` | ~278 MB |
| `nginx-fixed` | ~332 MB |
修复后的镜像比上游镜像大。这是预期的:官方镜像在优化层中使用预编译的二进制文件,而我们的镜像将构建产物和完整的运行时依赖(包括所有12个动态模块的库)安装到 `debian:bookworm-slim` 基础镜像中。体积差异是为了获得源码级构建控制而做出的权衡,并非功能退化。
---
## 残余风险评估
### 修复后镜像中残留的CVE
修复后的镜像仍包含系统库中的漏洞,本项目未予解决(基于当前时间点;运行 `make scan` 查看最新状态):
| 风险等级 | 软件包 | CVE 编号 | 说明 |
| ---------- | --------------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| 高 | nginx | CVE-2023-44487 | 上游 nginx ≥1.25.3 已缓解;扫描器仍标记,因为 Debian 跟踪系统尚未关闭该公告 |
| 严重 | libgnutls30 | CVE-2026-42010, CVE-2026-33845 | bookworm 中尚无修复 |
| 严重 | libc6/libc-bin | CVE-2026-5450 | 此 Debian 版本不会修复 |
| 高 | libc6/libc-bin | CVE-2026-5928, CVE-2026-5435, CVE-2026-4437 | bookworm 中 glibc 不会修复 |
| 高 | dpkg | CVE-2025-6297, CVE-2026-2219 | 可修复(升级至 1.21.23) |
| 高 | libldap-2.5-0 | CVE-2023-2953 | 不会修复 |
| 高 | curl/libcurl4 | CVE-2026-5773, CVE-2026-6276 | 不会修复 |
| 高 | ncurses | CVE-2025-69720 | 不会修复 |
这些漏洞继承自 `debian:bookworm-slim`,超出了本项目专注于 nginx CVE 修复的范围。
### 本方法的局限性
1. **非封闭式。** 基础镜像和 apt 软件包在构建时从实时 Debian 仓库解析。固定基础镜像 SHA 并使用 apt 快照可提高可重复性。
2. **无行为化 QUIC 测试。** HTTP/3 支持已编译进(通过标志验证),但测试套件中未实际执行 QUIC 流量。
3. **无性能验证。** 50 并发请求测试验证的是正确性,而非吞吐量或延迟等效性。
4. **仅模块加载。** 动态模块(GeoIP、image_filter、XSLT)已确认存在并可加载,但其功能行为未经过除基本加载之外的测试。
5. **单架构验证。** 测试仅在构建主机架构上运行(开发中为 arm64;多架构需要 CI)。
### 未来改进(尚未实现)
1. 固定基础镜像摘要并使用 apt 快照 URL 实现封闭式构建。
2. 剥离作为传递依赖引入的不必要软件包(ncurses、util-linux)。
3. 生成 SBOM(`make sbom` 目标已存在,使用 `syft`)并通过 SBOM purl 链接到 VEX。
4. 使用 `cosign` 签名镜像,并将 VEX、SBOM 和补丁证明作为 OCI 证明附加。
5. 添加 CI 管道(GitHub Actions)以实现自动化构建 → 测试 → 扫描 → 推送。
6. 添加 HTTP/3 协议测试(QUIC 客户端、证书设置、UDP 端口)。
7. 考虑使用 distroless 基础镜像,以完全消除操作系统级别的 CVE 噪声。
8. 添加 ASan(AddressSanitizer)构建变体,以证明测试负载下无堆内存损坏。
---
## 构建标志与上游对比
`build.sh` 中的 `./configure` 标志与官方 `nginx:1.25-bookworm` 镜像中 `nginx -V` 报告的标志完全相同。这一点已由 `test/compat.py::test_nginx_version` 验证,该测试将 `-ffile-prefix-map=<path>` 差异归一化后逐字符比较其余标志。
值得注意的标志(两个镜像均包含):
| 标志/特性 | 用途 |
| ------------------------ | ------------------------------------------------------------- |
| `--prefix=/etc/nginx` | Docker 镜像惯例(非 Debian 的 `/usr/share/nginx`) |
| `--with-http_v3_module` | 内置 QUIC 栈(独立于 OpenSSL QUIC API) |
| `--with-pcre` (PCRE2) | 链接 `libpcre2-8-0`(非旧版 PCRE1) |
| `--with-stream` | TCP/UDP 代理模块(动态) |
| 安全加固 | `-fstack-protector-strong`、`FORTIFY_SOURCE=2`、RELRO、PIE |
| 动态模块(×12) | geoip、stream_geoip、image_filter、xslt、http_js、stream_js(release + debug) |
| `--with-debug` (debug) | nginx-debug 二进制单独构建,标志相同 + debug |
---
## Debian 软件包
`.deb` 软件包元数据:```
Package: nginx
Version: 1.25.5-1~bookworm+echo1
Depends: libc6 (>= 2.34), libcrypt1 (>= 1:4.1.0), libpcre2-8-0 (>= 10.22),
libssl3 (>= 3.0.14), zlib1g (>= 1:1.2.11), libgeoip1 (>= 1.6.12),
libgd3 (>= 2.1.0~alpha~), libxml2 (>= 2.7.4), libxslt1.1 (>= 1.1.25),
lsb-base (>= 3.0-6), adduser
Provides: httpd, nginx, nginx-r1.25.5
Conflicts: nginx-common, nginx-core
Replaces: nginx-common, nginx-core
关键打包决策:
libssl3 (>= 3.0.14) - 明确要求修补后的 OpenSSL(上游声明 >= 3.0.0)。这是强制执行 CVE-2024-6119 修复的机制。Provides: nginx-r1.25.5 - 满足依赖此虚拟包的动态模块包。/etc/nginx/nginx.conf 和 /etc/nginx/conf.d/default.conf。这对于 IPv6 入口脚本中的 dpkg-query --showformat 检查是必需的。/bin/false(与上游一致 — 已在测试套件中验证)。.so 模块、完整的 /etc/nginx 配置树、init.d/systemd/logrotate 文件、man 手册页、HTML 默认文件,以及 /etc/nginx/modules → /usr/lib/nginx/modules 符号链接。包命名为 nginx,而非 nginx-custom。 若重命名,扫描器将无法将该包与 nginx 公告匹配 — 包括未修复的 CVE 在内的所有 nginx CVE 会静默地从报告中消失。保留 nginx 名称可确保扫描器报告诚实,并使 VEX 有意义。
VEX 需要 CVE 可见性。 VEX 的目的是抑制一个已被报告的 CVE。如果扫描器未发现该 CVE(因包重命名或数据库时间问题),VEX 就没有可抑制的内容。
将 libssl3 (>= 3.0.14) 作为包依赖项。 与其寄希望于运行时基础恰好拥有修补后的 OpenSSL,不如让 .deb 硬性要求包含 CVE-2024-6119 修复的版本。
扫描器数据库的波动性。 漏洞公告数据库会定期重建。某周存在的 CVE 可能在下周消失。此仓库中保存的扫描文件是即时证据;当前扫描结果可能有所不同。
使用 AI 工具。 GitHub Copilot 协助进行了补丁分析(比较 nginx 1.25.5 和 1.30.1 源代码树)、测试套件结构、识别包重命名导致的扫描器规避问题,以及 VEX 文档撰写。Debian 打包细节和 Grype 版本匹配启发式规则需要手动验证。
| 字段 | 值 |
|---|
| 组件 | nginx ngx_http_rewrite_module |
| 严重性 | 中等(nginx.org 分类) |
| 基准版本 | nginx 1.25.5 |
| 修复版本 | nginx 1.30.1(发布于 2026-05-13) |
| NVD | https://nvd.nist.gov/vuln/detail/CVE-2026-42945 |
| 安全公告 | https://my.f5.com/manage/s/article/K000161019 |
| 上游变更 | nginx 1.30.1 CHANGES:"重写模块中的堆内存缓冲区溢出" |
| Category | What is compared | Assertion count |
|---|
| 镜像元数据 | Entrypoint、Cmd、ExposedPorts、User、StopSignal、Env、Labels | 7 |
| nginx -V 参数 | 配置标志相同(针对 -ffile-prefix-map 路径做归一化) | 1 |
| 动态模块 | 所有 12 个上游 .so 文件存在 + /etc/nginx/modules 符号链接 | 13 |
| nginx-debug 二进制 | --with-debug 和 --with-http_v3_module 出现在 debug -V 中 | 2 |
| 文件系统布局 | 约 20 个关键路径,日志符号链接到 /dev/stdout 和 /dev/stderr | 约 20 |
| 用户/组配置 | getent passwd/group nginx 输出相同 | 2 |
| dpkg 配置文件 | dpkg-query 将 default.conf 和 nginx.conf 报告为配置文件 | 1 |
| 包提供 | dpkg -s nginx 显示 Provides: nginx-r1.25.5 | 1 |
| NJS 二进制 | njs -v 返回 0.8.4 | 1 |
| IPv6 入口点 | 10-listen-on-ipv6-by-default.sh 以相同方式修改 default.conf | 1 |
| Envsubst 模板 | NGINX_ENVSUBST 模板替换产生相同输出 | 1 |
| 启动日志 | 所有入口点日志行与上游格式匹配 | 1 |
| HTTP GET/HEAD/POST | 多个端点的响应体、状态、标头匹配 | 约 10 |
| 畸形请求 | 原始 TCP 错误响应相同 | 1 |
| 静态文件服务 | 挂载的文件以正确内容提供 | 1 |
| TLS 终止 | 使用自签名证书的 HTTPS 同样工作 | 1 |
| 优雅关闭 | SIGQUIT 产生干净退出(代码 0) | 1 |
| 配置重载 | SIGHUP 无需重启即可应用新配置 | 1 |
| 并发请求 | 50 个并行请求全部返回 200 | 1 |