Caloogy 文章
English

依赖投毒与 typosquatting

日期
2026 年 10 月 5 日
系列
基础设施
阅读
4 分钟
主题
InfraSupply chainNpmPypi

一句话版本

现代软件 90% 以上的代码来自第三方依赖。只要其中任何一个包被投毒,所有使用它的项目都会中招。常见手法有:冒名(注册一个和知名包只差一个字母的名字)、接管(盗取或骗取维护者账号)、长期潜伏(以贡献者身份花几年时间获得信任,再植入后门)、依赖混淆(让构建工具从公共仓库下载一个和内部包同名的恶意包)。 2024 年的 xz-utils 后门是最令人警醒的例子:攻击者用了约两年时间成为维护者,差一点就让后门进入主流 Linux 发行版。

全文分三层:

  1. 通俗理解:餐厅的食材来自几百家供应商
  2. 实际用处:event-stream、ua-parser-js、xz-utils、依赖混淆
  3. 原理与工程要点:安装脚本、锁文件与哈希、私有镜像、SBOM

第一层:通俗理解

1.1 几百家供应商

一家餐厅的菜,食材来自几百家供应商,供应商的供应商又有几百家。只要其中任何一家往原料里掺了东西,所有用这批原料的餐厅都会出事。

一个普通的 JavaScript 项目,直接依赖几十个包,加上间接依赖可能有上千个。你真正读过代码的,可能一个都没有。

1.2 四种投毒方式

方式 比喻
冒名(typosquatting) 开一家叫"可口可楽"的店
账号接管 偷走正规供应商的公章
长期潜伏 应聘进正规供应商,干了两年当上主管,再往原料里掺东西
依赖混淆 你公司内部有个叫"秘制酱料"的配方,有人在外面注册了同名商品,采购员买错了

第二层:实际用处

事件 方式 经过
event-stream(2018) 维护权转手 原维护者无暇维护,把这个每周下载数百万次的 npm 包交给了一个主动提出帮忙的陌生人。新维护者加入了一个依赖,其中的恶意代码专门针对一个比特币钱包应用,窃取用户的钱包
ua-parser-js(2021) 账号被盗 维护者的 npm 账号被盗,攻击者发布了带有挖矿程序和密码窃取程序的版本
依赖混淆(2021) 同名公共包 安全研究者发现,很多公司的构建系统在找内部包时,会优先从公共仓库下载版本号更高的同名包。他借此(以无害的方式)进入了数十家大公司的内部网络,获得了大额漏洞赏金
xz-utils(2024,CVE-2024-3094) 长期潜伏 一个化名开发者花了约两年时间为这个基础压缩库贡献代码、获得维护者信任,最终在发布包(而不是源码仓库)中植入了一个针对 SSH 的复杂后门。微软工程师 Andres Freund 因为注意到 SSH 登录慢了约半秒、CPU 占用异常,追查后发现了它。此时后门已进入几个发行版的测试版本

xz 事件暴露了开源基础设施的一个结构性问题:大量关键组件由一两个疲惫的志愿者维护。


第三层:原理与工程要点

3.1 安装就是执行

很多包管理器允许包在安装时运行脚本(如 npm 的 postinstall、Python 的 setup.py)。这意味着:npm install 某个包 就等于在你的电脑上以你的身份执行了它的作者写的代码。不需要你真的调用这个包。

对策:在 CI 和不需要的场合禁用安装脚本(如 npm ci --ignore-scripts),只对确实需要的包单独允许。

3.2 锁文件与哈希

措施 作用
锁文件(package-lock.json、poetry.lock、Cargo.lock) 固定每个依赖(包括间接依赖)的确切版本
完整性哈希 锁文件里记录每个包的哈希,下载后校验,防止仓库里的同版本文件被替换
CI 用 ci 而不是 install 严格按锁文件安装,锁文件和清单不一致就报错
更新要审查 依赖更新走人工审查的 PR,看变更内容,而不是自动合并

3.3 防依赖混淆

3.4 识别可疑的包

信号 说明
名字和知名包极其相似 typosquatting
刚发布不久、下载量低,却被你的依赖引入 可能是新植入的恶意依赖
维护者最近变更 event-stream 的教训
新版本突然加入安装脚本、网络请求、混淆代码 高危
发布的包内容和源码仓库不一致 xz 的后门就只存在于发布包里

有专门的工具和服务(如 Socket、OpenSSF Scorecard、各平台的依赖扫描)可以自动检查这些信号。

3.5 SBOM 和长期措施


总结

层次 要点
风险 上千个依赖,任何一个被投毒都会中招
手法 冒名、账号接管、长期潜伏、依赖混淆
关键事实 安装就是执行
对策 锁文件 + 哈希;禁用安装脚本;作用域 + 私有镜像;审查更新;SBOM 与来源证明