新发布SkillSec:把 Skills 安全从恶意检测,升维到能力审计SkillSec了解更多 →
技术解读

容器镜像的成分分析:比源码扫描多出来的那几层

安势研究院··4 分钟阅读
容器镜像的三层组件结构与扫描覆盖范围
容器镜像的三层组件结构与扫描覆盖范围

一个常见的困惑

团队把 SCA 接进了 CI,源码扫描干干净净,结果安全团队用镜像扫描器一扫,报出几十个高危漏洞。开发的第一反应通常是“扫描器有问题”。

扫描器没问题。两边扫的根本不是同一个东西。

镜像里到底有什么

一个典型的应用镜像包含三层性质不同的组件:

第一层:基础镜像(OS 层)——你在 Dockerfile 第一行写的 FROM。它带来一整个操作系统的用户空间:glibc、openssl、bash、coreutils、包管理器本身。这一层的组件数量通常远超应用依赖,而且完全不在任何应用清单文件里。

第二层:系统包——构建过程中 apt-get installapk add 装进去的东西:curl、git、编译工具链、各种运行时库。它们经常是为了构建而装、装完忘了删,最终留在生产镜像里。

第三层:语言依赖——package.jsonpom.xmlrequirements.txt 里声明的那些。这一层,也只有这一层,是源码 SCA 能看到的。

所以“源码扫描干净但镜像有漏洞”根本不矛盾:问题出在你没写、但确实在运行的那两层里。

为什么基础镜像是风险重灾区

基础镜像的问题不在于它质量差,而在于三个结构性因素:

体积即攻击面。 一个完整的 ubuntu:22.04 带着几百个包,其中绝大多数你的应用根本用不到——但它们的漏洞照样会被扫出来,照样需要你解释或修复。

更新滞后。 FROM node:18 这样的写法看似锁定了版本,实际上标签是浮动的,而团队往往几个月才重建一次镜像。基础镜像的安全更新不会自动到达你的生产环境。

继承的不可见性。 你的镜像 FROM 了某个团队的中间镜像,那个镜像又 FROM 了别的。最终生产镜像里的组件,可能来自三四层之外的某个决定,而没有人完整地看过这条链。

三条治理原则

一、选择精简且有维护承诺的基础镜像。

distroless、官方 slim 变体、alpine——选择的核心不是“哪个最小”,而是“哪个在满足运行需求的前提下组件最少,且有明确的安全更新节奏”。少一个包就少一个潜在的 CVE 和一次不必要的解释。

二、镜像扫描进 CI,与源码扫描并行。

两者互补而非替代:源码 SCA 在依赖引入时拦截(更早、修复成本更低),镜像扫描在交付物层面兜底(更全、覆盖你没声明的部分)。理想的门禁位置是镜像构建之后、推送到仓库之前——此时还能重建,推上去再发现就是回滚了。

三、基础镜像版本全公司统一管理。

不是每个团队各自选 FROM,而是公司维护一组经过安全审查的基线镜像,各团队从基线派生。这样做的收益在漏洞爆发时最明显:修一次基线,所有下游重建即可,而不是二十个团队各查各的。

SBOM 应该覆盖到哪一层

如果你要给客户或监管提交容器化产品的 SBOM,只导出语言依赖是不够的——收方期待的是“这个镜像里有什么”,而不是“这个应用声明了什么”。

完整的容器 SBOM 应当包含:基础镜像标识与摘要、系统包清单(含版本)、语言依赖树、以及各层的来源信息。好在这件事是可自动化的:在 CI 里对构建产出的镜像做一次成分分析,导出 SPDX 或 CycloneDX,随镜像一起发布。

需要留意的是层的可追溯性:同一个组件可能出现在多个层,来源不同、修复路径也不同。基础镜像里的 openssl 要靠换基础镜像修,应用依赖里的要靠改依赖声明修——SBOM 如果不区分来源,收到的人无法判断该找谁修。

从“扫描通过”到“成分清楚”

容器化把交付物从“一个 jar 包”变成了“一整个运行环境”,这让软件供应链的边界向下扩展了两层。相应地,成分透明的要求也必须跟着下沉。

值得强调的是:这不只是安全问题,也是许可证问题。基础镜像里的 GPL 组件、系统包的许可证义务,同样会随着镜像分发而传递。只看应用依赖的合规审计,在容器化交付面前是不完整的。

---

延伸阅读SBOM 字段实操指南 · 在 CI 流水线里配置 SCA 门禁 · CleanSource SCA 的容器镜像分析能力

容器安全镜像扫描SBOM基础镜像SCADevSecOps供应链安全
想看看这些能力在你的代码库上如何落地?预约演示

相关阅读

技术解读

SBOM 不止是合规清单:从“我用了什么”到“我能承受什么”

很多团队把 SBOM 当成一份交差用的清单。但真正有价值的 SBOM,是能驱动决策的:哪些漏洞可被利用、哪条依赖该先修、哪个许可证会带来风险。