flawopen.com/Teardowns/cve-2026-87902-wordpress-page-template-path-traversal
CVE-2026-87902:WordPressページテンプレートのパストラバーサル
未検証のデコード後ページ名がテンプレート解決に使われ、未認証でローカルPHPを読み込める状態でした。RCEにはテーマとPHP環境の特定条件が必要です。[1]
本を取る前に依頼を確認する図書館員を想像してください。WordPressはテンプレートへの一つの経路を確認しましたが、デコード後の名前の経路を見落としました。特殊な表記がテーマ外へ導く可能性がありました。修正では名前を検証し、許可されたディレクトリ内に制限します。[1], [2], [3]
主要な概念と専門用語
パストラバーサル- 入力を検証しないと、
..などの要素で意図したディレクトリから出られる場合があります。[1] ローカルPHP読み込み- サーバー上にあるPHPファイルを読み込みます。RCEには追加条件が必要です。[1]
`validate_file()`- 使用前に許可されないパス要素を拒否するWordPress関数です。[3]
フレームワーク対応
- このガイドで説明する弱点の CWE 参照: CWE-98
- CISA KEV — 追加日 2026-09-25 — CVE-2026-87902
根本原因の分析 (Root Cause)
get_page_template() は pagename をデコードして page-{name}.php を候補に加えますが、脆弱版では validate_file() を適用せず、隣接する分岐では適用していました。そのためテーマ外のパスを解決する可能性がありました。page-* というトップレベルディレクトリと読み取り可能なローカルPHPが必要で、RCEにはさらにPHP/サーバー条件が必要です。7.1.2はデコード値を検証し、locate_template()で解決パスを制限します。 WordPressのアドバイザリはこの弱点をCWE-98(PHPのinclude/requireで使うファイル名の不適切な制御)に分類し、発見と責任ある開示をRobert Resslに帰属させています。[1], [2], [3], [7]
CVE-2026-87902 の到達条件:影響範囲と条件付き RCE
| 導入条件 | 一次資料が確認している内容 | 運用上の対応 |
|---|---|---|
| 影響を受ける WordPress Core のブランチ | GHSA は影響を受けるバージョンをブランチごとに示しており、4.7.0–4.7.36 から 7.1.0–7.1.1 までが該当します。対応する修正版は 4.7.37(最も古いバックポート)から 7.1.2(現行ブランチ)までで、記載された各ブランチに個別の修正版があります。 | インストール済み Core のバージョンを確認し、該当ブランチの修正版を適用します。4.7 より前のバージョンはサポート対象へ更新します。 |
| 有効なテーマの構成 | 有効な親テーマまたは子テーマに、名前が page- で始まる最上位ディレクトリ(例: page-templates)が必要です。 | 有効な親テーマと子テーマの両方を確認します。有効なテーマを変更した後もこの条件を再確認します。 |
| 読み取り可能なローカル PHP ファイル | 選択対象の .php ファイルが有効なテーマのディレクトリ外に存在し、Web サーバーのアカウントから読み取り可能である必要があります。 | Web サーバーが読み取れるローカル PHP ファイルを確認します。CVE のバージョン範囲だけでファイル包含が起きたとは判断しません。 |
| 条件付き RCE の実行環境 | アドバイザリは追加の PHP/サーバー条件がある場合に限り RCE を説明しています。例は register_argc_argv=On の pearcmd.php、公式 PHP Docker イメージ、PHP 8.5 より前を使う既定の cPanel 構成です。 | 実際の PHP ランタイムと設定を評価します。WordPress の影響バージョンだけでは RCE や侵害を証明できません。 |
ステップ・バイ・ステップの攻撃フロー
ソースコード比較:脆弱 vs 堅牢化
この抜粋は公式7.1.1と7.1.2のタグを比較します。修正ではデコード後に検証し、同じリリースでlocate_template()の解決パスも制限します。これは上流のソース変更であり、単独の完全なパッチや攻撃の実演ではありません。[2], [3]
ソース抜粋: wp-includes/template.php — get_page_template()
if ( $pagename ) {
$pagename_decoded = urldecode( $pagename );
if ( $pagename_decoded !== $pagename ) {
$templates[] = "page-{$pagename_decoded}.php";
}
}
if ( $pagename ) {
$pagename_decoded = urldecode( $pagename );
if ( $pagename_decoded !== $pagename && 0 === validate_file( $pagename_decoded ) ) {
$templates[] = "page-{$pagename_decoded}.php";
}
}