2、镜像供应链安全基础:软件供应链攻击模型、镜像篡改与投毒风险分析
镜像供应链安全是云原生安全体系中最薄弱的环节之一。攻击者不再直接攻击运行时容器,而是将恶意代码植入镜像构建、分发或部署的任意一个上游环节,形成“一次投毒、扩散”的供应链级攻击。理解攻击模型与风险类型,是构建有效安全水位巡检的前提。
2.1 软件供应链攻击模型:从开发到部署的四个攻击面
镜像供应链的生命周期可抽象为四个连续阶段,每个阶段都存在独特的攻击向量。下表总结了各阶段的核心风险与典型攻击手法:
| 阶段 | 攻击面 | 典型攻击手法 | 影响范围 |
|---|---|---|---|
| 开发阶段 | 源代码、依赖库、构建脚本 | 依赖混淆、恶意NPM/PyPI包、Git泄露密钥 | 单个项目或组织 |
| 构建阶段 | Dockerfile、基础镜像、CI/CD管道 | 基础镜像投毒、构建缓存污染、CI凭证窃取 | 镜像仓库内所有派生镜像 |
| 分发阶段 | 镜像仓库、传输通道、镜像签名 | 镜像篡改、中间人攻击、仓库凭证泄露 | 所有拉取该镜像的集群 |
| 部署阶段 | Kubernetes Admission、策略引擎 | 运行态镜像替换、恶意Sidecar注入、未验证镜像部署 | 目标集群及关联服务 |
关键认知: 攻击者只需攻破四个阶段中的任意一个,即可将恶意镜像投递到生产集群。传统“运行时检测”无法防御构建阶段就已植入的后门。
2.2 镜像篡改与投毒风险深度分析
镜像篡改与投毒是供应链攻击中最常见、危害最大的两类风险。以下从技术实现角度拆解其攻击原理与检测难点。
2.2.1 镜像篡改:分层结构与摘要验证的盲区
Docker 镜像由多层只读文件系统叠加而成,每层对应一个 Layer ID(基于内容哈希)。镜像清单(Manifest)记录了所有层的哈希和大小。篡改攻击通常发生在以下场景:
- 中间人替换层: 在镜像拉取过程中,攻击者拦截 Registry 响应,替换 Manifest 中指向的某个 Layer Blob 为恶意层。若客户端未验证 Manifest 签名,则拉取到篡改镜像。
- 仓库侧篡改: 攻击者获得 Registry 写权限后,直接修改已有镜像的 Tag 指向另一个恶意 Manifest,或覆盖某个 Layer 的内容。
- 本地缓存投毒: 在共享 CI Runner 或开发机中,攻击者篡改本地
/var/lib/docker下的层缓存,使后续构建引用恶意层。
传统防护依赖 docker pull 后的摘要(Digest)比对,但摘要本身若未通过可信渠道分发,则比对无意义。这是 Sigstore 引入 无密钥签名 的核心动因——将摘要签名与 OIDC 身份绑定,使篡改行为可被加密验证。
2.2.2 镜像投毒:从基础镜像到依赖链的全面渗透
镜像投毒指攻击者将恶意代码嵌入镜像的某个合法组件中,使其在容器启动时自动执行。常见投毒路径包括:
- 基础镜像投毒: 攻击者上传带有后门的“官方”镜像(如
node:18-alpine-backdoor),或攻破官方镜像构建管道,在合法基础镜像中植入挖矿程序。 - 依赖包投毒: 通过 依赖混淆(Dependency Confusion)或 类型劫持(Typosquatting)将恶意包注入
requirements.txt或package.json。构建时包管理器自动下载恶意版本。 - 多阶段构建残留: 构建阶段使用了包含敏感文件(如 SSH 密钥、云凭证)的中间镜像,但未在最终镜像中清理干净,导致凭证泄露。
- 运行时数据投毒: 攻击者通过挂载卷或环境变量注入恶意配置,使容器启动后加载攻击者控制的动态库或二进制文件。
风险警示: 2023 年某知名云原生项目因基础镜像中的
libcurl 被替换为带后门的版本,导致所有下游用户容器在发起 HTTPS 请求时泄露环境变量。该事件持续 72 小时才被发现,影响超过 10 万个生产容器。
2.3 镜像供应链攻击链全景图
下图展示了从代码提交到容器运行的完整攻击链,以及每个环节可能被植入的恶意载荷类型:
2.4 镜像供应链安全水位巡检的核心指标
基于上述攻击模型,安全水位巡检需要覆盖以下六个关键维度。每个维度对应一个可量化的检查项:
| 巡检维度 | 检查项 | 高风险阈值 | 理想水位 |
|---|---|---|---|
| 基础镜像溯源 | 基础镜像是否来自可信源且版本锁定 | 使用 latest 标签或非官方仓库 |
使用摘要(Digest)锁定且经过签名验证 |
| 依赖库安全 | 是否存在已知漏洞或混淆包 | 存在 CRITICAL 级别 CVE 或未审计的第三方包 | SBOM 生成 + 漏洞扫描通过 + 依赖锁定 |
| 构建过程完整性 | CI/CD 管道是否具备防篡改能力 | 无构建日志审计、无签名步骤 | 构建产物自动签名 + 构建环境可复现 |
| 镜像签名覆盖率 | 仓库中已签名的镜像比例 | 签名覆盖率 < 80% | 100% 镜像强制签名(含基础镜像) |
| 部署策略强制 | K8s 是否验证镜像签名后再调度 | Admission 未启用签名验证 | 策略引擎拒绝未签名或签名失效的镜像 |
| 运行时一致性 | 运行容器是否与已签名镜像一致 | 容器哈希与签名摘要不匹配 | 运行时持续验证 + 异常告警 |
巡检建议: 建议将上述六个维度纳入每周自动巡检脚本,输出“安全水位评分卡”。评分低于 60 分的集群应立即阻断新镜像部署,直至修复完成。
2.5 从攻击模型到 Sigstore 集成:为什么需要无密钥签名?
传统镜像签名方案(如 Docker Content Trust)依赖中心化密钥管理,存在以下固有缺陷:
- 密钥泄露风险: 签名私钥一旦泄露,攻击者可对任意镜像签发合法签名。
- 密钥轮换成本高: 每次轮换需要重新签名所有历史镜像,操作复杂且易出错。
- 身份绑定弱: 签名仅证明“某个密钥签过”,无法关联到具体的开发者或 CI 流水线身份。
Sigstore 通过 无密钥签名(Keyless Signing)解决了上述问题:
- 签名者使用 OIDC 身份(如 GitHub OAuth、Google 邮箱)向 Sigstore 的 Fulcio CA 申请短期证书。
- 证书绑定签名者的身份信息,签名操作在内存中完成,私钥不落盘。
- 签名后的镜像摘要和证书被记录到 Rekor 透明日志中,任何人都可公开审计。
这种设计使得攻击者即使攻破了 CI 管道,也无法伪造历史签名,因为每次签名都需要实时 OIDC 认证,且透明日志提供了不可篡改的审计线索。