2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Shai-Hulud 2.0 マルウェアの解説と対策をまとめてみる

Posted at

この記事は何か

2025年11月に観測されたnpmサプライチェーン攻撃「Shai-hulud 2.0」。Bun導入を装ったpreinstallで侵入し、GitHubに秘密情報を吐き出すワーム型マルウェアです。公開リサーチ(HelixGuard / Check Point / Wiz)を基に整理してみました。

ざっくり全体像

  • 侵入: 改ざんパッケージのpreinstallsetup_bun.jsbun_environment.jsが動く(Bunを悪用して検知回避)。
  • 窃取: npm/GitHubトークン、SSHキー、AWS/GCP/Azure認証、環境変数などを収集。
  • ばらまき: GitHubに self-hosted runner SHA1HULUD を登録し、.github/workflows/formatter_*.ymldiscussion.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リポ増加を観測

防御・緩和(優先順)

  1. シークレット総ローテーション(GitHub PAT/GITHUB_TOKEN、npmトークン、SSH、AWS/GCP/Azure等)。
  2. 悪性バージョン除去+lockfile再インストール。Wiz IOC CSVで依存を照合し、該当を削除してクリーンインストール。
  3. CIで可能ならnpm_config_ignore_scripts=truenpm ci --ignore-scriptsでライフサイクルスクリプト停止(ビルドに必須なら除外)。
  4. GitHub棚卸し: self-hosted runnerにSHA1HULUDがないか、.github/workflows/formatter_*/discussion.yamladd-linter-workflow-*ブランチ、怪しい説明文を削除。
  5. 権限最小化: fine-grained PAT、短期失効トークン、環境分離。ワークフロー作成にレビュー必須、runner登録を制限。
  6. 監視と検知: lockfile/SBOM監査、署名検証(可能なら有効化)、定期スキャンでリポ説明/ブランチ/ワークフローを確認。

背景(1.0→2.0で変わったこと)

  • 実行タイミング: 1.0はpostinstall、2.0はpreinstallで早期に動く。
  • 実行基盤: Bunを悪用し、Node向け検知をすり抜けやすくした。
  • 拡散力: 盗んだトークンでnpm/GitHubへワーム的に再拡散。
  • 永続化: self-hosted runner登録+ワークフロー投入で長期アクセスを維持。

参考

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?