上亿台电脑可能因一次 npm install 而沦陷,这个始于一次看似平常的版本更新的故事,细思极恐。
2026年3月30日,全球最流行的 JavaScript HTTP 库 axios 被黑客成功劫持。攻击者仅通过发布两个版本——1.14.1 和 0.30.4——就在短短 2 小时内,将恶意代码植入了至少数百万开发者的电脑。
axios 每周下载量超过 1 亿次,几乎是所有前端和后端 Node.js 项目的标配。如果你使用过 Vue、React 或任何 Node.js 后端服务,大概率都依赖它。而这一次,攻击者用一种极其隐蔽的方式,在你毫无察觉的情况下,在你的系统中埋下了一个远程访问木马。
一次看似正常的“版本更新”
一切始于一个普通的版本号。
3月30日深夜,一个名为 jasonsaayman 的账号在 npm 上发布了 axios@1.14.1。39分钟后,同一个账号又发布了 axios@0.30.4。
从表面看,一切如常。版本号符合预期,发布者也是 axios 的官方维护者。没有任何人意识到:
这两个版本里,藏着致命的恶意代码。
攻击者在这两个版本的 package.json 中偷偷加入了一个从未有过的依赖项:
"plain-crypto-js": "^4.2.1"
这是一个从未在 axios 源代码中被 import 或 require 的包。它存在的唯一目的,就是为了触发一个恶意的 postinstall 脚本。一旦脚本运行,木马便随之而来。
攻击全流程复盘:精密如外科手术
回顾这次攻击,其精密程度令人脊背发凉。
第一步:提前布局,建立信任
攻击提前18小时开始布局。攻击者先在 npm 上发布了一个“干净”的包——plain-crypto-js@4.2.0。这个包看起来完全正常,只是一个加密库的副本,没有任何恶意代码。其作用是建立发布历史,让这个账号看起来像一位合法的维护者,为后续攻击铺平道路。
18小时后,攻击者发布了关键的 4.2.1 版本——这次,恶意代码被藏了进去。
第二步:劫持维护者账号
jasonsaayman 是 axios 的核心维护者之一。攻击者成功入侵该账户,将其邮箱更改为攻击者控制的 ifstap@proton.me,并利用这个被盗的账户发布了那两个恶意版本。
这里有一个关键细节:正常情况下,axios 的所有发布都应通过 GitHub Actions 的 OIDC 可信发布机制进行,发布记录会绑定加密签名。但 1.14.1 和 0.30.4 这两个版本没有。这表明它们是通过被盗的 npm token 手动发布的,巧妙地绕过了正常的 CI/CD 安全检查流程。
第三步:注入恶意依赖
如前所述,恶意依赖被悄悄注入 package.json。值得注意的是,在整个 axios 源码库中搜索 plain-crypto-js 都不会有任何结果。它纯粹是为了触发 postinstall 钩子而存在。
第四步:木马启动与自我销毁
当开发者运行 npm install axios@1.14.1 时,npm 会自动安装 plain-crypto-js,并执行其 postinstall 脚本。
这个脚本会执行一系列操作:
- 检测目标操作系统(Windows、macOS 或 Linux)。
- 向攻击者的命令与控制服务器
sfrclak.com:8000 发送请求。
- 下载针对该操作系统的第二阶段恶意负载。
- 立即删除自身,并替换
package.json 为一个“干净”版本。
整个过程可能不到2秒。在 npm 还在解析依赖树时,木马已完成攻击并清除了现场证据。
事后检查 node_modules,你什么都看不出来。包目录依然存在,版本号可能显示为 4.2.0——因为攻击者早已完成了偷梁换柱。
三平台无差别攻击:Windows、macOS、Linux无一幸免
这或许是迄今为止最“全面”的软件供应链攻击之一,覆盖了所有主流开发者平台。
- macOS 版本:木马伪装成 AppleScript,写入
/tmp/6202033,然后下载一个名为 com.apple.act.mond 的二进制文件到 /Library/Caches/ 目录下,使其看起来像是苹果官方的缓存进程。
- Windows 版本:更为隐蔽。它会定位系统的 PowerShell,将其复制一份到
%PROGRAMDATA%\wt.exe(伪装成 Windows Terminal),然后通过 VBScript 静默执行恶意的 PowerShell 脚本。
- Linux 版本:直接使用
curl 下载一个 Python 脚本到 /tmp/ld.py,并通过 nohup 命令在后台运行。
攻击者为每个平台分配了不同的标识符:macOS 对应 product0,Windows 对应 product1,Linux 对应 product2。这种精细化程度,已远超普通黑客攻击,展现出职业级 APT 攻击的水准。
2小时53分钟的紧急响应与下架
值得庆幸的是,这次攻击被相对迅速地发现了。
网络安全公司 StepSecurity 的 AI Package Analyst 在第一时间捕获了异常。他们的 Harden-Runner 工具在多个项目的 CI/CD 流程中检测到了对可疑地址 sfrclak.com:8000 的外向连接。
3月31日凌晨3点15分,npm 官方紧急下架了这两个恶意版本。从发布到删除,总共经历了不到3小时。
然而,对于供应链攻击而言,3小时已经足够漫长。
如果你在这期间恰好执行了 npm install 或相关构建命令,你的电脑可能已经处于风险之中。
真正令人后怕之处
现在,让我们思考一个关键问题:
你如何确定自己的电脑此刻是安全的?
axios 官方已经删除了恶意版本,但你 macOS 上的 /Library/Caches/com.apple.act.mond 是否已被清除?你 Windows 上的 %PROGRAMDATA%\wt.exe 是否还在运行?你 Linux 系统上的 /tmp/ld.py 进程是否已被终止?
攻击者部署的是一个持久化后门。即使你之后将 axios 升级到了安全版本,那个隐藏的恶意负载可能仍在后台静默运行,持续接收攻击者的指令。
这正是供应链攻击最可怕的地方——它利用的不是软件漏洞,而是生态系统中的信任。你信任 axios,信任 npm,信任那个维护者账号。而攻击者只需攻破其中一环,就能让全球开发者为其“传播”恶意代码。
行业需要彻底反思
事件发生后,社区评论中有一句话直指核心:
“Batteries included” ecosystems are the only persistent solution.
翻译过来就是:减少对第三方依赖的滥用,才是根本的解决之道。
axios 的核心功能——发送 HTTP 请求——现代浏览器的原生 fetch API 或 Node.js 的内置模块也能实现。我们之所以选择 axios,往往是出于“生态习惯”。而这份习惯,如今成了攻击者最锋利的突破口。
社区和安全专家给出了几条具体的防御建议:
- 设置 min-release-age:在包管理器中配置规则,禁止安装发布不足7天的新包,为安全团队留出分析和响应时间。
- 使用 ignore-scripts:全局禁用
npm 的 postinstall 等脚本执行功能,至少在 CI/CD 环境中强制禁用。
- 强制可信发布:npm 等平台应要求高下载量的核心包必须使用 OIDC 等可信发布机制,彻底禁止使用长期有效的手动 token 进行发布。
最后一点尤为关键。如果 axios 的发布流程被强制绑定在 GitHub Actions 上,即使攻击者窃取了密码,也无法发布恶意包——OIDC token 是临时的、情境绑定的,难以被窃取重用。
遗憾的是,这些防御措施目前尚未成为行业的普遍标准。
你最近一次运行 npm install 是什么时候?
是时候检查一下了。打开终端,执行以下命令:
npm list axios | grep -E “1\.14\.1|0\.30\.4”
ls node_modules/plain-crypto-js
如果命令没有任何输出,那么恭喜你,大概率是安全的。
如果发现了相关版本,请不要犹豫,立即按照系统已被入侵的流程进行排查和处理。
这次攻击堪称对开源软件供应链信任体系的一次精准打击。整个行业或许真的需要从“依赖狂欢”中冷静下来,重新审视“少即是多”的原则。如果你想了解更多此类深度技术安全分析,可以到 云栈社区 的开发者广场板块,与其他同行交流探讨。