【安全评论】npm 又中招了:”我们无法预防”——唯一经常发生此事的包管理器

> **导语**:2026年5月14日,npm 仓库中三个恶意版本的 node-ipc 包被同时发布,意图窃取全球开发者的云凭证、SSH 密钥和 AI 工具配置。这不是 npm 第一次发生这种事,当然也不会是最后一次。每隔几个月,整个 JavaScript 生态就会经历一次这样的\”自然灾难\”——而官方每次的回应,都是那句令人熟悉的\”我们真的无法预防\”。

## 一、事件回顾:一颗定时炸弹如何被引爆

### 1.1 事件经过

2026年5月14日,npm 账户 `atiertant`(关联邮箱 a.tiertant@atlantis-software.net)在同一时刻发布了三个恶意版本的 node-ipc 包:

– **node-ipc@9.1.6**(虚构版本号,9.x 分支此前从未有 CommonJS 构建)
– **node-ipc@9.2.3**(同上)
– **node-ipc@12.0.1**(针对特定目标的高精度攻击版本)

该包每周下载量高达 **822,000 次**,历史累计下载超过 **10,000,000 次**。一旦开发者在项目中使用 `npm install node-ipc`(未锁定版本),恶意代码便会通过 `require(‘node-ipc’)` 自动加载,无需任何触发条件。

### 1.2 恶意代码行为分析

恶意 payload 被注入在 `node-ipc.cjs` 文件末尾(第 1271 行),是一个 80,079 字节的混淆 IIFE(立即执行函数)。payload 执行流程如下:

**阶段一:配置解码**

“`javascript
// 自定义 Base-16 编码的 C2 配置
// 解码后得到:
r: ‘sh.azurestaticprovider.net:443’, // C2 地址(伪装成 Azure)
k: ‘qZ8pL3vNxR9wKmTyHbVcFgDsJaEoUi’, // HMAC 认证密钥
z: ‘bt.node.js’, // 请求标识符
“`

**阶段二:定向指纹(仅 12.0.1)**

12.0.1 版本内置了 SHA-256 定向指纹校验,会比对当前模块路径的哈希值。只有匹配预设目标的系统才会执行完整 payload,非目标系统则静默退出。这意味着攻击者已经锁定了特定的开发环境或项目。

**阶段三:凭证窃取**

payload 会尝试窃取超过 90 类敏感数据,包括:

| 类别 | 目标路径 |
|——|———-|
| 云凭证 | `~/.aws/credentials`、`~/.azure/accessTokens.json`、`~/.config/gcloud/credentials.db` |
| SSH 密钥 | `~/.ssh/id_rsa`、`~/.ssh/id_ed25519`、`/etc/ssh/ssh_host_*_key` |
| AI 工具配置 | `~/.claude.json`、`~/.claude/mcp.json`、`.kiro/settings/mcp.json` |
| 基础设施即代码 | `~/.terraform.d/credentials.tfrc.json`、`**/terraform.tfvars` |
| CI/CD 工作流 | `**/.github/workflows/*.yml`、`**/.gitlab-ci.yml` |
| Shell 历史 | `~/.bash_history`、`~/.zsh_history` |

**阶段四:双通道数据外泄**

1. **HTTPS 外泄**:将窃取数据压缩为 gzip,通过 HTTPS POST 上传至 `sh.azurestaticprovider.net`
2. **DNS TXT 外泄**:绕过企业 DNS 监控,直接将数据编码为 DNS 查询发送至 C2 服务器 IP

## 二、历史重演:npm 为何总是”无法预防”

### 2.1 这不是第一次

node-ipc 并非首次被滥用。2022年3月,该包的 10.1.1 和 10.1.2 版本曾被用于部署名为 **peacenotwar** 的恶意负载——在特定地区用户的家目录下创建 `USE_PEACE_NOT_WAR` 文件夹,并在特定触发条件下删除系统文件。

两次攻击,背后是**不同的威胁行为者**,却利用了**同一个 npm 包生态位**。

### 2.2 npm 的结构性缺陷

讽刺文章的核心论点在于:npm 是唯一一个**定期发生**此类事件的包管理器。为什么?

**问题一:默认执行任意代码**

npm 默认会在 `npm install` 时执行包中的 `preinstall`、`install`、`postinstall` 脚本。这意味着当你执行 `npm install foo` 时,你实际上是在告诉系统\”请下载这个未知来源的代码压缩包,然后以当前用户权限执行其中的任意脚本\”。

