メインコンテンツへスキップ
セキュリティ事件の技術解説
セキュリティ
脆弱性診断
PHP
インフラ
アーキテクチャ設計
信頼性

PHP2Shell(CVE-2026-109618・CVE-2026-109619)の技術解説 — 2つの脆弱性は別物。修正差分から読む「破れた不変条件」と、運用者が今日確認すること

PHPのリモートコード実行脆弱性PHP2Shell(CVE-2026-109618・109619)を、公式アドバイザリと修正コミットで解説。2つのCVEの違い、SAPI別の成立状況、修正版、緩和策の限界、自社の影響確認の手順、CVE-2026-115560との関係まで。

公開日
読了時間
25分
著者
友田 陽大
シェア
目次

2026年10月21日、PHPプロジェクトは、未認証のリモートコード実行(RCE)につながる2つの重大な脆弱性の修正版を公開しました。発見した研究者らが付けた通称が「PHP2Shell」です。世界のWebサイトの多くがPHPで動いているため、影響の大きさが注目されています。

この記事は次の3点に答えます。

  • 自社のサービスに影響があるか。何を、どの順で確認するか
  • 2つの脆弱性は何が違い、なぜ「リモートから任意のコードが動かされうる」のか
  • 修正は、プログラムのどのルールが破れていたことを直したのか

扱う範囲: 攻撃用のコードや手順は載せません。公式のアドバイザリと修正コミットを読んで分かる範囲の「仕組み」と「防御」だけを扱います。本記事のC言語の例は、同じ種類のバグを説明するための独立した教材で、PHP2Shellの再現ではありません。情報は2026年11月1日(日本時間)までに確認した一次資料に基づきます。

30秒で分かる要点

  • 脆弱性は2つで、別物です。CVE-2026-109618(メモリ不足の処理が内部状態を壊す)と、CVE-2026-109619(前のリクエストの古い状態が残る)。
  • 修正版: PHP 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12(2026年10月21日公開)。PHP 8.1以前はPHPプロジェクトの更新対象外です。
  • 悪用されている: CISAは10月26日に両方を悪用確認済み脆弱性(KEV)に追加しました。
  • 最も危険な実行環境: Apache mod_php。リクエストの受付直後、アプリのコードが動く前に攻撃が成立しうることが実証されています。
  • 設定による緩和は不完全です。公式は「更新だけが完全な対処」と明記しています。
  • 確認すること: WebサーバーのPHPのバージョン(CLIとは別)、OSベンダーの修正の取り込み、EOL版の有無、侵害の痕跡。

本番の顧客データを預かる前に

Webアプリ・SaaS のセキュリティ監査 — 認可・テナント分離・設定まで、コードを読んで確かめます

公式情報で確認できる事実

項目内容出典
公開日2026年10月21日(修正版の公開とアドバイザリ)php.net、GitHub Security Advisory
CVE-2026-109618「RCE via OOM corruption」。メモリ不足(OOM)の処理が、初期化途中の内部状態を壊すGHSA-w8hw-w9fx-j79w
CVE-2026-109619「RCE via stale global state」。リクエストをまたいで残った古い内部状態が使われるGHSA-6wmf-4rv5-p3rp
深刻度両GHSAとも「critical」。CVSS v4.0の基本値は両方とも9.2(AV:N/AC:H/AT:P/PR:N/UI:N)両GHSA
弱点の種類CWE-416(解放後メモリの使用)ほか。両方とも同じ分類が付いている両GHSA
修正版8.2.35、8.3.36、8.4.27、8.5.12(それぞれ未満が影響対象)両GHSA、php.net
悪用状況CISA KEVに2026-10-26追加(期限は10-29)。ランサムウェアでの使用は「不明」CISA KEV
発見者Calif、OpenAI、Wiz の研究者がGHSAに報告者として記載両GHSA

読み方の注意が3点あります。

  1. スコアはCVSS v4.0です。 筆者が確認した範囲では、両GHSAにv3.xのスコアは付いていません。古い資料でv3.1のスコアを見かけたら、出典と採点者を確認してください。
  2. 「攻撃が複雑(AC:H)」は「安全」を意味しません。 悪用にメモリ配置への依存があるという意味で、実証と悪用の観測は出ています。
  3. 発見の経緯: 研究者らの解説によれば、メモリ不足に関する問題は2020年にも報告され、2024年の外部監査でも再発見されましたが、悪用が示されなかったため修正されなかったとされています。後述するように、「悪用できると証明されるまで直さない」判断の難しさを示す一例です。

