Ectoplasm npm 安装 Hooks 窃取AWS凭证

外质:npm 安装 hooks 仅通过容器触发器收集 AWS 凭证

TL博士

一簇 五个 npm 包通过两个账户账号发布,已交付 postinstall 一个钩子程序,用于从主机读取云凭证并将其发送到外部服务器。这些软件包使用类似幽灵或海盗的名称—— coral-wraith, ecto-corsair-whisper-6f3b9, ecto-corsair-flag-x9m4, ecto-rust-read-f3a9c1, ecto-nightly-spirit ——并将他们收集到的任何东西序列化成假货 ecto_module: 在传输之前,我们会跟踪集群。 胞质.

有效载荷仅在检测到特定环境时运行:即主机名称为 a 的主机。 12 个字符的十六进制字符串 以及一个位于以下位置的工作目录 /app/node_modules —容器化构建或CI工作进程的形状。当该门通过时,钩子会查询 AWS实例元数据服务(IMDSv2) 对于 IAM 角色凭证,枚举 AWS机密管理器 跨越三个区域,转储环境变量,读取以下目录下的文件 /app并抓取夺旗赛字符串。然后,它通过两种方式泄露结果:向……发送信标。 webhook.site 收集器和清单 PUT 到原始 IP 端点,并包含 localhost 优先回退列表。

后期的软件包描述为“用于 verdaccio 供应链测试的 CTF 有效载荷”。我们报告此描述为可观察的事实。其行为本身——实时向公共 IP 地址发出出口流量、读取真实的 IMDS 凭证、调用真实的 Secrets Manager——与标签无关,也正是这些版本被判定为恶意的原因。

集群中的一个名称, coral-wraith但它并没有止步于一次发布。在短短几个小时内,它就迅速推出了数十个版本—— 1.0.0 爬到 6.0.0 ——而之前同名的版本曾使用过膨胀 9999.0.x 版本号,经典形状 依赖关系混淆尝试在这一过程中,有效载荷明显成熟了:从一次性的主机枚举信标发展成为完整的 AWS 凭证转移,并包裹在环境检查中,使其在目标之外保持静默。

5个名字; coral-wraith 仅此一项就被重新出版了数十个版本
生态系统 NPM
安装向量 postinstall 生命周期脚本
主要目标 AWS IAM 角色凭证 + Secrets Manager 密钥值、环境变量 /app
导出 webhook.site 信标 + 原始 IP C2 投递
触发门 12 位十六进制主机名 + /app/node_modules 当前工作目录,以及一个环境检查,用于抑制该上下文之外的有效载荷。
严谨求真 —云凭证和托管密钥泄露 容器化构建和运行时环境

攻击解剖

集群中的每个软件包都是以相同的方式构建的:一个几乎为空的软件包 index.js (module.exports = {}),一行 的package.json 脚本 - “postinstall”: “node postinstall.js” ——以及有效载荷 postinstall.js安装该软件包即可运行钩子;无需导入或调用。

目标锁定门。 在执行任何操作之前,外骨骼家族的有效载荷会检查周围环境:

function isAppWorker():   host = os.hostname()   if host does NOT match /^[0-9a-f]{12}$/  -> exit   if cwd does NOT contain "/app/node_modules" -> exit   if cwd contains "/tmp/npm-safe"            -> exit   otherwise -> proceed

Docker 默认分配给容器的主机名格式为 12 位十六进制。 /app/node_modules 这是一个传统的容器内安装路径。如果路径看起来像一个沙盒化的提取目录,则第三条规则会失效。最终结果是,有效载荷会保持在开发人员笔记本电脑或分析沙箱中处于休眠状态,仅在容器化的构建或运行时工作环境中激活——这种环境最有可能承载实时云凭证。集群中最早的软件包, 珊瑚幽灵没有这样的门,并且无条件地运行其(更简单的)集合。

购物当闸门通过时,钩子会从闸门处伸出。 执行文件同步(“/bin/sh”,[“-c”,...]) 并运行一个复合命令,该命令按以下顺序执行:

