CVE-2026-63030 · CVE-2026-60137

WordPress REST API Batch Route Confusion & Unauthenticated RCE Chain ("WP2Shell")

CVSS 9.8 Critical Patched Exploit Active CWE CWE-436 GHSA GHSA-ff9f-jf42-662q
TL;DR

WordPress 6.9で導入されたREST APIバッチエンドポイント(/wp-json/batch/v1)におけるルート混同(route confusion)の脆弱性。検証ループのインデックスズレにより、あるリクエストが別のリクエストのハンドラで実行される。CVE-2026-60137(SQLi)と連鎖することで、認証なしのRemote Code Executionに至る。2026年7月17日に公開され、同日中に実攻撃が観測され始めた。影響バージョン(6.9.0〜7.0.1)は即時のアップデートが必要。

タイムライン


2025-11 (WP 5.6)
REST APIバッチフレームワーク導入。/wp-json/batch/v1 エンドポイントがデフォルトで有効化。
2025-12 (WP 6.9)
脆弱なコード導入。バッチ処理の制御フローにルート混同の脆弱性が混入。
2026-07-17
脆弱性公開。Adam Kues (Searchlight Cyber) がHackerOne経由で報告。WordPressが6.9.5、7.0.2をリリース。強制自動アップデート有効化。Searchlightがwp2shell.comで公開チェッカーを公開。
2026-07-17 17:03 UTC
WAFルールデプロイ。Cloudflareが全プラン向けに2つの新WAFルールを自動デプロイ。
2026-07-17 ~19:00 ET
実攻撃の観測開始。PatchStackが攻撃を観測報告。
2026-07-17
Wordfence対応。Premiumユーザー向けにファイアウォールルールを即時配信。

根本原因


wp-includes/rest-api/class-wp-rest-server.phpserve_batch_request_v1() メソッド内、バッチ検証ループの制御フローに脆弱性があった。不正なpathを含むサブスクリプトがWP_Errorとして処理される際、$matches配列への追加が省略され、$matches配列と$validation配列のインデックスがズレた。

インデックスズレのメカニズム

wp-includes/rest-api/class-wp-rest-server.php — 脆弱な検証ループ
foreach ( $requests as $i => $single_request ) {
if ( is_wp_error( $single_request ) ) {
$has_error = true;
- $validation[] = $single_request;
- // ⚠️ $matches への追加が欠落 → インデックスズレ
continue;
}
// ... ルートマッチング ...
$matches[] = $handler;
$validation[] = $validation_result;
}

実行ループでは $matches[$i] を参照するため、インデックスが1つズレた状態で別ハンドラが実行される:

インデックスズレの例
requests: [error, request A, request B]
validation: [error, result A, result B]
- $matches: [match A, match B] ← index 0欠落
+ (修正後) $matches: [error, match A, match B] ← ダミーで維持
i=1: request A → $matches[1] = match B (本来はrequest B用!)
i=2: request B → $matches[2] = out of bounds

修正後の実装

修正コミット fa72c12 では、$matchesにもダミーエントリーを追加してインデックスの整合性を維持。さらに、rest_api_loaded()WP_REST_Server::serve_request()is_dispatching() チェックを追加し、バッチ処理中に新たなRESTサイクルが開始されることを防ぐ。

wp-includes/rest-api/class-wp-rest-server.php — 修正後のコード
if ( is_wp_error( $single_request ) ) {
$has_error = true;
+ $matches[] = $single_request;
$validation[] = $single_request;
continue;
}
wp-includes/rest-api.php — 追加の防御層
// Short-circuit before define()/die() if a REST dispatch is already in flight.
+ if ( isset( $GLOBALS['wp_rest_server'] )
+ && $GLOBALS['wp_rest_server'] instanceof WP_REST_Server
+ && $GLOBALS['wp_rest_server']->is_dispatching()
+ ) {
+ return;
+ }

