LabHub
学习 学习路径 课程

容器安全

扫描结果干净,不等于就安全

在 LabHub 中继续学习

一句话总结

镜像扫描器只做两个步骤:解开镜像层,生成已安装软件包清单,再把名称和版本与漏洞数据库比对。 它既不运行镜像,也不分析代码。

概念图: 解开镜像层,生成已安装软件包清单,再把名称和版本与漏洞数据库比对。 · (a) 没有软件包元数据,就无法发现。 · 既不会出现在 SBOM 中,也不会出现在扫描结果中 · (b) 只要出现在清单中,无论是否执行都会报告。

为什么需要它

如果把扫描器视为魔法黑盒,就会得出两个错误结论:“扫描结果干净,所以很安全”和“发现了 1,247 项,所以必须全部修复”。两者都源于不了解扫描器的工作方式。

理解工作原理后,其局限也自然显现。

(a) 没有软件包元数据,就无法发现。 Debian 系读取 /var/lib/dpkg/status,RPM 系读取 RPM DB,语言运行时则读取锁文件。因此,通过 Dockerfile 中的 curl | tar xz 放入的二进制文件,既不会出现在 SBOM 中,也不会出现在扫描结果中。这是扫描结果干净不等于安全的第一个原因。

(b) 只要出现在清单中,无论是否执行都会报告。 即使应用程序从未调用过 tar,它的漏洞也会照样列出。

工作原理

看实际数字就很直观。扫描 payments:1.4.2 (debian 12.6) 会得到 Total: 1247 (LOW 812, MEDIUM 289, HIGH 137, CRITICAL 9)。加上 --ignore-unfixed --severity CRITICAL,HIGH 后,结果变为 Total: 4,其中与已知被利用漏洞目录(KEV)的交集只有1 项

总结如下:1,247 项变成了 4 项。安全性没有发生任何变化,只是留下了当前能够处理的项目。

而真正的解决办法不是逐项打补丁,而是更换基础镜像。

基础镜像 大小 检测到的软件包 可修复 CRITICAL HIGH
node:22 1.12GB 432 9 137
node:22-slim 231MB 118 2 24
node:22-alpine 148MB 41 0 3
distroless/nodejs22 187MB 19 0 1
静态二进制文件 + scratch 24MB 0 0 0

消除 1,247 个漏洞靠的不是打补丁,而是更换基础镜像。 判断标准可以归结为一句话:如果应用实际链接的共享库只有 8 个,镜像里却有 432 个软件包,那么问题不只是漏洞,而是镜像构成

实际现场中的情况

门禁策略正是从这一困境出发。遇到任何 Critical 都让构建失败,开发就会停滞;不让构建失败,又没人会查看结果。 实际工作中行之有效的折中方案是:只有存在修复版本的 Critical/High 才使构建失败。 如果因为无法修复的问题阻断构建,人们首先学会的会是关闭门禁。任何例外都必须带有到期日(expired_at)。例外悄悄永久化,是门禁失效最常见的途径。

优先级不能只由 CVSS 分数决定。

CVE-2021-44228  epss 0.9444
CVE-2023-45853  epss 0.00412
CVE-2024-5535   epss 0.00196

这三个漏洞的 CVSS 等级都是 Critical。 实际优先级取决于:可利用性 × 暴露程度 × 可达性。

SBOM 格式分为两大类。SPDX 由 Linux Foundation 创建,已成为 ISO/IEC 5962:2021 标准,在许可证合规领域根基深厚。CycloneDX 始于 OWASP,后来成为 ECMA-424,重点是安全漏洞管理。但 SBOM 的真正用途不是归档文档,而是事故发生当天的 5 分钟:新漏洞爆发时,能否立即回答 40 个服务中哪些包含该软件包、分别是什么版本?这一个问题决定了 SBOM 的价值。

把扫描器数字转化为实际风险

首次扫描经常会出现 200 项 CRITICAL。如果原样把清单全部创建为工单,没有人会处理。必须有一个逐步缩小范围的顺序

第一个问题:该漏洞对应的代码是否真的会在这个镜像中执行? 大多数库虽然存在于基础镜像中,我们的程序却不会调用。现代扫描器可以检查二进制文件是否真正引用相关函数,仅启用这一功能就能大幅缩短清单。

第二个问题:攻击者能否到达该路径? 互联网服务 HTTP 解析器中的漏洞,与内部批处理任务所用压缩库中同分数的漏洞,风险并不相同。CVSS 分数是在不了解具体环境的情况下给出的值,不能直接用来排序。

第三个问题:能否修复? 启用 --ignore-unfixed 会排除尚无补丁的项目。这不是忽视,而是区分现在能做的事与需要持续观察的事。应另建清单跟踪无补丁项目,并在补丁发布当天处理。

trivy image --severity HIGH,CRITICAL --ignore-unfixed myapp:1.2.3
trivy image --format cyclonedx -o sbom.json myapp:1.2.3

大多数漏洞会随基础镜像更换而消失。 约八成漏洞来自我们根本不用的 OS 软件包。换成 slim 或 distroless 镜像,就能一次消除这八成。与逐个处理 CVE 相比,直接更换底座总是更便宜。

定期重新构建比扫描更重要。 镜像会一直携带构建当天的软件包;如果不重新部署,漏洞就会持续累积。即使代码没有变化,只要有每周一次的重新构建流水线,大多数问题就会自动解决。

记录例外时必须附带期限。 如果以“无法修复”为由无限期加入忽略清单,该项目将永久残留。应在 .trivyignore 中同时写明到期日和原因,并让构建在存在过期项目时发出警告。

下一项实验要做什么

亲自制作软件包清单,比较基础镜像与运行时镜像,用数字计算增加的攻击面。手工生成 SBOM 并编写策略门禁脚本,然后确认通过 ENV 放入的令牌会被固化在镜像中,再改为运行时注入。