ChainDrop npm 蠕虫分析:Runner 内存窃密、区块链 C2 与自传播
原创
Ti
Ti
TIPFactory情报工厂
2026年8月24日 20:20
江苏
在小说阅读器读本章
去阅读
ChainDrop 把一次 npm 包投毒扩展成了自动传播的供应链蠕虫。受污染包仍保留正常功能,只增加 setup.mjs、math_init.js 和一条 preinstall lifecycle hook;安装过程结束时没有明显错误,但后台载荷已经开始扫描开发者工作站、CI runner、云凭据与发布权限。
Unit 42 在调查中匹配到 453 个公开 GitHub 仓库、5 个账号,并在 10 个环境观察到执行。这个规模只是公开可见部分,不能直接等同于已确认受害组织数量,但说明攻击者正在利用开发者身份和自动化发布链持续放大影响。
从 preinstall 到 727 KB 混淆载荷
感染包的 package.json 只需增加一条命令:
{\"scripts\":{\"preinstall\":\"node setup.mjs\"}}
图 1:感染包通过 preinstall 在用户安装依赖前启动 dropper。
setup.mjs 首先检查 PATH 中是否存在 Bun;不存在时,按平台和架构从官方 Oven GitHub release 下载 Bun 1.3.13。Bun 本身没有遭入侵,它只是被用作跨平台执行器。随后 dropper 运行压缩成两行、约 727 KB 的 math_init.js,并删除临时 runtime 目录。
非 CI 环境中,payload 以 detached background process 运行,并设置 _NODE_RUNTIME_INIT=1 防止递归重启;CI 环境中则在 job 内联执行,因此 debug 输出可能留在 workflow log。载荷还检查系统语言,遇到俄语环境会直接输出退出提示并终止。
凭据收集与 Runner 内存读取
ChainDrop 搜索的目标远超 .npmrc:云 metadata 与短期 IAM token、GitHub/npm/PyPI/RubyGems 凭据、Docker/Helm、Terraform state、Vault token、SSH key、Kubernetes service account、kubeconfig、.env、shell history、wallet 文件以及多种开发和 AI 工具配置都在扫描范围内。
更关键的是,一个藏在加密 blob 中的 Python helper 会定位 GitHub Actions 的 Runner.Worker 进程,读取:
/proc/<pid>/maps
/proc/<pid>/mem
它在 live memory 中搜索 OIDC token 和 runner secret,而不是等待凭据写入磁盘。即使短期 token 会随 job 结束消失,只要恶意依赖在同一 runner 上执行,就可能在生命周期内完成提取。
图 2:从定位 Runner.Worker、解析内存映射到提取 OIDC token 与 secret 的流程。
VS Code 与 Claude Code 交叉持久化
蠕虫写入五个文件:
.claude/math_init.js
.claude/settings.json
.claude/setup.mjs
.vscode/setup.mjs
.vscode/tasks.json
.vscode/tasks.json 使用 folder-open task 调用 .claude/setup.mjs;.claude/settings.json 则在 SessionStart 调用 .vscode/setup.mjs。两个配置交叉引用,让每一侧看起来像另一工具的正常文件。
图 3:项目打开和 coding session 启动都可成为自动执行触发点。
这个样本存在实现缺陷:真正 payload 只写到 .claude/math_init.js,而 .vscode/setup.mjs 会寻找并不存在的 .vscode/math_init.js。因此已分析 build 中只有 VS Code 触发路径能执行到载荷。样本还携带 macOS LaunchAgent 与 Linux systemd user service installer,但主执行路径没有调用点,只能标记为潜在能力,不能写成已安装事实。
两条传播路径
常规自传播依赖被盗 npm token。只有 token 具备 package write 权限并可 bypass_2fa 时,蠕虫才会:
-
- 枚举账号可发布的包;
-
- 下载最新 tarball;
-
- 注入 dropper 和 payload;
-
- 修改
package.json添加preinstall;
- 修改
-
- 增加 patch version,重新打包并发布。
它还会创建 .github/workflows/codeql_analysis.yml,把 ${{ toJSON(secrets) }} 序列化为 artifact;若持有 GitHub App token,可向最多 50 个可访问分支写入 .claude/settings.json 和 .vscode/tasks.json。
第二条路径专门检查 opensearch-js 和 release-drafter.yml。满足条件时,蠕虫不做常规收集,而是向 npm trusted-publishing exchange 请求 OIDC publish credential,再修改 @opensearch-project/opensearch 包。
图 4:静默路径不添加 lifecycle script,而是植入近似官方 scope 的 optionalDependencies。
发布前它还获取 Sigstore audience 的第二枚 OIDC token,通过 Fulcio 证书和 ephemeral P-256 key 生成 in-toto SLSA v1 provenance,写入 Rekor,并把 bundle 附加到 npm publish。该 provenance 不是伪造的:恶意 tarball 确实由证书指定的仓库和 workflow 构建。签名只证明构建来源,不证明 workflow 没有被攻击者控制。
Unit 42 没有观察到这条 OpenSearch 路径实际执行;它在 payload 主入口中可达且实现完整,应标记为可用但未观测能力。
Ethereum 智能合约作为 C2 路由器
payload 不硬编码 C2 domain,而是查询合约:
0xE1f2395ee43e45A1556EC6438a88c31B83493103
合约是一个简单的 StringListStore,支持读取 domain list、读取 owner 和 owner-only setter。蠕虫轮询约 60 个公共 Ethereum RPC provider,任何一个可用即可取得当前外传端点。合约不发 event,operator 改写字符串时不产生直观业务告警。
初始列表包含 npm-cache[.]com、pypi-get[.]com 和 js-mirror[.]com,随后缩减为 npm-cache[.]com。2026 年 8 月 4 日,operator 通过一次链上 transaction 把 active C2 切换为 awqhnjewqjkl[.]icu,已部署样本无需更新。
合约解析失败时,蠕虫搜索 GitHub commit 中的 thebeautifulmarchoftime,验证 RSA 签名后接受备用 domain;再失败则使用受害者账号建立 Dune 主题公开仓库外传。
返回代码执行与加密外传
domain-based sender 会读取 HTTP response,解析 JSON,并对 code 字段执行 eval。这让 operator 能按 UUID 向特定受害者返回第二阶段代码,且不必落盘。
图 5:外传请求完成后,客户端解析并执行 C2 返回的 code。
研究人员使用完全匹配的加密格式向 live endpoint 发送 dummy data,服务器返回 200 OK 和空 body。可确认代码通道存在,但测试时可能处于关闭、按受害者条件 gate,或采用异步下发;不能声称 C2 已向该 probe 返回第二阶段。
收集结果先 JSON serialize 和 gzip,再用随机 AES-256-GCM key 加密;AES key 使用内嵌 RSA public key,以 RSA-OAEP-SHA256 包装。请求包含 encrypted envelope、wrapped key 和 host-derived UUID。
图 6:C2 请求中的 envelope、RSA 包装密钥与主机标识。
GitHub sender 还会把被盗 token 双重 Base64 编码,拼到以下固定 marker 后作为公开 commit message:
IfYouBlockThisAPIKeyItWillCrashTheLiveProductionServersOfAllThirdPartyClients
其他感染实例可搜索该 marker、解码 token 并检查 repo scope,让一个受害者的凭据变成整个蠕虫网络的共享资源。
基础设施与解混淆
三个早期 C2 domain 在 8 秒内完成注册;14 分 23 秒后,FixedFloat 向 operator wallet 转入 ETH;三天后同一 wallet 部署 resolver contract。资金流只能说明 funding rail,不能单独证明攻击者身份,Binance deposit address 才是更强的后续调查 pivot。
图 7:domain 注册、钱包入金、合约部署、C2 列表变化和交易所转账。
行为遥测中还出现 Bun 执行 Math_Symbol.js,随后启动 cmd.exe 调用 gh auth token 窃取 GitHub PAT。
图 8:从 VS Code 环境中的 Bun 载荷到 gh auth token 的进程链。
Unit 42 对 727 KB payload 完成了三层还原:
- • Layer 1:73 套 function-specific alphabet 的 Base91,加 14 位 array rotation,恢复 4,613 个 string entry;
- • Layer 2:PBKDF2-SHA256 200,000 次迭代和 seeded Fisher-Yates shuffle 组成的 byte permutation cipher,恢复 727 个 string;
- • Layer 3:AES-256-GCM 加 gzip 的 10 个 blob,包含 Bash/Python helper、runner memory scraper、workflow、持久化文件与 RSA key。
图 9:Base91、custom permutation cipher 与 AES-GCM blob 的逐层还原。
检测与处置
定位感染不能只回滚 npm tag。lockfile、cache、mirror、tarball 和 CI image 可能仍固定在恶意版本。处置至少包括:
- • 搜索
Math_Symbol.js、math_init.js、setup.mjs、router_runtime.js和新增preinstall; - • 检查
.vscode/tasks.json、.claude/settings.json、codeql_analysis.yml及 OS-level latent artifacts; - • 检测 Bun/Node 子进程调用
gh auth token、读取/proc/*/mem、查询 cloud metadata 和访问 Ethereum RPC; - • 监控对 resolver contract 的
eth_call和setStrings(),并阻断 domain/SNI,而不是误伤共享 CDN IP; - • 搜索两个固定 GitHub marker、Dune 仓库描述和异常 patch publish;
- • 轮换所有可能暴露的 npm、GitHub、cloud、Kubernetes、Vault、SSH 与 automation credentials;
- • 使用 disposable CI runner、workload-bound identity、egress allowlist 和 canary credential 缩小爆炸半径。
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论