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

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

- 公開日: 2026-11-01
- 著者: 友田 陽大
- タグ: セキュリティ, 脆弱性診断, PHP, インフラ, アーキテクチャ設計, 信頼性
- URL: https://tomodahinata.com/blog/php2shell-cve-2026-109618-109619-rce-analysis
- カテゴリ: セキュリティ事件の技術解説
- 総合ガイド: https://tomodahinata.com/blog/gmo-desktop-cloud-hack-2026-security-analysis

## 要点

- PHP2Shellは、PHPの2つの別々の脆弱性（CVE-2026-109618=メモリ不足の処理で内部状態が壊れる、CVE-2026-109619=前のリクエストの古い状態が残る）の総称。修正版は8.2.35・8.3.36・8.4.27・8.5.12で、2026年10月21日に公開された。CISAの悪用確認済みリスト（KEV）には両方が載っている
- 実証済みなのは主にApache mod_phpでの未認証RCE。PHP-FPMはソース解析では影響を受けうるが、109618の完全なFPM攻撃は実証されていない。『影響を受けうる』と『実証済み』は別で、どちらも『安全』ではない
- 修正が守る不変条件は4つで、中心は次の2つ。①メモリ不足でも、途中の状態を壊す longjmp を使わず、次の安全な地点で処理する。②リクエストの終わりに、そのリクエスト専用の状態を必ず初期化する。修正は300ファイル以上に及び、メモリ不足を故意に起こすテストを大量に追加した
- 設定による緩和策は完全ではない。memory_limit=-1 は109618の一経路を塞ぐだけで、session.upload_progress.enabled=0 は109619の確認済み経路を塞ぐだけ。公式は『更新だけが完全な対処』としている。CVE-2026-115560は別の脆弱性で、同じ設定が暫定緩和になる
- 確認すべきは、WebサーバーのPHPのバージョン（CLIとは別）、OSベンダーのバックポート、PHP 8.1以前のEOL版の有無、侵害の痕跡。本記事のC言語の教材はASanで検証した概念モデルで、PHP2Shellの再現ではない

---

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点あります。

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 を渡す構成でのみ条件つきで到達しうる |
| 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のワーカー（実際にリクエストを処理するプロセス）は、**ホテルの客室係**に似ています。

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 は違うのか

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

```text
関数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の種類）

```c
/* 教材: リクエスト専用の領域と、それを指す全域のポインタ */
#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;
}
```

```bash
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の種類）

```c
/* 教材: 一覧に追加した後、中身を初期化する前に脱出する */
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のプールごとに違う等）。

```bash
# 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などでは、パッケージの**バージョン番号が上がらずに**修正が取り込まれることがあります。番号だけで「未修正」と決めつけないでください。

```bash
# 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のワーカーが読んでいる設定**で確認します。

```bash
# 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の内部を対象とした脆弱性の発見。侵害の疑いがある場合は、まず専門のインシデント対応事業者へ
- 「完全に安全になる」とはお約束できません。見つかったことと、見ていない範囲を、そのまま報告します

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