セキュリティ●●●●○

サプライチェーン攻撃

Supply Chain Attack ・ さぷらいちぇーんこうげき

依存ライブラリやビルド基盤など開発の供給網を汚染し、間接的に標的へ届く攻撃。

✦ 30秒で試す — 航海あなたが一度も選んでいないライブラリが、本番で動いている更新を実行すると、悪性の新版はどうなる?予想してみる

概要

サプライチェーン攻撃は、標的そのものを直接攻撃するのではなく、標的が信頼して取り込んでいる「供給網」を汚染して、間接的に悪意あるコードを届ける攻撃です。供給網とは、依存ライブラリ、開発ツール、ビルド基盤、配布の経路などのことです。製造業で部品の供給網をサプライチェーンと呼ぶのになぞらえ、ソフトウェア開発の部品の供給網を狙うことからこの名が付きました。

防御する側にとって厄介なのは、「自分のコードをどれだけ堅く書いても防げない」ことです。いまのアプリケーションは、自分で書いたコードより、取り込んだコードのほうが圧倒的に多いのが普通です。npm のプロジェクトで依存ツリーを展開すると、直接依存の数十パッケージの裏に、間接依存が数百〜数千個ぶら下がります。そのすべての作者・メンテナ・公開基盤を信頼していることになり、攻撃者から見れば、どこか一箇所のいちばん弱い環を突けばよいのです。

なぜ生まれたか

背景にあるのは、ソフトウェア開発の生産性がコードの再利用の上に成り立っている、という構造そのものです。パッケージレジストリ(npm、PyPI、RubyGems など)が普及し、一行のインストールコマンドで世界中の誰かのコードを取り込めるようになって、開発の速度は大きく上がりました。しかしこの仕組みは「公開されているコードは善意で書かれている」という暗黙の信頼で動いていて、その信頼を確かめる仕組みは後回しにされてきました。

一方で、防御側の進歩が攻撃者を供給網へ向かわせました。脆弱性管理や WAF の普及で正面からの攻撃が難しくなるにつれ、「標的が自分から実行してくれるコードに紛れ込む」経路の費用対効果が相対的に上がったのです。象徴的なのが2020年に発覚した SolarWinds 事件です。ネットワーク管理製品のビルド基盤に侵入した攻撃者が、ビルドの途中でバックドアを差し込みました。できあがった更新プログラムには正規の公開鍵暗号の署名が付いていたので、利用者は疑う理由がなく、米政府機関を含む多数の組織がそれを適用して侵害されました。署名は「このベンダーのビルドから出てきた」ことを保証しますが、「ビルドが汚染されていない」ことまでは保証しないのです。

メンテナという「人」への攻撃も続いています。2018年の event-stream 事件では、多忙なメンテナから善意を装って保守を引き継いだ人物が、暗号通貨のウォレットを狙うコードを依存パッケージ経由で混入させました。2024年の xz-utils 事件(CVE-2024-3094)はもっと周到でした。「Jia Tan」と名乗る人物が2年ほどかけて貢献を重ねて共同メンテナの地位を得ると、配布用の tarball とテスト用のファイルにバックドアを仕込みました。多くの Linux ディストリビューションで SSH サーバが読み込む圧縮ライブラリを通じて、SSH の認証を乗っ取るものでした。SSH のログインがわずかに遅いことに気づいた開発者の Andres Freund が調べて発覚し、主要なディストリビューションの安定版に入る直前で止まりました。

詳細

攻撃経路の全体像

防御を考えるには、まず「どこが汚染されうるか」を経路として整理するのが近道です。コードが書かれてから本番で動くまでの各段階に、それぞれ固有の攻撃面があります。