这在设计上就是一次供应链攻击的完美入口。

**问题二:维护者账户长期不活跃**

node-ipc 的原作者 Brandon Nozalski Miller(GitHub 用户名 RIAEvangelist)自 2024年8月12日 发布 12.0.0 后便停止维护。21个月后,攻击者获得了维护者权限,发布了三个恶意版本。在 npm 的生态中,高下载量但无人维护的\”孤包\”是典型的狩猎场。

**问题三:版本发布无审核期**

当攻击者在同一天、同一时刻发布三个版本覆盖不同 semver 范围时,没有任何机制可以阻止这一切发生——直到安全研究员发现并手动举报。

**对比其他生态**

讽刺文章指出,在 Go、Rust 等生态中,由于语言自带强标准库和严格的构建工具链,开发者对第三方代码的依赖深度远不及 npm,因此类似事件的频率和影响也低得多。

## 三、蓝队视角:我们能做什么

### 3.1 立即排查

如果你的项目使用了 node-ipc,请执行以下命令:

“`bash
# 检查直接依赖版本
npm ls node-ipc

# 搜索 lockfile 中的受影响版本
grep -E ‘”node-ipc”.*”(9\.1\.6|9\.2\.3|12\.0\.1)”‘ package-lock.json

# 检查 CI/CD 和开发者机器上的安装历史
npm list node-ipc –all
“`

如果发现存在受影响版本,请**立即假设凭证已失陷**,执行以下操作:

1. 轮换所有在失陷系统上使用过的凭证(AWS密钥、SSH密钥、GitHub Token等)
2. 检查 CI/CD 环境的网络外泄日志
3. 检查 `$TMPDIR/nt-*` 目录是否存在(payload 临时文件)
4. 检查环境变量中是否存在 `__ntw=1`(daemon 进程标记)

### 3.2 构建检测规则

**SIEM 检测示例(Splunk SPL)**

“`spl
# 检测失陷版本安装
index=npm_logs event_type=install (version=”9.1.6″ OR version=”9.2.3″ OR version=”12.0.1″)
| stats count by host, user, version, timestamp

# 检测 C2 域名访问
index=network_logs dest_domain=”sh.azurestaticprovider.net” OR dest_ip=”37.16.75.69″
| stats count by src_ip, dest, timestamp

# 检测异常 DNS 外泄查询(直接到非标准DNS服务器)
index=dns_logs src_ip!=10.0.0.0/8 query=”*.bt.node.js”
| stats count by src_ip, query, timestamp
“`

### 3.3 长期防御策略

**纵深防御三层面**

**层面一:构建时(Build-time)**

– 启用 `npm config set ignore-scripts true`,禁止自动执行 install 脚本
– 使用 `npm ci` 而非 `npm install`,确保 lockfile 精确匹配
– 部署私有 npm 镜像(如 Verdaccio),所有包经过安全审核后再进入内部生态
– 使用 Socket.dev 或 Snyk 的包分析功能,在安装前评估包的安全风险

**层面二:运行时(Runtime)**

– 限制 CI/CD 环境的外向网络流量,仅允许必要端点
– 在开发者工作站上启用 EDR 监控 npm 安装行为
– 使用容器化构建环境,确保每次构建在隔离环境中执行

**层面三:运营时(Operations)**

– 建立 npm 包监控机制,追踪项目依赖的健康状态
– 对高风险包(长期不更新、大量下载、作用域可疑)进行标记
– 将供应链安全纳入漏洞响应流程,设立专门的应急响应通道

## 四、结语:我们还能说什么

每次 npm 供应链事件发生后,官方都会发表一份声明,表示遗憾,并强调\”无法预防\”。而社区则会在短暂愤怒后,继续 `npm install`,继续信任下一个 40 层深的依赖树。

作为蓝队,我们习惯了\”假设已被攻破\”的思维模式。但在 npm 这里,攻破从未停止——它就是日常。

讽刺文章的最后一句话说得很好:**\”直到明天早晨的下一场不可避免的灾难,我们必须保持韧性。”**

韧性当然重要。但韧性不意味着每次都用临时补丁去修补一个根本不该存在的伤口。

或许,是时候认真讨论一下 npm 的结构性安全问题了。

**版权声明**:本文由华盟网原创发布,保留所有权利。配图由华盟网授权使用。

© 版权声明
THE END
喜欢就支持一下吧
点赞14 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容