実行環境ごとの違い: 「実証済み」と「影響を受けうる」を分ける

2つのCVEを読み解くうえで最も大事なのは、実行環境(SAPI)ごとの状況の違いです。GHSAの記述を表にします。

実行環境CVE-2026-109618(OOM)CVE-2026-109619(古い状態)
Apache mod_php実証済み。デフォルトに近い設定のprefork構成で、HTTP経由の未認証RCEが検証された。アプリのコードが動く前に成立する実証済み(PHP 8.5の致命的エラーのバックトレース経路、セッションのアップロード進捗の経路)
PHP-FPMリクエスト開始の失敗時は子プロセスが終了するため、その経路は成立しにくい。起動後の経路はソース解析上は影響を受けうるが、完全なFPM攻撃は実証されていないセッションのアップロード進捗の経路はFPMでも検証済み。ただしアプリの操作に依存。INI設定は、フロントが PHP_VALUE を渡す構成でのみ条件つきで到達しうる
FrankenPHPmod_phpと同様にリクエストの後始末を無条件に呼ぶため、影響を受けやすいと見られる(ソース解析)既定構成では早期の到達経路は確認されていない。ただし免疫を意味しない
LSAPI(LiteSpeed)FPMに近い。アプリ依存の攻撃はあり得るが未検証環境変数経由のINI値で、条件つきの候補
CLIアドバイザリはCLIを扱っていない(筆者の推論: Webの入力経路がないため主な対象外。ただし同じPHPをWebでも使う環境では更新が必要)同左

PHPプロジェクトは、危険なのは「PHP-FPM、Apache mod_php、php-cgi、FrankenPHP、LiteSpeedを含む、Webリクエストを処理するすべてのPHP」と述べ、mod_phpのリスクが最も高いとしています。

基礎知識: PHPがリクエストを処理する流れ

専門用語に入る前に、全体像をたとえで説明します。

ホテルの客室係のたとえ

PHPのワーカー(実際にリクエストを処理するプロセス)は、ホテルの客室係に似ています。

  1. お客が来る(リクエストの開始)
  2. 係が、その客専用の部屋を用意する(そのリクエスト専用のメモリを確保する)
  3. 客の用事を済ませる(PHPのコードを実行する)
  4. 客が帰ったら、部屋を片づけて、次の客のために元の状態に戻す(リクエストの終了処理)

ここで重要なのは、係(ワーカー)は一人で何人もの客を順番に迎えることです。係の手元には、客が替わっても残るメモ(グローバルな状態)があります。そのメモに「3号室のカギはここ」と書いたまま、客が帰って3号室を壊してしまったら、次の客が来たときに、もう存在しない部屋のカギを信じて動いてしまいます。

PHP2Shellの2つの脆弱性は、この「片づけの失敗」が、次のように別々の形で現れたものです。

  • 109619: 客が帰った後も、メモが壊した部屋を指したまま残り、次の客の用意の前に使われる
  • 109618: 部屋の用意の途中で緊急停止が起き、まだ家具が揃っていない部屋のまま、片づけ担当が「全部揃っている」と信じて作業を始める

技術用語を押さえる

  • Zend Engine: PHPの中核。PHP言語を解釈して実行する部分で、メモリ管理もここが行います。
  • リクエストライフサイクル: 開始(起動)→実行→終了(後始末)の流れ。
  • OOM(Out Of Memory): メモリ不足。PHPでは memory_limit(既定128M)を超えたときに起きます。
  • longjmp: C言語の「今いる場所から、遠くの決まった地点へ一気に飛ぶ」仕組み。途中の関数の残りの処理は実行されません。PHPはエラーで処理を中断するときにこれを使っていました。
  • Use-After-Free(解放後の使用): 解放したメモリを、まだ有効だと思って読み書きするバグ。別の用途に再利用されたメモリの中身を、攻撃者が操作できる場合、プログラムの動作を乗っ取れる可能性があります。

なぜ「通常の制御フロー」と longjmp は違うのか

通常のプログラムは、関数が終わると、後始末のコード(例: 「確保したメモリを解放」「状態を元に戻す」)を実行してから呼び出し元に戻ります。