攻撃フロー(47Cid/wp2shell-lab の分析に基づく)


1
攻撃者が POST /wp-json/batch/v1 に3つのリクエストを送信(うち1つは不正path)
2
Loop 1 (validate): 不正path → WP_Error(matchesに追加されずインデックスズレ)
3
Loop 2 (dispatch): postsリクエストがbatch_handlerで処理 → ネストバッチとして再帰
4
ネストバッチ: categoriesリクエストがposts_handlerで処理 → author_excludeがサニタイズされずWP_Queryに到達
5
CVE-2026-60137: author__not_in経由でSQLインジェクション実行
6
MySQLから管理者パスワードハッシュ窃取 → クラック → ログイン → プラグインアップロード → RCE
⚠️ 重要な技術的ポイント: バッチエンドポイントはWordPress 5.6(2020年11月)から存在するが、ルート混同は6.9(2025年12月)で導入された。Cloudflareの分析によれば、永続オブジェクトキャッシュが無効な環境でRCEパスに到達可能(デフォルト設定)。respond_to_request()は依然としてpermission_callbackを呼び出すため、この脆弱性は「認証バイパス」ではなく「ルート混同」— 公開読み取り可能なエンドポイントを経由してSQLiに到達する。

影響バージョン


バージョンブランチ 影響範囲 修正バージョン 影響
WordPress 6.9.x 6.9.0 – 6.9.4 6.9.5 RCE chain
WordPress 7.0.x 7.0.0 – 7.0.1 7.0.2 RCE chain
WordPress 7.1 beta 7.1 beta1 7.1 beta2 RCE chain
WordPress < 6.9 影響なし(バッチルート混同は6.9で導入)

PoC / Exploit 状況


公開PoCあり — SQLi検証PoCが複数公開済み、完全RCEチェーンは未公開
教育用ラボあり — 47Cid/wp2shell-lab (root cause analysis + Docker lab)
PoCリポジトリIcex0/wp2shell-poc, Senanfurkan/wordpress-cve-2026-63030, attackercan/wp2shell-poc2
Exploit成熟度PoC (SQLi確認済み) → 完全RCEチェーンは未公開
実利用PatchStackが2026-07-17に攻撃を観測報告(詳細不明)
チェックツールSearchlight Cyber が wp2shell.com で公開

検出方法


ブラックボックステスト

バッチエンドポイント到達性確認
# テストA: 標準RESTパス
curl -sX POST "https://target.com/wp-json/batch/v1" \
-H "Content-Type: application/json" \
-d '{"requests":[]}'
# 期待応答: {"code":"rest_missing_callback_param","message":...}
# テストB: クエリパラメータパス
curl -sX POST "https://target.com/?rest_route=/batch/v1" \
-H "Content-Type: application/json" \
-d '{"requests":[]}'

ホワイトボックステスト

修正確認
# is_dispatching() チェックの存在確認
grep -n "is_dispatching" wp-includes/rest-api.php
grep -n "is_dispatching" wp-includes/rest-api/class-wp-rest-server.php
# $matches[] = $single_request の存在確認
grep -n 'matches\[\].*single_request' wp-includes/rest-api/class-wp-rest-server.php
# 結果が存在すれば修正済み

WAFルール (Cloudflare)

ルールManaged Ruleset IDFree Ruleset IDアクション
WordPress - Unauthenticated RCE7dfb2bd4...ebd3f2df...Block

対策


WordPressを修正バージョンにアップデート(6.9.5 / 7.0.2) Critical
Cloudflare WAFルールの有効化を確認(無料プラン含む) High
Wordfenceファイアウォールルールの適用を確認(Premiumは即時、Freeは8/16) High
パッチ適用不可の場合、/wp-json/batch/v1?rest_route=/batch/v1 をWAFでブロック Critical
データベースアクセスログの監視 — 不審なSQLクエリパターンをアラート設定 Medium

参考資料