拓冰建站拓冰建站
首页 / 资讯中心 / 正文

npm爆发大规模供应链攻击 蠕虫污染 2000+ 个包版本: 深度技术剖析

npm 蠕虫污染 2000 个包版本深度技术剖析2026 年 8 月 4 日究竟发生了什么以及每位开发者必须知道的事全景概览2026 年 8 月 4 日攻击者入侵了 Jared Wray 的 GitHub 账号, 他是广泛使用的keyv包的维护者该包是一个键值存储抽象库每周下载量约 1.27 亿次。短短数小时内一种名为ChainDrop的自我复制蠕虫Shai-Hulud 恶意软件家族的变种就污染了444 个不同包中的 2,234 个版本。这些受影响包的合计月下载量超过20 亿次。这次事件之所以令人震惊不仅在于其规模更在于其精巧程度。该蠕虫并未利用 npm 本身的任何漏洞而是滥用了合法功能可信维护者的身份、自动化发布流水线以及经过签名的来源证明provenance。攻击步骤详解步骤 1账户入侵攻击者首先控制了维护者的 GitHub 账户。初始入侵途径尚未确认但结果很明确攻击者现在拥有了推送代码和发布包的合法凭证。为何发生维护者的账户缺乏足够保护, 没有硬件安全密钥未对关键操作强制要求双因素认证或者凭证通过其他途径暴露。这是整个链条中最关键的一个失败点。步骤 2直接注入主分支UTC 时间 8 月 4 日 09:02攻击者直接向keyv仓库的main分支推送了一个恶意提交。该提交添加了两个文件setup.mjs—— 一个 29 KB 的加载器脚本Math_Symbol.js—— 一个 728 KB 的混淆第二阶段载荷同时修改了package.json添加了preinstall钩子scripts:{preinstall:node setup.mjs}为何发生攻击者拥有主分支的写入权限。仓库没有配置分支保护规则要求合并到主分支前必须经过 Pull Request 审查。这是 GitHub 仓库安全配置的基本失误。攻击者得到了什么通过直接注入主分支恶意代码成为了自动化发布流水线将会构建和发布的源码的一部分。步骤 3IDE 和 AI 代理持久化钩子UTC 09:04攻击者推送了额外的提交在开发者工具配置中植入了执行钩子.claude/settings.json—— 一个SessionStart钩子在 Claude Code 会话启动时运行node .claude/setup.mjs.vscode/tasks.json—— 一个folderOpen任务在仓库于 VS Code 中打开时运行node .vscode/setup.mjs为何发生攻击者深知开发者不只是安装包他们还会克隆仓库并在 IDE 中打开。这就创建了一个完全不需要npm install的次级感染向量。开发者或 AI 编码助手仅仅因为打开克隆的仓库文件夹就可能被攻陷。步骤 4发布到 npmUTC 09:35keyv6.0.0被发布到 npm。合法的发布流水线构建了已被木马化的源码并附带了有效的 npm 来源证明—— 一份签名的 SLSA 证明。keyv6.0.0 附带有效的 npm 来源证明发布 合法的发布流水线构建了已被木马化的源码 签名验证在恶意软件上通过了为何发生来源证明证明的是构建完整性而非源码完整性。构建系统执行了它设计要做的事, 构建仓库中存在的任何源码并对结果签名。系统信任源码因为维护者的账户是受信的。关键教训经过签名的来源证明并不意味着代码安全。它只说明该代码确实来自所声称的构建过程。步骤 5蠕虫激活当开发者运行npm install安装受影响包时preinstall钩子自动执行。setup.mjs实际执行的操作检查 Bun 运行时—— 若未安装则直接从 GitHub 官方发布页下载bun v1.3.13执行载荷—— 使用 Bun 运行Math_Symbol.js清除痕迹—— 执行后删除临时 Bun 目录Math_Symbol.js载荷经过重度混淆728 KB采用控制流扁平化和 Base91 字符串编码。载荷功能使用 TruffleHog 风格的正则表达式扫描磁盘查找凭证信息窃取 npm 和 GitHub 访问令牌窃取 AWS、GCP 和 Azure 云凭证提取 HashiCorp Vault 令牌和 Kubernetes 服务账户令牌读取 GitHub Actions OIDC 令牌和实例元数据捕获 Stripe 和 Slack 令牌、SSH 密钥以及数据库凭证使用 AES-256-GCM 在操作者公钥下加密窃取的数据将数据外泄至攻击者控制的 GitHub 仓库和通过 DNS 解析的目标地址步骤 6自我复制这才是它成为蠕虫的关键。利用窃取到的 npm 令牌恶意软件搜索被入侵身份拥有发布权限的其他包下载这些包注入相同的恶意文件setup.mjs以及Math_Symbol.js或math_init.js修改package.json添加preinstall钩子增加版本号将木马化后的包重新发布到 npm速度令人咋舌蠕虫在大约两小时内09:40 至 11:44 UTC重新发布了435 个包中的 1,557 个恶意版本。每隔两到七分钟就有一波新的被污染包发布。当窃取的凭据中包含 GitHub App 令牌时蠕虫进一步扩大影响范围 —— 向每个可访问仓库的最多 50 个分支提交恶意钩子。为何发生npm 令牌授予发布权限。一旦被窃取它们就成为蠕虫的主要传播机制。整个过程没有任何速率限制或异常检测来标记这种异常的发布模式。步骤 7动态 C2 基础设施恶意软件放弃了在载荷中硬编码 C2 域名的传统做法而是使用eth_call调用主网上的一个以太坊智能合约StringListStore动态获取当前的 C2 域名列表攻击者可以随时更新合约来更换 C2 基础设施而无需重新部署恶意软件为何发生静态 C2 域名容易被封堵。通过将以太坊作为动态配置存储攻击者使恶意软件能够抵御域名封禁。初始链记录显示有 3 个域名后来更新为仅返回一个。步骤 8令牌撤销陷阱这里尤其阴险。载荷安装了一个自毁开关将窃取的 GitHub 令牌和攻击者提供的处理命令写入~/.config/gh-token-monitor/将自身持久化为 macOS LaunchAgent 或 Linux systemd 用户服务并启用loginctl enable-linger以在注销后继续运行监控令牌是否被撤销陷阱如果你作为第一响应措施撤销了被窃令牌就会触发自毁开关。载荷将执行其处理命令 —— 可能销毁证据、外泄额外数据或造成其他破坏。为何发生攻击者预判了应急响应流程并内置了对抗措施。这是专业级别的战术手段。受影响的包值得注意的被入侵版本包括keyv6.0.0—— 月下载量约 6 亿flat-cache6.1.24—— 月下载量约 5.8 亿file-entry-cache11.1.6—— 月下载量约 5.7 亿cacheable-request13.0.20cacheable2.5.1cache-manager7.2.10ecto5.0.1蠕虫还波及了 Deliveroo、Ornikar、OneReach、Picsart、Qlik 和 ServiceTitan 等其他组织下的包。代码分析恶意载荷preinstall 钩子{scripts:{preinstall:node setup.mjs}}问题所在npm 的preinstall钩子在包安装之前执行且无需任何用户交互。受害者不需要导入包或编写任何应用代码 —— 仅仅运行npm install就足够了。攻击面这并不是 npm 的漏洞而是合法功能被滥用。包管理器信任包作者不会作恶。加载器setup.mjs —— 简化示意// 伪代码示意import{platform,arch}fromos;import{execSync}fromchild_process;import{existsSync,mkdtempSync,rmSync}fromfs;// 检查 Bun 是否已安装constbunPathwhichBun();if(!bunPath){// 从 GitHub 下载 Bun v1.3.13consturlhttps://github.com/oven-sh/bun/releases/download/bun-v1.3.13/bun-${platform()}-${arch()}.zip;downloadAndExtract(url);}// 执行混淆后的载荷execSync(bun run${__dirname}/Math_Symbol.js,{stdio:inherit});// 清理rmSync(tempDir,{recursive:true,force:true});危险之处加载器下载了一个完整的 JavaScript 运行时Bun并执行任意代码。这绕过了任何 Node.js 特定的安全控制。728 KB 的载荷经过重度混淆使得静态分析极为困难。凭证收割器Math_Symbol.js —— 概念性// 基于模式的凭证扫描TruffleHog 风格constpatterns{aws:/AKIA[0-9A-Z]{16}/g,github:/gh[psu]_[0-9a-zA-Z]{36,}/g,npm:/npm_[a-zA-Z0-9]{36,}/g,// ... 数百种其他模式};// 扫描磁盘上的文件functionscanDirectory(path){for(constfileofwalkSync(path)){constcontentreadFile(file);for(const[type,pattern]ofObject.entries(patterns)){constmatchescontent.match(pattern);if(matches){exfiltrate({type,matches,file});}}}}// 自我复制functionpropagate(npmToken){constpackagesgetPublishablePackages(npmToken);for(constpkgofpackages){consttarballdownloadPackage(pkg);constmodifiedinjectPayload(tarball);constnewVersionbumpVersion(pkg.version);publishPackage(pkg.name,newVersion,modified,npmToken);}}之所以如此有效的原因使用合法的 npm API 进行传播 —— 无需任何漏洞利用为修改后的包重新计算完整性哈希值为篡改后的版本生成全新的 Sigstore 来源记录安装后包的行为正常但主机已被攻陷这对开发者意味着什么攻击面已经扩大此次攻击表明供应链威胁模型现在包括维护者账户—— 开源中最关键的资产源码仓库—— 不仅是已发布的包IDE 配置——.vscode/、.claude/、.cursor/现在成为执行入口AI 编码助手—— 打开仓库即可触发执行构建流水线—— 带有 OIDC 令牌的 GitHub Actions 是重点目标来源证明并非银弹keyv6.0.0版本具有有效的 npm 来源证明附带签名的 SLSA 证明。证明验证通过因为构建系统从被入侵的源码构建构建系统对结果进行了签名签名证明该构建确实来自合法的流水线来源证明证明的是构建完整性而非源码完整性。这是业界许多人一直忽略的关键区别。如何修复补救指南立即行动1. 检查是否受影响查找以下入侵指标SHA-256 哈希值9fc2570b7cef51c1b8df116d144d11ff4096357be7d2c4c6367cfc2509cf1bcc → Math_Symbol.js / math_init.js728 KB 载荷 54dc7ea54a1317cca0e890a2770630cf7fa6c97813e0cb9d2caa93012b350668 → setup.mjs加载器29,918 字节 fd3ca4007b225fdf8de7af4345a19179d5efa8c4bb9205f88cda806e5684b1eb → setup.mjs加载器变体11,017 字节检查你的package-lock.json或yarn.lock中是否存在受影响的版本。2. 不要首先撤销令牌如果你怀疑被入侵切勿作为第一步就撤销 npm 令牌或 GitHub 令牌—— 这会触发自毁开关。正确做法首先识别并终止持久化机制删除 systemd 服务或 LaunchAgent删除~/.config/gh-token-monitor/然后轮换凭证3. 识别并移除受影响版本# 检查锁文件中是否存在受影响版本grep-Ekeyv: 6\.0\.0package-lock.jsongrep-Eflat-cache: 6\.1\.24package-lock.jsongrep-Efile-entry-cache: 11\.1\.6package-lock.json移除受影响版本改用已知良好版本。4. 将受影响系统视为已被入侵任何安装了受影响包或打开了受感染仓库的系统都应被视为可能已被入侵。从干净镜像重建这些系统。5. 轮换所有暴露的凭证在移除持久化机制后轮换npm 令牌GitHub 令牌和 PAT云服务商凭证AWS、GCP、AzureHashiCorp Vault 令牌Kubernetes 服务账户令牌SSH 密钥数据库凭证Stripe 和 Slack 令牌长期预防对于维护者启用分支保护—— 要求合并到主分支前必须经过 Pull Request 审查禁止直接推送。使用硬件安全密钥—— 对所有关键操作强制使用 WebAuthn 双因素认证。审计发布权限—— 定期审查谁和什么可以发布包。监控异常活动—— 留意意外提交尤其是对主分支的提交。使用独立账户—— 维护者账户用于编写代码使用单独且权限受限的服务账户进行自动化发布。对于组织在 CI 中阻止安装脚本—— 在 CI/CD 流水线中尽可能使用npm install --ignore-scripts。扫描依赖—— 使用能够检测恶意 preinstall 钩子和混淆代码的工具。锁定依赖版本—— 使用精确版本号而非^或~范围以避免意外的补丁升级。审计锁文件—— 定期审查package-lock.json是否有意外更改。将仓库视为执行面—— 在隔离环境中克隆和打开仓库尤其是在调查安全事件时。对于生态npm 应考虑对多年未使用preinstall钩子却突然添加该钩子的包进行额外验证。GitHub 应考虑标记那些通常使用 PR 流程却突然直接向主分支提交的账户。Sigstore 来源证明应包含源码完整性验证而不仅仅是构建完整性。真正的问题没有验证的信任此次事件从根本上说并非 npm、GitHub 或任何特定技术的漏洞而是关于我们如何将整个软件供应链建立在隐式信任之上。我们信任了维护者账户是安全的仓库中的源码是维护者所意图的来源签名意味着代码是安全的包管理器在安装期间只执行安全代码这些假设没有一项成立。攻击链之所以优雅并非因为它利用了一个零日漏洞而是因为它利用了信任—— 我们对维护者、构建系统、包注册表以及日常工具的信任。结语2026 年 8 月的 npm 蠕虫攻击是软件供应链安全的一个分水岭。它表明账户入侵已成为新的零日漏洞—— 最关键的漏洞不在代码中而在身份中来源证明不等于安全—— 一个已签名的包仍然可能是恶意的你的 IDE 现在是攻击向量—— 打开仓库与运行未知代码同样危险AI 编码助手是攻击目标—— 它们会在无人审查的情况下执行配置钩子应急响应必须进化—— 旧的行动手册先撤销后提问可能会适得其反维护者是受害者。npm 和 Sigstore 流水线完全按照设计运行。系统按设计工作 —— 而这正是问题所在。我们需要构建不依赖隐式信任的系统。每一步都需要验证。我们必须认识到在开源世界中维护者的笔记本电脑现在已成为攻击面的一部分。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门