蠕虫开始自己发布 npm 包:ChainDrop 之后,信任延迟、扫描与证明各退到了哪

八月四日,微软威胁情报发布了对 ChainDrop 的分析:四百多个互不相关的 npm 包在同一时段出现恶意 patch 版本,受害者包括 keyv、flat-cache、cache-manager 这类几乎每个 Node 项目都会间接依赖的"基础设施包"(Microsoft Security Blog, 2026-08-04)。让我停下来的不是数量,是两句话:很多恶意版本在源码仓库里没有任何对应的 commit、tag 或 PR——攻击者根本没碰源码;以及,经 GitHub Actions OIDC 路径重发布的包"可以携带有效 provenance,因为发布来自合法的 workflow 身份"。

我上个月刚写过《证明说了真话,但真话是伪造的》,结论是证明层记录"发生了什么"、不担保"本应发生什么"。ChainDrop 把这句话又推了一步:这次连"管道被接管"都不必发生——发布身份本身就在蠕虫手里,它用合法身份做"合法发布",证明字段字字为真。

疑问从哪来

五到七月,GitHub、npm、pnpm 把信任延迟产品化成了默认值:Dependabot 默认三天冷却、pnpm v11 默认 1440 分钟 minimumReleaseAge、npm 发布时扫描约 5–15 分钟。我当时在《三天的冷却、一天的冷却、五分钟的扫描》里的判断是"冷却只延迟不裁决"。ChainDrop 是这套产品化防线上线后的第一次自传播压力测试,而它的作答方式很直接:它根本不需要等过任何窗口,因为它就是用被偷到的 token"正常地"发布。

机制:一次被偷身份变成大量恶意版本

按微软一手博客,攻击链是这样的:恶意包通过 npm preinstall 生命周期钩子自动执行 setup.mjs,再拉起一个重度混淆的 Bun 运行时 payload。载荷先判断环境——在工作站上分离到后台继续跑,在 CI/CD 里保持附着,以便摸进 workflow secrets、runner 凭据和 GitHub Actions OIDC 发布权限。然后它收集本地文件、环境变量、shell 历史、命令行工具和 runner 内存里的凭据,并用这些身份去认证 npm、GitHub、AWS、Kubernetes 和 HashiCorp Vault——不是只扫 token 字符串,而是调用服务 API 验证并枚举可访问的资源。收集到的数据经 AES-256-GCM 加密后外传,C2 域名可以由链上合约切换(当时解析回 npm-cache[.]com),回退通道是公开 GitHub 仓库。

最关键的自动化传播段:拿到有发布权限的 npm token 后,蠕虫枚举该身份可写的所有包,下载每个包的最新 tarball,把自身与 loader 植入进去、加上 preinstall hook、patch 版本号递增一位、直接重发布。它还专门检查 token 是否具备"绕过 2FA"的能力——自动化类 token 是首选。微软明言:初始访问的向量证据指向盗用的维护者凭据;后续传播靠被偷的 npm 发布 token,以及在定向 workflow 里的 GitHub Actions OIDC 发布权限。

第二条传播腿更安静:偷到的 GitHub 凭据被用来往仓库分支里注入 .claude/settings.json、.claude/setup.mjs、.vscode/tasks.json、.vscode/setup.mjs 这类文件。开发者只要打开那个分支,Claude Code 或 VS Code 就会把配置当作指令执行,完成"开发者到开发者"的再感染。载荷遇到俄语系统会直接退出——这是归因线索,不是结论。

上一轮防线各自退到了哪

逐条对一下我前几篇文章写过的防线,ChainDrop 之后它们的实际位置是:

信任延迟/冷却——微软缓解清单第一条就是"升级 npm CLI v12 并使用 min-release-age 功能"。我上一篇讨论的消费者侧时间门,被官方写进了针对蠕虫的建议。但它的语义变了:不再赌"社区在窗口内发现",而是给扫描与下架留缓冲。它没有预防任何东西,只是把决策时间往后推。

源码审计——tarball 与源码仓库的背离(恶意版本无对应提交)是本轮最实用的检测信号,但它在事后分析中才成立,消费者默认不核对发布物与源码的一致性。这个信号本质上是 TanStack 事件里"失败 run 仍发布"那条监控建议在"无源码"场景的推广:关注"不该发生的发布"。

证明层——OIDC 路径下 attestation 字段为真、可携带有效 provenance。信任根没有消失,只是位置被确认了:它停在发布身份这一层。签名、provenance、可信发布这些机制全部默认"发布者可信",而 ChainDrop 恰恰住在发布者里。

扫描——The Register 的标题是"evades standard defenses":Bun 运行时加重度混淆,让 Node 视角的监控存在盲区(The Register, 2026-08-15)。这不是我上次说的"5–15 分钟可达性"问题,是检测视角问题:监控对象假设了运行时的形状。

实际止损——受影响版本被 npm 快速下架,加上凭据轮换、缓存清空和端点检测。预防缺席,检测与响应是唯一防线。微软自己也在结尾写了一句值得记住的话:"供应链响应不能止于主机排查,还必须验证发布流程本身没有被颠覆。"

我的判断修正与一条跨文章线索

判断修正:我此前把冷却/扫描当作消费者侧的主要防线。ChainDrop 的教训是——当攻击者持有发布身份本身,所有预防性机制都降级为延迟与检测;防线不是消失了,而是整体退守到"检测 + 快速下架"这一层。裁决仍然缺席:下架是裁决,但它是注册表侧的、事后的。

另一条线索跟我上一篇 MCP 文章接上了:ActiveState 的 CEO Abby Kearns 在被报道时说得直白——"把仓库提供的配置当作可执行内容,因为现在它就是"。.claude/.vscode 注入与 MCP 官方把"配置驱动的命令执行"划为预期行为,是同一条裂缝的两面:一边是官方把配置即命令定义为预期、成本转嫁给下游;另一边是攻击者把同一个机制当作传播通道,还不需要安装任何恶意包(The Register 引述:即使从不 npm install 也可能被感染)。

保留未核实

精确计数(微软说 400+,The Register 报 444 个包、合计月下载约 20 亿次,口径未交叉核实);"四小时攻陷"的时序(多篇报道提及,未取到一手时间线);受影响环境数量;注册表是否已对"无源码提交的发布"加标记或拦截(未见公开变更);归因未决。本轮的实底是:机制可信、防线退守的位置可信,数字口径与治理反应留待后续。

回复与讨论

留下回复

无需登录或邮箱。昵称可选;勾选保存后,WordPress 会用第一方 Cookie 在下次自动带回。本站不保存评论 IP 或浏览器标识。

每条 2–150 字;匿名每小时最多 5 条,登录用户每小时最多 15 条。

评论者识别当前追踪标识:未建立

↑ 回到顶部