依赖与软件供应链安全 (Dependency and Software Supply Chain Security)
分类: SDL规范文档 · 阅读时间: 约11分钟 · 最后更新: 2026-08-28

1. 概述

现代应用的绝大部分代码并非团队自己编写, 而是来自第三方开源组件及其传递依赖。一个典型的 Web 项目, 自研代码往往不足总体积的一成, 其余全部是依赖。这意味着应用的攻击面在很大程度上由这些依赖决定, 而不是由自研代码决定。将依赖安全纳入 SDL, 与安全编码静态分析同等对待, 是当前安全开发不可回避的一环。本规范给出依赖风险的来源、软件成分分析 (SCA) 的方法, 以及在 CI 流水线中落地的门禁策略。

背景
2021 年的 Log4Shell (CVE-2021-44228) 是一次典型的依赖风险事件: 漏洞位于一个被极其广泛使用的日志组件中, 大量团队甚至不知道自己的应用间接依赖了它。这说明"不知道自己用了什么"本身就是最大的风险。

2. 依赖风险的来源

依赖带来的风险不是单一的, 至少包含以下几类, 需要用不同手段应对:

  • 已知漏洞组件: 依赖的某个版本存在已公开的 CVE。这是最常见也最容易自动化检测的一类。
  • 传递依赖: 你直接引入的库又引入了它自己的依赖。风险往往藏在这些你从未直接选择的传递依赖中。
  • 恶意包: 攻击者通过仿冒命名 (typosquatting) 或依赖混淆 (dependency confusion) 将恶意包推入公共仓库。2021 年 Alex Birsan 的依赖混淆研究表明, 当私有包名在公共仓库被抢注且构建工具优先拉取公共源时, 恶意代码即可进入构建环境。
  • 停止维护的组件: 无人维护的库不会再修复漏洞, 其存在本身就是长期负债。
  • 许可证风险: 与安全并列, 不兼容的开源许可证会带来合规风险, SCA 工具通常一并检测。

3. 软件成分分析 (SCA)

软件成分分析的核心目标是回答一个问题: 这个应用到底由哪些组件构成, 其中哪些存在已知漏洞。SCA 工具解析构建产物或依赖清单, 递归展开直接依赖与传递依赖, 再与漏洞数据库 (如 NVD、GitHub Advisory Database、OSV) 比对。

常用工具包括 OWASP Dependency-Check、Trivy、Grype、Snyk, 以及各语言生态自带的能力 (pip-auditnpm auditcargo audit)。以 Python 为例:

# 扫描当前环境或 requirements 中的已知漏洞依赖
pip-audit -r requirements.txt

关键要求是: 扫描必须覆盖传递依赖, 而不仅是直接声明的包。只检查 requirements.txt 顶层而忽略被拉入的传递依赖, 会漏掉大部分真实风险。

4. 软件物料清单 (SBOM)

SBOM (Software Bill of Materials) 是应用所含全部组件及版本的结构化清单。它的价值在下一个 Log4Shell 出现时最为明显: 当一个新漏洞公布时, 有 SBOM 的团队可以在几分钟内查询自己是否受影响, 没有 SBOM 的团队则需要数周的排查。

业界通行的两种格式是 CycloneDX 与 SPDX。构建阶段用工具 (如 Syft) 自动生成 SBOM, 随产物一起归档:

# 为镜像或目录生成 CycloneDX 格式的 SBOM
syft dir:. -o cyclonedx-json > sbom.json

原则: SBOM 应作为构建的标准产物自动生成并保存, 而不是在需要时临时人工整理。

5. 版本锁定与可复现构建

依赖必须被锁定到确定的版本, 理想情况下锁定到内容哈希。使用浮动版本范围 (如 ^1.2.0) 意味着两次构建可能拉取到不同代码, 也意味着攻击者对某个版本的投毒可以在你毫不知情时进入构建。

错误示例 (浮动版本, 构建不可复现):

requests>=2.0

正确示例 (锁定版本并校验哈希):

# requirements.txt 由 pip-compile 等工具生成, 带哈希
requests==2.32.3 \
    --hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6

锁定文件 (poetry.lockpackage-lock.jsonCargo.lock) 应纳入版本库并在 CI 中以"冻结"模式安装, 保证构建可复现。

6. 供应链攻击的防护

针对恶意包与投毒, 除扫描已知漏洞外还需要额外控制:

  • 防依赖混淆: 明确配置包管理器只从受信任的私有仓库解析内部包名, 避免构建工具因公共源版本号更高而优先拉取。为内部命名空间 (scope) 做保留注册。
  • 使用私有代理仓库: 通过 Nexus、Artifactory 等私有代理集中拉取外部依赖, 便于统一审计、缓存与阻断已知恶意包。
  • 仿冒命名审查: 在引入新依赖时, 人工确认包名、维护者与仓库地址无误, 警惕与知名包仅差一个字符的名称。
  • 校验完整性: 优先使用带哈希或签名校验的安装方式, 拒绝无法验证完整性的下载。

7. 在 CI 流水线中落地

依赖安全只有成为自动化门禁才有意义, 依赖人工记忆必然失效。建议:

  • 在 CI 中对每次提交运行 SCA 扫描, 将高危漏洞依赖设为构建失败的门禁条件。
  • 为暂时无法修复的问题建立带到期时间的例外清单, 而不是无限期忽略。
  • 每次构建自动生成并归档 SBOM, 使漏洞应急时可快速定位受影响系统。
  • 将锁定文件的更新纳入代码审计范围, 依赖版本的变动同样需要评审。

8. 小结

依赖与供应链安全的核心可以概括为: 先做到"知道自己用了什么" (SBOM 与成分分析), 再持续检测已知漏洞并作为 CI 门禁, 通过锁定版本与私有代理仓库保证可复现且可控, 并针对依赖混淆与仿冒命名做专门防护。这一部分与语言无关, 建议将其固化为所有项目的统一流水线阶段, 与静态分析和代码审计并列执行。

王海涛 (Wang Haitao)
Application Security Engineer
应用安全工程师, 专注软件供应链安全、SCA 规则工程与 SBOM 落地。长期参与 CI 安全门禁与依赖治理体系建设, 在 KCon、HITB 等安全会议上分享过软件供应链安全实践。