世界を動かす技術を、日本語で。

自己増殖型NPMマルウェアが40以上のパッケージを侵害

概要

  • NPMエコシステム で新たなサプライチェーン攻撃が発生
  • @ctrl/tinycolor など40以上の人気パッケージが侵害対象
  • マルウェアは 自己伝播機能 を持ち、依存パッケージを連鎖的に感染
  • 認証情報の収集・外部送信、永続化のためのGitHub Actionsワークフロー挿入
  • 影響パッケージや 検知方法・インジケーター を詳細解説

NPMサプライチェーン攻撃の概要と進化

  • @ctrl/tinycolor を含む40以上のNPMパッケージでマルウェア感染
  • 週200万ダウンロード超 の人気パッケージが標的
  • マルウェアは 自己増殖型エンジン 搭載、下流パッケージを自動感染
  • postinstallスクリプト を悪用し、npm install時に不正コード実行
  • Linux/macOS開発環境 を主なターゲット、Windowsは回避

攻撃チェーンとマルウェアの構造

  • Webpackバンドル(bundle.js) による複数モジュール構成
  • process.env から認証情報を抽出、クラウドSDK/API利用で情報収集
  • TruffleHog を用いたファイルシステム全体のシークレットスキャン
  • 自己伝播 :NpmModule.updatePackage関数でメンテナーパッケージへ連鎖感染
  • GitHub Actionsワークフロー (shai-hulud-workflow.yml)を自動生成し永続化

マルウェアの詳細技術分析

  • OS情報収集 :getSystemInfo()でプラットフォーム・アーキテクチャ情報取得
  • AWS認証情報収集 :AssumeRoleWithWebIdentityCommand経由で検証・Secrets Managerから秘密情報を列挙
  • GCP認証情報収集 :@google-cloud/secret-managerで全シークレット取得・ペイロード復号
  • ファイルシステムスキャン :TruffleHogで高エントロピーなシークレット(AWSキー等)検出
  • NPMトークン取得・自己増殖 :.npmrcや環境変数からNPM_TOKEN抽出、所有パッケージを自動パッチ&公開

GitHub・クラウド認証情報の悪用と外部送信

  • GitHubバックドア :/userエンドポイントで認証、全リポジトリにshai-huludブランチ作成
  • ワークフロー挿入 :pushイベントで秘密情報をC2サーバへ送信
  • JSON形式で全情報集約、GitHub APIで"Shai-Hulud"公開リポジトリにアップロード
  • エラーはcatch {}で黙殺、TruffleHog実行を「セキュリティスキャン」と偽装

検知・対応のためのインジケーター

  • shai-hulud-workflow.yml の存在確認:.github/workflows/配下を検索
  • shai-huludブランチ の有無:ghコマンド等で全リポジトリを調査
  • bundle.js SHA-256 :46faab8ab153fae6e80e7cca38eab363075bb524edd79e42269217a083628f09
  • 外部送信先 :https://webhook.site/bb8ca5f6-4175-45d2-b042-fc9ebb8170b7
  • API/プロセス挙動 :NpmModule.updatePackage、AWS/GCP/NPM/GitHub APIへの異常アクセス、TruffleHogやnpm publish --forceの実行履歴

影響を受けたパッケージ一覧(一部抜粋)

  • @ctrl/tinycolor 4.1.1, 4.1.2
  • angulartics2 14.1.2, 14.1.1
  • @ctrl/ngx-codemirror 7.0.2, 7.0.1
  • @ctrl/ngx-csv 6.0.2, 6.0.1
  • @nativescript-community/gesturehandler 2.0.35
  • ngx-color 10.0.2, 10.0.1
  • ngx-toastr 19.0.2, 19.0.1
  • react-complaint-image 0.0.35, 0.0.32
  • ts-gaussian 3.0.6, 3.0.5
  • その他、40以上のパッケージ

