技術解説

PHP2Shell の仕組み

原因は 1 つ、CVE は 2 つ、バグは 4 つ:PHP は、リクエスト処理の途中で致命的エラー——特にメモリ枯渇——が起きたとき、確実に後片付けができません。

最終確認 2026-10-22

原因

PHP は memory_limit に達すると致命的エラーを発生させ、longjmp() で巻き戻ります。このとき、状態の初期化やリセットを完了させるはずのコードが飛ばされます。その結果、2 つの問題が起きます:

  • 作りかけのデータ(CVE-2026-109618)。確保済みだがまだ初期化されていないメモリが、リクエストの終了処理で有効なものとして読まれます。
  • 残留するグローバル変数(CVE-2026-109619)。リクエスト単位のグローバルポインタ(エラー、セッション、オブジェクト、拡張機能の状態)は消去されませんが、指す先のメモリはリクエスト終了時に解放されます。同じワーカーの次のリクエストが、通常の初期化より前にそれを使ってしまいます。

攻撃者は細工した multipart リクエストでこれらの経路に到達し、入力の解析中に PHP を OOM に追い込みます——ルーター、フレームワーク、認証が動く前の段階です。だからこそ「Hello World」スクリプトも WordPress と同じように危険にさらされます。

攻撃チェーン

  1. ステップ 1

    漏えい

    メモリ漏えいを使ってワーカーの実行中のメモリを読み、ASLR を無効化する。

  2. ステップ 2

    破壊

    use-after-free(アンシリアライザまたはバックトレース)を使って、制御したデータを書き込む。

  3. ステップ 3

    実行

    メモリ破壊をワーカーユーザーとしてのコード実行に変える——ログイン前に。

悪用は一発では成功しません。攻撃者は標的ごとのメモリ配置に合わせるため、何度も探りを入れます。研究者はこのチェーンを php2shell というスクリプト(非公開)にまとめ、当時最新の wordpress、drupal、joomla のコンテナイメージで実証しました。

4 つのバグ

CVE-2026-109618

アップロード進捗のアンシリアライザにおける use-after-free

セッションモジュールは、リクエストの初期化が終わる前にアップロード進捗の追跡用データをデコードし、BG(unserialize) に管理情報を保持します。メモリ確保に失敗するとリクエストのヒープは破棄されますが、このグローバル変数は残ります。同じワーカーの次のリクエストは、攻撃者が再利用したメモリを有効なアンシリアライザの状態として扱います。

CVE-2026-109618

プロセスメモリのリモート漏えい

RFC 1867 の multipart 解析の初期段階で、アップロード進捗のコードがセッション ID を PS(id) に公開することがあります。OOM による中断の後、このポインタは宙に浮いた状態になります。その後の multipart リクエストがポインタと長さを信用し、そこに今ある内容を URL エンコードして PHPSESSID Cookie に書き込みます——アプリが session_start() を呼ばなくても起こります。

CVE-2026-109619

致命的エラーのバックトレースにおける use-after-free

EG(last_fatal_error_backtrace) は、リクエストのヒープを破棄する OOM の後も残ります。次のリクエストの初期段階の診断処理が、この宙に浮いたポインタをたどります。fatal_error_backtraces を有効にした PHP 8.5 で最も直接的です。

CVE-2026-109619

OPcache 共有メモリにおける型の取り違え

zend_accel_script_persist() では、共有領域の境界が公開される前に CG(map_ptr_last) が進みます。その間でメモリ確保に失敗すると、ワーカー間で認識が食い違う map-pointer のスロットが残り、あるワーカーがそのスロットを別の型のオブジェクトに再利用できてしまいます。

バグと CVE の対応は Wiz Research の記事に基づいています。PHP のアドバイザリはクラスをより広く記述し、さらなる亜種にも言及しています。

サーバー構成別の影響

