axios 中毒:一次不改源码的 npm 投毒,给整个 JavaScript 世界敲了警钟

安全 2026年3月31日
axios 中毒:一次不改源码的 npm 投毒,给整个 JavaScript 世界敲了警钟
这不是一次普通的软件包污染,而是一场高度职业化的供应链攻击:攻击者拿下 axios 维护者的 npm 凭证,把恶意代码藏进一个“看起来很正常”的依赖里,让 macOS、Windows 和 Linux 都可能在安装时中招。更让人不安的是,真正危险的地方不在 axios 源码,而在生态系统对“依赖自动执行”的默认信任。

一次“没改源码”的投毒,却比改源码更可怕

如果你是前端、Node.js 开发者,axios 这个名字几乎不需要介绍。它就像 JavaScript 世界里的自来水阀门:你请求接口、拉数据、调服务,十有八九绕不过它。也正因为太常见,它一旦出事,影响就不是“一个库坏了”,而是半个生态都要跟着抖三抖。

StepSecurity 披露的信息显示,2026 年 3 月 31 日,npm 上出现了两个被投毒的 axios 版本:axios@1.14.1axios@0.30.4。攻击者并没有粗暴地往 axios 源码里塞后门,而是用了更隐蔽、也更现代的一招——新增一个根本不会被项目真正调用的依赖 plain-crypto-js@4.2.1,专门借助 postinstall 脚本在安装时下发远控木马(RAT)。

这种手法之所以阴险,在于它利用了开发者最松懈的时刻:npm install。大家装依赖时通常不会把每个子依赖翻开看,更不会想到一个大名鼎鼎的热门包,危险不在主代码,而在安装动作本身。你看到的是“升级 axios 小版本”,攻击者看到的是“把木马送进成千上万台开发机、CI 机器和服务器”的黄金通道。

攻击者不是在试水,而是在“做项目”

看完整个攻击时间线,我最大的感受不是“黑客真狡猾”,而是“这帮人很专业”。他们并不是临时起意,往 npm 上丢个恶意包碰碰运气,而是像做一场精心排练的演出。

在真正污染 axios 之前,攻击者先用一个临时账号发布了 plain-crypto-js@4.2.0,内容干净,伪装成正常的 crypto-js 变体,只为给这个包建立“历史记录”。18 小时后,再发布带有恶意 postinstall4.2.1。这一步很有意思:很多安全工具会盯着“刚发布、没历史、作者陌生”的包,但如果一个包已经有了前序版本,看起来就没那么扎眼。说白了,这像是先办了一张假身份证,再拿它去银行开户。

更关键的是,这次攻击不是从 GitHub 仓库的代码流程打进去的,而是直接利用了 axios 主要维护者的 npm 账号凭证。正常的 axios 1.x 发布,本来是通过 GitHub Actions 加上 npm 的 OIDC Trusted Publisher 机制完成,发布链路可验证、令牌也短时有效,理论上不容易被窃取。可这次的恶意版本跳过了整套 CI/CD 流程,直接用被盗的长期 npm token 手工发布,连对应的 GitHub commit 和 tag 都不存在。换句话说,这不是代码仓库失守,而是“钥匙被偷了,贼直接从正门进来”。

这一点非常值得行业警惕。过去几年,大家一直在强化代码审查、分支保护、自动化测试,但很多项目对包管理平台账号本身的保护仍然不够。现实很残酷:你把仓库门装成防弹玻璃,不代表家门钥匙就不会丢。

真正的危险,藏在依赖树最不起眼的角落

这次投毒最经典的一点,是它几乎没有碰 axios 业务逻辑。两个恶意版本与前一个干净版本相比,差异非常“克制”:只是多了一个运行时依赖 plain-crypto-js@^4.2.1。除此之外,其它依赖都没变。一个小改动,换来的是跨平台木马投递能力。

更夸张的是,这个依赖在 axios 的源码里根本没有被 importrequire() 过。它存在的唯一意义,就是触发安装阶段的 postinstall。这相当于有人往你的外卖里塞了一把不属于这道菜的钥匙,而你只有在门已经被打开后,才意识到这把钥匙根本不该出现。

从技术细节看,这个名为 setup.js 的安装脚本也不是随便写写。它做了双层混淆,隐藏系统判断、C2 地址、shell 命令和文件路径,运行后会按操作系统下发不同的二阶段载荷:在 macOS 上伪装成系统缓存目录里的可执行文件,在 Windows 上用 VBScript 和 PowerShell 悄悄落地,在 Linux 上则下载 Python 脚本后台运行。三套投递链路提前准备好,共用一个指挥控制服务器。

