2026年10月20日、Hacker Newsに「PHPのリモートコード実行(RCE)脆弱性が2件、近く公開される。深刻度はCritical、影響範囲はPHP 5まで遡る」という投稿がありました。投稿者が示した根拠は、本人の言葉を借りれば source: trust me, bro(信じてくれ)だけです。
ところが同日、PHPプロジェクトが臨時のセキュリティ告知を公開しました。翌日の午後には修正版もリリースされています。リークは本物で、スコアもほぼ当たっていました。CVE-2026-109618とCVE-2026-109619の2件は、完全なアドバイザリとともに公開済みです。研究者はこの2件をまとめて「php2shell」と呼んでいます。The Hacker Newsに詳しい時系列がまとまっています。
2件とも、CVSS 4.0で9.2(Critical)と評価されています。認証は不要で、最終的にWebサーバーワーカーの権限でコードを実行されます。アドバイザリでは8.2.35より前のすべてのバージョンが影響を受けるとされており、本番環境で稼働中のEnd-of-Life(EOL)のPHPリリースラインもすべて含まれます。
修正より先にリークが出た。しかもリリースは4ブランチだけ
PHPは2026年10月21日に、8.2.35、8.3.36、8.4.27、8.5.12で修正をリリースしました。両CVEを扱うセキュリティアドバイザリも同時に公開されています。この4つが公式にサポートされているブランチで、8.2自体も2026年12月31日にEnd-of-Lifeを迎えます。それより古い8.1、8.0、7.4以前にはリリースが出ません。
深刻な脆弱性であることから、セキュリティチームは7.4、8.0、8.1向けの最小限のソースバックポートをGitHubに公開しました。ただし、これはリリースではなく、PHPを自分でビルドしている人向けの未リリースのパッチです。パッケージからPHPを導入しているサーバーに届くのは、ベンダーが更新を出した場合に限られます。
該当する環境の数は少なくありません。リリース前日の10月20日、脅威インテリジェンス企業のMalloryの集計では、ShodanでHTTPバナーがPHP 8.1以前と報告するインターネット公開ホストが約88万4,000台ありました。PHPのバージョンを報告した150万ホストの59%にあたります。さらに約74万ホストは、バージョン文字列なしでPHPと報告しています。バナーは脆弱性の証拠にはなりませんが、今回の修正に対応する公式リリースがないブランチに、PHPの利用環境がどれだけあるかを測る目安にはなります。
私は今年、End-of-Lifeソフトウェアが最も攻撃にさらされやすいソフトウェアである理由を書きました。脆弱性の調査は続くのに、パッチの提供は止まるからです。今週の件はその典型例です。PHPはリーク翌日に両脆弱性のリリースを出しましたが、どのリリースも8.2より下には提供されません。
CVE-2026-109619:ワーカーに残った状態が攻撃に使われる
mod_phpやPHP-FPMのワーカーは、寿命のあいだに多数のリクエストを処理します。リクエストの合間には、エラー状態、セッション状態、オブジェクトや拡張機能の状態といったグローバル状態をリセットすることになっています。この状態の一部は単一のリクエストに属するメモリを指しているため、そのメモリを解放する前にリセットを済ませなければなりません。
CVE-2026-109619は、このリセットが行われなかった場合の問題です。致命的エラー、パース失敗、キャッチされない例外、require の失敗、実行タイムアウト、メモリ枯渇などで処理が中断されると、リセットが実行されないことがあります。リクエスト用メモリは解放されるのに、グローバル変数はそのメモリを指したままになります。同じワーカーが次に処理するリクエストがそれを拾うと、攻撃者が操作できるuse after free(解放後使用)が成立します。
攻撃者には2つの要素が必要です。状態を残す「プロデューサー」と、通常のリクエスト初期化がその状態を置き換える前に触れる「コンシューマー」です。アドバイザリが確認しているコンシューマーは2つあります。
- リクエスト初期化前に発生するINI診断: mod_phpでの.htaccessやphp_valueディレクティブ、FPMに送られるPHP_VALUEやPHP_ADMIN_VALUEパラメータが、警告や非推奨通知を起こす場合です。display_errorsの無効化、error_reportingの変更、@演算子の使用では防げません。
- セッションのアップロード進捗: デフォルトで有効です。session_start()を呼ばなくても動作し、中断されたリクエストが残したセッション状態を消費できます。
セッション経由のRCEは、mod_phpとPHP-FPMの両方で検証されています。ただしFPMの場合は、アプリケーション側に条件に合う処理が必要です。
CVE-2026-109618:メモリ不足がコード実行につながる
CVE-2026-109618は、リクエストがmemory_limitを超えたときのPHPの中断処理に原因があります。PHPは致命的なメモリ不足(OOM)エラーを発生させ、longjmp()で現在のコードパスを放棄します。コードパスによっては、リクエスト用メモリが完全に初期化される前に参照可能になります。その瞬間にどこかのメモリ確保が失敗すると(そのメモリと無関係な確保でも同じです)、ジャンプによって残りの初期化がスキップされます。メモリは不正な状態のまま残り、リクエストのシャットダウン処理がそれを読み込みます。
攻撃者はOOMを意図的に起こせます。アドバイザリには手法が詳しく書かれています。
- 使える Content-Length のないマルチパートリクエストは、post_max_sizeのチェックを通過したまま、ボディの送信を続けられます。
- 1つのマルチパートのパートに、無制限の数のヘッダーを付けられます。PHP内部のヘッダー表現は、通信上のサイズよりはるかに多くのメモリを消費します。
- 空のファイルコントロールはmax_file_uploadsにカウントされませんが、メタデータ分のメモリは確保されます。ネストしたフィールド名で、さらに処理量が増えます。
mod_phpでは、リクエスト開始が失敗してもリクエストのシャットダウンが実行されます。そのため、ボディの自動パースでメモリが枯渇すると、アプリケーションコードが1行も動く前に、シャットダウン処理が壊れた状態を処理します。アドバイザリでは、デフォルトの入力制限とメモリ制限のmod_php環境に対する、認証不要のHTTP経由RCEが確認されています。PHP-FPMは起動に失敗するとワーカーを終了させるため、この経路は塞がれます。しかしリクエストの後半でOOMが起きれば、シャットダウン時に不正な状態が残る可能性があります。ソース解析ではFPMも影響を受け得ると評価されていますが、FPMで完全に成立するエクスプロイトは実証されていません。
修正では、OOM処理からlongjmp()を完全に取り除きました。memory_limitを超えると、VM内の次の安全なポイントで遅延型のソフトOOMが発生します。メモリ使用量が2つ目のより高い上限に達した場合は、ハードな_exit()で終了します。PHPのメモリ処理に対する大規模な変更で、午後のうちにバックポートできるものではありません。
php2shellの緩和策は有効だが、どれも完全ではない
PHPプロジェクトのセキュリティアドバイザリは、どちらの脆弱性も完全な対策はアップデートだけだと明言しています。すぐに更新できない場合のつなぎの対策はアドバイザリに載っています。直感に反するものもあるため、ここで取り上げます。
CVE-2026-109618の緩和策
主な緩和策は、memory_limit=-1を管理者側で設定することです。すべてのプールとディレクトリに適用し、全ワーカーを再起動します。確認されているエクスプロイトは、リクエストごとの制限値が予測できることを前提にしています。制限を外せば、トリガーがなくなります。ただし完全な修正ではありません。基盤のmmap()が失敗すれば、脆弱なコードパスに入る可能性は残ります。
RLIMIT_ASやRLIMIT_DATAを代わりに使うのはやめてください。これらは制限値でメモリ確保を失敗させるため、避けたい状況そのものを作ってしまいます。
併せて、次の対策を取ってください。
- post_max_sizeは控えめな値にする
- デコード後のリクエストサイズをフロントエンドで制限する
- アプリケーションがマルチパートのフォームデータを受け付けないなら、フロントエンドでブロックする
CVE-2026-109619の緩和策
使っていなければ、session.upload_progress.enabled=0を設定します。アドバイザリでは、パッチ適用後も無効のままにしておくことを推奨しています。
- 8.5ではfatal_error_backtraces=0を設定する
- INI設定は、ディレクトリ単位やリクエスト単位のスコープから外し、起動時に適用されるphp.iniまたはプール設定に移す
- パフォーマンスに余裕があれば、pm.max_requests=1でワーカー1つにつき1リクエストに制限する。1つ目の脆弱性が依存する「状態が残る時間窓」を塞げます
これらは時間を稼ぐ手段です。パッチの代わりにはなりません。
PHP 8.1以前を使っている環境の問題
Malloryが数えた約88万4,000ホストを考えてみます。上記の緩和策は適用できますが、リリースは出ません。8.1.35も、8.0.31も、7.4.34も出る予定はありません。アドバイザリは、8.1以前は「PHPプロジェクトからの更新を受けられない」と明記し、それ以外の人にはベンダーへの問い合わせを案内しています。ソースバックポートは、PHPを自前でコンパイルし、パッチを自分で維持できる場合には役立ちます。しかし、該当ホストの大半はもともとPHPをコンパイルしていません。
定番の答えは「アップグレードすればいい」です。多くの環境では、それが正しい答えで、何年も前から正しい答えでした。ただ、7.4や8.0のまま動かしている理由は、怠慢であることはまずありません。
- 独自のPHPを同梱しているベンダーのアプライアンス
- バージョンを固定しているコントロールパネル
- 依存関係が8.1で壊れるのに、書き直す予算がないアプリケーション
- 来年に予定されていて、今週には前倒しできない移行
比較対象になるのは、2024年6月のPHP CGIの脆弱性CVE-2024-4577です。Akamaiは、公開から24時間以内に自社のハニーポットへのエクスプロイト試行を確認しました。php2shellについては、2026年10月21日時点で悪用の報告はなく、公開されたエクスプロイトコードも確認されていません。ただし、それは今日の時点の話です。アドバイザリは、意欲のある攻撃者が作業に取りかかれる程度に、両脆弱性を詳しく説明しています。CVE-2026-109618のマルチパートの手口も、特殊なものではありません。
HeroDevsはEOLのPHPのギャップを埋める
ここからは自社の取り組みの紹介です。手短にまとめます。
HeroDevsは、コミュニティのサポートが終了したソフトウェアに対して、Never-Ending Support(NES)を提供しています。PHPについては、CVE-2026-109618とCVE-2026-109619の両方がEOLのPHP向けに修正済みです。
PHPプロジェクトのソースバックポートは、PHPを自分でビルドし、再ビルドとテストを担える場合には役立ちます。ただし、次のCVEが出れば、同じ作業をもう一度繰り返すことになります。NES for PHPは、それ以外のすべての方向けです。本番でPHP 7.4、8.0、8.1を動かしていて、今週中にアップグレードできないなら、自分でコンパイルするブランチや部分的な緩和策の一覧の代わりに、サポート付きでパッチ適用済みのランタイムを使えます。
脆弱性ディレクトリには、CVE-2026-109619とCVE-2026-109618のエントリがあります。
HeroDevsのNES for PHPサポートの詳細はこちら
お問い合わせ
本記事の内容やHeroDevsのソリューション(NES等)について、日本語でのお問い合わせに対応しています。お気軽にHeroDevsお問い合わせページからご連絡ください。
*原文: PHP2Shell Is Two Critical PHP Flaws. Most of the PHP on the Internet Can't Take the Patch