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版の有無、侵害の痕跡。
公式情報で確認できる事実
| 項目 | 内容 | 出典 |
|---|---|---|
| 公開日 | 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点あります。
- スコアはCVSS v4.0です。 筆者が確認した範囲では、両GHSAにv3.xのスコアは付いていません。古い資料でv3.1のスコアを見かけたら、出典と採点者を確認してください。
- 「攻撃が複雑(AC:H)」は「安全」を意味しません。 悪用にメモリ配置への依存があるという意味で、実証と悪用の観測は出ています。
- 発見の経緯: 研究者らの解説によれば、メモリ不足に関する問題は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 を渡す構成でのみ条件つきで到達しうる |
| FrankenPHP | mod_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のワーカー(実際にリクエストを処理するプロセス)は、ホテルの客室係に似ています。
- お客が来る(リクエストの開始)
- 係が、その客専用の部屋を用意する(そのリクエスト専用のメモリを確保する)
- 客の用事を済ませる(PHPのコードを実行する)
- 客が帰ったら、部屋を片づけて、次の客のために元の状態に戻す(リクエストの終了処理)
ここで重要なのは、係(ワーカー)は一人で何人もの客を順番に迎えることです。係の手元には、客が替わっても残るメモ(グローバルな状態)があります。そのメモに「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-115560 | session.upload_progress.enabled=0 | 109619の緩和策と同じ設定 |
設定が実際に効いているかは、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を内蔵する機器や製品も確認の対象です。
経営層へ: 何を、どの順で判断するか
非エンジニアの方向けに、判断の順番を示します。
- 今日: 自社のサーバーとサービスで、PHPが動いているものを一覧にする(担当者または委託先に確認)。社内のIT部門が把握していないものほど危険です(古い管理画面、委託先が作ったサイト、機器の内蔵Webなど)。
- 今週: 更新対象の優先順位を決める。インターネットに公開されていて、PHPのバージョンが古く、顧客データを扱うものを先にします。mod_phpで動くものは最優先です。
- 同時に: 委託先・ホスティング事業者に、修正の適用状況を書面で確認する。レンタルサーバーでは、事業者側の対応に依存します。
- 事後: 侵害の痕跡の調査を、更新とセットで行う。更新で穴は塞がりますが、すでに入られていた場合の侵入者は残ります。
- 中期: 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を「同じ脆弱性」として扱ってはいけません。逆に、「関連が全くない」とも言えません(どちらもセッションのアップロード進捗の機能が関わります)。新しいアドバイザリが出るまでは、設定による緩和を維持してください。
筆者の見解: この件から学ぶこと
以下は、公表された事実からの筆者の考えです。
- 「悪用できると証明されるまで直さない」は、危ない判断基準です。2020年と2024年に指摘されていた問題が、悪用の実証が出るまで直らなかったと、研究者は述べています。エラー経路の状態の破損は、単独では無害に見えても、別のバグと組み合わさって深刻になります。
- 正常系のテストだけでは、この種のバグは見つかりません。 修正が追加したのは、失敗を注入するテストでした。自社のコードでも、「外部呼び出しがn番目に失敗する」「メモリ確保が失敗する」テストを入れると、普段通らない後始末の経路が動きます。
- 更新の速さが、そのまま被害の大きさを決めます。 悪用が観測されているため、「様子を見る」は選択肢になりません。更新作業を平時のうちに訓練しておくことが、こうした日の損失を決めます。
- 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の内部を対象とした脆弱性の発見。侵害の疑いがある場合は、まず専門のインシデント対応事業者へ
- 「完全に安全になる」とはお約束できません。見つかったことと、見ていない範囲を、そのまま報告します
範囲と料金は、下のボタンの先でご確認ください。