文章深入分析了软件供应链中存在的信任漏洞,详细阐述了诸如拼写错误攻击、依赖混淆、秘钥泄露、管道投毒和恶意维护者等攻击手段,并介绍了TypoGard、Zizmor、PyPI可信发布和证明、Homebrew构建溯源以及Go Capslock等新兴防御技术,旨在将隐式信任转变为显式、可验证的保障,从而提高软件供应链的安全性。
每次你运行 cargo add
或者 pip install
,你都是在进行一次信仰的飞跃。你信任你下载的代码包含你期望的内容,来自你期望的对象,并且执行你期望的操作。这些期望对于现代开发来说是如此的基础,以至于我们很少去思考它们。然而,攻击者正在系统性地利用这些假设。
仅在 2024 年,PyPI 和 npm 就移除了数千个恶意包;多个备受瞩目的项目被直接注入恶意软件到构建过程中;XZ Utils 后门几乎进入了全球数百万的 Linux 系统。
依赖扫描只能捕获已知的漏洞。它无法捕获拼写错误的包窃取你的凭据,当受损的维护者发布恶意软件,或者当攻击者毒害构建管道本身时的情况。这些攻击之所以成功,是因为它们利用了使现代软件开发成为可能的信任。
这篇文章分解了使软件供应链易受攻击的信任假设,分析了最近利用这些假设的攻击,并重点介绍了一些正在跨生态系统构建的前沿防御措施,以将隐式信任转化为显式、可验证的保证。
对于许多开发者来说,软件供应链始于并止于软件物料清单(SBOM)和依赖扫描,它们共同回答了两个基本问题:你拥有什么代码,以及它是否包含已知的漏洞?但了解你拥有什么只是最基本的要求。随着复杂的攻击变得越来越普遍,你还需要了解你的代码来自哪里以及如何到达你手中。
你信任你正在安装你期望的包。 你假设运行 cargo add rustdecimal
是安全的,因为 rustdecimal
是一个众所周知的且广泛使用的库。或者等等,也许它的拼写是 rust_decimal
?
你信任包是由包维护者发布的。 当一个流行的包开始附带一个预编译的二进制文件以节省构建时间时,你可能会决定信任该包的作者。然而,许多注册中心缺乏强有力的验证,以证明发布者是他们声称的身份。
你信任包是从包源代码构建的。 你可能在一个注重安全的团队中工作,该团队会在升级依赖项之前审核公共存储库中的代码更改。但如果分发的包是从未出现在存储库中的代码构建的,那么这毫无意义。
你信任维护者本身。 最终,安装第三方代码意味着信任包维护者。审核你依赖的每一行代码是不切实际的。我们假设那些已建立且被广泛采用的包的维护者不会突然决定添加恶意代码。
这些假设超出了传统的包管理器。当你运行 GitHub Action、使用 Homebrew 安装工具或执行方便的 curl ... | bash
安装脚本时,也存在同样的信任。理解这些隐式信任关系是评估和减轻供应链风险的第一步。
攻击者正在利用供应链每一层的信任假设。最近的事件范围从简单的域名抢注到多年的活动,展示了攻击者的策略是如何演变并变得越来越复杂的。
Typosquatting(域名抢注) 涉及发布一个名称与合法包相似的恶意包。运行 cargo add rustdecimal
而不是 rust_decimal
可能会安装恶意软件而不是预期的合法库。这个确切的攻击发生在 2022 年的 crates.io 上。恶意的 rustdecimal
模仿了流行的 rust_decimal
包,但包含了一个 Decimal::new
函数,该函数在调用时会执行一个恶意二进制文件。
该攻击的简单性使得攻击者很容易发起大规模的活动,特别是针对 PyPI 和 npm。自 2022 年以来,已经发生了多次针对软件包的 typosquatting 活动,这些软件包的总量达到了每周 12 亿次下载。仅在 PyPI 和 npm 上就发布了数千个恶意包。这种类型的攻击发生得如此频繁,以至于这里无法列出所有的例子。2023 年,研究人员记录了一个注册了 40 个流行的 PyPI 包的 900 个 typosquat 的活动,并发现了在 crates.io 上暂存的恶意软件。攻击只会加剧,在 2024 年的一次活动中,发布了 500 个恶意包。
Dependency confusion(依赖混淆) 采取了一种不同的方法,直接利用包管理器的逻辑。安全研究员 Alex Birsan 在 2021 年演示并命名了这种类型的攻击。他发现许多组织使用内部包的名称,这些名称要么被泄露,要么可以被猜测。通过将与这些内部包具有相同名称的包发布到公共注册中心,Birsan 能够欺骗包管理器下载他的版本。Birsan 的概念验证确定了跨三种编程语言和 35 个组织(包括 Shopify、Apple、Netflix、Uber 和 Yelp)的漏洞。
2022 年,一名攻击者使用这种技术在五天内将恶意代码包含在 PyTorch 的 nightly 版本中。一个名为 torchtriton
的内部依赖项是从 PyTorch 的 nightly 包索引托管的。一名攻击者将一个具有相同名称的恶意包发布到 PyPI,该包优先。结果,PyTorch 的 nightly 版本在被捕获之前包含了五天的恶意软件。
虽然这些攻击发生在安装点,但其他攻击通过破坏发布过程本身采取了更直接的方法。
被盗的帐户是另一个常见的攻击媒介。攻击者获取泄露的密钥、被盗的Token或猜测的密码,并且能够代表受信任的实体直接发布恶意代码。最近的一些事件显示了这种类型攻击的规模:
被盗的秘密仍然是最可靠的供应链攻击媒介之一。但是,随着组织实施更强大的身份验证和更好的秘密管理,攻击者正在从窃取密钥转移到破坏使用它们的系统。
一些攻击者不是窃取凭据,而是通过破坏构建和分发系统本身,设法通过合法渠道分发恶意软件。通过将恶意代码直接注入 CI/CD 管道中,完全绕过了代码审查和其他安全检查。
2020 年的 SolarWinds 攻击是此类别中众所周知的攻击之一。攻击者破坏了构建环境,并在编译过程中将恶意代码直接插入到 Orion 软件中。然后,恶意版本的 Orion 通过 SolarWinds 的合法更新渠道进行签名和分发。该攻击影响了数千个组织,包括多家财富 500 强公司和政府机构。
最近,在 2024 年末,一名攻击者破坏了 Ultralytics 构建管道,以发布多个恶意版本。攻击者在项目的 GitHub Actions 中使用了一个模板注入,以获取对 CI/CD 管道的访问权限,并毒害了 GitHub Actions 缓存,以将恶意代码直接包含在构建中。在攻击发生时,Ultralytics 每周有超过一百万次的下载。
2025 年,一名攻击者修改了 reviewdog/actions-setup
GitHub action v1 标签,指向一个包含转储秘密的代码的恶意版本。这可能导致了另一个流行的 action tj-actions/changed-files
的破坏,因为它依赖于tj-actions/eslint-changed-files
,后者又依赖于受到威胁的 reviewdog
action。这种级联破坏影响了数千个使用 changed-files
action 的项目。
虽然与域名抢注或凭据盗窃相比,中毒管道攻击相对罕见,但它们代表了攻击者复杂程度的升级。随着更强大的防御措施的到位,攻击者被迫向上移动供应链。最坚定的攻击者愿意花费数年时间为一次攻击做准备。
XZ Utils 后门于 2024 年 3 月被发现,差点让全球数百万的 Linux 系统受到攻击。攻击者花费了两年多的时间为该项目做出合法的贡献,然后才获得维护者权限。然后,他们滥用这种信任,通过一系列看似无辜的提交插入了一个复杂的后门,该后门将授予对使用受损版本的任何系统的远程访问权限。
最终,你必须信任你的依赖项的维护者。安全的构建管道无法防止决定插入恶意代码的受信任的维护者。随着开源维护者越来越不堪重负,并且随着 AI 工具使得大规模生成令人信服的贡献变得更容易,这种信任模型正面临着前所未有的挑战。
随着攻击变得越来越复杂,防御者正在构建工具来匹配。这些新方法正在使信任假设变得显式和可验证,而不是隐式和可利用。每种方法都解决了攻击者已经找到成功的供应链的不同层。
大多数包管理器现在都包含某种形式的域名抢注保护,但它们通常使用传统的相似性检查,例如那些测量 Levenshtein 距离的检查,这些检查会产生过多的误报,需要手动审查。
TypoGard 通过使用多种上下文感知指标(如下所示)来填补这一空白,以低误报率和最小的开销来检测域名抢注包:
rustdeciimal
)reqeusts
而不是 requests
)此工具针对 npm,但这些概念可以扩展到其他语言。Rust 基金会发布了一个 Rust 端口 Typomania,该端口已被 crates.io 采用 并且已成功捕获多个恶意包。
Zizmor 是 GitHub Actions 的静态分析工具。Actions 具有很大的攻击面,编写复杂的工作流程可能很困难且容易出错。工作流程可以通过许多微妙的方式引入漏洞。
例如,Ultralytics 通过其工作流程之一中的模板注入而受到攻击。
- name: Commit and Push Changes
if: (... || github.event_name == 'pull_request_target' || ...
run: |
...
git pull origin ${{ github.head_ref || github.ref }}
...
由 pull_request_target
事件触发的工作流程以对存储库秘密的写入权限运行。攻击者从具有恶意名称的分支打开了一个 pull request。当工作流程运行时,github.head_ref
变量扩展为恶意分支名称,并作为具有工作流程提升权限的运行命令的一部分执行。
reviewdog/actions-setup
攻击也是通过将 action 的 v1 标签更改为指向恶意提交来部分执行的。任何在其工作流程中使用 reviewdog/actions-setup@v1
的人都悄悄地开始获得恶意版本,而无需对其自己的工作流程进行任何更改。
Zizmor 标记了以上所有内容。它包括一个危险触发器规则来标记由 pull_request_target
触发的工作流程,一个模板注入规则,以及一个取消固定 uses 检查,该检查会在使用 reviewdog/actions-setup@v1
时警告 actions 不要使用可变引用(例如标签或分支名称)。
PyPI 已采取重要步骤,通过两个互补功能来解决一些隐式信任假设:Trusted Publishing 和证明。
Trail of Bits 与 PyPI 合作开发了 Trusted Publishing 1,从而无需长时间使用的 API Token。开发人员无需存储可能被盗的秘密,而是配置一次信任关系:“此 GitHub 存储库和工作流程可以发布此包。” 当工作流程运行时,GitHub 将包含有关存储库和工作流程声明的短期 OIDC Token发送到 PyPI。PyPI 验证此Token是否由 GitHub 的密钥签名,并响应一个短期 PyPI Token,工作流程可以使用该Token来发布包。使用自动生成的、作用域最小的短期Token大大降低了受损的风险。
如果没有长期存在的、权限过高的 API Token,攻击者必须破坏发布 GitHub 工作流程本身。虽然 Ultralytics 攻击表明 CI/CD 管道破坏仍然是一个真正的威胁,但消除用户手动管理凭据的需求消除了用户错误的来源,并进一步减少了攻击面。
在此基础上,Trail of Bits 再次与 PyPI 合作,通过 PEP 740 在 2024 年底引入了 index-hosted digital attestations(索引托管的数字证明)。证明使用 Sigstore 以加密方式将每个发布的包绑定到其构建来源。使用 PyPI 发布 GitHub action 的包会自动包含证明,这些证明充当可验证的记录,记录了包构建的确切位置、时间和方式。
图 1:我们已经 PEP 740 了吗?
超过 30,000 个包使用 Trusted Publishing,并且“我们已经 PEP 740 了吗?” 跟踪了最受欢迎的包中的证明采用情况(在撰写本文时,前 360 个包中有 86 个)。最后一部分,自动客户端验证,仍在进行中。像 pip 和 uv 这样的客户端工具尚未自动验证证明。在此之前,证明提供了透明性和可审计性,但不能在包安装期间提供主动保护。
隐式信任假设超出了编程语言和库的范围。当你运行 brew install
来安装二进制包(或 bottle)时,你信任你下载的 bottle 是由 Homebrew 的官方 CI 从预期的源代码构建的,并且它不是由攻击者上传的,攻击者找到了破坏 Homebrew 的瓶子托管或以其他方式篡改瓶子内容的方法。
Trail of Bits 与 Alpha-Omega 和 OpenSSF 合作,使用 GitHub 的证明向 Homebrew 添加了构建来源。现在,由 Homebrew 构建的每个 bottle 都会附带密码学证明,将其链接到创建它的特定 GitHub Actions 工作流程。这使得受到威胁的维护者更难悄悄地用恶意版本替换 bottle。
% brew verify --help
用法:brew verify [选项] formula [...]
使用 GitHub 的证明工具验证 bottle 的构建来源。
这是通过首先获取给定的 bottle,然后验证其来源来完成的。
每个证明都包括 Git 提交、运行的工作流程和其他构建时元数据。这会将信任假设(“我信任此 bottle 是从我期望的来源构建的”)转换为可验证的事实。
证明的实施通过“回填”过程处理了历史瓶子,为在系统到位之前构建的软件包创建了证明。因此,所有官方 Homebrew 软件包都包含证明。
brew verify
命令使检查来源变得简单,尽管该功能仍处于测试阶段,并且默认情况下验证不是自动的。计划最终将此功能扩展到第三方存储库,从而为更广泛的 Homebrew 生态系统带来相同的安全保证。
Capslock 是一种静态识别 Go 程序功能的工具,包括以下内容:
% capslock --packages github.com/fatih/color
Capslock 是一种用于静态分析 Go 软件包的实验性工具。
请在 https://github.com/google/capslock 上分享反馈并提交错误。
要获取其他调试信号,请使用带有 -output=verbose 的详细模式
要获取机器可读的完整分析输出,请使用 -output=jso`
分析的包:
github.com/fatih/color v1.18.0
github.com/mattn/go-colorable v0.1.13
github.com/mattn/go-isatty v0.0.20
golang.org/x/sys v0.25.0
CAPABILITY_FILES: 1 个引用
CAPABILITY_READ_SYSTEM_STATE: 41 个引用
CAPABILITY_SYSTEM_CALLS: 1 个引用
这种方法代表了供应链安全性的转变。能力分析不是关注谁编写了代码或代码的来源,而是检查代码实际可以做什么。 неожиданно 获得网络访问权限的 JSON 解析库会立即引发危险信号,无论更改来自受损的供应链还是直接来自维护者。
实际上,静态能力检测可能很困难。运行时反射和不安全操作等语言功能使得完全准确地静态检测能力成为不可能。尽管存在局限性,但能力检测作为针对供应链攻击的分层防御的一部分,提供了关键的安全网。
Capslock 开创了 Go 的这种方法,该概念已成熟,可以在其他语言中采用。随着供应链攻击变得越来越复杂,能力分析提供了一条有希望的前进道路。验证代码可以做什么,而不仅仅是它的来源。
供应链攻击并未放缓。如果有的话,它们正变得更加自动化、更加复杂和更加精密,以便针对更广泛的受众群体。域名抢注活动的目标是数十亿次下载的软件包,发布者Token和 CI/CD 管道正在受到攻击,以从源头上毒害软件,并且耐心的攻击者正在花费数年时间来建立信誉,然后再发动攻击。
使软件生态系统得以扩展的隐式信任正在被武器化来对付我们。理解你的信任假设是第一步。问自己以下问题:
一些生态系统已经开始构建防御措施。了解有哪些工具可用,并立即开始使用它们。在发布 到 PyPI 或 到 crates.io 时,请使用 Trusted Publishing。使用 Zizmor 检查你的 GitHub Actions。使用 It-Depends 和 Deptective 了解软件实际依赖的内容。在可行的情况下验证证明。使用 Capslock 查看 Go 包的功能,更重要的是,注意何时引入了新功能。
但没有一个生态系统是完全覆盖的。在缺少工具的地方推动更好的默认设置。每个经过验证的证明、每个被捕获的域名抢注包以及每个被标记的易受攻击的 GitHub action 都会使整个行业更具弹性。我们无法完全消除供应链中的信任,但我们可以努力使这种信任变得显式、可验证和可撤销。
如果你需要帮助理解你的供应链信任假设,请联系我们。
- 原文链接: blog.trailofbits.com/202...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!