供应链攻击

一个软件供应链由用于创建和维护软件产品的所有软件和工具组成。这不仅包括为产品本身开发的软件,还包括其生产过程中使用的所有软件和工具。

在供应链攻击中,攻击者针对产品供应链的一部分,以危及产品本身。

最明显的例子是第三方库。如果你使用,例如,一个由第三方开发的 npm 包,它有能力危及你的网站。它可能故意这样做,如果它是恶意的,或者意外地,如果它包含其自身的无意漏洞。本质上,你必须像信任自己的代码一样信任你的第三方依赖项。

不太明显的是,同样的原则适用于你在创建软件时使用的所有工具,包括代码编辑器、编辑器插件、版本控制系统、构建工具等等。这些工具中的任何一个都可能在它们应用的转换过程中,将恶意或易受攻击的代码插入到你的最终软件产品中。

在本文档中,我们将概述确保软件供应链安全的实践。它分为两个主要部分

保护你的开发环境

供应链攻击的一种途径是攻击者直接将漏洞或恶意代码引入你自己的产品中。本节将介绍一些可以应对此威胁的实践。

实施访问控制

对项目中的每个人实施强有力的访问控制,包括对代码仓库具有写入权限或有权修改构建或测试配置的任何人。这里的好做法包括

  • 要求团队成员进行多因素认证
  • 遵循最小权限原则:即,只向需要权限的团队成员授予权限,并积极最小化被授予非常强大权限的团队成员数量。

保护工具

评估你在网站生产中使用的任何工具的安全风险,包括

  • 文本编辑器和 IDE
  • 编辑器插件
  • 源代码控制系统
  • 所有参与你的构建、测试和部署过程的工具

对于开源软件依赖项,你可以使用 OpenSSF 发布的评估开源软件的简明指南作为指导。

保护你的配置

了解并应用你的工具的安全设置,尤其是你的源代码控制系统。关键保护措施有

  • 确保拉取请求 (PR) 在合并之前经过代码所有者的审查和明确批准。
  • 确保 PR 在合并之前通过持续集成检查。
  • 要求提交的代码是已签名的。

请参阅 OpenSSF 的源代码管理平台配置最佳实践,其中包括 GitHub 和 GitLab 的具体清单。

管理第三方依赖项

第三方依赖项不仅包括你的代码使用的库和框架,还包括开发过程中涉及的所有第三方工具,如编辑器、IDE、源代码控制系统、包管理器和构建工具。

为了缓解第三方依赖项带来的问题,我们将讨论四种实践

  1. 评估新依赖项
  2. 更新现有依赖项
  3. 维护一份 软件物料清单 (SBOM)
  4. 对外部脚本使用子资源完整性

评估新依赖项

在添加新的依赖项之前,你应该评估它代表了多少安全风险。你需要确信该依赖项得到积极维护,它有修复问题的记录,并有报告和响应安全漏洞的流程。

你应该考虑添加依赖项的风险是否大于自己实现该功能的成本。

OpenSSF 发布的评估开源软件的简明指南列出了你在添加新依赖项之前应该提出的问题。

更新依赖项

一旦你将依赖项添加到你的项目中,依赖项的供应商通常会发布带有新功能、错误修复和安全修复的新版本。你通常会希望利用这些更新,通过实现一种机制来保持依赖项的最新状态。像 GitHub 的 dependabot 这样的工具可以帮助解决这个问题,它通过检测新版本的依赖项并自动打开拉取请求来更新你的项目。

然而,过于急切地更新依赖项也伴随着其自身的风险。例如,假设你添加了一个对可信第三方包的依赖项。然后攻击者获得了包开发者的账户控制权,并发布了一个恶意更新。如果你立即接受了更新,你的项目就会受到威胁。

使用锁定文件

确保依赖项更新安全的第一步是为依赖项使用锁定文件,将其提交到源代码管理,并在构建项目时使用它。

npmYarn 这样的包管理器允许你提供一个文件,比如 package.json,它列出了你项目的依赖项。然后你可以运行一个命令来安装给定的依赖项,以便项目可以使用它们。

然而,依赖项列表并没有确定每个包的确切版本:如果包供应商发布了新版本,那么它可能会在你的项目构建时自动包含在内。如果依赖项的新版本是恶意的,它可能会在你不自知的情况下自动包含在你的项目中。

例如,假设你的 package.json 包含一个名为“example-dependency”的依赖项

json
{
  "name": "example-project",
  "version": "1.0.0",
  "dependencies": {
    "example-dependency": "^1.0.2"
  }
}

假设你的项目构建过程在供应商发布新版本时自动运行。构建过程通过调用 npm install 来启动构建。这将根据版本范围 "^1.0.2" 获取“example-dependency”的最新版本。

在版本 1.0.2(也就是你将其添加到项目的时间点),“example-dependency”是一个有用的、无害的包。然后攻击者接管了“example-dependency”开发者的账户,并发布了一个恶意的 1.0.3 版本。你的构建过程运行,安装了恶意包,然后你的项目就受到了攻击。

所有这些都发生在你的项目直接构件没有任何变化的情况下,也没有任何机会让你审查更新并查看它是否看起来可疑。

解决此问题的方法是在构建项目时使用锁定文件。锁定文件在项目依赖项安装时自动生成,它列出了项目中使用的直接和间接依赖项的精确版本。

也就是说,如果 package.json 告诉你你的项目正在使用“example-dependency”,那么 package.lock 将告诉你确切使用哪个版本的“example-dependency”,以及其依赖项的版本是什么。