関数A → 関数B → 関数C
         ↑ 関数Cが終わる → 関数Bが後始末 → 関数Aが後始末   (通常)

関数A → 関数B → 関数C ── longjmp ──▶ 関数Aの外側の安全地帯
                          (関数B・Cの後始末は、すべて飛ばされる)

longjmp で飛ぶと、飛び越えた関数の後始末は走りません。そのため、プログラムの内部状態が「途中のまま」止まります。この「途中の状態」が、あとで誰かに読まれると、問題になります。

破れた不変条件

「不変条件(invariant)」とは、プログラムのどの時点でも成り立っているべきルールです。たとえば「連結リストの末尾の要素の buf は、確保済みか NULL のどちらか」です。

  • 109618が破ったルール: 「リクエストのメモリに外から届くとき(後始末の対象になるとき)は、その中身が完全に初期化されている」
  • 109619が破ったルール: 「リクエストが終わったら、そのリクエスト専用のメモリを指す全域の状態は、必ず初期化されている」

GHSAは、109618について「リクエストメモリが、完全に初期化される前に一時的に到達可能な状態があり、その間にメモリ確保が失敗すると、早期脱出が考慮されず、無効な状態が残る」と述べています。

修正差分を読む: 何を、どう直したのか

PHPの修正は、php-src のコミット 506e87fa7dcd「Avoid state corruption via bailout」(2026年10月18日、PHP-8.4ブランチ)にまとまっています。コミットメッセージは次の2行です。

  • Replace bailout on OOM with UnwindExit
  • Properly reset various globals that may be accessed during startup

このコミットは、PHP-8.4ブランチ側で300ファイル以上を変更しています(GitHubの比較表示は300件で打ち切られるため、正確な件数は確認していません)。筆者が読んだのは、差分の統計、メモリ確保の中核(Zend/zend_alloc.c)、セッション拡張(ext/session/session.c)の部分です。大半は、メモリ不足を故意に起こすテストの追加です。以下は、筆者が実際に差分を読んで確認した範囲です。

修正1: メモリ不足で longjmp しない(Zend/zend_alloc.c)

修正前は、メモリ上限を超えると zend_mm_safe_error() が、エラーを出して zend_bailout()(longjmpによる脱出)を呼んでいました。

修正後の考え方は次のとおりです。

状況修正前修正後
通常の上限(memory_limit)を超えるその場で longjmp で脱出遅延OOM: 「保留中」と記録し、VMの割り込みフラグを立ててそのまま処理を続ける
次の安全な地点(VMの割り込み確認)—zend_handle_delayed_oom() が、巻き戻し用の例外(UnwindExit)で正規の経路で脱出する
さらに高い上限(ハード上限)を超える—標準エラーに「Hard memory limit of … bytes exhausted」と出して、_exit(137) でプロセスごと即終了

要点は、「その場で飛ぶ」をやめ、「記録して、安全な地点で止める」に変えたことです。ハード上限を超えるほど暴走した場合は、後始末を一切せずにプロセスを終了させます。これは、壊れた状態を誰にも読ませないための、意図的に乱暴な最後の砦です。

新しい設定 memory_hard_limit_percent も追加されました。筆者が読んだ範囲では、既定値は未設定(-1)で、その場合は memory_limit の200%を目安とし、memory_limit が1GBを超えるときは「memory_limit + 1GB」が上限になる実装でした。100未満(-1 を除く)の値は警告になって拒否されます。

運用への影響(筆者の読み取り): ハード上限に達すると、PHPは通常のエラーページではなくプロセスの即時終了(終了コード137)になります。FPMでは子プロセスが落ちて親が再起動し、mod_phpではApacheの子プロセスが落ちます。更新後は、ログに「Hard memory limit」が出ていないか、ワーカーの異常終了が増えていないかを、監視に加えてください。

修正2: リクエスト専用の状態を、必ず初期化する(ext/session/session.c ほか)

セッション拡張の差分では、次の変更が確認できます。

  • リクエスト開始時の初期化(php_rinit_session_globals)に PS(in_rfc1867_update) = false; を追加
  • 古いコメント「これらは、実行が終わったとき、php_rshutdown_session_globals() が必ず初期化するべきだ」というTODOを削除
  • アップロード進捗の処理(リクエストの開始処理の最中、通常の初期化の前に走る)では、オブジェクトを復元せず「不完全なクラス」として扱い、ユーザーのコードが動かないようにする