这类设计说明攻击者对开发环境很熟。开发机不只是“写代码的电脑”,往往还存着云平台密钥、SSH 凭证、CI token、生产环境访问权限、企业 VPN、私有 npm 源账号,甚至浏览器里还开着各种后台。攻下一台开发机,有时比攻下一台普通办公电脑值钱得多。过去不少供应链攻击——从 event-stream 到 ua-parser-js,再到 3CX 事件——都在提醒同一个现实:软件供应链最贵的不是代码本身,而是代码背后连着的信任网络。

最让人后背发凉的,是它会“擦屁股”

很多恶意 npm 包并不高明,安装之后一查目录,证据基本都在。但这次不一样。plain-crypto-js 在执行完以后,会删除自己的 setup.js,再把带 postinstallpackage.json 删掉,最后用预先准备好的干净 package.md 重命名回 package.json。也就是说,当你事后去翻 node_modules,看到的可能是一个“没有任何异常”的包。

这招非常像职业犯罪里的清理现场:门锁看着完好,地面拖得很干净,监控还被覆盖了。对于很多企业来说,这比直接留后门更麻烦,因为它会误导排查方向。开发者可能会以为“我检查过依赖目录,没发现 postinstall 啊”,但实际上,证据已经被程序自己抹掉了。

所以 StepSecurity 才给出了相当强硬的结论:只要你装过 axios@1.14.1axios@0.30.4,就应该默认机器已经失陷。不是“建议检查一下”,而是“按被攻破处理”。这类判断听起来刺耳,但在响应供应链攻击时,宁可多算,也不能少算。因为一旦远控已经跑起来,损失往往不止这一台机器,而是这台机器曾经接触过的所有系统、密钥和内部网络。

这件事真正刺痛行业的地方:默认信任,可能到了该改的时候

为什么这次事件格外重要?因为它打中了现代软件开发最脆弱、也最难彻底修补的一环:我们对开源依赖的默认信任。

JavaScript 生态的繁荣,建立在极低的复用门槛上。一个项目动辄上千个包,开发者很难逐个审计,只能相信热门项目、相信维护者、相信包管理器、相信自动化流程。这种信任平时让创新跑得飞快,但一旦被利用,破坏力也会被同样的网络结构瞬间放大。axios 每周下载量超过 3 亿次,这意味着哪怕只有极小一部分用户在短窗口期内升级到恶意版本,受影响面都可能非常可观。

更值得思考的是,像 postinstall 这种机制,到底该不该继续被如此宽松地默认启用?它确实解决了部分原生模块编译、环境准备、安装后配置的问题,但也长期是恶意包最爱的入口之一。过去几年,社区已经越来越频繁地讨论“最小化安装脚本权限”“对未使用依赖做高风险提示”“将发布来源验证默认展示给用户”等方向。axios 这次中毒,很可能会把这些讨论再次推上台面。

在我看来,未来的开源安全不会只拼“谁扫描得更快”,而是拼谁能把信任做成分层结构。比如:维护者强制硬件密钥和多重验证、npm 侧默认高亮非 OIDC 发布、企业内部对高危 postinstall 做拦截、CI 环境把依赖安装与密钥访问隔离开来、SBOM 和依赖行为分析成为默认配置。开发者不可能手查每一行代码,但平台至少应该把“哪里不符合历史模式”这件事大声说出来。

对普通开发团队来说,现在最实际的动作也很明确:如果装过这两个版本,立刻回退到 axios@1.14.0axios@0.30.3,并轮换受影响机器上的所有凭证,检查到可疑域名和相关 C2 的网络连接记录。别把它当成一次普通的依赖升级事故,它更像是一场悄无声息的办公室入侵。

说到底,这不是 axios 一家的耻辱,而是整个开源世界的集体压力测试。只是这次,考卷来得有点太突然了。

Summary: 我的判断是,axios 这次事件会成为 npm 生态的一个分水岭。它证明了攻击者已经不满足于“发一个恶意包等人上钩”,而是开始系统性地研究维护者账号、发布流程和取证盲区。接下来,包管理平台与大型开源项目大概率会加速推进强制 OIDC、硬件级身份验证以及安装脚本审计。对开发者来说,一个不太舒服但必须接受的现实是:未来“npm install”不再只是装软件,它也是一场安全边界的谈判。
npm 供应链攻击axiospostinstall 脚本JavaScriptNode.jsnpm依赖投毒RAT 远控木马plain-crypto-jsStepSecurity