项目的锁定文件应被检入到版本控制中。在构建项目时,应使用锁定文件来控制安装的依赖项版本:在 npm 中,你通过使用 npm ci 而不是 npm install 来实现此操作。

注意:以这种方式固定依赖项的版本有时被称为“版本锁定”。

这意味着要更新依赖项,你的构建系统必须发起一个拉取请求来更新锁定文件,这给你一个机会来审查更新并确保你想要接受它。

审查更新

在审查依赖项的更新时,考虑这是否是你想要接受的更新

  • 阅读发布日志,了解它声称提供什么(以及此时你是否需要完全接受它)。
  • 查看它是否引入了任何额外的依赖项。
  • 如果可能,审查源代码更新,看看是否有任何更新是无法解释的或与更改日志不符。
  • 考虑等待一段时间再更新:通常,供应链攻击很快就会被安全研究人员发现,如果更新在您接受之前被发现是恶意的,那么对您更有利。

维护软件物料清单

为了更深入地了解您的依赖项,您可以维护一份详细的依赖项清单。这被称为 软件物料清单 (SBOM)。

锁定文件实际上是一种 SBOM:但是,“SBOM”一词通常指用于表示依赖项的独立标准格式。这些标准通常比锁定文件更广泛和更深入。也就是说

  • 它们可以捕获锁定文件中未表示的依赖项,例如 Web 服务。
  • 它们可以捕获每个依赖项的附加信息,而这些信息未在锁定文件中表示。

使用标准格式表示 SBOM 也意味着你可以

  • 与第三方共享您的 SBOM
  • 集成能够理解您的 SBOM 的工具,用于法规遵从或漏洞监控等目的。

表示软件物料清单的两种最常见的标准是

这两个标准都得到了很好的支持,您可以使用其中任何一个来表示您的项目 SBOM。SPDX 最初专注于帮助产品确保符合开源软件许可证,但已添加了支持安全用例的功能。CycloneDX 是一个较新且更轻量级的标准,从一开始就专注于促进供应链安全。

SBOM 的构成

注意:在本节中,我们将使用 CycloneDX 作为 SBOM 格式的具体示例。

本节仅对 CycloneDX 对象模型中最基本的部分进行简要介绍。有关完整详情,请参阅 CycloneDX SBOM 权威指南

在 CycloneDX 中,所有依赖项都是组件服务

  • 组件包括但不限于软件框架、库、应用程序和配置数据。
  • 服务代表软件可能通过端点 URI 等方式调用的外部 API。

产品中直接或间接使用的每个组件和服务都由 SBOM 中的一个对象表示。该对象包含有关项目的信息,包括其名称、版本、作者、许可证、描述、哈希值(对于组件)和端点 URI(对于服务)。

SBOM 还列出了产品依赖项中已识别的漏洞。列表中的每个项目都包含有关此漏洞的信息,包括描述、一组 CWE 代码、缓解措施、咨询链接以及受漏洞影响的组件或服务的标识符。

创建 SBOM

您可以使用单独的工具(如 cdxgen)或命令(如 npm sbom)为产品生成 SBOM。SBOM 通常作为构建过程的一部分生成,尽管也可以在软件生命周期的其他阶段生成。

使用 SBOM

SBOM 使您能够实施多项针对供应链攻击的防御措施,我们在此列出三项重要的措施

  • 漏洞管理:SBOM 的主要用途之一是响应已在您的依赖项中识别的漏洞。您可以使用第三方工具,例如 OWASP 的 Dependency-Track,该工具通过扫描漏洞报告来源(如 NIST 国家漏洞数据库GitHub 咨询)来自动化此过程。
  • 完整性验证:如果 SBOM 包含依赖项的哈希值,则可以验证您所依赖的组件的来源是否未从其原始发布形式进行修改。
  • 供应商风险管理:通过捕获有关依赖项供应商的信息,SBOM 可以帮助您了解何时依赖来自不再被认为是可靠的供应商的组件或服务。

使用子资源完整性

许多网站都包含外部托管脚本:最值得注意的是,但不限于,从内容分发网络(CDN)提供的脚本

html
<script src="https://cdn.example.org/library.js"></script>

这给你的供应链带来了风险:如果攻击者能控制 cdn.example.org 域名,他们就能用恶意脚本替换原有脚本,从而危及你的网站。

外部脚本,像其他软件依赖项一样,应该成为你的 SBOM 的一部分,但一个额外的防御措施是设置脚本的 integrity 属性

html
<script
  src="https://cdn.example.org/library.js"></script>

此属性的值包含脚本内容的加密哈希。如果脚本被攻击者修改,浏览器将拒绝加载它,您将受到保护。

这确实增加了额外的维护负担:每次源文件更改(例如,每次发布新版本)时,您都必须更新代码中属性的值。

<link> 元素也支持 integrity 属性,因此您也可以(并且应该)将其用于 CSS 样式表以及脚本。

有关更多详细信息,请参阅子资源完整性

防御总结清单

  • 要求团队成员进行多因素认证并最小化授予的权限。
  • 评估参与构建、测试和部署过程的工具。
  • 确保拉取请求经过审查并通过持续集成检查。
  • 最小化你的依赖项,并遵循评估新依赖项的流程。
  • 使用锁定文件控制对依赖项的更新,并遵循接受更新的流程。
  • 维护 SBOM 并使用它来检查漏洞。
  • 对外部引用的脚本和样式表使用子资源完整性。

另见