尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

ChainDrop npm蠕虫攻陷1300+包:供应链投毒实战复盘、检测脚本与加固配置清单

ChainDrop npm蠕虫攻陷1300+包:供应链投毒实战复盘、检测脚本与加固配置清单 摘要2026‑08‑04 UTC爆发ChainDrop供应链蠕虫攻击攻击者接管知名npm包维护者GitHub账号依托GitHub Actions OIDC可信发布链路产出携带合法SLSA来源证明的恶意版本。恶意包通过preinstall钩子执行蠕虫载荷窃取本地与CI环境各类凭证拿到npm发布Token之后自动横向扩散最终污染超过1300个npm包合计月下载量逼近20亿。整套攻击没有使用0day漏洞全部复用平台合法能力。大量团队迷信SLSA签名、软件来源证明却忽略上游源代码被篡改的风险防护体系直接失效。本文还原完整攻击链路给出可直接落地检测脚本、凭证轮转清单、CI流水线加固配置拆解EtherHiding区块链C2技术细节梳理现有供应链防护机制的真实边界。目录事件原始现场与受害资产梳理ChainDrop完整攻击链路拆解蠕虫载荷内部实现细节EtherHiding区块链C2信道技术分析SLSA Provenance失效底层逻辑第一性原理视角受感染环境应急处置实操全套可复制检测脚本与自查命令CI/CD、开发机、npm项目三层加固配置现有商用安全工具的漏判点面向未来的供应链风险预判总结与互动提问1 事件原始现场与受害资产梳理TeamPCP组织发起ChainDrop攻击入侵目标账号Jared Wray。该账号维护一批Node生态底层缓存组件这些包不是业务直接依赖而是ESLint、构建工具、大量后端项目的二级、三级间接依赖。攻击者拿到GitHub账号控制权不是窃取npm平台Token。账号开启GitHub OIDC Trusted Publishing仓库配置分支保护但main分支仍允许维护者直接推送提交。攻击者向main分支提交混有恶意代码的commit触发GitHub Actions流水线。流水线正常构建自动发布npm新版本同时生成SLSA v2 provenance签名记录。外部安全扫描工具读取provenance校验签名完全合法判定包来源可信恶意版本流入npm公共仓库。攻击爆发后4小时受污染包数量冲到444个后续持续蠕虫式横向扩散最终统计1300包被篡改公有开源包之外大量企业私有命名空间包遭到污染。核心受害包清单这些包会顺着依赖树潜入绝大多数Node项目keyv通用键值存储周下载1.27亿flat‑cache磁盘缓存ESLint强间接依赖file‑entry‑cachecacheable、cacheable‑request HTTP缓存组件恶意版本号keyv6.0.0flat‑cache6.1.24file‑entry‑cache11.1.6cacheable‑request13.0.20npm官方后续执行版本下架。但lock文件、旧CI缓存、私有镜像源会持续保留恶意版本下架不等于风险消失。很多企业内部私有npm镜像不会自动同步官方删除操作风险长期驻留内网环境。C2通信用户环境GitHub平台侧攻击者操作拿到权限无权限接管维护者GitHub账号main分支提交恶意代码commitGitHub Actions流水线自动触发OIDC可信发布生成SLSA合法签名恶意版本推送至npm仓库开发者/CI执行npm installpreinstall钩子触发蠕虫执行窃取本机全部凭证获取npm发布Token蠕虫自动批量篡改账号下全部包潜伏驻留等待新凭证出现蠕虫读取以太坊合约存储拿C2配置加密外带窃取到的敏感数据图1 ChainDrop完整攻击链路架构2 ChainDrop完整攻击链路拆解很多供应链投毒事件攻击者手动修改1‑2个包发布攻击范围有限。ChainDrop本质是蠕虫具备自我复制扩散能力。整个链路全部调用平台公开合法接口没有破坏npm、GitHub任何协议。第一步账号接管。攻击者获取维护者账号访问权限。公开报告没有披露入侵路径大概率是钓鱼劫持会话、泄露凭证硬件MFA未启用。账号本身拥有仓库main分支写入权限。第二步代码注入。直接往main分支推送commit混入正常业务代码和混淆恶意载荷setup.mjs、math_init.js。代码做混淆处理简单肉眼review很难快速识别异常。仓库开启分支保护的场景如果维护者拥有管理员权限可以绕过分支保护规则直接push main这是很多团队配置的盲区。大量开源项目给核心维护者放开管理员权限分支保护规则对管理员失效。第三步CI流水线自动构建发布。仓库配置GitHub Actions工作流推送main自动执行构建借助OIDC Trusted Publishing不需要仓库硬编码npm token。流水线运行结束直接发布新版本到npm同步生成provenance来源证明。这里是整个事件最容易产生认知误区的地方provenance签名记录明确写明“这个包由该GitHub Actions流水线构建发布”密码学校验完全通过。签名只能证明构建来源不能证明输入源代码是否干净。攻击者篡改上游源码流水线输出产物自然带毒。第四步受害者执行npm install触发preinstall。package.json写入preinstall脚本项目安装依赖阶段自动执行脚本。载荷优先调用Bun运行时执行不是Node.js。选用Bun是刻意的对抗手段大量Node安全检测工具不会监控Bun子进程行为。第五步本地环境凭证搜刮。蠕虫遍历磁盘敏感文件同时读取进程内存提取瞬时令牌。收集到npm发布token就进入自传播分支。第六步蠕虫自传播。拿到有效npm发布权限蠕虫调用npm API枚举该账号名下全部包公有包、企业私有命名空间包全部纳入范围。自动升级小版本号写入一模一样preinstall载荷调用发布接口上传新版本。一台沦陷CI机器就可以批量污染数十个包。第七步C2信道回传窃取数据。不硬编码域名读取以太坊智能合约存储字段获取C2地址。攻击者只需要一笔链上交易就能批量修改全网所有蠕虫实例的C2目标。传统域名黑名单、IP封禁手段完全失效。蠕虫内置地域判断检测到俄语系统环境直接退出规避安全研究人员沙箱分析。额外攻击面攻击者向仓库写入VS Code、Claude Code配置文件。受害者git clone仓库打开VS Code部分载荷就可以触发不需要执行npm install。这代表防护边界不能只盯着npm install生命周期钩子。3 蠕虫载荷内部实现细节拆开恶意版本压缩包package.json增加preinstall字段。{scripts:{preinstall:bun run setup.mjs}}setup.mjs做多层字符串混淆字符串分段编码base64变形静态扫描很难直接提取完整逻辑。运行时才拼接出真实执行代码加载math_init.js主体蠕虫。载荷执行后做几类动作3.1 磁盘文件遍历窃取遍历宿主环境常见敏感路径.env.env.local各类环境变量文件.netrc git与npm凭证文件SSH私钥目录 ~/.sshgit credential存储terraform state部署状态文件shell历史记录 .bash_history .zsh_historyVault本地配置、云厂商credentials配置文件把读取到的内容加密准备向外传输。3.2 进程内存读取抓取瞬时OIDC令牌这是本次攻击杀伤力很强的设计。GitHub Actions Runner运行时OIDC令牌只驻留在进程内存不会落地磁盘。常规扫描磁盘文件完全抓不到这类临时凭证。蠕虫遍历/proc目录枚举全部进程pid读取进程内存空间扫描内存中OIDC token、runner密钥特征字符串。CI环境一旦被触发直接拿到短时高权限凭证。拿到OIDC令牌就可以向云厂商请求临时IAM权限。很多企业安全策略认为OIDC令牌不落地磁盘不会泄露。ChainDrop直接推翻这个假设。只要主机已经沦陷内存里面的短时凭证一样可以被掠夺。3.3 蠕虫自传播分支逻辑检测本机是否存在可用npm发布token来源可以是环境变量、磁盘配置文件、内存读取。拿到有效token之后执行流程调用npm whoami验证token有效性调用npm registry接口枚举当前账号管理的全部包包含私有scope包遍历每一个包拉取当前最新版本版本号小版本1修改package.json注入preinstall脚本塞入混淆载荷本地打包tgz调用npm api发布新版本整套逻辑全部在受害机器本地完成攻击者不需要手动操作每一个包。只要一台CI或者开发机拿到发布权限就形成链式投毒。3.4 痕迹清理蠕虫执行完成会尝试删除node_modules内部自身载荷文件setup.mjs、math_init.js。事件结束之后受害主机磁盘不一定留存恶意样本。不能依靠文件是否存在判断环境是否被入侵。大量团队事后只扫描node_modules特征文件直接漏判。4 EtherHiding区块链C2信道技术分析EtherHiding不是ChainDrop原创近两年恶意样本逐步开始大规模使用该技术。传统恶意程序硬编码C2域名IP安全设备可以拉黑域名阻断通信。攻击者更换C2就必须更新全部恶意样本代码重新发布新版本。EtherHiding把配置存储在以太坊智能合约存储槽。蠕虫运行发起链上RPC查询读取合约指定存储位置字节数据解码得到C2域名、端口、指令。攻击者不需要修改任何蠕虫代码。仅提交一笔以太坊交易改写合约存储槽内容全网所有已经部署的蠕虫实例全部切换到新C2地址。攻击者提交以太坊交易改写合约存储槽链上状态永久更新受害主机蠕虫样本调用公开RPC节点读取合约存储解码得到最新C2配置加密窃取数据回传攻击者服务器图2 EtherHiding工作流程防御难点RPC节点是公开以太坊节点流量和正常区块链查询无差别网络设备很难区分恶意查询和正常链上访问。C2配置不在样本二进制内静态代码扫描看不到域名。合约可以废弃切换全新合约地址攻击持续进行。ChainDrop蠕虫还设置备用信道GitHub仓库作为备选回传通道。主C2链路阻断就利用GitHub gist提交加密数据。蠕虫加入沙箱对抗逻辑读取系统locale、系统语言变量如果识别俄语区域设置直接终止全部执行逻辑不留下任何行为痕迹。安全研究员搭建沙箱分析样本如果系统语言设置俄语样本直接静默退出。5 SLSA Provenance失效底层逻辑第一性原理视角大量企业落地SLSA供应链安全引入provenance来源证明把provenance校验当作供应链防护的核心屏障。ChainDrop事件证明这套机制存在明确边界。先拆解provenance的第一性原理provenance只记录“这个制品是由哪一套流水线、哪一份代码提交构建出来”。签名保证这条记录不能被篡改。它验证制品 ↔ 构建流水线 ↔ git commit三者绑定关系。provenance不会校验git commit提交的源代码本身是否恶意。信任链链路信任平台 → 信任流水线 → 信任输入源码 → 信任输出制品整条信任链任意一环断裂后面全部失效。攻击者接管账号向main分支提交恶意commitcommit本身是git合法提交。GitHub Actions拿到这份恶意commit执行构建输出恶意npm包生成合法provenance记录。制品、流水线、commit三者绑定关系完全真实。安全工具校验签名返回OK但是输入源码已经被敌人控制。现实中很多团队实现错误逻辑只要provenance签名合法就直接放行依赖包。这是架构层面的错误。provenance只能回答“包是谁构建”回答不了“构建用的代码是不是坏人写的”。SLSA等级里面SLSA‑3、SLSA‑4要求源码完整性保护。源码完整性保护指保护git仓库不被未授权修改。一旦账号被攻陷授权攻击者提交恶意commit源码完整性保护同样失效。不要把SLSA当作防投毒银弹。SLSA解决构建过程防篡改解决不了上游授权账号被劫持带来的源码篡改。6 受感染环境应急处置实操只要主机、CI Runner在攻击窗口期执行过携带恶意版本的npm install认定主机已经完全沦陷。不要简单删除node_modules、卸载恶意包就恢复业务。蠕虫已经窃取内存、磁盘各类凭证密钥可能已经流出。6.1 凭证轮转优先级清单按顺序执行npm账号全部发布Token作废重新生成。如果多人共用账号全部token轮换。GitHub PAT、OIDC提供商信任配置重新审核作废旧有OIDC信任条件。云厂商IAM访问密钥临时角色条件全部重置。AWS、阿里云、K8s service‑account token全部轮换。Vault、密钥管理系统访问令牌全部作废。SSH私钥全部替换把旧密钥从服务器authorized_keys全部清理。开发机器本地.env、各类业务账号密码只要存在本机全部视作泄露风险业务密码重置。关键点CI Runner内存被读取拿到短时OIDC令牌。即使令牌生命周期很短攻击者可以在令牌有效期内调用云接口。不要觉得短时令牌无留存磁盘就不需要轮转关联权限。6.2 项目仓库处理检查lock文件确认是否引入恶意版本。npm官方已经下架恶意版本但私有npm镜像不会自动删除。内网镜像管理员需要手动删除对应恶意tgz包。不要直接升级依赖版本解决问题。依赖树里面间接依赖依旧可能拉取恶意版本。使用overrides强制锁定受害包到安全版本。// package.json overrides片段{overrides:{keyv:^6.0.1,flat-cache:^6.1.25,file-entry-cache:^11.1.7,cacheable-request:^13.0.21}}开启分支保护取消管理员绕过分支保护的权限。很多开源项目维护者为开发便利开启管理员可以绕过PR规则这是高危配置。仓库维护账号强制硬件MFA禁止短信、软件TOTP作为唯一二次验证手段。6.3 CI流水线紧急配置变更CI环境立刻禁用npm脚本执行。npmconfigsetignore-scriptstrueignore‑scripts开启之后preinstall、postinstall、prebuild全部生命周期脚本不再执行。业务包确实需要脚本做白名单不要全局放开。GitHub Actions runner镜像只要运行过受污染install直接销毁重建不要复用旧runner。Runner内存里面的窃取行为已经发生重启不足以清除全部风险。7 全套可复制检测脚本与自查命令重要提醒蠕虫运行完成会自行删除本地载荷文件。脚本没有检出特征文件不代表没有被入侵。脚本只能做辅助筛查不能作为判定安全的唯一依据。7.1 shell脚本扫描lock文件匹配恶意版本可直接复制保存为check_chaindrop.sh#!/bin/bashset-eecho ChainDrop npm蠕虫简易检测脚本 MAL_VERSIONSkeyv6.0.0|flat‑cache6.1.24|file‑entry‑cache11.1.6|cacheable‑request13.0.20LOCKS(package-lock.jsonyarn.lockpnpm-lock.yaml)found0forlockfilein${LOCKS[]};doif[-f${lockfile}];thenecho[] scanning${lockfile}ifgrep-E${MAL_VERSIONS}${lockfile};thenecho[!] 发现恶意版本存在于${lockfile}found1fifidoneecho[] 扫描node_modules查找蠕虫特征文件 setup.mjs math_init.jsfindnode_modules-typef\(-namesetup.mjs-o-namemath_init.js\)2/dev/nullif[$found-eq1];thenecho-e\033[31m[警告] lock文件检出恶意版本请立刻执行凭证轮转\033[0melseecho[ok] lock文件未命中恶意版本字符串fiecho 检测结束 赋予执行权限运行chmodx check_chaindrop.sh ./check_chaindrop.sh7.2 node脚本读取SBOM物料清单做依赖版本扫描 check_sbom.js适合已经生成SPDX SBOM的项目constfsrequire(fs);constmaliciousnewSet([keyv6.0.0,flat‑cache6.1.24,file‑entry‑cache11.1.6,cacheable‑request13.0.20]);// 传入spdx json sbom文件路径 node check_sbom.js ./sbom.spdx.jsonconstsbomPathprocess.argv[2];if(!sbomPath){console.log(usage: node check_sbom.js sbom.spdx.json);process.exit(1);}constrawJSON.parse(fs.readFileSync(sbomPath,utf8));lethitfalse;for(constpkgofraw.packages||[]){constpkgId${pkg.name}${pkg.version};if(malicious.has(pkgId)){console.log([!] 命中恶意包${pkgId});hittrue;}}if(!hit){console.log([ok] SBOM未检出目标恶意版本);}process.exit(hit?1:0);7.3 git仓库检查命令查找提交历史中preinstall脚本注入# 在git仓库根目录执行检索package.json历史出现preinstall脚本变更gitlog-p-- package.json|grep-ipreinstall8 CI/CD、开发机、npm项目三层加固配置8.1 CI流水线加固默认开启ignore‑scriptstrue业务确实需要脚本使用npm--ignore‑scriptsfalse针对单包白名单。不要全局关闭脚本禁用。OIDC信任条件最小化不要写通配符信任全部分支。只信任main分支指定仓库名称收紧sub、iss条件。错误配置示例允许任意分支触发发布# 高危不要复制使用issuer:https://token.actions.githubusercontent.comsubject:repo:*:*安全配置示例限定仓库分支issuer:https://token.actions.githubusercontent.comsubject:repo:org/myrepo:ref:refs/heads/main流水线Runner用完即销毁不复用长时间存活Runner。长生命周期Runner内存泄露风险极高。流水线环境禁止放入高权限云密钥优先OIDC短时间令牌。短时间令牌也不能无视内存读取风险。8.2 开发机本地加固npm全局配置开启ignore‑scripts个人项目按需关闭。npmconfigsetignore‑scriptstrue--global高权限GitHub、npm账号必须硬件MFA拒绝TOTP软件二次验证。本地.env文件不要提交git同时不要放置高权限生产密钥到开发机本地环境变量。开发机一旦被恶意包执行全部环境变量直接被窃取。8.3 npm项目仓库层加固package.json不要随便引入preinstall/postinstall第三方脚本。第三方依赖的生命周期脚本是供应链投毒最高频入口。配置overrides锁定高危底层依赖版本不要等待上游依赖更新。开启分支保护关闭“允许管理员绕过分支保护规则”选项。很多开源项目管理员忽略这个开关。私有npm镜像建立恶意包黑名单定期同步官方撤销包清单。私有镜像不能被动等待上游删除需要自己维护黑名单。8.4 SBOM持续审计定期导出SPDX格式SBOM接入自动化检测。不要只扫描直接dependencies绝大多数风险来自二级、三级间接依赖。keyv这类底层缓存库几乎不会出现在业务直接依赖列表。9 现有商用安全工具的漏判点复盘ChainDrop事件市面上多款供应链安全工具出现漏判这里梳理真实盲区。第一过度信任provenance签名。工具校验SLSA签名合法直接降低风险等级不去解析源码行为。签名只校验构建链路源码本身投毒无法识别。第二扫描只看磁盘静态样本。蠕虫运行后删除自身载荷磁盘没有恶意文件工具直接判定安全忽略内存凭证窃取行为。第三忽略Bun运行时载荷。大量Node安全检测只监控node子进程样本调用bun执行恶意逻辑直接绕过检测规则。第四C2静态特征缺失。C2域名存储在以太坊合约样本代码没有硬编码域名静态扫描拿不到IOC。传统IOC匹配完全失效。第五不处理私有npm scope包。很多安全工具只扫描公共npm包企业内部私有命名空间包被蠕虫篡改之后完全不在监控范围。ChainDrop蠕虫会同时污染公有和私有包。第六lock文件扫描不全。部分工具只扫描package.json直接依赖不去深度解析lock文件间接依赖恶意版本直接漏掉。安全工具可以作为辅助不能当作唯一防护屏障。供应链安全不能把全部希望寄托扫描工具输出报告。10 面向未来的供应链风险预判ChainDrop不是孤立事件是成熟攻击范式落地。后续同类攻击会复用这套模式。攻击者会优先瞄准下载量巨大的底层工具链依赖而不是业务应用包。ESLint、构建工具、缓存库、网络请求库这类包间接依赖数量庞大污染一个包影响几十万项目。区块链C2信道会越来越普遍。EtherHiding技术门槛不高攻击者可以低成本获得抗封禁C2信道。网络防火墙、域名黑名单的传统阻断手段持续失效。蠕虫式自传播会成为标准能力。拿到维护者权限之后不再手动发布每一个包程序自动批量篡改账号下全部包攻击爆发速度会从几天压缩到几小时。攻击扩散速度会超过企业安全团队响应速度。VS Code、AI编码助手相关配置文件作为攻击入口。不需要npm install打开仓库就触发载荷。攻击入口不再局限包管理器生命周期钩子。账号劫持依旧是供应链攻击最高效入口。0day漏洞获取成本很高钓鱼劫持维护者账号成本低收益巨大。不管你SLSA做到几级账号失守整套防护崩塌。供应链安全的短板永远是人以及账号权限不是密码学签名机制。密码学解决机器之间信任解决不了人账号被劫持。11 总结与互动提问ChainDrop蠕虫没有利用任何高危漏洞全部使用GitHub、npm平台自带合法能力。攻击者劫持维护者账号注入恶意源码CI流水线产出携带合法SLSA签名的恶意包安装后窃取凭证拿到npm权限就自动批量投毒更多包以太坊合约作为抗封禁C2信道。事件戳破行业一个普遍误区SLSA provenance来源证明不等于供应链安全。签名保护构建过程保护不了上游源码被授权账号篡改。应急处置核心只要环境运行过恶意包就认定沦陷全盘轮转凭证不能只删除依赖。防御层面三层落地账号硬件MFA收紧权限、CI默认禁用npm脚本、持续SBOM审计私有镜像维护黑名单。供应链防护不存在银弹必须分层防御。不要把某一项安全能力当成绝对安全的终点。互动问题你们公司内部是否已经落地SLSA provenance校验落地过程中遇到过哪些认知误区在你们的CI流水线npm生命周期脚本是全局开启还是默认禁用业务依赖脚本如何做白名单管控
返回列表