本文へ移動
AI-nativeエンジニアリング

PHPパッチ手順:2026年10月に修正された認証不要のRCE 2件

ログインなしでリモートコード実行を許すPHPの脆弱性2件について、修正版、稼働中のworkerの確認方法、ini変更だけでは足りない理由を解説します。

公開日 更新日 8 分で読める
セキュリティPHPパッチ適用脆弱性管理運用

この記事は教育目的であり、正式なスマートコントラクト監査、法的助言、金融助言、またはプロトコル固有のセキュリティレビューに代わるものではありません。

俯瞰の静物。.deb/.rpm風のパッケージ、USB、UPDATE付箋、PHPバージョン比較のタブレット。
イメージイラスト。

2026年10月21日、PHPプロジェクトはサポート中の4つのブランチで、認証なしでのリモートコード実行につながりうる2件の重大な脆弱性、CVE-2026-109618とCVE-2026-109619の修正を公開しました。修正版はPHP 8.5.12、8.4.27、8.3.36、8.2.35です。リリースのアナウンスは明快で、完全な緩和策はなく、更新だけが完全な修正だとしています。2件目の脆弱性のGitHubアドバイザリも表現は違いますが同じ趣旨で、完全なパッチを含むリリースへの更新だけが信頼できる緩和策だと述べています。

古いPHPブランチを動かしている組織や、公開当日に更新できない組織向けに、一時措置とその限界も示します。

この手順では3つのことを扱います:Webリクエストを処理するPHPをすべて見つけること、再起動後に稼働中の各プロセスが報告するバージョンを確認すること、そして今日すぐに更新できない場合の一時的な対策です。

php.netが挙げるiniの対策は、特定の攻撃経路への露出を減らしますが、php.net自身が、PHPのインストールそのものを修正するものではないと述べています。修正版が稼働するまでの数時間から数日のつなぎであり、代わりにはなりません。そして、サーバーがパッチ適用済みと言えるのは、再起動後に稼働中のworkerが修正済みのバージョンを報告したときであり、パッケージをインストールしたときではありません。

何が修正されたか

php.netによる2件の脆弱性の説明は次のとおりです:

  • CVE-2026-109618(GHSA-w8hw-w9fx-j79w)、メモリ不足時の破損によるリモートコード実行:メモリ不足の処理が、リクエストの内部データが一部しか初期化されていない時点でPHPを中断させることがあり、その後のリクエストのクリーンアップが無効なデータを有効なものとして扱うおそれがあります。
  • CVE-2026-109619(GHSA-6wmf-4rv5-p3rp)、古いグローバル状態によるリモートコード実行:あるリクエストが所有する内部状態が、そのメモリが解放された後も残ることがあり、同じworkerが処理する後続のリクエストがそれを使うおそれがあります。

2つのGitHubアドバイザリはどちらもCriticalと評価しており、CVSS v4.0で9.2です:攻撃元区分はネットワーク、特権は不要、ユーザーの関与も不要ですが、攻撃の複雑さは高く、攻撃の前提条件があります。各ブランチで8.2.35、8.3.36、8.4.27、8.5.12より前のすべてのバージョンが影響を受けます。php.netは10月20日に、同じ評価とともにこのリリースを事前予告していました。

8.4.27のChangeLogは、修正を1行でまとめています:「Avoid state corruption via bailout」。同じ日に公開されたPHP 8.6.0 RC4にも修正が含まれています。php.netは、これはテスト版であり本番環境向けではないと念を押しています。

誰にリスクがあるか

php.netはこう書いています:「all PHP installations serving web requests, including PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP and LiteSpeed」、つまりWebリクエストを処理するすべてのPHPのインストールです。リスクが最も高いのはmod_phpだとしており、実証済みのエクスプロイトでは、認証されていないリクエストが「before any application code runs」、つまりアプリケーションのコードが動く前に攻撃を引き起こせます。

この点は重要です。mod_phpでは、アプリケーションコードの品質は露出の度合いを変えません。きれいなコードでも、脆弱なインタープリターは守れません。

アドバイザリは環境ごとの詳細を補足しています:

  • 1件目:デフォルトの入力制限とメモリ制限のmod_phpに対して、HTTP経由での完全なリモートコード実行が検証されています。アドバイザリのソース解析では、FrankenPHPはmod_phpに近く、LiteSpeedのLSAPIはFPMに近いとされています。後者では、アプリケーションに依存するエクスプロイトの可能性は高いものの、検証はされていません。
  • 2件目:セッションの経路が、mod_phpとFPMに対して検証されています。
  • 両方に共通:workerの増加、無関係なトラフィック、workerの再生成によって1回ごとの成功率は下がりますが、条件そのものはなくなりません。

