
Composer の Perforce リポジトリドライバーにおけるコマンドインジェクションの脆弱性を実証し、差分検証 する自己完結型のOOPスタイルPHP概念実証(PoC)です。
このPoCは、同じ悪意のある composer.json を2つのComposerバイナリ(影響を受ける リリース 2.9.5 と 修正済み リリース 2.9.6)に対して実行し、影響を受けるバージョンでは発火するが修正済みバージョンでは発火しない副作用(インジェクションされたシェルコマンドによって書き込まれるマーカーファイル)を観察することでバグを証明します。
⚠️ 許可されたセキュリティ研究および防御的テスト専用です。 責任ある利用 を参照してください。
Composer は複数のバージョン管理システムからパッケージを解決できます。Perforce の場合、リポジトリはホスト、ポート、ユーザー/ストリームをエンコードした p4:// URL で識別されます。Composer の Perforce ドライバーが基盤となる p4 コマンドラインを構築する際、攻撃者が制御するURL から取得したフィールドは、シェルに渡される前に十分にサニタイズされません。
マニフェストの作成者はリポジトリURLを完全に制御できるため、被害者に悪意のある composer.json に対して composer update/composer install を実行させることができる攻撃者(例えば、改竄された依存関係、悪意のあるリポジトリ、信頼できないプロジェクトファイルを処理するCIジョブなど)は、意図された p4 呼び出しを抜け出し、Composer プロセスの権限で任意のOSコマンドを実行できます。
これは、URL/ブランチ/ストリームの値がエスケープなしでシェルコマンドに流れ込むという、過去の Composer VCS ドライバー引数インジェクションの問題と同じ系統に属します。Composer 2.9.6 は Perforce ドライバーを強化し、インジェクションされたペイロードが実行されないようにしています。
ここで実証されている動作の権威ある説明は、PoC ソース自体(
CVE202640176Test.php)です。アップストリームの修正の詳細については、公式アドバイザリと Composer のチェンジログを参照してください。
PoC は単一のクラス CVE202640176Test で、制御された A/B(差分)実験 を実行します:
2.9.5)と修正済み(2.9.6)の両方の Composer バイナリで --version を照会し、どちらかが起動できない場合は早期に中止します。repositories セクションに、インジェクションされたシェルペイロードを含む悪意のある p4:// URL を持つ perforce エントリを含む composer.json を書き込みます。composer update を実行します。finally ブロックが、プロジェクトディレクトリ内の元の composer.json を常に復元します。PASS を出力します。各実行について、validateRun() は次の3つをチェックします:
| チェック | 証明されること |
|---|---|
実行が「OK」となるのは、3つすべてが合格した場合のみです。テスト全体が 合格 となるのは、影響を受けるバージョンの実行がOKで、修正済みバージョンの実行が 不合格 の場合です — これは、その後パッチが適用された実際の脆弱性の正確なシグネチャです。
悪意のあるリポジトリURLは writeComposerJson() で構築されます:
p4://127.0.0.1:1666:attacker_user;touch <marker> && echo '<runId>' > <marker>:client_test
分解すると:
p4://127.0.0.1:1666:attacker_user — 正しく形成されたように見える Perforce URL(ホスト、ポート 1666、ユーザー)。;touch <marker> && echo '<runId>' > <marker> — インジェクションされたシェルコマンド。先頭の ; は意図された p4 コマンドを終了させます。touch はマーカーファイルを作成し、echo '<runId>' > <marker> は一意の実行IDをそのファイルに書き込みます。これにより、PoC はペイロード(無関係なプロセスではなく)がファイルを生成したことを確認できます。:client_test — URL の残りの解析を妥当に見せるための末尾テキスト。影響を受ける ドライバーではシェルのメタ文字が解釈され、マーカーファイルが作成されます。修正済み ドライバーでは値が適切にエスケープ/クォートされるため、同じ文字列は無害なデータとして扱われ、マーカーは作成されません。
注:PoC は一意のタイムスタンプ付き実行IDを使用し、マーカーを分離された一時ディレクトリ内に書き込むため、ペイロードは破壊的ではなく、無害で自己クリーンアップされます。
2.9.5(影響を受けるバージョン)2.9.6(修正済みバージョン)exec() は cd … && php … を実行)。Linux/macOS 向けに設計されています。composer.json(起動時に読み取られ、各一時実行にコピーされ、その後復元されます)。通常、稼働中の Perforce サーバーは 不要 です。脆弱性は Composer が
p4コマンドラインを 構築する 方法にあり、インジェクションされたペイロードは実際のp4接続の前/周辺で実行されます。Composer が Perforce 接続エラーをログに記録する場合がありますが、これは想定どおりで、マーカーファイルによる証明には影響しません。
PoC をクローン/配置 します(作業ディレクトリ内)。
composer.json を用意 します(PoC と同じディレクトリに)。最小限のもので十分です:
{
"name": "research/cve-2026-40176-poc",
"description": "Base manifest for the CVE-2026-40176 differential PoC",
"require": {}
}
2つの Composer バイナリを取得 し、PoC が期待する場所に配置します(デフォルトは以下のとおり):
/usr/local/bin/composer-2.9.5.phar # affected
/usr/local/bin/composer-2.9.6.phar # fixed
公式アーカイブから特定の Composer リリースをダウンロードできます。例:
curl -Lo /usr/local/bin/composer-2.9.5.phar https://getcomposer.org/download/2.9.5/composer.phar
curl -Lo /usr/local/bin/composer-2.9.6.phar https://getcomposer.org/download/2.9.6/composer.phar
パスが異なる場合は、
CVE202640176Test.phpの末尾にある2つのコンストラクタ引数を編集してください。
php CVE202640176Test.php
ハーネスは両方の Composer バージョンを順に実行し、最終的な判定を出力します。実行が失敗した場合でも、元の composer.json は自動的に復元されます(作業は使い捨ての一時ディレクトリで行われます)。
このリポジトリには、環境を正確に再現するコンテナ化されたラボが同梱されています:PHP CLI ランタイムと、PoC が期待するパスにある2つの固定バージョンの Composer リリースで構成され、実行時には完全にネットワーク分離されます。
docker compose run --rm poc
これにより cve-2026-40176-lab:latest がビルドされ(ビルド中に Composer 2.9.5 と 2.9.6 をダウンロードし、それぞれの --version を検証)、非特権かつ外部通信なしのコンテナ内で差分テストが実行されます。
ラボが保証すること:
poc サービスは internal ブリッジネットワーク上で実行され(ホスト/インターネットへの外部通信なし)、cap_drop: ALL と no-new-privileges が設定されています。インジェクションペイロードは封じ込められたままです。ビルド引数でバージョンを再固定できます(PoC の2つのコンストラクタパスと同期している必要があります):
docker compose build --build-arg COMPOSER_AFFECTED_VERSION=2.9.5 --build-arg COMPOSER_FIXED_VERSION=2.9.6
オプション — 稼働中の Perforce サーバー。 full-lab プロファイルの下で p4d サービスを利用できます(docker compose --profile full-lab up)。マーカーによる証明には 不要 ですが、稼働中の p4:// エンドポイントを必要とする研究者向けに用意されています。PoC のペイロードは 127.0.0.1:1666 をターゲットにしているため、別の p4d コンテナ経由でルーティングするには、PoC の URL をホスト p4d に向ける必要があります。
正直な結果: 実際に公開されている Composer
2.9.5および2.9.6に対して、PoC は 現在発火しません。ラボはINCONCLUSIVE / FAILを報告します。
影響を受ける Composer(2.9.5)を PoC の悪意のあるマニフェストに対して実行すると、p4/シェルコマンドが構築される 前 に、Composer 内部で例外がスローされます:
In PerforceDriver.php line 40:
[ErrorException]
Undefined array key "depot"
PerforceDriver::initialize() は最初に $this->repoConfig['depot'] を読み取りますが、PoC のリポジトリエントリは type と url のみを提供します(depot キーはありません)。ドライバーはその時点で中止されるため、URL 内のインジェクションされた ;touch <marker> ペイロードには到達せず、マーカーは作成されません。ネットワーク分離が原因では ありません — 完全な外部通信が可能な場合でも同じエラーが発生します。
これが意味すること:
INCONCLUSIVE という結果は、環境ではなく PoC ペイロード の特性です。depot キーが必要です(現実的には full-lab プロファイルの下で稼働中の p4d エンドポイントも必要です)。そこまでペイロードを洗練させることは、「ラボを立ち上げる」を超えたエクスプロイト開発であり、ここでは意図的にスコープ外とされています。以下の「期待される出力」は PoC の 意図された/理想化された 結果であり、参考用に保持されています。実際のドライバーに対して現在のペイロードが生成するものではありません。
成功したデモンストレーションはおおよそ次のようになります(パスとIDは異なります):
=== CVE-2026-40176 PoC started ===
- Composer 2.9.5 version: 2.9.5
- Composer 2.9.6 version: 2.9.6
Prepared temp dir: /tmp/cve20264176_5_20260610_142233
Written malicious composer.json to /tmp/cve20264176_5_20260610_142233
Running Composer in /tmp/cve20264176_5_20260610_142233…
- Parsed Composer version: 2.9.5
- Marker /tmp/cve20264176_5_.../poc_marker_5.txt created with expected ID.
- Output shows Perforce driver activity.
- Affected run exit code: 1
Prepared temp dir: /tmp/cve20264176_6_20260610_142233
Written malicious composer.json to /tmp/cve20264176_6_20260610_142233
Running Composer in /tmp/cve20264176_6_20260610_142233…
- Parsed Composer version: 2.9.6
✘ Marker file /tmp/cve20264176_6_.../poc_marker_6.txt not found.
- Output shows Perforce driver activity.
- Fixed run exit code: 1
=== CVE-2026-40176 PoC finished ===
=== TEST RESULT: PASS (affected succeeded, fixed failed) ===
Composer の非ゼロの終了コードは正常です — composer update は最終的に(偽の)パッケージのフェッチに失敗します。証明となるのは マーカーファイル であり、Composer の終了ステータスではありません。
| 結果 | 意味 |
|---|---|
| PASS(影響を受けるバージョン成功、修正済みバージョン失敗) | 確認済み:2.9.5 はインジェクションされたコマンドを実行し、2.9.6 は実行しなかった。脆弱性とその修正の両方が再現された。 |
| INCONCLUSIVE / FAIL | 1つ以上のチェックが一致しなかった。実行ごとの ✓/✗ 行を確認:バイナリパスの誤り、バージョンの不一致、影響を受けるバージョンのマーカー欠落(環境/エスケープの違い)、または修正済みバージョンの実行が予期せずマーカーを作成した。 |
判定不能(INCONCLUSIVE)な結果の一般的な原因:
PerforceDriver.php:40 で Undefined array key "depot" により中止される — リポジトリ設定に depot キーがないため、Composer は p4 コマンド構築パスに到達しません。これは、実際の 2.9.5/2.9.6 に対する PoC の現在のペイロードで発生することです(再現ステータス を参照)。2.9.5 / 2.9.6 ではない。exec() がサンドボックス化/無効化されている。p4 が含まれていない(ドライバーパスに到達していない)。CVE2026-40176/
├── CVE202640176Test.php # The PoC: CVE202640176Test class + entry point
├── composer.json # Base manifest the PoC reads/restores at runtime
├── Dockerfile # Lab image: PHP CLI + pinned Composer 2.9.5 & 2.9.6
├── docker-compose.yml # `poc` runner (+ optional `p4d` under full-lab profile)
├── .dockerignore # Trims the build context
├── README.md # This file
└── .gitignore # Excludes local agent/tooling state
PoC 全体は1つのファイルです:
__construct() — 2つのバイナリパスを保存し、タイムスタンプ付きの実行IDを生成し、元の composer.json のスナップショットを取ります。run() — 事前チェック、影響を受けるバージョンの実行、修正済みバージョンの実行、復元、判定を調整します。prepareTempDir() — 実行ごとに分離された作業ディレクトリを作成します。writeComposerJson() — インジェクションされた p4:// URL を含む悪意のあるマニフェストを構築します。runComposer() — composer update を実行し(引数は escapeshellarg() でエスケープ)、出力と終了コードを取得します。validateRun() — バージョン、マーカーファイル、Perforce ドライバーの動作をチェックします。preflightVersion() — 指定されたバイナリから --version を読み取ります。composer.json は結果に関係なく finally ブロックで復元されます。escapeshellarg() でエスケープします(PoC が自身の exec() 呼び出しに誤ってインジェクションしないように)。脆弱性 はもう1層深いところ、つまり Composer 自身が p4 コマンドを構築する方法にあります — これはまさにペイロードが標的とするところです。touch/echo するだけなので、PoC はホストに副作用を与えず繰り返し安全に実行できます。exec("cd … && php …") パターンと ;/&& ペイロードは Unix 系シェルを前提としています。Windows は現状のままではサポートされません。mkdir の競合 / パーミッション。 一時ディレクトリはモード 0777 で作成されます。共有環境で実行する場合は厳格化してください。Composer X.Y.Z を解析します。通常とは異なる Composer バナーはマッチを妨げる可能性があります。このリポジトリは CVE-2026-40176 を 理解し、防御する ために存在します。
composer.json ファイルに対して composer install/update を実行しないでください(例:サードパーティのプロジェクトソースを処理する CI パイプラインなど)。2.9.5 / 2.9.6 PHAR を取得するため)2.9.6 のチェンジログ(権威ある修正の詳細については、お使いのディストリビューションのセキュリティトラッカー / GitHub Advisory Database を参照)著者:Ikarolaborda · PoC 作成日:2026-06-10。
| CVE | CVE-2026-40176 |
| コンポーネント | Composer — Perforce(perforce)リポジトリ/VCS ドライバー |
| クラス | 攻撃者が制御するリポジトリURLを介したOSコマンドインジェクション |
| 攻撃面 | type: perforce の細工された repositories エントリを含む composer.json |
| 影響を受けるバージョン | Composer 2.9.5 |
| 修正済みバージョン | Composer 2.9.6 |
| トリガー | 悪意のあるマニフェストに対する依存関係の解決/更新(composer update) |
| 影響 | Composer を実行しているマシン上での任意のコマンド実行 |
| PoC 言語 | PHP(単一ファイル、外部依存関係なし) |
インジェクションされた touch/echo ペイロードが実際に実行された — すなわち、コマンドインジェクションが成功した。 |
Composer の出力に p4 が含まれる | Perforce ドライバーのコードパスに到達した(ペイロードが無関係なステップではなく、正しいコンポーネントによって処理された)。 |
| 解析された Composer バージョン == 期待値 | 実行されたのが正しいバイナリ(2.9.5 vs 2.9.6)であった。 |