🔥 紧急预警:Axios 供应链被投毒,恶意版本针对全栈开发者

5 阅读4分钟

2026年3月31日,npm 最流行的 HTTP 客户端库 axios 遭遇供应链攻击,两个恶意版本已发布。每一个用 axios 的前端/Node.js 项目都必须立即检查!


📢 事件回顾

攻击时间线(UTC):

| 时间 | 事件 |

|------|------|

| 03-30 05:57 | 攻击者发布 plain-crypto-js@4.2.0(伪装良性包) |

| 03-30 23:59 | 发布恶意版本 plain-crypto-js@4.2.1(含后门) |

| 03-31 00:21 | 发布 axios@1.14.1(注入恶意依赖) |

| 03-31 01:00 | 发布 axios@0.30.4(针对 0.x legacy 版本) |

攻击手法:

  • 盗取核心维护者 jasonsaayman 的 npm 账号

  • 手动发布恶意包,绕过 GitHub Actions CI/CD

  • 注入隐蔽依赖 plain-crypto-js@4.2.1

  • 自动执行 postinstall 脚本,部署跨平台远控木马(RAT)

  • 攻击后自毁痕迹,无声无息窃取数据

⚠️ 这波攻击非常专业:18小时预潜伏、3平台payload、痕迹自毁。是npm生态有史以来最复杂的供应链攻击之一。


🚨 影响范围

  • axios 周下载量:3000万+

  • 影响版本: axios@1.14.1axios@0.30.4

  • 危险行为: 安装时自动下载并执行远控木马,窃取敏感数据


🛡️ 立即检测你的项目

方案一:npm 命令(单项目)


# 检查当前项目安装的 axios 版本

npm list axios --depth=0 2>/dev/null | grep -E "1\.14\.1|0\.30\.4"

  


# 如果输出为空,说明安全;如果显示版本号,立即处理!

方案二:递归检测脚本(适合排查所有项目)


#!/usr/bin/env python3

"""

Axios 恶意版本检测脚本

检测 Documents 目录下所有前端项目

用法: python3 axios-check.py

"""

  


import json

import os

import sys

  


def check_projects(root_path):

    """递归检测所有 package.json 和 package-lock.json"""

    

    home = os.path.expanduser(root_path)

    pkgs = []

    locks = []

    

    # 收集所有项目文件

    for root, dirs, files in os.walk(home):

        if 'node_modules' in root:

            continue

        for f in files:

            if f == 'package.json':

                pkgs.append(os.path.join(root, f))

            elif f == 'package-lock.json':

                locks.append(os.path.join(root, f))

    

    print(f"📦 扫描 package.json: {len(pkgs)} 个")

    print(f"🔒 扫描 package-lock.json: {len(locks)} 个")

    print()

    

    # 恶意版本列表

    MALICIOUS_VERSIONS = ['1.14.1', '0.30.4']

    

    # ========== 1. 检查 package.json ==========

    print("=" * 70)

    print("【1】package.json 直接依赖检查")

    print("=" * 70)

    

    risk_pkgs = []

    for pkg_file in sorted(pkgs):

        try:

            with open(pkg_file, 'r', encoding='utf-8') as f:

                data = json.load(f)

            ver = data.get('dependencies', {}).get('axios', None)

            if not ver:

                ver = data.get('devDependencies', {}).get('axios', None)

            if ver and ver in MALICIOUS_VERSIONS:

                risk_pkgs.append((pkg_file, ver))

                print(f"🔴 风险: {pkg_file} -> axios@{ver}")

        except:

            pass

    

    if not risk_pkgs:

        print("✅ package.json 检查通过")

    

    # ========== 2. 检查 package-lock.json ==========

    print()

    print("=" * 70)

    print("【2】package-lock.json 实际安装版本检查")

    print("=" * 70)

    

    risk_locks = []

    for lock_file in sorted(locks):

        try:

            with open(lock_file, 'r', encoding='utf-8') as f:

                data = json.load(f)

            packages = data.get('packages', {})

            for key, info in packages.items():

                if 'axios' in key:

                    ver = info.get('version', '')

                    if ver in MALICIOUS_VERSIONS:

                        risk_locks.append((lock_file, ver, key))

                        print(f"🔴 风险: {lock_file} -> axios@{ver}")

        except:

            pass

    

    if not risk_locks:

        print("✅ package-lock.json 检查通过")

    

    # ========== 3. 检查恶意依赖 plain-crypto-js ==========

    print()

    print("=" * 70)

    print("【3】恶意依赖 plain-crypto-js 检查")

    print("=" * 70)

    

    plain_found = []

    for lock_file in sorted(locks):

        try:

            with open(lock_file, 'r', encoding='utf-8') as f:

                data = json.load(f)

            packages = data.get('packages', {})

            for key, info in packages.items():

                if 'plain-crypto-js' in key:

                    plain_found.append((lock_file, key, info.get('version', '?')))

                    print(f"🔴 风险依赖: {lock_file} -> {key}")

        except:

            pass

    

    if not plain_found:

        print("✅ 无 plain-crypto-js 恶意依赖")

    

    # ========== 总结 ==========

    print()

    print("=" * 70)

    print("【总结】")

    print("=" * 70)

    total = len(risk_pkgs) + len(risk_locks) + len(plain_found)

    if total == 0:

        print("✅ 全部安全!未发现恶意版本")

    else:

        print(f"⚠️  发现 {total} 个风险点,请立即处理!")

        print()

        print("📋 修复方案:")

        print("   1. 删除 node_modules 和 package-lock.json")

        print("   2. 安装安全版本: npm install axios@1.14.0 或 axios@0.30.3")

        print("   3. 重新安装: npm install")

    

    return total

  


if __name__ == "__main__":

    root = sys.argv[1] if len(sys.argv) > 1 else "~/Documents"

    check_projects(root)

使用方法:


# 保存为 axios-check.py

chmod +x axios-check.py

  


# 检测当前目录

python3 axios-check.py .

  


# 检测 Documents 目录

python3 axios-check.py ~/Documents

  


# 检测指定目录

python3 axios-check.py /path/to/your/projects


🔧 如果发现风险,这样修复


# 1. 进入项目目录

cd your-project

  


# 2. 删除安装缓存

rm -rf node_modules package-lock.json

  


# 3. 重新安装安全版本

npm install axios@1.14.0   # 1.x 系列

# 或

npm install axios@0.30.3   # 0.x 系列

  


# 4. 验证

npm list axios --depth=0


📝 总结

| 检查项 | 状态 |

|--------|------|

| 恶意版本 1.14.1 / 0.30.4 | ❌ 立即排查 |

| plain-crypto-js 依赖 | ❌ 立即排查 |

| 推荐升级到的安全版本 | ✅ 1.14.0 / 0.30.3 |


📢 呼吁

请转发提醒身边的开发者:

每一个用 axios 的项目都必须检查!攻击者针对的是整个 JavaScript 生态,不要心存侥幸。


🔗 参考链接


本文检测脚本基于真实事件编写,已通过作者本地 120+ 项目验证。