PHP のアドバイザリに基づきます。「影響あり」は、ソース解析で脆弱と判断されたものの、その SAPI 向けの汎用エクスプロイトは公開されていないことを意味します。

構成…109618…109619補足
Apache + mod_phpRCE 確認済みRCE 確認済み両 CVE ともデフォルト制限で RCE まで確認済み。最もリスクの高い構成です。
PHP-FPM(nginx、Apache、Caddy など)影響ありRCE 確認済みセッションのライフサイクル経由の RCE が FPM で確認済み。OOM 経路はアプリケーション層のコードパス経由で確認されていますが、汎用的な FPM 向けエクスプロイトは公開されていません。
FrankenPHP / LiteSpeed LSAPI影響の可能性大影響の可能性大アドバイザリでは影響の可能性が高いとされています。常駐ワーカーでは、リクエストをまたいだ残留状態の問題がむしろ大きくなります。
CLI(cron、キューワーカー、artisan)対象外対象外アドバイザリでは扱われておらず、通常の使い方では HTTP リクエストから到達できません。ただし同じパッケージなので、いずれにせよ更新してください。

CVE 情報

CVE-2026-109618GHSA-w8hw-w9fx-j79wCVSS 4.0 · 9.2

メモリ枯渇(OOM)時の破損による RCE

PHP はメモリが枯渇すると longjmp() で現在のコンテキストを中断します。それがリクエスト用メモリの初期化途中に起きると、後のリクエスト終了処理で無効なデータを読んだり、それに基づいて分岐したりします。複数の multipart 解析経路で、通常の制限内でも PHP を OOM に追い込めます。しかもアプリケーションコードが動く前の段階です。

影響:
認証なしで、PHP の Web ワーカーユーザーとしてコードを実行できます。
実証:
WordPress、Drupal、Joomla(mod_php、デフォルト制限)で認証前 RCE を実証。
影響を受けるバージョン
<8.2.35、<8.3.36、<8.4.27、<8.5.12(および 5.4 までさかのぼるサポート終了ブランチ)
CWE
CWE-416, CWE-755, CWE-824, CWE-1325, CWE-1419
CVE-2026-109619GHSA-6wmf-4rv5-p3rpCVSS 4.0 · 9.2

リクエストをまたいで残るグローバル状態による RCE

致命的エラー(OOM を含む)によってリクエストごとのリセットが飛ばされ、エラー・セッション・オブジェクト・拡張機能のグローバル状態が、リクエスト終了時に解放されたメモリを指したままになることがあります。同じワーカーの次のリクエストが、通常の初期化より前にその解放済みメモリを使ってしまいます。

影響:
use-after-free により、ワーカーのメモリ漏えいやコード実行につながります。
実証:
WordPress と Drupal で認証前 RCE を実証。アプリケーションに依存しないリモートのメモリ漏えいも実証。
影響を受けるバージョン
<8.2.35、<8.3.36、<8.4.27、<8.5.12(および 5.4 までさかのぼるサポート終了ブランチ)

ベクトル(両方とも):CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N。ネットワーク経由で到達可能、権限もユーザー操作も不要。「AC:H / AT:P」は、エクスプロイトを標的に合わせて調整する必要があることを示しています。漏えい直後の報道にあった CVSS 10.0 は誤りです。

修正で何が変わったか

パッチでは、メモリ枯渇時に longjmp() を使わなくなりました。代わりに、memory_limit に達するとソフト OOM が設定され、状態が整合している VM の次の安全なポイントで発生します。さらに高い 2 つ目のしきい値に達するとハード OOM が発生し、_exit() でプロセスを終了します——シャットダウンハンドラは動かず、中途半端に片付いた状態も残りません。

個々の入口ではなく不具合のパターンそのものを修正しているため、既知の亜種もカバーしていると PHP チームは述べています。

副作用:OOM の後もシャットダウン関数が動くことを前提にしていたコードは、ハードリミットに達した時点で挙動が変わります。

関連ページ