前言¶
2026 年 8 月 4 日,Microsoft Threat Intelligence 披露了一起代号为 ChainDrop 的大规模 npm 供应链攻击。攻击者在数小时内向 npm 注册表发布了 440 余个包、2200 多个恶意版本,波及 keyv、flat-cache、cache-manager 等高频依赖,累计周下载量超过 5 亿次。这不是一次简单的包投毒——蠕虫通过 preinstall 生命周期钩子在 npm install 阶段自动执行,窃取 npm、GitHub、AWS、Kubernetes、HashiCorp Vault 等凭证,再利用被盗身份自动修改并重新发布更多包,形成自我传播的感染链。部分变种还会向仓库注入 Claude 与 VS Code 配置文件,建立持久化后门。
本文基于 Microsoft、StepSecurity、SecurityWeek 等公开分析报告,梳理 ChainDrop 的攻击链路、技术特征与可操作的防护建议。
事件概览:从 11 个种子包到 400+ 感染¶
ChainDrop 属于 Mini Shai-Hulud 蠕虫家族的演进版本,是 2025 年以来 npm 供应链攻击浪潮中的最新一轮。与此前 Shai-Hulud 攻击类似,但引入了 EtherHiding(以太坊区块链 C2)等新技术。
时间线(UTC,2026-08-04):
- 攻击者首先入侵
keyv、cacheable等命名空间维护者 Jared Wray 的 GitHub 账户。 - 约 09:00 UTC,
keyv@6.0.0等 11 个「种子包」被发布,携带完整蠕虫载荷。 - 恶意包在开发者工作站与 CI/CD _runner 上执行后,窃取凭证并自动向 npm 重新发布被感染包。
- 四小时内,StepSecurity 观测到 444 个包、2212 个版本 被投毒;npm 团队在约两小时内开始下架恶意版本,但蠕虫仍可通过新被盗账户持续传播。
受影响范围:
| 指标 | 数据 |
|---|---|
| 感染包数量 | 440+(StepSecurity 统计 444) |
| 恶意版本数 | 2200+(StepSecurity 统计 2212) |
| 种子包 | 11 个(keyv、cacheable 生态) |
| 蠕虫自动传播 | 433 个额外包 |
| 周下载量 | 超 5 亿次(SecurityWeek) |
典型高危包包括 keyv@6.0.0(周下载约 1.5 亿)、flat-cache@6.1.24(约 1.5 亿)、file-entry-cache@11.1.6(约 1.47 亿)——它们广泛出现在 ESLint、cache-manager 等工具链中,意味着大量 JavaScript 项目可能间接依赖了被感染版本。
攻击链路:preinstall 钩子如何成为入口¶
Microsoft 的分析指出,许多恶意版本 没有对应的 GitHub 提交、PR 或 Tag,说明攻击者并非逐个入侵源码仓库,而是直接修改 tarball 并通过被盗 npm 发布令牌重新上传。这是 ChainDrop 传播速度极快的根本原因之一。
1. 入口:preinstall 生命周期钩子¶
npm 在安装依赖时会按顺序执行生命周期脚本。ChainDrop 在被感染包的 package.json 中加入 preinstall 钩子,指向包内的 setup.mjs:
{
"scripts": {
"preinstall": "node setup.mjs"
}
}
由于 preinstall 在包安装完成 之前 就会运行,恶意代码可以在单元测试、SAST 扫描等常规安全检查启动前就完成执行——无论是本地 npm install,还是 CI 流水线中的依赖安装步骤,都可能触发。
2. 二阶段载荷:Bun 运行时 + 混淆脚本¶
setup.mjs 会下载合法的 Bun JavaScript 运行时,并加载约 710 KB 的混淆二阶段脚本(常见文件名为 Math_Symbol.js 或 math_init.js)。使用 Bun 而非 Node.js 本身,有助于绕过部分针对 Node 进程的监控规则。
Microsoft Defender 已将该行为标记为 Suspicious usage of Bun runtime、Trojan:NPM/MalBun.A 等检测项。
3. 凭证收集:从本地文件到云 API¶
载荷启动后会区分 开发者工作站 与 CI/CD 环境:
- 在开发者机器上,进程会 detach 到后台 继续运行;
- 在 CI 环境中,进程保持附着,以便读取 workflow secrets、runner 凭证及 GitHub Actions OIDC 发布权限。
收集范围包括:
- 本地凭证文件、Shell 历史、SSH 密钥、云配置文件;
- 进程环境变量(含
NPM_TOKEN、GITHUB_TOKEN等); - 通过
gh auth token、gcloud、az等 CLI 获取的令牌; - 利用已获凭证调用 AWS、Kubernetes、HashiCorp Vault API,枚举并拉取更多密钥。
4. 数据外传:HTTPS + GitHub 备用通道¶
窃取的数据经 JSON 序列化、gzip 压缩后,用 AES-256-GCM 加密,密钥再用攻击者 RSA 公钥封装。外传路径:
- 主通道:攻击者控制的动态 HTTPS 端点。C2 域名通过以太坊智能合约(
0xE1f2395ee43e45A1556EC6438a88c31B83493103)或签名 GitHub commit(标记thebeautifulmarchoftime)下发,分析时活跃域名为npm-cache.com,历史候选包括pypi-get.com、js-mirror.com。 - 备用通道:利用被盗 GitHub 令牌创建公开仓库,描述为 「Shai-Hulud: Here We Go Again」,将加密结果以
results-*.json文件提交。
5. 蠕虫传播:一个令牌感染所有可发布包¶
ChainDrop 最具破坏力的能力在于 自动化传播。获得 npm 发布令牌后,蠕虫会:
- 枚举该身份可访问的所有包;
- 下载各包最新 tarball;
- 插入恶意
setup.mjs与Math_Symbol.js,替换 lifecycle scripts; - patch 版本号 +1 后重新发布。
一个被盗维护者账户即可在数小时内产出数百个恶意 patch 版本——且这些版本往往看起来只是普通的补丁升级,源码仓库却毫无变动。
6. GitHub Actions OIDC 滥用¶
对于配置了 npm Trusted Publishing(OIDC)的仓库,蠕虫还有专门路径:利用 workflow 身份获取合法 provenance 的发布权限,使恶意包携带 看似可信的来源签名。这意味着仅靠 provenance 校验不足以防御此类攻击。
7. 持久化:Claude / VS Code 配置注入¶
蠕虫还会利用被盗 GitHub 凭证,向可写仓库分支注入配置文件:
.claude/settings.json、.claude/setup.mjs.vscode/tasks.json、.vscode/setup.mjs
当开发者后续使用 Claude Code、VS Code 或 GitHub Copilot 打开项目时,载荷可能再次被触发,形成 开发者到开发者 的二次感染路径。部分 macOS/Linux 变种还安装了「死手开关」:每 60 秒轮询 GitHub API 检测令牌有效性,令牌被撤销则自毁;另有 24 小时自清理机制。
与 Shai-Hulud 的演进关系¶
ChainDrop 被安全社区视为 Shai-Hulud 2.0 蠕虫的重度演进版本,主要新增能力包括:
| 特性 | Shai-Hulud(早期) | ChainDrop |
|---|---|---|
| 执行入口 | postinstall / preinstall | preinstall + Bun 二阶段 |
| C2 通信 | 静态域名 / GitHub | EtherHiding(以太坊合约动态下发) |
| 传播方式 | 手动或半自动 | 全自动 tarball 修改 + 重发布 |
| 持久化 | 有限 | Claude / VS Code 配置注入 |
| CI 目标 | 凭证窃取 | 凭证窃取 + OIDC 发布滥用 |
蠕虫还会检测 俄语系统环境 并主动退出,这一特征在 Microsoft 与 StepSecurity 的分析中均有提及。
如何自查:你的项目是否受影响¶
1. 检查 lockfile 与 node_modules¶
对照 StepSecurity、Wiz、Socket 等厂商维护的 受影响包列表,检索 package-lock.json、yarn.lock、pnpm-lock.yaml 中的版本号。不要只看直接依赖——被感染包多为 传递依赖。
# 示例:在 lockfile 中搜索已知高危包
grep -E '"(keyv|flat-cache|file-entry-cache|cache-manager)"' package-lock.json
2. 关注 IOC(入侵指标)¶
Microsoft 公布的文件哈希:
| SHA256 | 说明 |
|---|---|
54dc7ea54a1317cca0e890a2770630cf7fa6c97813e0cb9d2caa93012b350668 |
setup.mjs(npm preinstall 加载器) |
fd3ca4007b225fdf8de7af4345a19179d5efa8c4bb9205f88cda806e5684b1eb |
setup.mjs(.claude / .vscode 加载器) |
9fc2570b7cef51c1b8df116d144d11ff4096357be7d2c4c6367cfc2509cf1bcc |
Math_*.js 蠕虫主体 |
网络 IOC:npm-cache.com、pypi-get.com、js-mirror.com,以及 https://npm-cache.com:443/router。
3. 检查 CI 与本地环境¶
在终端或 SIEM 中检索以下进程特征:
# 可疑 preinstall 执行
node setup.mjs
# Bun 二阶段加载(路径含 bun-dl- 或 node_modules)
bun ... Math_Symbol.js
若发现匹配,应将该机器视为 已失陷 处理。
应急响应与长期防护¶
若已安装被感染版本¶
Microsoft 与 JFrog 的建议可归纳为以下步骤:
- 隔离:断开受影响工作站与 CI runner 的网络,保留 tarball、npm 日志、CI 日志、GitHub audit log 以便界定暴露窗口。
- 清理:清除
node_modules、npm/yarn 缓存(含共享 CI 缓存);重建 runner 镜像与 golden build 环境。 - 轮换凭证:在 已知干净的环境 中撤销并轮换 npm token、GitHub PAT、AWS 密钥、K8s ServiceAccount、Vault token 等——顺序很重要,先隔离再轮换。
- 审计仓库:检查
.claude/、.vscode/目录是否有异常注入;审查 GitHub Actions workflow 变更与 npm 发布记录。 - 重建制品:从已知良好的依赖基线重新构建,确认缓存与制品库中不存在被感染哈希。
日常防护建议¶
- 升级 npm CLI 至 v12+,启用
min-release-age功能,延迟安装刚发布的版本,为社区下架争取窗口。 - 锁定依赖版本(pin / lockfile commit),避免 CI 中无锁自动拉取最新 patch。
- CI 中禁用 lifecycle scripts 或使用
--ignore-scripts(需评估对原生模块的影响):
npm ci --ignore-scripts
- OIDC Trusted Publishing 仍推荐使用,但需配合 workflow 审批、环境保护规则与异常发布告警,不能作为唯一防线。
- 最小权限原则:npm 发布 token、GitHub PAT、云 IAM 角色应限定到必要范围;CI secrets 按 job 隔离。
- 供应链监控:接入 Socket、Snyk、Dependabot 等工具,对新增 patch 版本做 diff 与行为分析。
写在最后¶
ChainDrop 再次证明:npm 供应链的安全边界早已超出「某个包有没有被入侵」——CI/CD 凭证、OIDC 发布链、AI 开发工具配置 都可能成为攻击跳板。一个维护者账户的 GitHub 泄露,可以在四小时内演变成波及数百个包、数亿次周下载的蠕虫风暴。
对普通开发者而言,当下最务实的动作是:核对 lockfile、清理缓存、轮换可能暴露的令牌。对团队而言,则需要把依赖安装、发布流程和凭证管理当作同一套安全体系来审视——因为下一次攻击,很可能还是从 npm install 的那一行 preinstall 脚本开始。