2026年5月11日,一场代号"Mini Shai-Hulud"的供应链攻击在npm生态中爆发。攻击者接管阿里巴巴AntV维护者账户后,仅用20分钟就向317个npm包推送了630多个恶意版本。一周后Microsoft发布技术报告确认:这次攻击的载荷专门设计用于从GitHub Actions环境中窃取六大平台的CI/CD凭据——包括GitHub Token、AWS密钥、HashiCorp Vault令牌、npm发布令牌、Kubernetes Secrets甚至1Password密码管理器。GitHub随后紧急吊销了61,274个npm细粒度访问令牌。更令人不安的是,攻击者还伪造了SLSA供应链溯源签名,动摇了整个软件供应链证明体系的信任根基。

这不是"某个开发者中了木马"的故事,而是一面镜子——照出了整个软件工业对默认信任的脆弱依赖。


在这里插入图片描述

一、攻击全貌:从@antv到你的CI/CD流水线

1.1 时间线

时间 事件
5月11日 攻击者攻破npm上@antv组织一名维护者账户,开始发布恶意版本
5月11日(20分钟内) 317个npm包推送630+恶意版本
5月12-13日 恶意代码在依赖链中向下游扩散,echarts-for-react(周下载100万+)等顶级包被波及
5月14-18日 Microsoft安全团队确认攻击,发布深度技术分析报告
5月20日前 GitHub完成清理:移除640个恶意包,吊销61,274个npm令牌

1.2 传播链路

@antv维护者账户被攻破
        ↓
317个npm包 × 630+恶意版本(20分钟内完成推送)
        ↓
echarts-for-react(100万+周下载)等下游大包自动拉取
        ↓
数千个项目CI/CD流水线执行 npm install
        ↓
preinstall钩子自动触发恶意脚本
        ↓
六大平台凭据被窃取并外泄到C2服务器

一句话概括:你什么都没做错,只是运行了一次 npm install——然后你公司所有的CI/CD密钥就全没了。

1.3 窃取目标清单

Microsoft报告确认,恶意载荷针对六大平台进行系统性凭据窃取:

平台 窃取手法 窃取内容
GitHub 提取GITHUB_TOKEN、扫描ghp_/ghs_前缀令牌、通过/user API验证有效性 仓库Secrets、组织级Secrets
AWS 查询实例元数据服务(169.254.169.254)、ECS元数据、遍历所有区域调用SecretsManager:ListSecrets IAM凭据、Secrets Manager密钥
HashiCorp Vault 扫描12+个令牌路径(/var/run/secrets/vault/token等),连接127.0.0.1:8200 Vault令牌、存储的密钥
npm 使用/-/whoami验证令牌、通过OIDC令牌交换发布权限 npm发布令牌、包管理权限
Kubernetes 读取Service Account令牌、枚举命名空间Secrets K8s集群凭据
1Password 与1Password CLI交互,尝试提取主密码、绕过2FA 密码管理器存储的所有凭据

二、技术深潜:恶意代码到底做了什么

2.1 环境门控——只在CI/CD中激活

恶意代码首先进行"环境体检",只在目标环境中执行:

// 解密后的逻辑(原始代码使用PBKDF2+SHA-256加密所有关键字符串)
this.isGitHubActions = process.env['GITHUB_ACTIONS'] === 'true';
this.isLinuxRunner     = process.env['RUNNER_OS'] === 'Linux';

// 非目标环境:静默退出,不留痕迹
if (!this.isGitHubActions || !this.isLinuxRunner) {
    return;  // 开发机器、Mac/Windows Runner:不受影响
}

这意味着:开发者在自己的电脑上跑npm install什么都不会发生,恶意代码只在CI/CD Runner(GitHub Actions + Linux)中激活——这正是凭据最集中的地方。

2.2 进程内存抓取——绕过Secret屏蔽

GitHub Actions会将Secret值自动屏蔽(***),但恶意载荷有一个大招——直接从Runner进程内存中抓取明文密钥

# 1. 通过/proc扫描定位Runner.Worker进程PID
findRunnerWorkerPIDLinux

# 2. 从进程内存直接提取密钥(绕过GitHub的Secret屏蔽机制)
tr -d '\0' | grep -aoE '"[^"]+":{"value":"[^"]*","isSecret":true}' | sort -u

