PHP2Shell の技術解説|OOM 時の longjmp(bailout)と stale global state による UAF、修正コミットの中身
CVE-2026-109618 と CVE-2026-109619 を、PHP 公式の勧告(GHSA)と修正コミット「Avoid state corruption via bailout」から読み解く。リクエストの起動と終了、ワーカーの再利用、CVSS v4 のベクター、各社で食い違う説明まで、技術者向けにまとめた。
PHP2Shell の技術解説|OOM 時の longjmp(bailout)と stale global state による UAF、修正コミットの中身
- CVE-2026-109618(GHSA-w8hw-w9fx-j79w)は「RCE via OOM corruption」。OOM を longjmp(bailout)で抜けると、初期化の途中のリクエストのメモリが残り、shutdown の処理がそれを読む
- CVE-2026-109619(GHSA-6wmf-4rv5-p3rp)は「RCE via stale global state」。リクエストのヒープを指すグローバル変数が初期化されず、同じワーカーの次のリクエストで use-after-free になる
- 両方を直したのは1つのコミット(01a40e89fb「Avoid state corruption via bailout」)。OOM の bailout を UnwindExit に置き換え、起動中に触るグローバル変数を初期化する
- 公式の勧告では、mod_php の既定の設定で RCE を確認。PHP-FPM は、109619 のセッションの経路で RCE を確認、109618 はソースの解析で影響ありと判断
前提:PHP のリクエストの一生
PHP は、Web サーバーから受け取ったリクエストごとに、おおむね次の順で処理する
- request startup:$_GET・$_POST・$_COOKIE・$_FILES を組み立てる。application/x-www-form-urlencoded や multipart/form-data(RFC 1867)の本文は、ここで自動で解析される
- スクリプトの実行:アプリのコード(WordPress など)が動く
- request shutdown:出力を送り、デストラクタを呼び、リクエスト用のヒープ(emalloc で確保した領域)を解放する
CVE-2026-109618:OOM corruption
PHP は、メモリ不足(OOM)のとき、longjmp() で今の処理から一気に抜けて、リクエストを打ち切る
- 使える Content-Length がない
- パートごとのヘッダーの数に上限がない
- 中身が空のファイルの項目と、入れ子の項目名の組み合わせ
CVE-2026-109619:stale global state
PHP は、エラー・セッション・オブジェクト・拡張機能の状態を、グローバル変数に持つ
- INI の早い段階の診断:リクエストの初期化より前に、警告を出すか・表示するか・記録するかを決める前に、グローバルの状態を読む
- セッションのアップロードの進み具合:ふつうのセッションの初期化より前に動く。session.upload_progress.enabled は既定で有効で、session_start() を呼ばなくても動く
修正コミットの中身
両方の勧告を直したのは、1つのコミット 01a40e89fb(タイトルは「Avoid state corruption via bailout」)だ
- OOM の bailout を UnwindExit に置き換える:longjmp() で飛ぶのをやめ、例外のように巻き戻して、後片付けとデストラクタを必ず通す
- 起動中に触るグローバル変数を、きちんと初期化する
CVSS v4 のベクターを読む
2つとも 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、基本値は 9.2 だ
- AV(攻撃の経路):N
- AC(攻撃の複雑さ):H
- AT(攻撃の前提条件):P
SAPI ごとの影響
- Apache の mod_php(prefork):109618 は既定の設定で HTTP 越しに RCE を確認。109619 も fatal_error_backtraces とセッションの経路で RCE を確認
- PHP-FPM:109619 のセッションの経路で RCE を確認。109618 はソースの解析で影響ありと判断(完全な攻撃は未実証)
- php-cgi・FrankenPHP・LiteSpeed:Web のリクエストを受ける PHP として対象(Datadog)
用語
- SAPI:PHP を Web サーバーなどにつなぐ層(mod_php・FPM・CGI など)
- bailout:致命的なエラーのとき、longjmp で処理を打ち切る PHP の仕組み
- UnwindExit:例外のように巻き戻して処理を終える仕組み。後片付けが通る
PHP2Shell は、PHP のアプリケーションのコードではなく、SAPI(PHP を Web サーバーにつなぐ層)と Zend Engine の、リクエストの起動と終了の処理に見つかった脆弱性だ。この記事では、PHP 公式の2つの勧告(GHSA)と、両方を直した修正コミットをもとに、何が壊れていたのかを技術者向けに説明する。
攻撃のコード(PoC)の中身や、実際に動かす手順は載せない。仕組みの理解と、防御・検知の判断に必要な範囲にとどめる。
- CVE-2026-109618(GHSA-w8hw-w9fx-j79w)は「RCE via OOM corruption」。OOM を longjmp(bailout)で抜けると、初期化の途中のリクエストのメモリが残り、shutdown の処理がそれを読む
- CVE-2026-109619(GHSA-6wmf-4rv5-p3rp)は「RCE via stale global state」。リクエストのヒープを指すグローバル変数が初期化されず、同じワーカーの次のリクエストで use-after-free になる
- 両方を直したのは1つのコミット(01a40e89fb「Avoid state corruption via bailout」)。OOM の bailout を UnwindExit に置き換え、起動中に触るグローバル変数を初期化する
- 公式の勧告では、mod_php の既定の設定で RCE を確認。PHP-FPM は、109619 のセッションの経路で RCE を確認、109618 はソースの解析で影響ありと判断
前提:PHP のリクエストの一生
PHP は、Web サーバーから受け取ったリクエストごとに、おおむね次の順で処理する。
- request startup:
$_GET・$_POST・$_COOKIE・$_FILESを組み立てる。application/x-www-form-urlencodedやmultipart/form-data(RFC 1867)の本文は、ここで自動で解析される - スクリプトの実行:アプリのコード(WordPress など)が動く
- request shutdown:出力を送り、デストラクタを呼び、リクエスト用のヒープ(emalloc で確保した領域)を解放する
大事なのは、1の自動の解析が、アプリのコードより前に動くことだ。認証もルーティングも、まだ何も始まっていない。だから PHP2Shell は「認証前(pre-auth)」に届く。
もう1つの前提は、ワーカーの再利用だ。PHP-FPM のワーカーや、Apache の prefork MPM の子プロセス(mod_php)は、1つのプロセスで多くのリクエストを順に処理する。リクエストのヒープは毎回解放されるが、プロセスのグローバル変数は残る。
CVE-2026-109618:OOM corruption
PHP は、メモリ不足(OOM)のとき、longjmp() で今の処理から一気に抜けて、リクエストを打ち切る。これを bailout と呼ぶ。
公式の勧告によると、一部の場面では、リクエストのメモリが完全に初期化される前に、ほかから参照できる状態になっている。その瞬間にメモリの確保が失敗すると、bailout は途中の状態を考えずに抜けてしまう。残された中途半端なデータを、そのあとの request shutdown が読んだり、それをもとに分岐したりする。
勧告が OOM のきっかけとして挙げているのは、multipart の解析の問題だ。
- 使える Content-Length がない
- パートごとのヘッダーの数に上限がない
- 中身が空のファイルの項目と、入れ子の項目名の組み合わせ
Datadog の分析は、application/x-www-form-urlencoded の本文で、とても長い項目名を送って $_POST の組み立て中に OOM を起こす手口を説明している。どちらも、request startup の自動の解析でメモリを使い切らせる、という点は同じだ。
弱点の種類(CWE)は、CWE-416(use after free)・CWE-755(例外的な状態の扱いの誤り)・CWE-824(初期化されていないポインタへのアクセス)・CWE-1325・CWE-1419 が挙げられている。
CVE-2026-109619:stale global state
PHP は、エラー・セッション・オブジェクト・拡張機能の状態を、グローバル変数に持つ。その中には、リクエストのヒープの中を指すポインタもある。こうしたポインタは、リクエストのメモリを解放する前に、必ず初期化(リセット)しなければならない。
公式の勧告によると、致命的なエラー(OOM を含む)でこのリセットが飛ばされることがある。また、shutdown の途中の致命的でない診断のメッセージが、いったん消したエラーの状態を、もう一度埋めてしまうこともある。その結果、リクエストのヒープは解放されたのに、グローバル変数はそこを指したまま残る(ダングリングポインタ)。
同じワーカーの次のリクエストが、このポインタを使うと use-after-free になる。勧告が挙げている「使う側」は次の2つだ。
| 使う側 | 中身 |
|---|---|
| INI の早い段階の診断 | リクエストの初期化より前に、警告を出すか・表示するか・記録するかを決める前に、グローバルの状態を読む |
| セッションのアップロードの進み具合 | ふつうのセッションの初期化より前に動く。session.upload_progress.enabled は既定で有効で、session_start() を呼ばなくても動く |
勧告は、PHP 8.5 の fatal_error_backtraces(致命的なエラーのときにバックトレースを残す機能)の状態を「もっとも強く実証された例」としている。確認された影響は、認証なしのヒープの use-after-free、メモリの漏えい、Web のワーカーの権限での任意のコードの実行だ。
2つを組み合わせると
109618 で OOM を起こすことは、109619 のリセットを飛ばす引き金にもなる。OffSec の解説は、壊れた OOM の POST を送ったあと、同じワーカーに続けて multipart のリクエストを送ると、解放済みのヒープの中身が PHPSESSID の Cookie の値として URL エンコードされて返ってくる、という連鎖を説明している。漏れたメモリの中身は、ASLR(アドレスの無作為化)をくぐるための手がかりになりうる。
JPCERT/CC が侵害の調査で「異常に長い・変な形の PHPSESSID が返っていないか」を確かめるよう求めているのは、この経路があるからだ。
PHP 公式の勧告は、109618 を OOM corruption、109619 を stale global state(アップロードの進み具合と fatal_error_backtraces を含む)としている。一方、Wiz の表は、アップロードの進み具合の use-after-free とメモリの漏えいを 109618 に、fatal_error_backtraces と OPcache の型の取り違えを 109619 に割り当てている。この記事は、PHP 公式の勧告に合わせた。
修正コミットの中身
両方の勧告を直したのは、1つのコミット 01a40e89fb(タイトルは「Avoid state corruption via bailout」)だ。DEV Community の読み解きによると、変更の要点は2つある。
- OOM の bailout を UnwindExit に置き換える:
longjmp()で飛ぶのをやめ、例外のように巻き戻して、後片付けとデストラクタを必ず通す - 起動中に触るグローバル変数を、きちんと初期化する
公式の勧告は、修正で OOM を「遅延したソフト OOM」と「_exit() によるハード OOM」に分けたとしている。変更は 300 ファイル・約 1 万行で、その多くは、配列・unserialize・JSON・PCRE・mbstring・SOAP・GD などで、わざと確保を失敗させても Zend Engine が壊れないかを確かめる oom_* のテストだという。
修正が先に公開されていた
同じ読み解きによると、このコミットは公表の3日前(10月18日・UTC)に、php-src の公開のリポジトリに入っていた。コミットのメッセージには勧告の番号があり、追加されたテストは、どこが入口になるかを示していた。公表前に中身が漏れた原因の1つとみられている。
CVSS v4 のベクターを読む
2つとも 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、基本値は 9.2 だ。
| 項目 | 値 | 意味 |
|---|---|---|
| AV(攻撃の経路) | N | ネットワーク越しに届く |
| AC(攻撃の複雑さ) | H | 高い。メモリの配置など、成功させるのに工夫が要る |
| AT(攻撃の前提条件) | P | あり。特定の設定や状態がそろう必要がある |
| PR(必要な権限) | N | 不要(認証前) |
| UI(利用者の操作) | N | 不要 |
| VC / VI / VA | H / H / H | 機密性・完全性・可用性への影響が大きい |
| SC / SI / SA | N / N / N | ほかのシステムへの直接の影響はない |
「AC:H」と「AT:P」が付いているのに 9.2 なのは、認証も操作も要らず、成功すれば全部を失うからだ。
SAPI ごとの影響
| SAPI | 公式の勧告での扱い |
|---|---|
| Apache の mod_php(prefork) | 109618 は既定の設定で HTTP 越しに RCE を確認。109619 も fatal_error_backtraces とセッションの経路で RCE を確認 |
| PHP-FPM | 109619 のセッションの経路で RCE を確認。109618 はソースの解析で影響ありと判断(完全な攻撃は未実証) |
| php-cgi・FrankenPHP・LiteSpeed | Web のリクエストを受ける PHP として対象(Datadog) |
| CLI | Web のリクエストを受けないので、勧告の対象の外 |
公開されている PoC について
GitHub には、PoC と称するリポジトリがいくつも出ている。Sophos は、10月22日にいったん「悪用の成功を1件確認」と発表したが、同じ日のうちに訂正し、成功した侵害の証拠はないとした。観測できたのは、スキャンと少数の失敗した試みだった。同社は、攻撃を完成させるのは簡単ではなく、出回っている PoC は実験の環境でしか動かない可能性があるとしている。
出回っている PoC の中には、いたずらのものもあると報じられている。検証に使うとしても、信用できないコードとして扱い、本番の環境では動かさない。
用語
| 用語 | 意味 |
|---|---|
| SAPI | PHP を Web サーバーなどにつなぐ層(mod_php・FPM・CGI など) |
| bailout | 致命的なエラーのとき、longjmp で処理を打ち切る PHP の仕組み |
| UnwindExit | 例外のように巻き戻して処理を終える仕組み。後片付けが通る |
| use-after-free(UAF) | 解放したメモリを、あとから使ってしまう不具合 |
| ダングリングポインタ | 解放済みの領域を指したまま残ったポインタ |
| RFC 1867 | multipart/form-data によるファイルのアップロードの決まり |
| ASLR | メモリの配置を毎回変えて、攻撃を難しくする仕組み |
| GHSA | GitHub のセキュリティ勧告の番号 |
結論
仕組みを見ると、設定の変更で完全にふさぐのが難しい理由が分かる。OOM はさまざまな場所で起きうるし、リクエストをまたいで残る状態も1か所ではない。公式の勧告が「修正版への更新だけが確実」とするのはこのためだ。修正版は 8.5.12・8.4.27・8.3.36・8.2.35。8.1 以前には正式な修正版がないので、移行を急ぐ。
よくある質問
なぜアプリのコードより前に攻撃が届くのですか?
$_POST や $_FILES の組み立ては、request startup で自動に行われるからです。
認証やルーティングが動く前の段階なので、認証前(pre-auth)の攻撃になります。
PHP-FPM なら mod_php より安全ですか?
影響は受けます。
公式の勧告では、CVE-2026-109619 のセッションの経路で、PHP-FPM でも RCE が確認されています。
mod_php のほうが条件はそろいやすいですが、どちらも更新が必要です。
CVSS の攻撃の複雑さが「高」なのに、なぜ 9.2 なのですか?
認証も利用者の操作も要らず、成功すれば機密性・完全性・可用性のすべてを失うからです。
成功のしにくさより、成功したときの影響の大きさが重く見られています。
参考にした情報
- GitHub Advisory:RCE via OOM corruption(GHSA-w8hw-w9fx-j79w / CVE-2026-109618)(英語)
- GitHub Advisory:RCE via stale global state(GHSA-6wmf-4rv5-p3rp / CVE-2026-109619)(英語)
- DEV Community:PHP's 9.2 RCE fix was public for three days before the release(修正コミットの読み解き)(英語)
- Datadog Security Labs:PHP2Shell の分析(英語)
- OffSec:PHP2Shell - Critical Vulnerabilities in PHP Core(英語)
- Wiz:PHP2Shell(英語)
- Sophos:Proof-of-concept exploit released for PHP vulnerabilities(英語)
- JPCERT/CC:PHPにおける複数の脆弱性に関する注意喚起(JPCERT-AT-2026-0033)
2026年10月22日時点の公開情報にもとづく。攻撃のコード(PoC)の中身は、悪用を助けないよう載せていない。最新の情報は、PHP の公式の勧告と JPCERT/CC を確認すること。
自社のサーバーの構成で、どの層から手を付けるべきかを、構成を聞いてから伝える。相談は無料。
無料で相談する


