
CVE-2026-87902(認証不要のWordPressパストラバーサル)向けの検出ツールキットと再現可能なラボ。リモートチェッカー、IoCアナライザー、Sigmaルール、Dockerテストベンチを含む。
検出ツールと再現可能なテストベンチ GHSA-7hp8-65ch-5whp / CVE-2026-87902 — page-template 解決における未認証パストラバーサルによる条件付き RCE(WordPress、CWE-98、CVSS 4.0:9.2)。
check/ | リモートコントローラー、パッシブ、サーバーアクセス不要 |
detect/ | IoC アナライザー + Sigma ルール |
offensive/ | トレース生成器、実ログでの検出を検証するため |
docker-compose.yml + provision/ | テストベンチ、3つの構成 |
tests/validate.py | 品質ゲート — すべての公開を条件付ける |
docs/ANALYSIS.md | 脆弱性、修正、測定された到達可能性分析 |
make up # ベンチを起動 make ioc # 攻撃コーパス -> ログ -> 検出
make scan # ベンチを検査 make test # 品質ゲート
127.0.0.1 上の3つの WordPress インスタンス。それぞれが判定の各要因を分離する。
8092 が最も示唆に富む:8091 と同じ脆弱なコアだが、テーマの前提条件が欠けている。これにより、バージョンのみに基づくトリアージが露出を過大評価することが分かる。8091 と 8092 の間の唯一の変数はテーマ、8091 と 8093 の間の唯一の変数は修正である:3つとも同じカスタム投稿タイプ(provision/mu-plugins/00-lab-cpt.php)と同じリクエスト状態プローブ(10-lab-debug.php、is_page()、ローダーが見た pagename の値、最終的にインクルードされたテンプレートを公開する X-Lab-* ヘッダー)を搭載している。
make up # 起動とプロビジョニング — 冪等、再実行可能
make status # 各インスタンスのバージョン
make down # 停止 make clean : ボリュームも削除
公式 Docker イメージは /var/log/apache2/access.log を /dev/stdout にポイントする:ログはファイルではなくコンテナの出力に流れる。
docker compose logs --no-log-prefix vuln-pre # アクセス + エラー
docker compose logs --no-log-prefix vuln-pre > access.log # 分析用
docker compose logs -f --no-log-prefix vuln-pre # ライブ
make logs # 3インスタンスすべて
通常のサーバーでは:/var/log/apache2/access.log、/var/log/nginx/access.log、またはほとんどの共有ホスティングでは /home/*/logs/。フォーマットにはクエリ文字列が含まれなければならない — %r や combined フォーマットには含まれるが、%U で構築された LogFormat では失われ、それなしでは検出は不可能である。
check/wp-ghsa-7hp8-check.pyインターネットから、サーバーアクセスなしで。ペイロードなし、トラバーサルなし、書き込みなし。 各ホストについて:WordPress 検出、5つのソース(meta generator、RSS フィード、wp-links-opml.php、readme.html、コアリソースの ?ver=)で照合したバージョン、有効テーマ、page-* ディレクトリのプローブ。
python3 check/wp-ghsa-7hp8-check.py --hosts-file hosts.txt --csv out.csv --json out.json
「AFFECTED」は脆弱なコードが存在することを意味するのであり、攻撃者がコードを実行できることを意味しない。docs/ANALYSIS.md を参照。
--transport browser(デフォルト)はインストールされた Google Chrome を操作する;サブリクエストはページ内で実行される fetch() から発行され、その TLS スタック、HTTP/2 ヘッダー順序、Cookie を継承するため、URL が読まれる前に CDN でフィルタリングされるのを避けられる。--transport direct は標準ライブラリのみを使用する。--scheme http|https は https → http フォールバックを避ける。これがないと、ターゲットのログに生の TLS ClientHello を含む 400 行が残る。
各リクエストは JSONL ログにミリ秒単位でタイムスタンプされる:セッション ID、リクエスト番号、フェーズ、URL、ステータス、サイズ、所要時間、送信元 IP、マーカー。マーカー SECAUDIT/<nonce> は X-Security-Audit ヘッダーおよび User-Agent のサフィックスとして送信される — 追加され、決して置換されないため、ブラウザ署名を壊すことなく標準アクセスログで可視となる。--marker でカスタマイズ可能。
リモートオラクルなし。
--probe-inclusionオプションは不活性ターゲット(wp-includes/version.php、ブートストラップで既にロード済み:require_onceは完全な no-op となる)に対して差分比較を行う。標準インストールでは 脆弱なコアでもNOT_REACHABLEを返し、修正済みと未修正のインスタンス間でバイト単位で同一の応答となる。 これはツールの限界ではない:WordPress はテンプレート階層を参照する前に 404 を返す。数値による実証はdocs/ANALYSIS.mdのセクション3にある。
pagename=.*%2e%2e%2f のようなリテラル正規表現は、大文字小文字の変更(%2E)、もう一度のエンコード(%252e)、またはリテラルとエンコードの混在(templates/..%2f../)で回避される。あらゆるパターンリストは構造的に不完全である。
したがって、攻撃者の記述ではなくコードから出発する:
pagename はディスクに到達する前に最大2回のデコードを受ける — クエリ文字列に対する PHP のデコード、次に get_page_template() の明示的な urldecode()。file_exists() に渡されるパスに .. コンポーネントが含まれなければならない。Linux では、親ディレクトリは正確に2バイト 0x2E 0x2E で書かれる;ファイルシステムレベルで他の表現は存在しない。したがって:不動点までデコードし、各レベルでテストする。これは WordPress が行うことの厳密なスーパーセットである — エンコード層を追加しても、マッチが1レベル移動するだけで、それも通過する。
python3 detect/wp-ghsa-7hp8-ioc.py access.log
docker compose logs --no-log-prefix vuln-pre | python3 detect/wp-ghsa-7hp8-ioc.py -
Sigma ルールは detect/sigma-wp-ghsa-7hp8.yml にある。Sigma は再帰的にデコードできないため、レベル0から3を列挙する:これは容認された近似であり、一次トリアージ用である。マッチをアナライザーに戻して判断する。
WP::parse_request() は $_GET の前に $_POST を読む。したがって pagename はリクエストボディで到着する可能性があり、どのアクセスログにも存在しない。WAF または ModSecurity レベルでのボディに対するカバレッジが必要。アクセスログに関するルールは最初の点をカバーしない。これはサポートの限界であり、ルールの限界ではない — しかし完全なカバレッジを宣言する前に知っておく必要がある。
offensive/generate-traces.py は同じペイロードの12の異なる記述(リテラル、シングル/ダブル/トリプルエンコード、大文字と小文字、混在、UTF-8 過剰エンコード、ドットスペース)に加えて、それらに似た7つの正当なリクエスト(ドット付きスラッグ、スラッグ内の wp-includes、日付パーマリンク、パーセントエンコード)を再生する。
何も得られない:標準インストールではこのベクターに対するリモートエクスプロイトは存在しない。トレースを生成する — それが唯一の目的である。
make ioc # コーパスを生成し、実ログを取得し、アナライザーを実行
期待値、そして実 Apache ログで make test により検証済み:
12ペイロード中12検出(11 CRITICAL、過剰エンコードは Linux で悪用不可のため MEDIUM)、7つの正当なリクエストで0アラート、そして3つの異なるデコードレベルでの実効検出。
ターゲットはローカルベンチに限定;それ以外は --i-have-authorization が必要。
php.ini で register_argc_argv = Off;未使用なら PEAR をアンインストール — これは勧告が引用するインクルージョン → 実行のピボットである。open_basedir をサイトルートに限定:すべてのローカルインクルージョンを封じ込める。make test は公開の条件である:勧告の25ブランチのマトリクス(比較の落とし穴を含む — 数値的に 6.8.9 < 6.8.10、プレリリース、マトリクス外のブランチ)、3インスタンスに対するコントローラー、実ログで検証された IoC ルール。プローブの NOT_REACHABLE アサーションは意図的に固定されている:これが壊れた場合、動作が変化したのであり、分析をやり直す必要がある。
責任を持つ資産、または書面による委任の下でのみ使用すること。ベンチは 127.0.0.1 でのみ待ち受ける;脆弱なインスタンスは決して公開してはならない。プローブ 10-lab-debug.php はサーバーパスを漏洩する:ベンチ専用である。
| ポート | インスタンス | コア | 有効テーマ | 期待される判定 |
|---|
| 8091 | vuln-pre | 6.8.1 — 未修正 | Twenty Twelve、page-templates/ あり | AFFECTED_PRECONDITION_MET |
| 8092 | vuln-nopre | 6.8.1 — 未修正 | Twenty Twenty-Four、page-* なし | AFFECTED_CORE_ONLY |
| 8093 | patched | 6.8.10 — 修正済み | Twenty Twelve、page-templates/ あり | NOT_AFFECTED |
| 判定 | 意味 |
|---|
AFFECTED_PRECONDITION_MET | 脆弱なコア かつ 有効テーマに page-* ディレクトリ。優先。 |
AFFECTED_THEME_UNKNOWN | 脆弱なコア、テーマ未確定。 |
AFFECTED_CORE_ONLY | 脆弱なコア、テーマ前提条件なし。それでも修正すべき。 |
VERSION_UNKNOWN | WordPress 検出、バージョン非表示。 |
NOT_AFFECTED | バージョン ≥ そのブランチの修正版。 |
| ルール | 重大度 | トリガー |
|---|
GHSA-7hp8-traversal-pagename | CRITICAL | pagename 内の .. コンポーネント、任意のデコードレベル |
GHSA-7hp8-traversal-param | HIGH | 他のパラメータ内の同じプリミティブ(テーマや拡張機能も locate_template() を呼び出す) |
GHSA-7hp8-traversal-path | HIGH | URL パス内の .. コンポーネント(nginx は %2f を通過させるが、Apache はデフォルトで通過させない) |
GHSA-7hp8-overlong-encoding | MEDIUM | UTF-8 過剰エンコード(%c0%ae)。Linux では無効だが、決して正当ではない |
GHSA-7hp8-theme-page-dir-probe | LOW | テーマの page-* ディレクトリのプローブ — 偵察 |