当信任崩塌:从 Fluke 到 Shai‑Hulud,npm 生态的七年安全裂变

114 阅读4分钟

引子:一位开发者的七年回声

2018 年 7 月 12 日,Jamie Kyle 敲下了这样一段文字——

“如果有人真的想做,他们完全可以写一个 npm 蠕虫:能够凭借维护者账户权限,把恶意版本推送到下游成千上万个项目中。我们唯一的防线是什么?人性。人们没做,只是因为他们是好人。”

他甚至用一个失败的样本 Fluke 做了验证——只因代码里有 bug,否则它可能会成为首个自我传播的 npm 攻击。

npm 官方的回应是“按预期工作,无意修复”。这一句话,为七年后 JavaScript 史上最大规模的供应链攻防战埋下了伏笔。


第一幕:AI 初入场的黑色礼花 —— S1ngularity 攻击

时间:2025 年 8 月 26 日
目标:Nx 构建系统生态
影响:几小时内泄露 1,000+ GitHub Token、数十个云凭证、约 2 万个敏感文件

作案过程

  1. 巧妙撬锁——攻击者利用 GitHub Actions 工作流注入漏洞,将恶意代码混入 CI 流程。
  2. “偷天换日”——窃取到长期有效的 npm 发布令牌(NPM_TOKEN)。
  3. AI 武器化——调用本地 CLI 版 Claude / Gemini / Amazon Q,生成“定制化”扫描策略,秒懂文件结构与命名模式,找到最值钱的密钥文件。
  4. 无声搬运——双重 Base64 编码,上传至以 s1ngularity-repository 为前缀的公共 GitHub 仓库。

🚨 关键突破:这是历史上第一次,AI 工具被编织进供应链恶意代码执行链,成为攻击者的中枢神经系统,使扫描与窃取更隐蔽、更精准。


第二幕:热门依赖的精准狙击 —— 钓鱼劫持核心包

时间:2025 年 9 月 8 日
目标chalkdebugansi-stylesstrip-ansi 等 18 个核心依赖
影响:累计每周下载量超过 26 亿,堪称“毒源入血”

攻击向量

  • 邮件发件人:support@npmjs.help
  • 主题:紧急:需要在 XX 日前更新双因素认证

在一个疲倦的早晨,核心维护者 Josh Junon 点击了钓鱼链接——
一次 AiTM 劫持,即时夺走了他的密码与 2FA 短信 Token。

结果:这批支撑无数 CLI 工具、构建框架、终端渲染的基础库,瞬间被注入后门。

⚠️ 依赖链杀伤效应:当基础组件被毒化,相当于在操作系统的 libc 下毒,几乎所有上层应用都会入口感染。


第三幕:沙丘巨虫出击 —— Shai‑Hulud 蠕虫

时间:2025 年 9 月 14 日至 15 日
攻击模型:首次实现 npm 生态自我复制传播

蠕虫传播算法(简化伪码)

function propagate() {
  const token = findNpmToken();
  if (!token) return;
  const pkgs = getAccessiblePackages(token)
                 .sort(by('downloads'))
                 .slice(0, 20);
  pkgs.forEach(pkg => {
    injectPayload(pkg);
    publishNewVersion(pkg, token);
  });
}

“技能树”全开:

  • 自动横向移动:发现新 NPM_TOKEN → 全面接管可访问包。
  • 持久化:植入恶意 GitHub Actions 到受害者仓库。
  • 外泄链路
    • 创建 “Shai-Hulud” 公共仓库,双 Base64 存放窃取的密钥
    • Webhook 实时推送
  • 数据层突破:扫描 .ssh/.aws/credentials.env,以及云服务元数据接口(AWS IMDS、GCP/Azure Metadata)。

深度溯源:为什么 npm 抵挡不住?

  1. 长期有效 Token + 无发布审批 = 单点全死
    一旦获取 Token,你就是这个账户下所有包的“上帝”。
  2. postinstall 的权限裸奔
    没有容器隔离,没有权限沙箱,读写系统和连外网如入无人之境。
  3. CI/CD 权限配置松散
    PR 与主干构建共用一套高权限 Secrets,等于把钥匙挂在大门外面。
  4. 平台级失职
    GitHub/NPM(微软)掌握全球 JS 代码与包分发入口,却将核心安全特性(包签名、强制 2FA、脚本审查)设为可选。

实战级缓解方案

开发者(包使用者)

  • 锁版本:提交 package-lock.json / yarn.lock 并启用 npm ci
  • 切换 pnpm 或安装时使用 --ignore-scripts
  • CI 集成依赖扫描:Socket.dev / Snyk

维护者(包发布者)

  • 启用硬件 2FA
  • 迁移至 OIDC Trusted Publishing
    permissions:
      contents: read
      id-token: write
    
  • 粒度化 Token:限制某个 Token 只能发布一个包
  • 审核工作流权限:Secrets 按分支控制,仅必要范围开放

平台方(日光下的“裁判”)

  • 将 OIDC 发布模式列为强制
  • 默认禁用 postinstall
  • 对“高影响包”引入多签发布与人工审批

未来三大变数

  1. AI 驱动的“智能蠕虫”
    可实时自适应目标环境与防御策略。
  2. 零信任供应链管理
    不信任任何依赖,全部验签 + 沙箱执行。
  3. 法规硬约束
    欧盟 CRA、美 NIST SSDF 等将要求开源供应链安全达到合规标准。

尾声:信任是砂,不是钢

Fluke 没成,Shai‑Hulud 成了。
七年时间,技术债、信任债一起复利滚成了沙丘巨虫。

这场攻击提醒我们:

  • 供应链安全不是产品功能,而是系统生命线
  • 信任不能只寄托于人的善意。
  • 安全不是锦上添花,而应是上线门槛。

代码世界的沙海很美,但在巨兽觉醒前,我们最好先把海底的地雷排干净。