
2026年3月31日凌晨,JavaScript生态经历了一次堪称“核武级”的供应链打击。
axios,这个每周下载量超过1亿次、被超过17万个项目直接依赖的HTTP客户端库,被黑客通过劫持维护者账号的方式,悄无声息地注入了恶意代码。
攻击者没有修改任何一行axios源码,仅仅是添加了一个被称为“幽灵依赖”的包,就在短短15秒内,完成了对macOS、Windows和Linux系统上开发者的无差别渗透。今天,我们就来详细拆解这次攻击的始末、手法以及开发者该如何应对。
账号被盗:一场精心策划的入侵
3月31日00:21 UTC,有人使用axios主要维护者jasonsaayman的账号,向npm推送了两个新版本:axios@1.14.1和axios@0.30.4。
这两个版本在GitHub上没有任何对应的提交记录,没有PR,也没有CI/CD构建日志。它们是直接用被盗的npm发布令牌手动发布的。更狡猾的是,攻击者在接管账户后,将注册邮箱替换为攻击者控制的ProtonMail地址,完成了账号归属的静默转移,为后续持久化控制埋下伏笔。
整个攻击并非临时起意,而是经过了周密的预演。在主攻发起前的18小时,攻击者就开始了布局:
- 3月30日05:57 UTC:发布一个干净的
plain-crypto-js@4.2.0。这一步是为了建立发布历史,绕过安全扫描器对“全新可疑包”的警报。
- 3月30日23:59 UTC:发布带有恶意代码的
plain-crypto-js@4.2.1。
- 3月31日00:21 UTC:发布
axios@1.14.1,在依赖中引入恶意包plain-crypto-js。
- 3月31日01:00 UTC:发布
axios@0.30.4,覆盖仍在使用旧版0.x分支的用户。
两个恶意版本之间仅间隔39分钟,同时覆盖了1.x和0.x两条版本线,这显然是精心规划后的协同行动。
幽灵依赖:最精妙的设计
这次攻击最令人不寒而栗的细节在于:axios的源码本身是完全干净的,没有任何恶意代码。
安全研究人员对比了干净版本与恶意版本的差异,发现唯一的改动位于package.json中的依赖声明部分:
{
“dependencies”: {
“follow-redirects”: “^1.15.0”,
“form-data”: “^4.0.0”,
“proxy-from-env”: “^1.1.0”,
“plain-crypto-js”: “^4.2.1” // ← 唯一新增的恶意依赖
}
}
关键在于,这个plain-crypto-js包从未被axios的任何代码import或require。它存在的唯一目的,就是触发npm install过程中的postinstall生命周期钩子。
当你执行npm install axios时,npm会自动解析并下载这个依赖,然后执行其postinstall脚本。不需要你写任何额外代码,不需要你调用axios的任何特定功能,只要你安装了受污染的版本,攻击就已开始。
这种攻击模式被称为“幽灵依赖攻击”——一个仅为了副作用(执行脚本)而存在,与项目功能无关的依赖。
15秒沦陷:攻击链条全解析
当你运行npm install axios@1.14.1时,以下链条在后台被自动触发:
- 依赖解析:npm解析依赖树,发现axios新增了对
plain-crypto-js@^4.2.1的依赖。
- 自动下载:npm下载这个从未在axios历史版本中出现过的包。
- 执行钩子:npm执行该包的
postinstall脚本:node setup.js。
- 联系C2:
setup.js脚本运行,连接攻击者的命令与控制(C2)服务器(sfrclak.com / 142.11.206.73)。
- 下载载荷:根据受害者操作系统,下载对应的远程访问木马(RAT)。
- 自我擦除:删除
setup.js脚本,并将package.json替换为一个干净的版本以隐藏痕迹。
- 完成:全程耗时仅约15秒。
当你发现异常时,攻击者可能已经获得了你系统的完全控制权。
跨平台RAT:三套针对性载荷
恶意投放器(setup.js)会根据操作系统架构下载不同的第二阶段有效载荷:

Windows版本还会创建注册表项以实现持久化:
HKCU\Software\Microsoft\Windows\CurrentVersion\Run\MicrosoftUpdate
这三个变体共享同一套C2通信协议:使用HTTP POST请求发送Base64编码的JSON数据。木马支持多种命令,包括执行任意脚本、注入二进制程序、浏览文件系统以及自毁。
一旦这些文件出现在你的系统中,就意味着该系统已完全沦陷。
毁尸灭迹:反取证的艺术
这是本次事件中技术成熟度极高的一个环节。投递器在完成载荷投递后,会执行三步自我清除操作:
- 删除自身:
fs.unlink(__filename)。
- 删除证据:删除包含恶意
postinstall钩子的package.json文件。
- 狸猫换太子:将一个预先准备好的、干净的存根文件
package.md重命名为package.json。这个文件版本号显示为4.2.0,且没有任何scripts字段。
操作完成后,node_modules/plain-crypto-js/目录看起来就像从未安装过恶意版本一样。setup.js消失了,package.json显示的也是干净版本的信息。
这一设计使得事后取证极为困难——除非在npm install运行时进行实时的进程级监控,否则仅凭磁盘上的静态文件分析,几乎无法判断系统是否曾被植入过恶意代码。
归因分析:朝鲜 Lazarus 组织
Google威胁情报团队和Elastic Security Labs将这次攻击归因于朝鲜 Lazarus 组织(具体由其下属的BlueNorOff子组织执行)。
归因的关键证据来自macOS平台的载荷。那个伪装成Apple守护进程的Mach-O可执行文件,与2026年2月Mandiant披露的WAVESHAPER后门存在显著的代码重叠:
- 路径高度相似:落地路径
/Library/Caches/com.apple.act.mond与WAVESHAPER使用的/Library/Caches/com.apple.mond几乎一致。
- 硬编码指纹:木马中硬编码的User-Agent字符串
mozilla/4.0 (compatible; msie 8.0; windows nt 5.1; trident/4.0)与WAVESHAPER完全一致。
- 命令相同:用于收集macOS用户进程列表的命令
sh -c ps -eo user,pid,command也完全相同。
Lazarus组织活跃已久,尤其擅长供应链攻击,此前已发起过3CX攻击、Operation Brainleeches等多起知名事件。
如何检测:完整排查指南
第一步:检查项目中的axios版本
# 检查当前项目依赖树中的axios版本
npm list axios 2>/dev/null | grep -E “1\.14\.1|0\.30\.4”
# 检查package-lock.json或yarn.lock
grep -A1 ‘“axios”’ package-lock.json | grep -E “1\.14\.1|0\.30\.4”
# 检查全局安装的axios
npm list -g axios 2>/dev/null | grep -E “1\.14\.1|0\.30\.4”
第二步:检查是否存在“幽灵依赖” plain-crypto-js
# 检查项目node_modules目录
ls node_modules/plain-crypto-js 2>/dev/null && echo “🚨 POTENTIALLY AFFECTED”
# 在全系统范围深度扫描
find ~ -name “plain-crypto-js” -type d 2>/dev/null
注意:node_modules/plain-crypto-js/目录的存在本身就足以证明恶意投放器已执行,因为这个包从未出现在任何合法的axios版本中。
第三步:检查系统级RAT残留文件
# macOS 检查
ls -la /Library/Caches/com.apple.act.mond 2>/dev/null && echo “🚨 COMPROMISED”
# Linux 检查
ls -la /tmp/ld.py 2>/dev/null && echo “🚨 COMPROMISED”
# Windows (在cmd.exe中检查)
dir “%PROGRAMDATA%\wt.exe” 2>nul && echo COMPROMISED
第四步:检查网络痕迹 (IOC)
# 检查系统日志中是否有连接恶意C2的记录
grep -r “sfrclak.com” /var/log/ 2>/dev/null
grep -r “142.11.206.73” /var/log/ 2>/dev/null
如何修复:紧急响应步骤
场景一:仅发现恶意axios版本,未发现RAT残留
-
立即降级到安全版本:
npm install axios@1.14.0 # 1.x 用户
npm install axios@0.30.3 # 0.x 用户
-
在package.json中锁定版本,防止依赖解析再次拉取恶意版本:
{
“overrides”: { “axios”: “1.14.0” },
“resolutions”: { “axios”: “1.14.0” }
}
-
清除并重新安装依赖(忽略脚本):
rm -rf node_modules/plain-crypto-js
rm -rf node_modules
npm install --ignore-scripts
-
审计CI/CD流水线,检查是否有构建任务安装了受影响版本,如有则需轮换所有相关的密钥和凭证。
场景二:发现RAT残留文件(系统已沦陷)
切勿尝试原地清理!
- 立即网络隔离:断开受影响机器的网络连接,防止数据持续外泄。
- 从干净环境重建:重装操作系统,或从已知干净的备份镜像恢复。
- 全面轮换凭据:包括SSH密钥、所有API Key、数据库密码、云服务凭证、CI/CD Secrets、环境变量中的任何敏感信息。
- 进行横向排查:检查同一网络环境下的其他设备是否受到影响。
CI/CD 防护:防止自动化构建中招
在自动化构建流程中,务必使用--ignore-scripts参数来阻止所有postinstall/preinstall脚本的执行。
# GitHub Actions 示例
- name: Install dependencies
run: npm ci --ignore-scripts # 关键:阻止所有生命周期脚本
同时,在package.json中锁定依赖的精确版本,避免使用^或~等自动升级的版本范围符号:
{
“dependencies”: {
“axios”: “1.14.0” // 使用精确版本号,而非 “^1.14.0”
}
}
在网络层面,可以预先阻断已知的恶意基础设置(IOC):
- 防火墙规则:屏蔽IP地址
142.11.206.73。
- DNS黑名单:拦截域名
sfrclak.com。
给开发者的安全加固建议
短期行动(立即执行)
- 运行上述检测脚本,全面评估自身系统与项目风险。
- 在所有项目中锁定axios版本至安全版本(1.14.0 或 0.30.3)。
- 确保所有CI/CD流程都使用
--ignore-scripts参数。
- 审视并轮换可能已泄露的凭据。
中期加固(本周内完成)
- 启用专业的依赖安全扫描工具(如Socket、Snyk等)。
- 审查项目关键直接依赖的维护者账号安全状况(如是否启用2FA)。
- 建立依赖更新审批流程,禁止对核心依赖进行自动重大版本升级。
- 在网络设备(防火墙、DNS)层面阻断本次事件中已知的恶意IOC。
长期策略(持续改进)
- 精简依赖:定期评估每个依赖的必要性,减少攻击面。
- 代码内置(Vendoring):对于极其核心的依赖,考虑将其代码直接纳入项目仓库,而非通过包管理器安装。
- 建立私有仓库:使用Verdaccio等工具搭建内部npm镜像,对外部包进行审查和审计后再同步给内部开发者使用。
- 推动生态改进:支持社区关于
postinstall脚本沙箱化、强制2FA、包发布签名验证等安全提案。
结语:建立深度防御体系
Axios投毒事件再次无情地揭示,现代软件开发建立在一条漫长而脆弱的信任链之上。一个维护者账号的失守,就足以让数百万开发者及其所在组织暴露在风险之中。
作为开发者,我们无法完全规避风险(那意味着停止使用一切外部依赖),但可以致力于构建深度防御体系:
- 锁定版本:不盲目追求最新版本,优先使用经过验证的稳定版本。
- 审计依赖:不盲目信任,了解项目引入了什么,以及谁在维护它。
- 隔离环境:使用容器、虚拟机等隔离技术,避免在主机上直接“裸奔”运行项目。
- 监控异常:建立安全监控,对网络连接、文件创建等异常行为保持警觉。
这次事件是一个警钟。建议你立即着手检查你的项目,并按照指南轮换可能受到影响的密钥。在技术社区如云栈社区中保持对这类安全事件的关注与交流,是提升集体防御能力的重要一环。安全之路,道阻且长,行则将至。