1. PUT /latest/api/token to 169.254.169.254          (IMDSv2 token request) 2. GET .../iam/security-credentials/                  (IAM role name) 3. GET .../iam/security-credentials/<role>            (temporary credentials) 4. dump env | sort                                    (environment variables) 5. list /app (excl. node_modules) + cat first 15      (application files) 6. aws secretsmanager list-secrets                    (us-east-1, eu-west-1, eu-central-1) 7. scrape readable files for HTB{...}                 (capture-the-flag strings)

步骤 1-3 是标准的 IMDSv2 检索流程:请求会话令牌,然后将其作为附件附加。 X-aws-ec2-metadata-token 请求头用于获取实例的 IAM 角色及其临时访问密钥。选择实现 IMDSv2 而不是更简单的无需身份验证的 IMDSv1。 的GET 值得注意的是,这意味着即使在配置为需要基于令牌的元数据访问的实例上,有效载荷也能正常工作,而这正是 AWS 推荐的安全加固措施。步骤 3 返回的凭证是短期有效的。 访问密钥 ID/秘密访问密钥/Token 三元组的作用域限定于实例的角色;该角色可以执行的任何操作,持有这些密钥的人都可以在凭证的生命周期内执行。

步骤 4-6 扩大了采摘范围。 ENV dump 文件会捕获构建或运行时过程继承的所有内容——实际上,注册表令牌、数据库连接字符串和 API 密钥通常都存储在这里。 /应用 文件遍历最多可以读取外部的十五个应用程序文件。 节点模块它可以显示表面配置, .ENV 文件或源。步骤 6 调用 aws secretsmanager list-secrets 在三个区域中;步骤 1-3 中提取的凭据正是用于验证这些调用的凭据,因此 IMDS 读取和 Secrets Manager 枚举链结合成一个单一的升级过程:实例角色 → 托管密钥清单。步骤 7 是对夺旗模式的致敬——当 HTB{…} 如果找到标志,则单独发送;否则,收集到的原始数据块将被分成四块发送。

集群中的后续版本进一步升级了功能。它们不仅完成清单清点,还会解析 IMDS 响应,并将临时密钥导出为 AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN 环境变量,确认身份 aws sts 获取来电者身份然后遍历返回的每个密钥 秘密列表 调用 aws secretsmanager get-secret-value 在每个程序中——检索秘密内容,而不仅仅是它们的名称。相同的版本还可以读取进程替换标志二进制文件(/readflag 和朋友们)并尝试 货物运行 针对任何在以下位置找到的 Rust 项目 /应用将收获范围从云凭证扩大到构建环境暴露的所有内容。

这些后续版本也采取了更严格的自我限制措施。除了 12 位十六进制主机名之外, /app/node_modules 该有效载荷会检查活动软件包注册表配置和工作目录路径,如果它们指示的是分析或镜像上下文而非实时目标,则会静默退出。综合来看,该有效载荷在大多数检测环境中不会执行任何可观察到的操作,并且仅在判断自身位于真正的容器化主机上时才会运行完整的收集过程。

渗出收集到的数据通过两个通道离开主机。首先是信标通道。 解决方案&帖子 固定 webhook.site 收集器携带主机名、数字 UID、工作目录以及最多 120 KB 的收集数据。其次,数据被折叠成一个伪造的 YAML“模块清单”并 PUT/api/modules/ 在目标服务器上:

ecto_module:   name: "<flag-or-chunk-0>"   version: "1.0.0"   power_level: "<chunk-1>"   ship_deck: "<chunk-2>"   cargo_hold: "<chunk-3>"

清单字段名称(功率水平, 船甲板, 货舱)只是装饰——被盗数据隐藏在字符串值中,这就是为什么网络监控器看到的是看似无害的软件包注册表清单上传,而不是明显的数据转储。信标通道承载更多信息: 解决方案&帖子 身体 webhook.site 包括主机名、数字 UID、工作目录以及最多 120 KB 的收集到的数据块,因此即使只有一个成功的信标也能提供完整的数据。 webhook.site 是一项免费的请求检查服务;将其用作收集器意味着运营商永远不必为该通道建立自己的接收基础设施,并且记录的请求会保留在该服务的存储库中。

