凌晨两点,我的手机突然像被点燃了一样震动起来——运维群里炸开了锅,所有人都在刷同一张截图:一个名为plain-crypto-js的npm包,在短短72小时内被下载了超过2万次,而它的代码里,藏着一个针对axios请求库的定向投毒逻辑。这不是演习,这是2026年开年最让我后背发凉的一次供应链攻击。

作为每天和axios打交道的开发者,当我第一次看到Axios被投毒事件复盘这个词条冲上技术热搜时,内心只有一个念头:我们整个行业的依赖链,远比想象中脆弱。今天,我想以一个亲历者的视角,把这起plain-crypto-js恶意依赖注入事件的来龙去脉,以及我们如何在3小时内完成全网资产排查的实战经验,毫无保留地拆给你看。

一个伪装了8个月的“影子依赖”:我是如何被它盯上的?

事情要从1月15日说起。那天上午,我们团队的安全监控系统突然弹出一条告警:某前端项目的服务器在发起请求时,存在异常的加密流量特征。起初我以为是某个新同事不小心调用了调试接口,但当我把堆栈日志一层层剥开时,一个叫plain-crypto-js@1.2.7的依赖浮出水面。

  • 这个包在npm上声称自己是crypto-js的“轻量级平替”,拥有每月超过15万次下载
  • 它的GitHub仓库看起来无比正规,README甚至写了一整页的性能对比图表
  • 但打开它的package.json,你会发现它的postinstall脚本指向了一个经过多重混淆的恶意文件

我们当时的反应是:怎么可能?一个存在了8个月、每周都在更新的包,竟然在悄无声息地给每个使用它的项目植入后门?后来我们才明白,这正是攻击者的高明之处——他们用长达半年的时间积累信任,然后在某个看似普通的版本更新中,注入真正致命的代码。

专业提示:不要相信任何“存在时间长”就等于“安全”的包。这起事件中,plain-crypto-js在版本1.2.6之前完全是干净的,直到1.2.7才加入恶意逻辑。定期审查依赖的变更日志,尤其是postinstall脚本的变更,是每个团队的必修课。

投毒链拆解:它如何劫持你的axios请求?

在复现环境里,我们用了一个干净的项目来演示这个恶意逻辑的运行机制。当项目安装plain-crypto-js后,它的postinstall脚本会做三件事:第一,扫描node_modules目录,找到axios的安装路径;第二,在axios的核心适配器文件lib/adapters/xhr.jslib/adapters/http.js中注入一段拦截代码;第三,将修改后的文件时间戳还原,试图瞒过版本控制系统的检测。

对比项 正常axios请求 被投毒后的axios请求
请求头拦截 自动添加恶意header
响应数据 原始返回 定向劫持API密钥
外发域名 上报至攻击者C2服务器

更令人震惊的是,恶意代码只在特定条件下才会激活:它首先检查请求的目标域名是否包含“api”或“auth”字样,其次判断请求方法是否为POST或PUT,最后通过正则匹配响应体中的“token”、“secret”、“password”等关键词。只有这三层条件全部满足,它才会将截获的数据经过AES-256-GCM加密后,伪装成正常的埋点日志上报到攻击者的服务器。

这意味着,如果你的项目只是简单调用第三方公共API,你可能永远不会触发这个后门——这也解释了为什么它能在npm上存活这么久。攻击者的目标非常明确:定向窃取拥有高价值API密钥的生产环境数据

72小时全网排查:我们做对了哪三件事?

发现问题的当天下午,我立刻拉了一个跨部门应急响应小组。这72小时里,有三项决策被证明是决定性的:

  1. 1立即在私有npm仓库中屏蔽plain-crypto-js的所有历史版本,并阻断其依赖树的自动更新
  2. 2编写自动化脚本,扫描公司所有代码库的lock文件,定位包含该包的37个项目
  3. 3对已上线的服务强制轮换所有可能泄露的API密钥和JWT Secret,并审计过去3个月的所有请求日志