依存パッケージレジストリから取得ソースコードリポジトリビルド基盤CI/CD成果物の配布利用者へ届くパッケージの汚染タイポスクワッティング依存関係かく乱メンテナの乗っ取りevent-stream・xz-utilsリポジトリへの混入開発者アカウントの侵害悪意あるコミットビルド基盤の侵害ビルドの途中で改ざんCI の資格情報を窃取SolarWinds配布経路のすり替えダウンロードサイト更新サーバ・ミラーどの段階が汚染されても、署名つきの「正規の」成果物として利用者に届いてしまう
ソフトウェア供給網の各段階と、その汚染ポイント。どの段階が汚染されても、最後は正規の成果物として利用者に届いてしまう

依存パッケージの段階では、人気パッケージと一文字違いの名前で偽物を公開する「タイポスクワッティング」が古典的です。社内パッケージと同じ名前のものを公開レジストリに置き、取り違えを誘う「依存関係かく乱(dependency confusion)」は、2021年に研究者の Alex Birsan が実証して広く知られました。社内名のパッケージを高い版番号で公開レジストリに置いたところ、Apple や Microsoft を含む30社以上のビルドがそれを取り込んでしまったのです。メンテナ自身も標的になります。アカウントの乗っ取り(2025年には、フィッシングで npm のアカウントを奪われ、chalk や debug といった非常によく使われるパッケージに悪性の版が公開されました)や、xz-utils 事件のような長期の潜入がここに入ります。ビルド基盤の侵害はもっとも深刻で、ソースコードが正しくても、ビルドの途中で改ざんされれば、署名つきの「正規の」成果物として汚染コードが配られます。

一つの弱い環が全体に届く

依存は木の形をしています。アプリは直接依存を読み込み、直接依存はそれぞれの依存を読み込み……と、何段も下まで続きます。npm の範囲指定(^2.1.0 なら「2.x の最新」)のもとでは、奥のどれか1つに悪性の新版が公開されると、次の npm install でそれが黙って取り込まれ、親から親へと読み込まれて、最後はアプリの一部として本番で動きます。あなたが選んでいない、名前も知らない間接依存でも同じです。下の部品で、ツリーを展開して奥の1つに毒を置き、ロックファイルの有無で結果がどう変わるかを確かめてください。

⚡ 体験: 依存ツリーの奥に毒を置く
ロックファイル
あなたのアプリ攻撃者の毒 — どれか1つのパッケージへドラッグ(タップでおまかせ)悪性の新版を公開同じ版の中身をすり替え
あなたが選んだ依存3 個見えている間接依存0 / 8 個汚染されたパッケージ0 個アプリ無事

あなたが package.json に書いた依存は3つだけです。▾ を押してツリーを開いていくと、選んだ覚えのない間接依存がぶら下がっています。 奥の1つに、左下の「悪性の新版」をドラッグしてください。

防御の第一層 — 依存を固定し、監査する

防御の出発点は、「何に依存しているかを把握し、それが勝手に変わらないようにする」ことです。ロックファイル(package-lock.json など)は、ツリーの全パッケージの版と、中身のハッシュ(integrity の sha512)を記録します。ロックファイルを必ず Git にコミットし、CI/CD ではその記録どおりに厳密にインストールする(npm なら npm ci)のが基本です。こうすると、範囲に合う新版が出ても、ロックファイルを更新するまでは入ってきません。

ハッシュが守るものは少し違います。npm のレジストリは、一度公開した「名前@版」の再公開を禁じています(取り下げたあとでも、同じ版番号は二度と使えません)。そのため「同じ版の中身の差し替え」は、公式レジストリの上ではほぼ起きません。起きうるのは、社内のミラーやプロキシ、キャッシュが改ざんされる場合です。ロックファイルのハッシュは、こうした経路で届いた中身が記録と違えば、インストールを止めます(EINTEGRITY)。