組織での検知・対応策

  • GitHub検索 でshai-hulud-workflow.ymlやshai-huludブランチを徹底調査
    • 例:org:ACME path:/shai-hulud-workflow.yml
  • 全リポジトリのブランチ一覧取得、shai-huludブランチの存在確認スクリプト
  • bundle.jsのハッシュ値 でファイル照合
  • 不審なAPIアクセス・プロセス実行履歴 の監査
  • 感染パッケージの即時アンインストール・クリーンインストール推奨

まとめ・今後の注意点

  • NPMサプライチェーンの脅威 は高度化・自動化傾向
  • メンテナ権限の乗っ取り によるエコシステム全体への波及リスク
  • クラウド認証情報・GitHubシークレット の漏洩による二次被害懸念
  • 依存パッケージの監査・アップデート と、CI/CD環境のセキュリティ強化が必須
  • 継続的な脅威インテリジェンス収集 と、検知・対応体制の整備が重要

Hackerたちの意見

残念ながら、こういうサプライチェーン攻撃は現代のJavaScriptエコシステムに組み込まれてるってことを実感してきたよ。ベンダリングで即座のリスクは軽減できるけど、根本的な問題は解決しない。これがサーバーレンダリング(JSなし)をもっと真剣に考えるきっかけになったかも。HTMXの人たちに、JavaScriptなしでもかなりのことができるって納得させられたし、アプリも多分速くて、ゴチャゴチャしないと思う。

VueJs(JSON AjaxレスポンスをUIに変換するJavaScriptライブラリ)とHtmx(HTML AjaxレスポンスをUIに変換するJavaScriptライブラリ)の開発依存関係の数の違いって意味あるの?違いはあるけど、桁違いってわけじゃないし、どちらも完全に独立してるわけじゃない。サーバーでJSを使わないって決めるのはこの記事の文脈では合理的だけど、クライアント側ではhtmxも他のJSライブラリと同じように(開発)依存関係があるよ。 https://github.com/bigskysoftware/htmx/blob/master/package.j... https://github.com/vuejs/core/blob/main/package.json

私の見たところ、この攻撃が依存しているのは、新しい依存関係を追加する際の開発者のチェック不足だけだね。このチェック不足がJavaScriptエコシステムに特有のものでない限り、この攻撃はRustやGolangでも起こり得たと思う。

マルウェアを取りに行くまで、サプライチェーン攻撃はパッケージ管理やマシンへのベクトル、コードのあらゆるレイヤーで起こるよ。NPMが本当に気にしてるなら、公開するのに2FAを必須にするべきだね。公開前にスキャンを要求する。パッケージにハードキーと署名で署名する。インストールされた全てのパッケージが署名と一致するか確認する。セマンティックバージョニングの一致だけじゃ不十分だし、CRCチェックも足りない。これをパッケージとパッケージ管理に組み込む必要がある。

残念ながら、こういうサプライチェーン攻撃は現代のJavaScriptエコシステムに組み込まれてるってことを実感してきたよ。 こういう奇妙な見解をよく見るけど、攻撃の範囲を最近起こった単一のエコシステムに自動的に狭めるのは、技術的な根拠がないよね。特に懸念すべきは、セキュリティ業界でこういう見解が見られること。例えばNPMをターゲットにした対策が講じられているけど、PyPiやCratesには全くない。これが奇妙なのは、そうすることで他のエコシステムが無防備になるだけじゃなく、対策は非常に似ているはずだから、大きな利益のために最小限の追加労力で済むのに。

JavaScriptは過剰に使われすぎてるし、依存されすぎてる。多くのウェブサイトはテキストや画像を表示するだけなのに、みんなが知ってるからって重いJavaScriptライブラリを使ってる。これが現代のウェブを支えるトラッキングを可能にするからね。ユーザーにとってのメリットはないし、もし本当にJavaScriptを使わざるを得ない選択肢がなければ、こういうサイトが存在しない方がいいと思う。