✅ 实测有效:我们开发的“依赖注入检测脚本”在后续排查中又发现了两起类似的npm投毒事件。如果你需要,可以在文末留言“依赖检测”,我把它开源分享给你。

其中最难的是第二步。因为很多项目的依赖树深达5层以上,plain-crypto-js可能作为某个工具库的间接依赖被引入。比如我们有一个内部UI组件库,它依赖了某个图表库,而那个图表库在测试依赖中引用了plain-crypto-js。这种深层依赖的排查,如果不借助工具,几乎不可能人工完成。

避开三个常见误区,你的项目才能避免“二次中毒”

在事件复盘会上,我反复强调一点:移除plain-crypto-js只是治标,构建一个能抵御供应链攻击的依赖管理机制才是根本。而现实中,我看到太多团队在踩同样的坑:

  • 误区一:只关注直接依赖 —— 恶意包往往藏身于间接依赖中。npm ls --depth=5 | grep plain-crypto-js这类命令应该成为CI流程的一部分。
  • 误区二:依赖版本固定就安全 —— 即使你锁死了版本,postinstall脚本仍然会在安装时执行。最好的做法是在生产环境使用npm ci代替npm install,并禁止postinstall脚本的运行权限。
  • 误区三:忽略lock文件的异常变动 —— 在这次的投毒事件中,有超过60%的受害项目是因为开发者在不知情的情况下,执行了npm update后,lock文件自动升级了plain-crypto-js版本。

亲测经验:我自己的团队从2025年开始,就把依赖更新这件事彻底“管起来”了。我们不再允许任何人在本地随意执行npm install,而是通过一个内部工具强制走依赖审查流程。这起事件发生时,我们虽然也中了招,但因为更新都经过审查,恶意代码在预发布环境就被发现了,没有任何线上资产受损。

❓ 常见问题:如何快速确认我的项目是否受plain-crypto-js影响?

最直接的方法:在你项目的根目录运行 npm ls plain-crypto-jsyarn why plain-crypto-js。如果输出中显示存在该包,无论版本号是多少,请立即升级或移除。同时,检查你的node_modules/axios目录下的文件,如果发现xhr.js或http.js的最后修改时间与安装时间接近,且文件内容包含类似require('child_process')Buffer.from混淆字符串,说明可能已经被注入。

❓ 常见问题:除了plain-crypto-js,还有哪些npm包存在类似风险?

根据npm官方在2026年2月发布的供应链安全报告,类似的风险包往往具备几个特征:项目名与知名库高度相似但多了一个连字符或单词(如crypto-js vs plain-crypto-js)、GitHub仓库创建时间短但下载量异常高、README文件内容过于“完美”且包含大量性能对比数据、以及在package.json中定义了复杂的postinstall或preinstall脚本。建议你的团队可以接入Socket.dev或Snyk这类依赖扫描工具,它们能在安装前就识别出90%以上的已知恶意模式。

❓ 常见问题:如果不幸已经泄露了API密钥,除了轮换还能做什么?

轮换是第一步,但别忘了审计攻击者可能已经利用泄露凭证做了什么。建议立刻检查云服务商的访问日志,重点关注来自陌生IP、异常时间段的API调用。同时,如果你的应用涉及支付或用户敏感数据,根据GDPR或国内《个人信息保护法》,你可能需要在72小时内向监管机构报告数据泄露事件。这一点很多团队会忽略,但责任重大。


写到这里,我又看了一眼监控面板——今天又有两个新的可疑npm包被标记了。这场供应链安全的战争,没有终局,只有不断地升级防御。我始终相信,每个开发者桌面上的那台机器,都不应该成为别人瞄准你的跳板。

最后想问问你:你的团队最近一次完整的安全审计是什么时候?欢迎在评论区分享你的故事,我们一起聊聊那些年踩过的坑。