つまり、ここで直したのは、後始末が飛ばされても、次のリクエストの初期化が古い状態を信じない、という方向です。コミットメッセージの "Properly reset various globals that may be accessed during startup" がそれに当たります。

修正が守る不変条件のまとめ

#守る不変条件修正の手段
1初期化の途中で、処理を勝手に脱出しないlongjmp をやめ、遅延OOMと安全地点での巻き戻しにする
2暴走したら、壊れた状態を誰にも読ませずに止まるハード上限と _exit(137)
3リクエストをまたいで、古いメモリへの参照を残さない全域の状態を、リクエストの開始時・終了時に確実に初期化する
4初期化前に動く処理は、信頼できない状態に触れない起動中の処理を限定し、ユーザーコードを動かさない

なぜ「正常系のテスト」だけでは足りないのか

この種のバグは、エラーの経路(メモリ不足・致命的エラー)でしか動かないコードに潜みます。普通のテストは、メモリが足りる前提で書かれるため、この経路を通りません。

修正コミットは、Zend/tests/oom_*.phpt などメモリ不足を故意に起こすテストを大量に追加しています。コードのコメントには、ZEND_MM_MAX_ALLOCS という仕組みが、そのリクエストのn番目のメモリ確保を失敗させることで、「メモリ上限を超えるほど大きな確保」だけでなく任意の確保位置でメモリ不足の処理を試せるようにする、と書かれています。

これは、一般のアプリケーションにも使える考え方です。「n番目の呼び出しを失敗させる」テスト(障害注入)を作ると、普段は通らないエラー処理の経路が動きます。

安全な教材: メモリの寿命を AddressSanitizer で観察する

ここから、同じ種類のバグを、小さなC言語の例で見ます。繰り返しますが、PHPのコードではなく、PHP2Shellの再現でもありません。リクエストごとの領域・全域の参照・longjmp という3つの要素を、単純化して並べただけです。手元の隔離された環境で動かしてください。

例1: 全域の参照が、解放済みの領域を指したまま残る(109619の種類)

/* 教材: リクエスト専用の領域と、それを指す全域のポインタ */
#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static jmp_buf bailout;
static char *arena;        /* リクエスト専用のメモリ */
static char *cached_name;  /* リクエストをまたいで残る全域のポインタ */

static void *req_alloc(size_t n, size_t limit) {
  if (n > limit) longjmp(bailout, 1);   /* 「メモリ上限超過」で脱出 */
  return arena;
}

static void handle(const char *input, size_t limit) {
  cached_name = req_alloc(16, limit);
  strcpy(cached_name, "session-A");
  req_alloc(strlen(input) * 1000, limit);   /* ここで脱出するかもしれない */
  cached_name = NULL;                       /* 成功した場合しか実行されない */
}

static void request(const char *input, size_t limit) {
  arena = malloc(64);
#ifdef FIXED
  if (setjmp(bailout) == 0) handle(input, limit);
  cached_name = NULL;                       /* 修正: 境界で、どの経路でも初期化する */
#else
  if (setjmp(bailout) == 0) handle(input, limit);
#endif
  free(arena);                              /* リクエスト終了: 領域を解放 */
}

int main(void) {
  request("x", 1u << 20);                   /* 正常なリクエスト */
  request("a-very-long-input", 64);         /* 異常系: 途中で脱出 */
  if (cached_name) printf("next sees: %s\n", cached_name);   /* 解放済みを読む */
  else puts("next sees: (cleared)");
  return 0;
}
clang -g -O0 -fsanitize=address lifetime.c -o bug
clang -g -O0 -fsanitize=address -DFIXED lifetime.c -o fixed
./bug     # 異常終了して、解放済みメモリの読み取りを報告する
./fixed   # 正常終了する

筆者が実行した結果(Apple clang 21.0.0、-fsanitize=address):

  • 修正前: AddressSanitizer: heap-use-after-free。解放した場所(request 内の free(arena))と確保した場所が、それぞれスタックトレースで示された
  • 修正後(-DFIXED): next sees: (cleared) と表示され、検出なしで終了