そうだよ。パッケージマネージャーに依存しすぎて、標準ライブラリがない言語はこういうのに脆弱だね。いつか人々は気づいて、バニラJSに戻る必要があると思うけど、それはかなり難しいだろうな。Rustエコシステムも同じだし。パッケージへの依存が多すぎる。正しくやってる例はGoだね。

伝統的なJSは実は今まで作られた中で最も安全な環境の一つなんだ。毎日、何十億ものデバイスが信頼できないJSコードを実行していて、他のプラットフォームではこんなに大規模なサンドボックス実行は見られない。ほぼ30年の間に、ブラウザエンジンに対する大規模な攻撃はほとんどなかった。それが、ブラウザから派生したJSエンジンをサーバーサイドフレームワークを作るための完璧なツールにしている。ただ、NodeJSやnpmのプロセスや実践はセキュリティの見直しが急務だね。leftpadは解決すべき文化的な問題だよ。まずは、スニペットはnpmに置く必要はない。

単にJavaScriptを避けるだけじゃダメだよ。npmは大きくて簡単なターゲットだけど、一般的な問題はすべてのパッケージリポジトリに存在するからね。攻撃者が使ってないパッケージリポジトリを狙うのを期待するより、サプライチェーン攻撃の緩和戦略を考えた方がいいと思う。JavaScript開発の文化では、抽象化を重ねるコストを無視する風潮があるけど、それに乗っかる必要はないよ。多分、一番簡単なのは遷移依存関係を数えることだね。

npmの「postinstall」って概念があるせいで、影響範囲がさらに悪化してるんだよね。これによって、インストール後にホストシステムでコマンドを実行できるパッケージが増えちゃう。依存関係の依存関係にもこれが適用されるから、node_modules内の何でもこのフックにアクセスできるんだ。これはひどいアイデアで、削除するかもっと安全なものに置き換えるべきだと思う。

サプライチェーン攻撃 OSSに関しては、この用語を使うのはやめた方がいいよ。サプライチェーンって関係を示唆するけど、これらの企業や開発者はパッケージを含める以外にクリエイターとの関係はないから。「フリーコード攻撃」とか「ホビイストコード攻撃」みたいな名前にした方がいい。

左パッドの騒動の時、ここであるコメント者が有名なnpmのメンテナーについて「600のnpmパッケージの作者で、1200行のJavaScriptを書いている」って言ってたのを覚えてる。その後あまり変わってないよね。私が知ってる最良の反例はesbuildで、これは完全な機能を持つバンドラー/ミニファイアで、Goの標準ライブラリとGoプロジェクト自身がメンテナンスしている1つのパッケージ以外は外部依存関係がゼロだよ。 https://www.npmjs.com/package/esbuild?activeTab=dependencies https://github.com/evanw/esbuild/blob/755da31752d759f1ea70b8... 他の「次世代」プロジェクトは、問題のあるエコシステムを別のものに置き換えているだけ。例えばbiomejsやswcの依存関係を調べると、結構良さそうだよ。 https://www.npmjs.com/package/@biomejs/biome/v/latest?active... https://www.npmjs.com/package/@swc/types?activeTab=dependenc... eslintの炎上(数百から数千の依存関係)をゼロに置き換えるのはすごく励みになるけど、Rustのソースを見つけると... https://github.com/biomejs/biome/blob/a0039fd5457d0df18242fe... https://github.com/swc-project/swc/blob/6c54969d69551f516032... こういうプロジェクトがもっと勢いを増すと、cargoエコシステムでも似たようなことが起こると思う。esbuildと同じくらい厳格なスタイルで書かれた他の大きなプロジェクトを知ってる人いる?