清单 PUT 遍历一个以多个开头的备用列表 127.0.0.1/本地 端口,然后落入三个公共地址154.57.1​​64.0/24` 范围,直到第一个响应 2xx 状态码的端点为止。本地主机优先的顺序与“verdaccio 测试”的自我描述(环回地址上的本地注册表)一致,但公网 IP 回退机制意味着,只要环回地址不监听,数据就会离开主机——也就是说,在任何非作者自己的测试机器上都会发生这种情况。

时间线

该集群展现出能力的逐步增长,而非一次性下降。我们按观察到的行为而非发布时间对其进行排序:

阶段 软件包/版本 宠物行为研究
早期运行 coral-wraith 9999.0.x 版本号虚高,疑似试图混淆依赖关系;安装时枚举和数据泄露
种子 coral-wraith 1.0.0 安装后收集 id/env/flag 文件;单个 PUT 请求 154[.]57[.]164[.]71:30782标记 ECT-472839
快速迭代 coral-wraith 1.0.1 → 6.0.0 数小时内进行了数十次释放;有效载荷增加 isAppWorker() 门禁、IMDSv2凭证提取、完整 get-secret-value 枢轴、注册表/路径环境检查和双接收器标记
平行名称 ecto-corsair-whisper-6f3b9 1.0.14–1.0.18 相同的门控有效载荷; webhook.site 信标和多端点回退列表
变种 ecto-rust-read-f3a9c1 1.0.1–1.0.2 增加额外的水槽标记 ECT-987654, ECT-654321, ECT-839201
变种 ecto-corsair-flag-x9m4 1.0.0, ecto-nightly-spirit 1.1.0 相同的门控有效载荷、相同的C2和信标

该集群最显著的特点是其发布节奏:并非只有一个软件包和一个版本,而是同一个名称快速地反复发布,每个版本都只是前一个版本的微小变化,同时还有几个名称不同的同级软件包携带相同的有效载荷。 耳语 该家族代码分裂成两个非常接近的指纹——一组触发两个关键检测,另一组触发三个(一个额外的文件读取接收器)——但两者都解析到相同的有效载荷;区别在于代码漂移,而不是行为上的分支。版本 耳语 超出分析范围(截至撰写本文时至少达到 1.0.25)的序列已在注册库中实时观测到,并且 珊瑚幽灵 名称继续沿着同一窗口向上攀升。

妥协指标

以下所有指标均提取自磁盘上的软件包源代码。网络指标已进行去重处理。

网络

指示符 职位
hxxp://154[.]57[.]164[.]71:30782 C2 PUT 目标(coral-wraith)
hxxp://154[.]57[.]164[.]80:30543 C2 PUT 回退(ecto-*)
hxxp://154[.]57[.]164[.]82:31250 C2 PUT 回退(ecto-*)
hxxp://154[.]57[.]164[.]71:31289 C2 PUT 回退(ecto-*)
hxxps://webhook[.]site/602a4c72-7033-4e28-92ea-dc66e59206e5 信标收集器
169[.]254[.]169[.]254/latest/... IMDSv2 凭证读取(目标端,AWS 元数据)

行为/文件

指示符 职位
"postinstall": "node postinstall.js" 安装向量
ecto_module: YAML power_level / ship_deck / cargo_hold 撤离清单方案
水槽标记 ECT-472839, ECT-987654, ECT-654321, ECT-839201 C2路径段 /api/modules/<marker>
isAppWorker() 门:主机 /^[0-9a-f]{12}$/,cwd 包含 /app/node_modules 激活条件
aws secretsmanager list-secrets 超过 us-east-1, eu-west-1, eu-central-1 秘密枚举
HTB{...} 正则表达式抓取 夺旗收割

文件哈希值(sha256,分析时捕获)

文件 sha256
coral-wraith/postinstall.js ce5ff035cfdfed1d0015446424b352c27b66bcb77e9fdb0a51e4245199146824
ecto-corsair-whisper-6f3b9 1.0.18/postinstall.js b58432acba376aa6976f0490d9a1c04257ccdbc856d8390260c50322d63e31c3

归因与观察行为

这五个软件包名称分别发布在两个不同的 npm 账户下,但它们共享足够的基础设施,可以将它们视为一个集群:相同的 ecto_module 清单模式相同 ECT-472839 主要汇水标志,相同 webhook.site 收集器 ID 和 C2 端点位于同一位置 154.57.1​​64.0/24` 区块。种子包(`coral-wraith`)(更简单且无限制)因此,受限制的 ecto-family 可以理解为对同一工具包的迭代,而不是独立努力。