読み取るべきは次の点です。

  • バグの原因は「成功した経路にしか初期化がない」ことでした。cached_name = NULL; が、脱出(longjmp)すると実行されません
  • 修正は「どの経路でも通る境界で初期化する」ことです(PHPの修正3に対応する考え方)

例2: 初期化の途中で脱出して、後始末が壊れた状態を読む(109618の種類)

/* 教材: 一覧に追加した後、中身を初期化する前に脱出する */
struct node { struct node *next; char *buf; };
static struct node *head;      /* リクエスト終了時にたどられる一覧 */

static void add_header(size_t len) {
  struct node *n = malloc(sizeof *n);    /* 中身は未初期化 */
  n->next = head; head = n;              /* 一覧に公開してしまう */
  n->buf = req_alloc(len);               /* ここで脱出するかもしれない */
}

static void shutdown_request(void) {     /* 必ず実行される後始末 */
  for (struct node *n = head, *next; n; n = next) {
    next = n->next;
    free(n->buf);                        /* buf は有効かNULLだと信じている */
    free(n);
  }
}

例2は抜粋です。req_alloc は、例1の関数から上限の引数を省いた1引数版を想定しています。-DFIXED の版と main は省略しているため、この抜粋だけでは下の結果を再現できません。

修正版(-DFIXED)は、確保の失敗をその場では脱出せず「記録」し、次の安全な地点で処理します(PHPの修正1と同じ考え方)。

筆者が実行した結果:

  • 修正前: ASanが SEGV on unknown address(shutdown_request 内、free(n->buf) の行)を報告
  • 修正後: 「OOM handled at a safe point」「shutdown completed」と表示して正常終了

この例が示すのは、「公開する前に、初期化を終える」というもう一つの原則です(例: リストに載せる前にフィールドを埋める)。

教材の限界

  • これは概念モデルです。PHPのメモリ管理(zend_mm)や、実際の悪用に必要な条件は再現していません
  • 最適化の有無・OS・アロケータで、挙動は変わります(ASanの結果は、筆者の環境での観測)
  • 実際に任意コードが動くところまでの再現は、ここでは行いませんし、行うべきでもありません

運用者向け: 自社への影響を確かめる手順

ステップ1: Webサーバーで動いているPHPを特定する

CLIの php -v と、Webを処理しているPHPのバージョンは別のことがあります(複数のバージョンが同居、コンテナ、FPMのプールごとに違う等)。

# CLIのバージョン(Webとは別かもしれない)
php -v

# FPMのバージョンと、動いているプロセス
php-fpm -v 2>/dev/null; ps aux | grep -E 'php-fpm|php-cgi|frankenphp|lsphp' | grep -v grep

# Apache mod_php か(読み込まれているモジュール)
apachectl -M 2>/dev/null | grep -i php

「Webで動いているバージョン」を確実に知るには、そのサーバーで実際にPHPを実行して、バージョンを出す方法があります。たとえば、管理者だけが見られる経路で phpversion() の結果を出し、公開しないでください。バージョン情報を外部に漏らすページを作らないことが重要です。

ステップ2: OSベンダーのバックポートを確認する

Debian・Ubuntu・RHELなどでは、パッケージのバージョン番号が上がらずに修正が取り込まれることがあります。番号だけで「未修正」と決めつけないでください。

# Debian/Ubuntu: 変更履歴に CVE 番号があるか
apt changelog php8.3-fpm 2>/dev/null | grep -E 'CVE-2026-10961[89]'

# RHEL系
rpm -q --changelog php-fpm 2>/dev/null | grep -E 'CVE-2026-10961[89]'

ここで見つからなければ、ベンダーの公式のセキュリティ情報(トラッカー)で、自分のディストリビューションの版が修正済みかを確認します。

ステップ3: EOL版がないかを確認する

  • PHP 8.1以前は、PHPプロジェクトの更新対象外です。アップグレード、またはベンダーのバックポート付きLTSが必要です
  • PHPプロジェクトは、重大性を考慮し、PHP 7.4・8.0・8.1に対して最小限のソースバックポートを提供しています(ソースからビルドする場合や、LTSの入手経路がない場合の応急手段)
  • PHP 8.2のセキュリティサポートは2026年12月31日に終了する、とCisco Talosは指摘しています。今回を機に、8.3以降への移行計画を立ててください

ステップ4: すぐ更新できない場合の暫定緩和と、その限界