Goを主な言語に切り替えた理由の一つは、純粋なGo実装のトレンドがあって、通常は標準ライブラリとgolang.org/x以外の依存関係をゼロにしようとするからなんだ。こういうプロジェクトは、CGO_ENABLED=0で動くことを目指しているから、ライブラリが非常にポータブルで、異なるシステムコールバックエンドでも動くのがいいんだよね。それに、依存関係のスナップショットをgo mod vendorで管理するのも好きなんだけど、短期的な修正にはいいけど、長期的な原因解決にはならない。だけど、Goエコシステムもパッケージの更新に署名がないから脆弱なんだよね。「誰がこのパッケージに署名したのか」をエンドツーエンドで確認できない限り、状況は良くならないと思う。さらに、過去のサプライチェーン攻撃はCI/CDインフラに集中してたけど、そこも同じくらい問題が多いからね。署名キーがランナー自体に必要ないCI/CDワークフローが必要だと思う。そうじゃないと、攻撃対象が別の場所に移るだけだし。個人的には、パッケージマネージャーにも少し責任があると思う。GPG署名を推奨し、特に配布にgitタグを使うときはgitコミットでも義務付けるべきだよ。

自分で簡単に書けるものに依存関係を持ち込まないのが答えだね。これで多くのプロジェクトでは依存関係を2/3くらい減らせると思う。特に、left-padみたいなやつ。ちゃんと自己完結した小さなパーツといくつかのテストを書けば、あんまり触る必要もなくなるし、メンテナンスの負担もそんなに高くないよ。left-padみたいな小さな依存関係を全部チェックするのと比べるとね。もし依存関係が絶対必要じゃないなら、やらない方がいいよ。

うん、eslintは特にイライラするね:https://npmgraph.js.org/?q=eslint コミュニティには依存関係の数を減らす手助けをしてくれる人がたくさんいるけど、やっぱりメンテナーがそれを優先しないといけない。そうじゃないと、oxlintみたいな別のソリューションに切り替えるしかないよね。

これは新しいパッケージやバージョンの監査がないから起こる。ディストリビューションのメンテナーと開発者が同一人物なんだ。一般的な解決策は、Debianがやってることをすること。新しいパッケージが追加されず、バージョンがめったに変わらない安定したディストリビューションを維持する(セキュリティアップデートやバグ修正のみ、新機能は追加しない)。これがほとんどの人が使ってるやり方。新しいパッケージやバージョンが追加できるテスト/不安定なディストリビューションを維持するけど、それもディストリビューションのメンテナーによってのみ追加されるべきで、パッケージの開発者によってはダメ。ここで監査が行われる。NPM、Python、Rust、Go、Rubyはみんなこの問題に悩まされてる。なぜなら、中央集権的でオープンなパッケージリポジトリを持っているから。

この問題に苦しむ この機能の恩恵を受ける。

Rustではcargo vetがあって、監査を共有して自動化して使ってるよ。GoogleやMozillaみたいな企業も監査に貢献してる。

そうだね、ある意味でDebian(または他のディストロ)は拡張された標準ライブラリだよ。

新しいパッケージが追加されず、バージョンがほとんど変わらない安定したディストロを使うべきだよ(セキュリティアップデートとバグ修正だけ、新機能はなし)。これがほとんどの人が使ってるやり方。残念ながら、ほとんどの人は新しいハードウェアをサポートしない古いソフトウェアを使いたがらないから、結局Debian stableを使わないんだよね。

GoのパッケージリポジトリはGitHubだけなんだよね。結局、すべてはURLなんだ。特別なURLのセットを求めてるわけだ。誰かにそのメンテナンスに時間をかけさせる必要があるよ。

君のアイデアの問題は、Node/Python/Rubyライブラリのすべてのバージョンを監査したい人を見つける必要があるってことだね。