2つ目のアドバイザリには、インシデント対応への注意もあります:古い状態は「without a predictable log entry」、つまり予測できるログの記録を残さずに公開されたり使われたりすることがあります。ログがきれいでも、何も起きなかった証拠にはなりません。

ステップ1:動いているPHPをすべて見つける

1台のサーバーに複数のPHPがあることがあります:CLI、1つ以上のバージョンのFPM、古い構成から残ったphp-cgi、LiteSpeedのlsphp、FrankenPHPのバイナリ、あるいはApacheのモジュールです。それぞれを個別に更新する必要があります。Linuxでは、次のコマンドでPHPのバイナリと、それぞれが報告するバージョンを一覧できます:

bash
find / \( -path /proc -o -path /sys -o -path /var/lib/dpkg \) -prune -o -type f -perm -u+x \
  \( -name php -o -name 'php[0-9].[0-9]' -o -name 'php[0-9][0-9]' -o -name php-cgi -o -name 'php-cgi[0-9].[0-9]' \
     -o -name php-fpm -o -name 'php-fpm[0-9].[0-9]' -o -name lsphp \) -print 2>/dev/null |
  while read -r b; do echo "$b: $("$b" -v 2>/dev/null | head -1)"; done

テスト用のマシン(Debian 13)では、2つ見つかりました:/usr/bin/php8.4(cli)と/usr/sbin/php-fpm8.4(fpm-fcgi)で、どちらも8.4.27です。FrankenPHPとmod_phpはここには出てきません。FrankenPHPは単一のバイナリで、バージョン表示用のサブコマンドを持っています。mod_phpはApacheが読み込むライブラリなので、Webサーバーのモジュール一覧で確認します。たとえば「apachectl -M」、Debianなら「a2query -m」です。

コンテナは独自のPHPを持っています。このコマンドはホストだけでなく、各イメージの中でも実行する必要があります。

ステップ2:パッケージのバージョンはアップストリームのバージョンではない

php.netによると、LinuxディストリビューションとLTSベンダーは事前にパッチを受け取っているため、更新は通常パッケージマネージャー経由で届きます。パッケージのバージョンはディストリビューションのものであり、アップストリームの番号付けに従うとは限りません。ベンダーが、より古いバージョン番号のビルドに修正を入れることもあります。基準になるのは、この2件のCVEに関するベンダーのアドバイザリであり、番号だけではありません。DebianまたはUbuntuでは:

bash
dpkg-query -W -f='${Package} ${Version}\n' 'php*-fpm' 'php*-cli' 'php*-cgi' 'libapache2-mod-php*' 2>/dev/null

10月25日、テスト用マシンのDebian 13が提供していたのはphp8.4-fpm 8.4.27-1~deb13u1でした。RPMベースのシステムでは、パッケージ名を付けた「rpm -q」が同等の確認になります。

ステップ3:インストールされたものではなく、動いているものを確認する

パッケージをインストールすると、ディスク上のファイルが置き換わります。すでに動いているプロセスは、再起動するまで古いコードをメモリ上に保持します。php.netは対策を適用した後にすべてのworkerを再起動するよう求めており、更新についても同じです。

次のコマンドは、PHPとWebサーバーのプロセス、それぞれの起動時刻、実行しているバイナリを一覧します。最後の行は、すでに削除または置き換えられたPHPのバイナリやライブラリをまだマップしているプロセスを見つけます:

bash
for p in $(pgrep -f 'php-fpm|php-cgi|lsphp|frankenphp|apache2|httpd'); do
  echo "$p | started $(ps -o lstart= -p "$p") | $(sudo readlink "/proc/$p/exe")"
done
sudo grep -lE '(php|libphp)[^ ]* \(deleted\)' /proc/[0-9]*/maps 2>/dev/null

テストでは、FPMが動いている状態で、自動再起動を無効にしてパッケージを再インストールしました。出力は次のとおりです:

text
678767 | started Sun Oct 25 10:49:55 2026 | /usr/sbin/php-fpm8.4 (deleted)
678768 | started Sun Oct 25 10:49:55 2026 | /usr/sbin/php-fpm8.4 (deleted)
678769 | started Sun Oct 25 10:49:55 2026 | /usr/sbin/php-fpm8.4 (deleted)
/proc/678767/maps
/proc/678768/maps
/proc/678769/maps

「(deleted)」は、そのプロセスが更新前に存在していたバイナリをまだ実行していることを意味します。FPMを再起動した後、同じコマンドはその印のない新しいプロセスを表示し、最後の行は何も出力しませんでした。mod_phpでも確認方法は同じで、対象はApacheのプロセスです。libphpライブラリがその中にマップされているためです。