更新が最優先で、以下は時間稼ぎです。公式も「特定の攻撃経路を減らすだけで、安全にはならない」としています。

目的設定注意
109618の経路を減らすmemory_limit=-1(アプリが上書きできない方法で)、低い post_max_size、プロキシ側でのサイズ・項目数の制限memory_limit=-1 は完全ではない。基盤のメモリ確保が失敗すれば、脆弱な経路は残る。RLIMIT_AS・RLIMIT_DATA は使わない(確保失敗を誘発する)。また memory_limit=-1 は要求ごとのメモリ上限を外すため、1件の要求でホストのメモリを使い切れる。cgroupやコンテナの上限と併用し、更新までの暫定にする
109619の確認済み経路を塞ぐsession.upload_progress.enabled=0(php.ini・FPMプール・サーバー設定で、全ワーカーを再起動)。ini_set() や .user.ini に頼らない他の経路は塞がない
109619(PHP 8.5)fatal_error_backtraces=0、全ワーカーを再起動確認済みの1経路のみ
109619の残りを減らす攻撃者から届く致命的エラーの除去、pm.max_requests=1(性能を犠牲にできる場合)完全な設定上の緩和策は存在しないと公式が明記
CVE-2026-115560session.upload_progress.enabled=0109619の緩和策と同じ設定

設定が実際に効いているかは、Webのワーカーが読んでいる設定で確認します。

# CLIの設定(Webのワーカーと違うことがある)
php -i | grep -E 'session.upload_progress.enabled|memory_limit|fatal_error_backtraces'

# FPMが実際に読み込む php.ini の値(Webのワーカーに近い)
php-fpm -i | grep -E 'session.upload_progress.enabled|memory_limit|fatal_error_backtraces'

# プールごとの上書き(php_value / php_admin_value)。php.ini の値は -tt には出ない
php-fpm -tt 2>&1 | grep -E 'php_(admin_)?(value|flag)'

ステップ5: 侵害の痕跡を調べる

JPCERT/CCは、発見者らの情報として、次の点の確認を挙げています。一つの検知ルールで全ての攻撃を拾えるとは限りません。

  • 過去のHTTPレスポンスで、不自然に長い、または異常な形式の値を持つ PHPSESSID のCookieが設定されていないか
  • 不審な multipart/form-data のリクエストと、その前後のPHPのメモリ上限に関するエラーや HTTP 500
  • PHPのワーカーが予期せず異常終了、または頻繁に再起動していないか

ログに痕跡がないことは、安全の証明ではありません。公式も、監査ログは「実行された経路にしか及ばない」と述べています。

JPCERT/CCは10月29日の更新で、攻撃活動の調査で、あるファイアウォール製品の特定バージョンの侵害を示唆する痕跡が報告されたこと、攻撃者がPHPの脆弱性とLinuxカーネルの権限昇格の脆弱性を組み合わせたと見られること、内部ネットワークへの横展開が試みられた形跡があることを伝えています。Webサイトだけでなく、PHPを内蔵する機器や製品も確認の対象です。

経営層へ: 何を、どの順で判断するか

非エンジニアの方向けに、判断の順番を示します。

  1. 今日: 自社のサーバーとサービスで、PHPが動いているものを一覧にする(担当者または委託先に確認)。社内のIT部門が把握していないものほど危険です(古い管理画面、委託先が作ったサイト、機器の内蔵Webなど)。
  2. 今週: 更新対象の優先順位を決める。インターネットに公開されていて、PHPのバージョンが古く、顧客データを扱うものを先にします。mod_phpで動くものは最優先です。
  3. 同時に: 委託先・ホスティング事業者に、修正の適用状況を書面で確認する。レンタルサーバーでは、事業者側の対応に依存します。
  4. 事後: 侵害の痕跡の調査を、更新とセットで行う。更新で穴は塞がりますが、すでに入られていた場合の侵入者は残ります。
  5. 中期: PHP 8.1以前、まもなくサポートが切れる8.2の移行計画を、予算に載せる。

「ウチはWordPressだけだから大丈夫」とは言えません。PHPの実行環境が未修正なら、WordPress自体に脆弱性がなくても影響を受けうるからです。

フレームワーク別の整理: LaravelとWordPress

