この記事は何か
2025年11月に観測されたnpmサプライチェーン攻撃「Shai-hulud 2.0」。Bun導入を装ったpreinstallで侵入し、GitHubに秘密情報を吐き出すワーム型マルウェアです。公開リサーチ(HelixGuard / Check Point / Wiz)を基に整理してみました。
ざっくり全体像
- 侵入: 改ざんパッケージの
preinstallでsetup_bun.js→bun_environment.jsが動く(Bunを悪用して検知回避)。 - 窃取: npm/GitHubトークン、SSHキー、AWS/GCP/Azure認証、環境変数などを収集。
- ばらまき: GitHubに self-hosted runner
SHA1HULUDを登録し、.github/workflows/formatter_*.ymlやdiscussion.yamlでSecretsをactionsSecrets.json等にまとめて外部リポへアップロード。説明文「Sha1-Hulud: The Second Coming」が目印。 - 拡散: 盗んだトークンで
npm publishやリポ生成を自動化するワーム挙動。セカンドフェーズでは「The Continued Coming」などの文言やDiscord誘導も確認。
簡単な確認手順
GitHub(Org/個人)
- 説明文: 「Sha1-Hulud: The Second Coming」
- ブランチ:
add-linter-workflow-* - ワークフロー:
.github/workflows/formatter_*.yml,.github/workflows/discussion.yaml - ランナー: self-hosted
SHA1HULUD
# 説明文マーカー
gh repo list YOUR_ORG --limit 1000 --json nameWithOwner,description \
--jq '.[] | select(.description != null and (.description | contains("Sha1-Hulud: The Second Coming"))) | .nameWithOwner'
# suspicious ブランチ(攻撃が作成する add-linter-workflow-* ブランチ)
gh repo list YOUR_ORG --limit 1000 --json nameWithOwner --jq '.[].nameWithOwner' | \
while read repo; do
gh api -X GET "/repos/$repo/branches?per_page=100" --paginate \
--jq '.[] | select(.name | startswith("add-linter-workflow")) | "'"'"'${repo}'"'"'/'"'"'" + .name'
done
大規模Orgはプレフィックスで絞り、ページ数を抑えてAPI制限を回避。
lockfileをIOCでざっくり照合
zsh -c 'set -euo pipefail; command -v rg >/dev/null || { echo "rgが必要" >&2; exit 1; }; CSV_URL="https://raw.githubusercontent.com/wiz-sec-public/wiz-research-iocs/main/reports/shai-hulud-2-packages.csv"; tmp=$(mktemp); curl -fsSL "$CSV_URL" -o "$tmp"; locks=(); for lf in package-lock.json pnpm-lock.yaml yarn.lock; do [[ -f $lf ]] && locks+=($lf); done; (( ${#locks[@]} )) || { echo "lockfileなし"; exit 0; }; while IFS=, read -r name version _; do [[ $name == "Package" || -z $name || -z $version ]] && continue; ver_raw=${version// /}; ver_trim=${ver_raw#=}; for lf in $locks; do if rg -F --no-filename --quiet "$name@$ver_trim" "$lf"; then echo "HIT: $name@$ver_trim in $lf"; fi; done; done < "$tmp";'
何も出なければヒットなし。ヒットした依存は削除+キャッシュクリア+クリーンインストール、関連シークレットを総ローテーション。
影響規模(公開リサーチの数字)
- Check Point: 621悪性npmパッケージ / 約25,000リポ / 487 Org / 14,206漏えいシークレット(2,485有効)
- Wiz: 悪性npm 約700 / 25k+リポ / 約500ユーザー、最大30分で約1,000リポ増加を観測
防御・緩和(優先順)
- シークレット総ローテーション(GitHub PAT/GITHUB_TOKEN、npmトークン、SSH、AWS/GCP/Azure等)。
- 悪性バージョン除去+lockfile再インストール。Wiz IOC CSVで依存を照合し、該当を削除してクリーンインストール。
- CIで可能なら
npm_config_ignore_scripts=trueやnpm ci --ignore-scriptsでライフサイクルスクリプト停止(ビルドに必須なら除外)。 - GitHub棚卸し: self-hosted runnerに
SHA1HULUDがないか、.github/workflows/formatter_*/discussion.yaml、add-linter-workflow-*ブランチ、怪しい説明文を削除。 - 権限最小化: fine-grained PAT、短期失効トークン、環境分離。ワークフロー作成にレビュー必須、runner登録を制限。
- 監視と検知: lockfile/SBOM監査、署名検証(可能なら有効化)、定期スキャンでリポ説明/ブランチ/ワークフローを確認。
背景(1.0→2.0で変わったこと)
- 実行タイミング: 1.0は
postinstall、2.0はpreinstallで早期に動く。 - 実行基盤: Bunを悪用し、Node向け検知をすり抜けやすくした。
- 拡散力: 盗んだトークンでnpm/GitHubへワーム的に再拡散。
- 永続化: self-hosted runner登録+ワークフロー投入で長期アクセスを維持。