最も直接的な確認は、pool自体に問い合わせることです。次のスニペットは、Webサーバーを通さずにsocket経由でFPMにリクエストを送り、バージョンとpoolが実際に適用している値を表示します。socketのパスはディストリビューションとバージョンによって異なります。これはDebianの8.4のものです:

bash
sudo tee /run/phpcheck.php >/dev/null <<'PHP'
<?php
echo PHP_VERSION, " ", PHP_SAPI, "\n";
foreach (['memory_limit', 'post_max_size', 'upload_max_filesize', 'session.upload_progress.enabled'] as $k) {
    echo $k, " = ", var_export(ini_get($k), true), "\n";
}
echo "ini_set memory_limit: ", var_export(ini_set('memory_limit', '128M'), true), "\n";
PHP
sudo SCRIPT_FILENAME=/run/phpcheck.php REQUEST_METHOD=GET \
  cgi-fcgi -bind -connect /run/php/php8.4-fpm.sock
sudo rm /run/phpcheck.php

テスト用のファイルはWebルートの外に作成され、最後に削除されるため、HTTP経由でアクセスできる状態にはなりません。phpinfoのページやバージョンを表示するページを公開したままにしてはいけません。

CLIはpoolの代わりにはなりません。同じマシンで、コマンドラインのphpはmemory_limitが-1、session.upload_progress.enabledが1と報告しましたが、変更していないFPMのpoolは128Mと1でした。SAPIごとに、それぞれの設定を読み込みます。

ステップ4:今日更新できない場合

php.netは、これらの対策は特定の攻撃経路への露出を減らすだけで、PHPのインストールそのものを修正するものではないと明言しています。1件目の脆弱性について:

  • memory_limit=-1を、アプリケーションが上書きできない方法で設定します。たとえばFPMのpoolやApacheの設定でphp_admin_valueを使います。どのpool、ディレクトリ、アプリケーションも有限の制限を戻していないことを確認してください。php.netは、根本にあるメモリ割り当ての失敗が、依然として脆弱な経路に到達しうると警告しています。
  • post_max_sizeを低く設定します:アプリケーションがファイルのアップロードを受け付けないなら8M、受け付けるなら32Mにし、upload_max_filesizeはそれより小さくします。php.netは、デコード後のリクエストサイズ、入力フィールド数、multipartのパート数、ヘッダー数、ネストの深さについて、Webサーバーやプロキシでの制限も勧めています。使えるContent-Lengthがないリクエストも対象に含めます。
  • PHPのメモリ制限がない場合、可用性の境界はコンテナやcgroupの制限が担う必要があり、影響を受けたworkerだけを終了させるポリシーが望ましいです。php.netは、代わりにRLIMIT_ASやRLIMIT_DATAを使うことは勧めていません。脆弱なメモリ割り当ての失敗を引き起こすおそれがあるためです。

2件目の脆弱性について:

  • session.upload_progress.enabled=0にしてすべてのworkerを再起動します。設定場所は、php.ini、FPMのpool、またはリクエストの解析前に適用されるサーバー設定です。ini_set()やFPMの.user.iniファイルでは行いません。確認済みのセッションの経路は防げますが、ほかの変種は防げません。
  • PHP 8.5では、fatal_error_backtraces=0にしてすべてのworkerを再起動します。防げるのは、確認済みの致命的エラーのbacktraceの経路だけです。
  • リクエストごとに適用される設定のうち、初期化の前に診断メッセージを出す無効な設定や非推奨の設定を取り除きます。error_reporting、display_errors、@で診断を隠しても効果はありません。
  • 攻撃者が引き起こせる致命的エラーや実行タイムアウトを修正します。コールバック、アップロードやインポートの処理、クリーンアップの中で起きるものも含みます。

FPMのpoolでは、設定は次のようになります。upload_max_filesizeの4Mは一例で、重要なのはpost_max_sizeより小さくしておくことです:

ini
; Temporary measures for CVE-2026-109618 and CVE-2026-109619.
; Remove memory_limit and post_max_size once the fixed version is running.
; Keep session.upload_progress.enabled off until the next PHP release (CVE-2026-115560).
php_admin_value[memory_limit] = -1
php_admin_value[post_max_size] = 8M
php_admin_value[upload_max_filesize] = 4M
php_admin_flag[session.upload_progress.enabled] = off

その後、設定をテストして再起動します:

bash
sudo php-fpm8.4 -t && sudo systemctl restart php8.4-fpm

テストでは、これらの行をwww poolの末尾に加えたところ、設定テストは成功し、poolはmemory_limitが-1、post_max_sizeが8M、upload_max_filesizeが4M、session.upload_progress.enabledが0と報告しました。スクリプトからmemory_limitに対してini_setを呼ぶとfalseが返りました。つまり、アプリケーションはphp_admin_valueを上書きできません。テスト用マシンにはsystemdがないため、再起動は手動で行いました。