論点整理
脆弱性の場所PHPの実行環境。LaravelやWordPressのコードではない
影響そのPHPを使うアプリは、フレームワークの脆弱性の有無にかかわらず影響を受けうる
対処PHPを修正版に更新する。フレームワークの更新では、本件は直らない
別の問題フレームワークやプラグイン固有の脆弱性は、別に追う

なお、109619の「致命的エラーが経路になりうる」点は、プラグインや拡張が引き起こす致命的エラー(例: ファイルアップロード・インポート・エラーページ・シャットダウン処理)にも関わるため、攻撃者が届く範囲の致命的エラーを減らすことは、更新後も防御の層として有効です。

CVE-2026-115560との関係

  • php.netが10月23日に情報提供として公表した、別の脆弱性です
  • 成立するのは、session.serialize_handler を設定で php_binary または php_serialize にしたうえで、アプリが実行時に ini_set() で php に切り替え、session_start() を呼び、さらにセッションのアップロード進捗が有効なときだけの、特殊な組み合わせです
  • php.netは、この構成は実際には稀として、通常のリリースサイクルで修正し、完全なアドバイザリは次回リリースで公表するとしています
  • 2026年10月31日に筆者が確認した時点では、cve.orgとNVDに該当のCVEレコードは公開されていません(「存在しない」という応答)
  • 今回の修正版(8.2.35 ほか)で直ったかどうかは、公式の記述では「直った」とはされていません。公式の暫定の緩和策は session.upload_progress.enabled=0 です

PHP2Shellの2つのCVEと、115560を「同じ脆弱性」として扱ってはいけません。逆に、「関連が全くない」とも言えません(どちらもセッションのアップロード進捗の機能が関わります)。新しいアドバイザリが出るまでは、設定による緩和を維持してください。

筆者の見解: この件から学ぶこと

以下は、公表された事実からの筆者の考えです。

  1. 「悪用できると証明されるまで直さない」は、危ない判断基準です。2020年と2024年に指摘されていた問題が、悪用の実証が出るまで直らなかったと、研究者は述べています。エラー経路の状態の破損は、単独では無害に見えても、別のバグと組み合わさって深刻になります。
  2. 正常系のテストだけでは、この種のバグは見つかりません。 修正が追加したのは、失敗を注入するテストでした。自社のコードでも、「外部呼び出しがn番目に失敗する」「メモリ確保が失敗する」テストを入れると、普段通らない後始末の経路が動きます。
  3. 更新の速さが、そのまま被害の大きさを決めます。 悪用が観測されているため、「様子を見る」は選択肢になりません。更新作業を平時のうちに訓練しておくことが、こうした日の損失を決めます。
  4. PHPのバージョンは、サーバーごとに違うことが普通です。資産の把握ができていない組織ほど、見落としが出ます。

現時点の結論と、今後の確認事項

  • 事実として言えること: 2つの別々のCVEが、PHP 8.2.35・8.3.36・8.4.27・8.5.12で修正された。両方がKEVに載り、悪用の観測がある。実証されたRCEは主にmod_phpで、FPMには条件つきの経路が検証されている。
  • まだ言えないこと: 全実行環境での攻撃の成否と、実際の被害の規模。FPMでの109618の完全な攻撃の可否。CVE-2026-115560の詳細。
  • 確認すべき続報: 115560のアドバイザリとCVE、次回のPHPリリース、各ディストリビューションのバックポートの状況、JPCERT/CCとIPAの更新。

自社サービスの脆弱性診断・コード監査をご検討の方へ

筆者は、WebアプリケーションとクラウドのセキュリティをNext.js・Python・Go・AWSの本番運用の経験から診断しています。PHPの更新で穴は塞がりますが、更新後に残るアプリケーション側の問題(認可の不備、設定の誤り、秘密情報の扱い)は別に確認する必要があります。

対応できること、できないことを先に書きます。

  • できること: ソースコードと設定を読む、Webアプリの脆弱性診断とコード監査。優先順位つきの修正方針と、修正後の再確認
  • できないこと: 侵害を受けた後のフォレンジック調査、PHPの内部を対象とした脆弱性の発見。侵害の疑いがある場合は、まず専門のインシデント対応事業者へ
  • 「完全に安全になる」とはお約束できません。見つかったことと、見ていない範囲を、そのまま報告します

範囲と料金は、下のボタンの先でご確認ください。

よくある質問