这些软件包在后续版本中将自身描述为: “用于 verdaccio 供应链测试的 CTF 有效载荷。” 我们将该标签作为可观察的事实呈现,而非将其重新表述为关于目的的结论。代码的功能明确无误,与其标签无关:它从实例元数据服务读取 IAM 角色凭证,枚举跨三个 AWS 区域的托管密钥,并将结果传输到公有 IP 地址和第三方 Webhook 收集器。一个真正仅使用环回的测试框架不需要公有 IP 地址回退列表、IMDS 凭证读取或跨区域的 Secrets Manager 调用。由于出口流量和凭证访问是真实的,因此受限制的版本被判定为恶意版本。

容器内访问限制是该机制在操作层面上最显著的特征。它既是一种规避措施(在笔记本电脑和分析沙箱中保持静默),也是一种目标定位措施,仅在最有可能存在真实身份与访问管理 (IAM) 角色和有效密钥的地方才会触发。在通用沙箱中运行这些软件包的分析人员不会观察到任何异常;该机制仅在使用 Docker 风格的主机名和容器内安装路径时才会显现。

这里存在的风险是云凭证和密钥在构建和运行时容器内部泄露。从 IMDS 获取的 IAM 角色凭证包含该角色拥有的所有权限; secretsmanager:列出秘密 (以及任何后续) 获取秘密值这会将泄露范围扩大到存储的应用程序密钥。环境变量转储通常包含注册表令牌、数据库 URL 和 API 密钥。在持续集成 (CI) 或容器环境中(这正是该安全门所针对的),只需传递安装其中任何一个软件包,就足以泄露这些信息。

Ectoplasm 符合我们持续观察到的一种模式:安装时有效载荷会获取云端元数据和托管密钥,而不是本地文件,并且会限制自身仅在高价值环境中触发。以下是两点防御性观察。

  • 该形状可检测。一个 npm/PyPI 安装钩子,其调用图同时连接到云密钥 API(AWS SecretsManager, gcloud 秘密, az keyvault或者 IMDS 地址和网络出口接收器是一种狭窄的高信号模式——它几乎不会出现在合法的生命周期脚本中。 静态流分析 无需依赖任何特定域名或 IP 地址即可将其标记出来。
  • 环境硬化会削弱它的作用。 强制执行 IMDSv2 并将跳数限制设为 1 可以防止容器工作负载访问实例元数据;将 IAM 角色权限范围限定为最小权限可以限制任何泄露凭证的影响范围;以及运行安装程序时使用 –忽略脚本 CI 会完全移除不需要安装钩向量的软件包的安装钩向量。

对于防御者而言,实际的检查措施包括:在构建/CI 容器与非允许列表中的公共 IP 地址建立出站连接时发出警报。 npm安装; 注意来自软件包生命周期脚本的 IMDS 访问;并将任何调用云 CLI 的安装钩子视为可疑,直到证明并非如此为止。

针对此集群还有两点需要注意。首先,由于激活仅限于容器化环境,因此在工作站上验证时看似无效的软件包在生产环境中仍可能处于活动状态——验证需要重现容器的主机名和路径条件,或者直接读取源代码,而不能仅仅依赖于“我安装了它,但什么也没发生”这种说法。其次,使用公共请求检查服务作为信标收集器意味着部分泄露的数据可能可以恢复用于事件响应:如果组织在其依赖树中发现此类软件包,则可以根据有效负载的收集逻辑推断成功的信标会包含哪些内容,并应轮换任何可从受影响的构建或运行时环境访问的 IAM 角色凭据、注册表令牌和托管密钥。 资格轮换安装完成后,有效的补救措施是删除软件包,而不是删除软件包。

sca-tools-软件-成分分析工具
确定软件风险的优先级、进行补救并加以保护
注册免费账号。
不需要信用卡。

保护您的软件开发和交付

使用 Xygeni 产品套件