これらの変更はどれも、ベースラインの設定から外れるものです。特にmemory_limit=-1は、多くのハードニングガイドが意図的に設けている制限を外します。変更ごとに理由と撤去日を記録し、修正版が稼働したら取り除いてください。

CVE-2026-115560:残しておく設定が1つある

10月23日、php.netは情報提供のためのアドバイザリを公開しました。可能性は低いとしながらも、リモートコード実行の追加の経路であるCVE-2026-115560があるため、次の通常のバグ修正リリースまでsession.upload_progress.enabled=0を維持するよう勧めています。10月21日にこの設定を適用していれば、そのままにしておけばよく、ほかに対応は必要ありません。

php.netによると、この問題には珍しい組み合わせが必要です:アプリケーションが設定でsession.serialize_handlerをphp_binaryまたはphp_serializeにし、session_start()を呼ぶ前にini_set()で実行時にphpへ切り替え、さらにセッションのアップロード進捗が有効なままになっている場合です。実行時にシリアライズハンドラーを変更しないアプリケーションは、この機能を有効のままにできます。

CVE-2026-115560の完全なアドバイザリはまだ公開されていません。php.netは次のリリースとともに公開するとしています。それまでは、引用できる影響バージョンや修正バージョンの一覧はありません。

実際には、更新後にmemory_limitとpost_max_sizeの変更は元に戻せますが、アップロード進捗のスイッチはそのリリースまでオフのままにします。

PHP 8.1以前:例外であって計画ではない

PHP 8.1以前は、PHPプロジェクトからの更新をもう受けていません。一部のディストリビューションやLTSベンダーはバックポートした修正を提供しています。深刻度を考慮して、PHPのセキュリティチームは例外として、サポートが終了した3つのブランチ向けに最小限のソースのバックポートを用意しました。ソースからビルドする人や、LTSベンダーを利用できない人を助けるためです。対象は7.4、8.0、8.1です。

php.netはこのバックポートを2件の脆弱性の深刻度と結びつけており、サポート終了日が変わるわけではありません。8.1は2025年12月31日に、8.0は2023年11月26日に、7.4は2022年11月28日にサポート終了を迎えています。8.2ブランチは2026年12月31日までセキュリティ修正を受け、8.2.35のアナウンスはサポート対象のバージョンへの移行を計画するよう求めています。ステップ1の洗い出しで8.2以前が見つかったら、パッチで今回のアドバイザリには対応でき、次の課題は移行です。

今週確認すること

  • すべてのサーバーとイメージにあるすべてのPHP:CLI、FPM、php-cgi、lsphp、FrankenPHP、mod_phpと、それぞれが報告するバージョン。
  • そのうちWebリクエストを処理しているもの。まずmod_phpから。
  • インストール済みのパッケージのバージョンが、CVE-2026-109618とCVE-2026-109619に関するベンダーのアドバイザリと一致しているか。
  • 削除されたPHPのバイナリやライブラリをまだマップしているプロセスがないか。
  • 各FPMのpoolが、socket経由で問い合わせたときに修正済みのバージョンを報告するか。
  • どの一時的な対策を、どこに適用し、いつ外すか。session.upload_progress.enabled=0は次のリリースまで残します。
  • 8.2以前を動かしているサーバーと、その移行予定日。

リポジトリやディスク上のパッケージは一つの段階であって、結果ではありません。CVE-2026-109618とCVE-2026-109619について意味のある確認は、稼働中の各PHPプロセスが修正済みのバージョンを報告することです。

これらの確認をご自身のサーバーやパイプラインにどう適用するか相談したい場合は、お問い合わせページからご連絡ください。メッセージには、この記事と想定している環境を書き、認証情報、トークン、機密の運用資料は含めないでください。

出典一覧

出典は2026年10月25日に参照し、執筆前にマドリード時間10:42にphp.netを再確認しました。Debian 13上でphp8.4-cliとphp8.4-fpm 8.4.27-1~deb13u1を使って実行し、出力を保存しました:洗い出し、パッケージの照会、バイナリ置き換え前後のプロセス確認、デフォルトのpoolと一時的な対策を入れたpoolでのFPMへの問い合わせ、設定テストです。実行していないもの:systemctl(テスト用マシンにsystemdがないため)、mod_phpを使うApache、FrankenPHP、LiteSpeed、RPMベースのシステム、Windows。

この内容をローンチやワークフローに適用したいですか?

技術レビューを依頼し、最も安全な第一歩を整理しましょう。

関連記事