PHP2Shellとは何ですか?CVEは1つですか?
PHP2Shellは、2026年10月21日に公表されたPHPの2つの重大な脆弱性の総称です。CVE-2026-109618は、メモリ不足の処理中に内部状態が壊れ、リクエスト終了時の後始末が壊れた状態を読む問題。CVE-2026-109619は、前のリクエストが残した内部状態が、解放済みのメモリを指したまま次のリクエストの初期化前に使われる問題です。メカニズムが異なるため、1つの脆弱性として扱わないでください。
どのバージョンに更新すればよいですか?
PHP 8.2系は8.2.35、8.3系は8.3.36、8.4系は8.4.27、8.5系は8.5.12以降です。PHP 8.1以前はPHPプロジェクトの更新対象外で、サポートされる版へのアップグレード、またはOSベンダーのバックポートが必要です。Debian・Ubuntu・RHELなどはバージョン番号が変わらずに修正だけを取り込むことがあるため、パッケージの変更履歴にCVE番号があるかで確認します。
実際に攻撃されていますか?
はい。CISAは2026年10月26日に、両CVEを悪用確認済み脆弱性(KEV)に追加しました。JPCERT/CCは、悪用を試みる通信の観測と、多数の実証コードの公開を報告しています。一方で、実証コードで任意のコードが実行できるかは、PHPのバージョン・実行環境・メモリ配置に依存するとしています。『多くが走査や単純な試行』という観測もあり、攻撃の成否と件数は環境によります。
更新できない場合の緩和策はありますか?
あります。ただし完全ではありません。109618には memory_limit=-1(上書きされない方法で)と低い post_max_size、プロキシ側のサイズ制限。109619には session.upload_progress.enabled=0(php.ini・FPMプール・サーバー設定で、全ワーカーを再起動)と、PHP 8.5では fatal_error_backtraces=0。ただし memory_limit=-1 は要求ごとの上限を外すため、cgroupやコンテナの上限と併用し、更新までの暫定にしてください。公式は『更新のみが完全な解決』としています。RLIMIT_AS や RLIMIT_DATA は、脆弱な経路を誘発するため使わないでください。
CVE-2026-115560はPHP2Shellと同じですか?
別の脆弱性として公表されています。php.netの10月23日の情報提供によれば、session.serialize_handler を設定で php_binary または php_serialize にしたうえで、実行時に ini_set() で php へ切り替えてから session_start() を呼び、さらにセッションのアップロード進捗が有効な、特殊な組み合わせでのみ成立します。完全なアドバイザリは次回リリースで公表予定で、2026年10月31日の時点でCVEレコードは公開されていません。session.upload_progress.enabled=0 が暫定の緩和策です。
LaravelやWordPressは影響を受けますか?
脆弱性はフレームワークではなくPHPの実行環境(ランタイム)にあります。Laravel・WordPress・Symfonyなどを動かしているPHPが未修正なら、アプリケーションに脆弱性がなくても影響を受けうる、というのが公式の整理です。逆に、PHPを修正版にすれば、フレームワーク側の対応は不要です。フレームワークやプラグイン固有の脆弱性は、別の問題として扱ってください。

参考文献

友田

友田 陽大

経済産業大臣賞 受賞プロダクト開発者。TypeScript + Python + AWS で、SaaS・業界DX・実用レベルの生成AI(RAG)を、要件定義からインフラ・運用まで一人で完遂します。

本番の顧客データを預かる前に

Webアプリ・SaaS のセキュリティ監査 — 認可・テナント分離・設定まで、コードを読んで確かめます

ツールが拾える穴に加えて、「誰がどのデータに触れてよいか」という設計の穴まで、ソースコードと設定から確かめ、優先順位つきの修正方針を出します。Next.js / Supabase に限らず、Rails・Laravel・Django・Go などの Web アプリと AWS / GCP / Vercel の設定も対象です。「完全に安全」とは言いません。

プロジェクト単位(請負)・技術顧問のどちらにも対応可能です。まずは30分の無料技術相談から。

最短ルート:カレンダーから直接予約

相談内容が固まっている方は、フォーム送信よりその場で日程を確定する方がスムーズです。下記から空き時間をお選びください。

  • 30分のオンライン無料相談
  • Google Meet / Zoom / Microsoft Teams
  • NDA 商談前締結可・無理な営業はいたしません
無料相談の空き枠を予約する

あわせて読みたい