こういうことをやりつつ、分散型を維持する方法があると思いたいな。例えば、あるパッケージが一定期間に[閾値]以上のダウンロードを超えたら、さらなるコード更新には2FAの再認証が必要になるとか。もしくは、人気のあるパッケージについては、再現可能なビルドを持つ自動ビルドシステムの選ばれたリストだけが直接NPMにプッシュできるようにするとか。そうすれば、マルウェアを仕込むにはまずソースコードリポジトリを侵害しないといけないからね。正直言って、これでこのワームの拡散を完全に止められたわけではないけど、進行をかなり遅らせることはできたと思う。これは「NPMのユーザー体験や分散化を全て犠牲にする」って話じゃなくて、「リリース管理をするべき規模になった時だけ、少し手動のユーザー体験にする」ってことなんだ。

セキュリティアップデートとバグ修正だけってこと?ちょっと気になったんだけど、攻撃面が減るのはいいけど、まだ攻撃面はあるよね?

これに関するブログの多くはAI生成で、こういうのが進化してるから、いろんなリソースをリンクしておくね: Aikido - https://www.aikido.dev/blog/s1ngularity-nx-attackers-strike-... Socket - https://socket.dev/blog/ongoing-supply-chain-attack-targets-... Ox - https://www.ox.security/blog/npm-2-0-hack-40-npm-packages-hi... Safety - https://www.getsafety.com/blog-posts/shai-hulud-npm-attack Phoenix - https://phoenix.security/npm-tinycolor-compromise/ Semgrep - https://semgrep.dev/blog/2025/security-advisory-npm-packages...

関連情報(7日前):NPMのdebugとchalkパッケージが侵害された(1366ポイント、754コメント):https://news.ycombinator.com/item?id=45169657

この攻撃を実際に発見したのは誰なんだろう?彼らにクレジットを与えられるのかな?これらの投稿の表現が面白いね。直接クレジットを主張している人もいれば、ただ事件を認めている人もいる。Aikidoは言う:> 「npmに対する大規模な攻撃が警告された…」 Socketは言う:> 「Socket.devがさまざまなCrowdStrikeのnpmパッケージが侵害されたことを発見した…」 Oxは言う:> 「攻撃者が新しいリリースに悪意のあるコードを滑り込ませた…」 Safetyは言う:> 「Safetyの研究チームがNPMエコシステムに対する攻撃を特定した…」 Phoenixは言う:> 「別のサプライチェーンとNPMのメンテナーが侵害された…」 Semgrepは言う:> 「いくつかの侵害されたnpmパッケージについて認識している」

まだ広がっているのかな?そのブログには違うパッケージがリストされているみたい。

Aikido Securityによると、攻撃は180以上のパッケージをターゲットにしているみたいだよ: https://www.aikido.dev/blog/s1ngularity-nx-attackers-strike-...

こういう攻撃は、JSに強力な標準ライブラリがあればかなり減ると思う。もし提供されていれば、小さなユーティリティライブラリの依存関係ツリーを大幅に削減できるはず。人気のあるパッケージを分析して、共通で小さくて安全な依存関係の「ディストロ」を作るために、コミュニティ全体で協力する必要があるかもしれないね。

Shai Hulud 面白い名前だけど、マルウェアの作者にはもう少し隠れてほしかったな。巨大なワームにちなんで名付けるなんて、まさにそのままだし。 この攻撃の核心には、約3.6MBのminified bundle.jsファイルがある うん、マルウェアも膨れ上がることがあるんだね。これがNPMの精神ってことかな…

こういうサプライチェーン攻撃が、意図せずに別の無関係なサプライチェーン攻撃を引き起こすのも時間の問題だと思う。

マルウェアはムーアの法則に従わなきゃいけないんだよね。テキーラウイルスは1991年に約2.6KBだった。

先週、誰かが「弾を避けた」ってブログ記事を書いてたけど、結局はブラウザベースの暗号ウォレットのスクレイピングだけだったみたい。今回は避けられなかったね。

この脆弱性は2016年にNPMに報告されたんだよね:https://blog.npmjs.org/post/141702881055/package-install-scr... https://www.kb.cert.org/vuls/id/319816 でも、NPMの反応はWAIだった。

[重複] 以前の投稿:https://news.ycombinator.com/item?id=45256210