这个攻击手法的本质是:GitHub的Secret屏蔽只在日志层面起作用,Runner进程内存中密钥是明文的。 攻击者不需要破解任何加密,只需要读取内存。

2.3 三级数据外泄通道

恶意载荷构建了三级冗余外泄通道:

通道 方式 特点
主通道 HTTPS加密直连C2(t.m-kosche.com:443 加密传输,难以检测
备用通道 Git Data API(在受害者仓库非保护分支创建blob/commit) 利用合法API,绕过出站防火墙
隐蔽标记 创建公开仓库,描述为反向字符串"niagA oG eW ereH :duluH-iahS" 已观察到2,200+个此类标记仓库

2.4 SLSA溯源伪造——捅破供应链信任

最具破坏性的是:攻击者伪造了SLSA来源证明。通过Sigstore(Fulcio/Rekor),恶意包被打上了看似合法的供应链证明标签。

这意味着:就算你的组织已经实施了SLSA框架做依赖验证,这次攻击中也可能被绕过。 供应链安全的"信任根"本身被污染了。


三、本质问题:默认信任是整个软件工业的阿克琉斯之踵

3.1 攻击者真正测试的是什么?

不是某个具体漏洞,而是:

代码托管(GitHub)
    ↓  默认信任
包管理(npm registry)
    ↓  默认信任  
自动构建(CI/CD Runner)
    ↓  默认信任
云基础设施(AWS/Vault/K8s)
    ↓  默认信任
企业核心资产

信任链太长,责任链太散。 开发者享受开源的速度,CI/CD自动拉最新版依赖,但没有人对整条链的安全负责。

3.2 "unzip and run"的致命缺陷

npm的preinstall/postinstall钩子本质上就是"下载后自动执行"——这在任何其他安全模型中都是不可接受的。一条package.json里的"scripts": {"preinstall": "node setup.js"},可以执行任意系统命令。

更致命的是,CI/CD Runner通常拥有:

  • GITHUB_TOKEN(仓库读写权限)
  • AWS_ACCESS_KEY_ID(云资源完全控制权)
  • NPM_TOKEN(包发布权限,可以进一步投毒)
  • VAULT_TOKEN(企业密钥库的钥匙)
  • 各种数据库密码和API密钥

一旦Runner沦陷,等于把公司所有基础设施的钥匙交给了攻击者。


四、防御方案:在CI/CD中建立凭据零信任体系

在这里插入图片描述

面对这类攻击,单点防御不够,需要建立纵深防御的凭据安全体系

4.1 第一层:消除硬编码凭据

根本问题:为什么你的CI/CD环境里有那么多长期有效的明文Token?

正确做法

# ❌ 错误:硬编码凭据(攻击者的首要目标)
env:
  AWS_ACCESS_KEY_ID: "AKIAXXXX"
  AWS_SECRET_ACCESS_KEY: "xxxxx"
  NPM_TOKEN: "npm_xxxx"

# ✅ 正确:OIDC临时凭据 + 最小权限
jobs:
  deploy:
    permissions:
      id-token: write  # OIDC临时令牌,仅本次运行有效
      contents: read
    steps:
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789:role/github-actions-deploy
          role-session-name: ${{ github.run_id }}

OIDC的核心优势:没有长期密钥可以偷。 每次CI/CD运行生成一次性令牌,攻击者即使拿到Runner内存也无法复用。

4.2 第二层:凭据管理服务统一管控

将CI/CD中所有凭据统一接入凭据管理服务,实现:

┌─────────────────────────────────────────────┐
│              凭据管理服务 (SMS)               │
│                                             │
│  ┌──────────┐ ┌──────────┐ ┌────────────┐  │
│  │ GitHub   │ │ AWS      │ │ Vault      │  │
│  │ Token    │ │ IAM Key  │ │ Token      │  │
│  │ TTL: 1h  │ │ TTL: 30m │ │ TTL: 2h    │  │
│  └──────────┘ └──────────┘ └────────────┘  │
│                                             │
│  动态凭据  │  自动轮转  │  审计日志  │  最小权限  │
└─────────────────────────────────────────────┘
         ↑                ↑
         │ 动态获取        │ 按需注入
    ┌────┴───┐        ┌───┴──────┐
    │ CI/CD  │        │ 应用容器  │
    │ Runner │        │  (Pod)   │
    └────────┘        └──────────┘

关键能力:

能力 作用 对本次攻击的防御效果
动态凭据 不设长期静态密钥,每次请求动态生成 攻击者无法窃取"固定的"密钥
TTL自动过期 凭据存活时间设为分钟级 窃取的密钥很快失效
自动轮转 密钥按策略自动替换 即使泄露也无法长期利用
统一审计 所有凭据访问留痕 及时发现异常凭据使用行为
最小权限 只授予当前任务必需的最小权限 限制泄露后的横向移动范围

4.3 第三层:密钥生命周期管理

对于无法完全消除的长期密钥(如CI/CD与外部系统对接的API Key),建立完整的密钥生命周期管理体系:

密钥生成 → 安全分发 → 加密存储 → 使用监控 → 定期轮转 → 安全销毁
  │                                           │
  │  硬件加密机(HSM)保护的密钥根               │  自动通知 + 强制更新
  │                                           │
  └───────────────────────────────────────────┘
             统一密钥管理平台 (KSP)

实操层面的四个要点:

  1. 密钥分级:将密钥分为临时凭据(分钟级TTL)和长期密钥(按天级轮转),分别管理
  2. 轮转自动化:CI/CD Runner启动时自动从凭据管理服务拉取当前有效凭据,退出时自动回收
  3. 访问审计:每次凭据访问记录操作者、时间、来源IP和用途,异常行为实时告警
  4. HSM根保护:密钥管理平台的根密钥存储在硬件加密机中,即使平台本身被攻破,根密钥也无法导出

五、CI/CD凭据加固三步检查清单

你可以在30分钟内完成以下自查:

第一步:盘点(5分钟)

# 检查你的CI/CD环境中暴露了哪些凭据
# GitHub Actions
gh secret list -R owner/repo

# GitLab CI
curl --header "PRIVATE-TOKEN: $TOKEN" \
  "https://gitlab.com/api/v4/projects/$PROJECT_ID/variables"

列出所有Secrets,逐个问自己:这个密钥必须长期有效吗?可以改用OIDC吗?

第二步:加固(15分钟)

# 最关键的改动:从长期Token切换到OIDC
# 为每个Cloud Provider配置OIDC Trust

# GitHub Actions示例
permissions:
  id-token: write  # ← 这是OIDC的核心
  contents: read   # 只读仓库代码,不暴露写权限

# GitLab CI示例  
id_tokens:
  AWS_TOKEN:
    aud: https://gitlab.com

第三步:验证(10分钟)

# 确认CI/CD Runner的环境变量中没有硬编码凭据
# 在所有CI/CD步骤开头加入检查
- name: Check for leaked secrets
  run: |
    if [ -n "$AWS_SECRET_ACCESS_KEY" ]; then
      echo "WARNING: Static AWS key detected!"
      exit 1
    fi
    echo "OK: No static credentials found"

六、总结

Mini Shai-Hulud攻击暴露的不是npm的漏洞,而是整个软件工业对默认信任的过度依赖。攻击者在20分钟内就完成了从维护者账户到CI/CD凭据的链式入侵,而防御方可能需要数周才能完成凭据轮转。

三条核心教训:

  1. "unzip and run"不安全。 你的CI/CD Runner拥有公司所有基础设施的钥匙,但它在每次构建时都会自动执行来自第三方依赖的代码。npm install不是无害的"安装",而是"执行攻击者可能控制的代码"。

  2. 长期静态凭据是定时炸弹。 GitHub紧急吊销6万多个npm令牌是亡羊补牢——真正的解决方案是用OIDC临时凭据 + 凭据管理服务取代所有长期密钥。

  3. 供应链信任需要端到端验证。 SLSA溯源伪造暴露了一个根本问题:当信任根本身被污染时,整个信任体系都会崩塌。需要从代码提交、构建到部署的全链路可验证性。


推荐阅读

工具推荐npm audit --production / gh secret list / OIDC配置检查工具


💬 话题讨论:你的CI/CD流水线中有多少长期有效的静态密钥?有没有考虑过切换到OIDC临时凭据?欢迎评论区聊聊你的实践经验。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