グローバル状態の不整合によるリモートコード実行の脆弱性。
## 概要
PHPには、エラー、セッション、オブジェクト、拡張機能の状態など、様々なグローバルな状態が存在します。これらの状態は、ワーカーによって保持されますが、特定の要求に関連するメモリを指しています。この状態は、要求に関連するメモリが解放される前にリセットされる必要があります。一部の入力、設定、セッション、およびエラー処理操作は、次の要求の通常の初期化の前に実行されます。これらの操作は、前の要求によってリセットされなかった古い状態に誤ってアクセスし、初期化がその状態を置き換える前に、メモリ解放後のアクセス(use-after-free)を引き起こす可能性があります。巧妙に設計されたヒープの配置により、リモートコード実行(RCE)が可能になる場合があります。
状態が維持される理由はいくつかあります。致命的なエラー、特にメモリ不足によるエラーは、対応するリセット処理が実行される前に操作を中断する可能性があります。警告やその他の非致命的な診断メッセージも、シャットダウン中に状態がクリアされた後に、エラー状態を再設定する可能性があります。その結果、要求のヒープメモリが解放される一方で、ワーカーのグローバルフィールドがそのメモリを依然として参照している状態になります。その後、通常の要求初期化の前に実行される操作が、この古い値を消費する可能性があります。
この脆弱性を悪用するには、プロデューサーとコンシューマーの両方が必要です。プロデューサーは、PHPの操作であり、ある要求が所有する状態を、PHPが後続の要求でアクセスできる場所に保存します。コンシューマーは、後続のPHP操作であり、プロデューサーによって以前に保存された古い状態を読み取ったり、解放したり、分岐したり、または何らかの方法で操作したりします。
具体的な脆弱性は、以下の条件が満たされた場合に発生する可能性があります。
1. 実行可能なPHP操作が、要求が所有する状態を、グローバルにアクセス可能な場所に保存し、プロデューサーとして機能します。
2. その状態がリセットされない、または別の操作によって、通常の初期化後に再設定されます。
3. PHPが、要求に関連するメモリを解放する一方で、永続的なワーカーが、その状態を参照するグローバル変数を保持し続けます。
4. 同じワーカーが、別の要求を処理します。
5. PHP操作が、通常の要求初期化によってリセットされる前に、古い状態を読み取ったり、解放したり、分岐したり、または何らかの方法で操作し、コンシューマーとして機能します。
同一のワーカーによって処理される異なるHTTPリクエストにおいて、データの生成と消費が同時に発生する状況が確認されています。影響を受ける具体的なバージョン、設定、および対策は、生成操作によって影響を受ける内部状態に依存します。
## 脆弱性
### 生成元
生成操作が危険になるのは、リクエストに関連付けられた状態が、リクエストのメモリ領域が解放された後も公開されたままになっている場合です。致命的なエラーによって、操作が完了する前に状態がリセットされずに中断されることがあります。致命的な中断には、解析またはコンパイルの失敗、未キャッチのエラー、`require`の失敗、および「最大実行時間超過」が含まれます。
メモリ不足(OOM)も、影響を受けるエラー、例外、オブジェクト、または拡張の状態が既に公開されている場合に、同様の中断を引き起こす可能性があります。
さらに、後続のシャットダウン段階における拡張機能またはサーバーのクリーンアップ中に発生する、致命的でない診断情報によっても、新しいリクエストに関連付けられたエラー情報がグローバルな状態に再登録される可能性があります。
### 消費元
通常の初期化プロセス以前に、確認されている消費元は2つあります。
#### リクエスト初期化前のINI診断
初期化中に発生する警告、通知、または非推奨メッセージは、古いエラー状態を消費する可能性があります。これは、必ずしも致命的なものではなく、表示される必要もログに記録される必要もありません。このような警告は、通常の実行エンジン初期化の前に、無効または非推奨のINI設定が適用された場合に発生する可能性があります。例としては、以下のようなものがあります。
- `auto_detect_line_endings=1`、デフォルト以外の`filter.default`の値、PHP 8.3以降のレガシー`assert.*`設定、PHP 8.4以降のレガシーセッション設定、およびPHP 8.5の`report_memleaks=0`またはゼロ以外の`intl.error_level`など、決定論的な非推奨設定。
- 明示的に設定された`mbstring.http_input`、`mbstring.http_output`、`mbstring.internal_encoding`、`iconv.input_encoding`、`iconv.output_encoding`、または`iconv.internal_encoding`。
- 無効または競合する`zend.assertions`、`memory_limit`、`date.timezone`、DBA、EXIF、mail、OpenSSL、zlib、およびロードされた拡張機能の値。
- 形式が不正またはオーバーフローしている値、および無効な`memory_limit`、ボディサイズ、入力、タイムアウト、スタックなど。
PHP 8.2 以降の拡張機能の値。
- `open_basedir` で制限された、ログ、メール、セッション、または SOAP キャッシュのパスが設定されている場合、拒否されます。
グローバル変数は、PHP が警告を報告、表示、またはログに記録するかどうかを決定する前にアクセスされます。 `error_reporting` の変更、`@` 記号の使用、または `display_errors` または `log_errors` の無効化は、この脆弱性から保護しません。
##### アクセス可能性
Apache mod_php の場合、`.htaccess` ファイルおよび Apache の `php_value`/`php_flag`/`php_admin_*` ディレクトリ設定は、`php_request_startup()` の前に適用されます。 通常の `php.ini` ファイルは、起動時の設定であり、リクエストごとに再適用されることはありません。
FPM は、nginx、Apache の `mod_proxy_fcgi`、またはその他のフロントエンドが、リクエストの初期化前に診断情報を生成する `PHP_VALUE`/`PHP_ADMIN_VALUE` パラメータを送信する場合に、条件付きで公開されます。 FPM プールの値は、ワーカーが起動したときに適用され、`.user.ini` ファイルは通常の初期化後に適用されます。 これらは、同じように早期に適用されるわけではありません。 また、FPM は、`php_request_startup()` が失敗した場合、子プロセスを終了します。
LSAPI は、特別な環境 INI 値を早期に適用することができ、条件付きで該当する可能性があります。 FrankenPHP のデフォルト設定では、同様の早期設定適用メカニズムは確立されていません。 これは、この環境が完全に安全であることを意味するわけではありません。 アプリケーションまたは拡張機能の動作によって、異なるメカニズムが公開される可能性があります。
#### 通常の初期化前のセッションアップロードの進行状況
セッションアップロードの進行状況は、通常のセッション初期化よりも前に、セッション処理に到達する可能性があります。 そのため、中断されたリクエストによって残されたセッションまたはデコーダの状態が、通常の初期化によってリセットされる前に、消費される可能性があります。 このメカニズムは、INI ファイルによる診断パスとは独立しており、設定からの診断を必要としません。
## 具体的なケースと候補となるケース
### PHP 8.5 の致命的なエラーのトレースバック:確認済み
`fatal_error_backtraces` が有効になっている場合、致命的なエラーの報告は、リクエストが所有するトレースバックの状態を保存します。 このグローバル変数のリセットは、シャットダウン時に常に見過ごされます。 サブセ
ある特定の状況下では、通常のリクエストの初期化によってリセットされるはずの古い値が、別のリクエストによって先に参照されることがあります。
これは最も明確に示された事例であり、確認されたmod_phpの脆弱性の根拠となっています。
### セッションアップロードの進行状況のライフサイクル:確認済み
アップロード処理は、通常のモジュールの初期化よりも前に、リクエストが所有するセッションやデコードの状態を初期化する可能性があります。その状態が確立された後に、メモリ不足(OOM)やその他の致命的なエラーが発生すると、状態のリセットが正常に行われないことがあります。その結果、PHPはリクエストのメモリ領域を解放しますが、永続的なセッションやデコーダのグローバル変数などがそのメモリ領域を参照し続けることになります。その後、別のアップロードの進行状況処理が、初期化が行われる前にこれらのグローバル変数にアクセスし、それらを有効な状態として扱ってしまいます。
この脆弱性を引き起こす一般的な要因は、入力処理中に発生するメモリ不足(OOM)です。この段階で発生するその他の致命的なエラーも、同様の状態遷移を引き起こす可能性がありますが、通常は追加のアプリケーション、拡張機能、または設定条件が必要です。
mod_phpの場合、この問題を引き起こす操作と致命的なエラーは、multipartの開始時に発生する可能性があります。FPMは、この開始が失敗すると子プロセスを終了するため、正確なシーケンスは再現されません。検証されたFPMの経路では、正常に開始された後にアプリケーションの実行中に状態が確立され、その後、致命的なエラーが発生し、セッションアップロードの進行状況が早期に利用されることになります。これは、通常の動的なFPMプールで動作しますが、適切なアプリケーションの操作に依存します。
セッションアップロードの進行状況は、デフォルトで有効になっており、アプリケーションから`session_start()`を呼び出す必要はありません。これは、プロデューサ、コンシューマ、またはその両方を提供できますが、脆弱性の悪用には、状態が確立された後に中断が発生し、その後、同じワーカーによって処理される別のリクエストが必要になります。
### その他のエラー、例外、オブジェクト、および拡張機能の状態:条件付き
サポートされているPHPのバージョンには、リクエストの境界を超えて安全でなくなる可能性のある、他のリクエストの状態が含まれています。完全な修正プログラムは、これらの広範な状態をリセットします。なぜなら、これらの状態は、通常の初期化よりも前の操作で生き残ってはいけないからです。これらの状態にアクセスできるかどうかは、アプリケーション、拡張機能、
そして、その状態が公開された正確な箇所。
PHPの処理内で、アプリケーションにコールバックを行う操作で発生する致命的なエラーは、特に注意が必要です。コールバックによって、操作が一時的な状態を保持しているにもかかわらず、致命的なエラーが発生する可能性があります。適用可能性は、攻撃者が到達できる経路、そのアプリケーションまたは拡張機能によって登録されたコールバック、そして通常の要求初期化前の後続の処理に依存します。このようなすべての操作をカバーする、設定ベースの対策は存在しません。
## 対策
> [警告]
> 完全に状態をリセットする修正が含まれるバージョンにアップデートすることが、唯一の確実な対策です。**アプリケーション、拡張機能、またはサーバーのパスによっては、古い状態が公開されたり、予測可能なログエントリなしに状態が消費されたりする可能性があるため、完全な汎用的な設定による対策は存在しません。**
PHP 8.5のインストールで、アップデートできない場合は、管理者が`fatal_error_backtraces=0`を設定し、すべてのワーカーを再起動してください。これにより、確認されたリモートコード実行(RCE)のケースで使用されていた機能が無効になります。ただし、他のリクエストが所有するグローバル変数や拡張機能の状態は対象外です。
セッションのライフサイクルに関する問題については、管理者が`session.upload_progress.enabled=0`を設定し、すべてのワーカーを再起動してください。これにより、デモンストレーションされた、セッションの初期段階での処理と消費が防止されます。ただし、他のライフサイクルのパスは対象外です。この機能が不要な場合は、修正を適用した後でも`session.upload_progress.enabled=0`を設定することをお勧めします。これは、攻撃者がこのほとんど使用されず、ほとんどテストされていない機能の脆弱性を悪用しようとする可能性があるためです。
残りのリスクを軽減するために、可能な限り多くのライフサイクルのリンクを断ち切ってください。
1. 攻撃者が到達できるパスでの致命的なエラーを排除します。通常のページリクエスト中にのみ発生するエラーだけでなく、アップロード、インポート、エラーページ、シャットダウン、デストラクタ、シリアライゼーション、出力ハンドラ、プラグイン、拡張機能などのパスで発生する、リクエストの流れに依存するエラーも含まれます。
2. 通常のリクエスト終了後、サーバーのクリーンアップ時に発生する警告やその他の診断情報を排除します。
エラー状態はすでに解消されています。
3. リクエストの初期化前に適用されたすべてのINI設定から、不要な設定を削除してください。起動時に適用される`php.ini`、FPMプール、またはサーバー設定を、リクエストごとの設定よりも優先し、古い設定を削除してください。
4. パフォーマンスが問題にならない場合は、ワーカーが実行できるリクエスト数を`pm.max_requests=1`で制限することを検討してください。
PHPの実行時間制限は、他の致命的なエラーと同様に扱うべきです。攻撃者が制御するリクエストが、PHPが影響を受ける処理中に「最大実行時間を超過しました」というエラーに到達しないようにする必要があります。時間制限を変更するだけでは、その可能性のある中断を回避するだけであり、他の問題に対処するわけではありません。
### ログと設定の監査
隔離された監査中には、`log_errors=On`、機能的な`error_log`、および起動設定で`error_reporting=E_ALL`を有効にしてください。Apache、PHP-FPM、リバースプロキシ、およびサービスログを以下について確認してください。
- ターゲットスクリプトの実行前に繰り返される警告、非推奨、およびコアエラー。
- 拡張機能またはサーバーのクリーンアップ中に発生する警告およびその他の診断。
- 致命的なエラー、解析エラー、および未キャッチのエラーメッセージ。
- 「最大実行時間を超過しました」というメッセージ。
- 攻撃者が制御するルート、コンテンツタイプ、ファイル、コールバックの実装、またはプラグインオプションに限定されるエラー。
観察されたエラーを修正することで、脆弱性を軽減できますが、ログにエラーがないことは安全であることの証明ではありません。過去のログは、非推奨やその他の関連する重大度を除外して収集されている可能性があります。`E_ALL`を使用した監査でも、実際に実行されたルートとアプリケーションの状態しかカバーできません。また、PHPがメッセージを書き込む前にエラーが発生する可能性もあります。ログは、静的な設定とアプリケーションフローの監査を補完する必要があります。
**重要:** 関連するレポートからの修正に関するアドバイスも必ず確認してください。
https://github.com/php/php-src/security/advisories/GHSA-w8hw-w9fx-j79w
## 影響と信頼性
確認された影響は、認証されていないヒープの解放後の使用、メモリの漏洩、および除去です。
ウェブワーカーの権限でコードが実行される脆弱性。この脆弱性により、mod_phpに対してリモートコード実行(RCE)が可能であることが確認されました。セッションライフサイクルに関するRCEも、mod_phpとFPMに対して検証されました。その他の脆弱性については、具体的なプロデューサーとコンシューマーが特定された場合にのみ、セキュリティへの影響が評価されます。
プロデューサーとコンシューマーからのリクエストは、同じワーカーに到達する必要があります。複数のワーカーやリサイクルによって、リクエストごとの成功率は低下しますが、この条件は解消されません。
影響を受けるパッケージ:
- php php-src: 影響を受けるバージョンは8.2.35未満、8.2.35で修正済み
- php php-src: 影響を受けるバージョンは8.3.36未満、8.3.36で修正済み
- php php-src: 影響を受けるバージョンは8.4.27未満、8.4.27で修正済み
- php php-src: 影響を受けるバージョンは8.5.12未満、8.5.12で修正済み