そのうえで、npm audit や Dependabot のような監査ツールで、既知の脆弱性を継続してスキャンします。ただし「自動で最新に上げ続ける」ことは、汚染された新版をすぐに取り込むリスクと表裏一体です。ロックファイルは勝手な変化を止めるだけで、新版の中身の善し悪しは判断しません。公開直後の版は数日寝かせてから取り込む、更新の差分と公開元を確かめる、など、速さと検証のバランスを取ります。バンドラでビルドする場合は、最終的な成果物に何が含まれたかが見えにくくなる点にも注意が必要です。

防御の第二層 — SBOM・署名・ビルドの来歴

一歩進んだ対策が、供給網そのものを見える化し、検証できるようにすることです。SBOM(Software Bill of Materials、ソフトウェア部品表)は、成果物に含まれる全コンポーネントの一覧です。事件が起きたとき、「うちはこの汚染されたパッケージを使っているか」に即答するための土台になり、インシデント対応の初動を大きく早めます。

成果物への署名(Sigstore の cosign など)と、ビルドの来歴(provenance)の記録は、「この成果物は、確かにこのソースから、この CI でビルドされた」ことを検証できるようにします。署名には公開鍵暗号が使われますが、SolarWinds の教訓どおり、署名だけではビルドの汚染を防げません。来歴まで記録してはじめて、「どのリポジトリのどのコミットから、どのビルド手順で作られたか」を後から確かめられます。npm も2023年から、GitHub Actions などの CI が OIDC のトークンで身元を示して公開したパッケージに、来歴を付けられるようにしました。SLSA は、ビルド基盤の信頼性を段階的に高めるための指針です。いずれも根にあるのは「信頼する」から「検証できる」への転換で、ゼロトラストの考え方を供給網に当てはめたものです。

防御の第三層 — 被害を閉じ込める

汚染を完全には防げない前提で、コードが実行される場所の権限を絞ることも欠かせません。CI/CD のパイプラインは供給網攻撃の主戦場で、ここで盗まれた資格情報が次の攻撃の足がかりになります。2025年に npm で広がった自己増殖型のマルウェアも、感染した開発環境や CI から npm のトークンを盗み、そのトークンで別のパッケージに自分を書き込んで広がりました。CI のジョブには最小権限の原則を徹底し、長命の資格情報を置かず(シークレット管理の基盤や、OIDC で得る短命のトークンを使う)、サードパーティの CI アクションやプラグインもコミットのハッシュで固定します。インストール時に任意のコードが走る仕組み(npm の postinstall スクリプトなど)は、無効にするか、隔離された環境で動かすことを検討します。

依存を一つ増やすことは、その作者と、その先の供給網を丸ごと信頼することです。この感覚をチームで共有することが、どのツールよりも先に来る防御になります。

試問

あなたのアプリが直接選んだ依存パッケージは3つですが、▾ を押して開くと、その下に選んだ覚えのない間接依存がぶら下がっています。下の2つの毒のチップ(悪性の新版を公開・同じ版の中身をすり替え)をどれかのパッケージへドラッグすると、何が起きるかが見えます。上のスイッチで、ロックファイル(全パッケージの版と中身のハッシュの記録)を使うかどうかを選べます。

✦ 命題挑戦中

あなたが選んでいない3段下の間接依存に置かれた「悪性の新版」と「中身すり替え」を、どちらもアプリに届く前に止めよ

  • 3段下の間接依存に置かれた「悪性の新版」を止める
  • 3段下の間接依存に置かれた「中身すり替え」を止める
  • いま、アプリは無事
⚡ 体験: 依存ツリーの奥に毒を置く
ロックファイル
あなたのアプリ攻撃者の毒 — どれか1つのパッケージへドラッグ(タップでおまかせ)悪性の新版を公開同じ版の中身をすり替え
あなたが選んだ依存3 個見えている間接依存0 / 8 個汚染されたパッケージ0 個アプリ無事

あなたが package.json に書いた依存は3つだけです。▾ を押してツリーを開いていくと、選んだ覚えのない間接依存がぶら下がっています。 奥の1つに、左下の「悪性の新版」をドラッグしてください。