WordPress Coreに、認証なしの第三者がコード実行に至る脆弱性「wp2shell」(CVE-2026-63030/CVE-2026-60137)が公表されました。プラグインもテーマも入れていない素のWordPressで、前提条件なしに成立します。影響バージョンと更新手順はすでに各所でまとまっています。この記事が扱うのは、その先で「対応したつもり」になりやすい落とし穴です。影響範囲内のバージョンでも /batch/v1 へのGETが404を返すため404を安全の証明と誤読してしまうこと。そして、Docker公式イメージを引き直しても直らないこと。以下はすべて実機のWordPressで実行し、出力を確認したものです。
2026年8月8日 追記 — この記事が「修正版」として案内していた 7.0.2 は、もう安全ラインではありません。
2026年8月6日に公開された WordPress 7.0.3 で12件のセキュリティ問題が修正され、そのうち1件がログイン画面の事前認証リフレクテッドXSS(CVE-2026-64638、CVSS v4.0で8.9)です。発見者のpwn.aiはこれを「XSS2Shell」と名付けています。この問題は 7.0.2 / 6.9.5 / 6.8.6 を含む従来のすべての系統に影響します。新しい安全ラインは 7.0.3 / 6.9.6 / 6.8.7 です(公式は4.7系まで遡って修正版を出しています)。
何をどう確認すればよいかは、次の章「7.0.2はもう安全ラインではない(CVE-2026-64638)」にまとめました。以降の章の記述も、この追記を踏まえて更新済みです。
30秒でわかるポイント
「バージョンを上げれば終わり」ではない人向けの記事です。
・安全ラインが動いた。2026年8月6日の 7.0.3 で、旧修正版 7.0.2 / 6.9.5 / 6.8.6 自体がログイン画面の事前認証XSS(CVE-2026-64638)の影響範囲になった。現在の安全ラインは 7.0.3 / 6.9.6 / 6.8.7
・バージョンが当てにならないなら、パッチそのものを見る。grep -qF 'esc_html( $username )' wp-includes/user.php が通れば修正済み。4.7.34 から 7.0.3 まで7系統のtarballで判定が一致することを実測済み
・GETの404は安全の証明にならない。/batch/v1 はPOST専用のため、影響範囲内の6.9.4でもGETは「存在しない経路」と同じ404を返す。curl -X OPTIONS でないと到達性を判定できない
・wordpress:latest は今この瞬間も脆弱。2026年8月8日の実測でコンテナ内は 7.0.2、wordpress:7.0.3 は manifest unknown。さらに wordpress:6.9 の中身は 6.9.4=wp2shellの影響範囲そのもの
・batch/v1 の遮断はCVE-2026-64638には効かない。露出しているのは wp-login.php であって、バッチ経路ではない
・更新だけでは終わらない。wp2shell側は公開エクスプロイトが出回り悪用の兆候も報告されている。更新前に侵入されていないかの確認が要る
なお、今回のようにソフトウェアそのものの取り込み経路が攻撃面になる問題を体系的に押さえたい場合は、サプライチェーンセキュリティ2026|攻撃手法・防御ツール・実践チェックリストを合わせて読んでください。本記事はそのうち「WordPressコアの脆弱性に対して、自分の環境をどう判定するか」に絞った実務編にあたります。
7.0.2はもう安全ラインではない(CVE-2026-64638)
2026年8月6日、WordPress 7.0.3 が公開されました。公式リリースノートによれば12件のセキュリティ問題が修正されており、その筆頭が「ログイン画面の事前認証リフレクテッドXSS、条件次第でPHPコード実行に至る可能性がある」(pwn.ai 報告)というものです。CVEは CVE-2026-64638、NVDのCVSS v4.0基本値は 8.9(HIGH)、CWEは CWE-79 です。
この記事にとって重要なのは、本記事が当初「修正版」として案内していた 7.0.2 / 6.9.5 / 6.8.6 が、今回の問題については未修正であるという点です。wp2shellへの対応として7.0.2へ上げた読者は、対応済みではありません。
何がどう変わったか
| 項目 | wp2shell(2026年7月) | XSS2Shell(2026年8月) |
|---|---|---|
| CVE | CVE-2026-63030 + CVE-2026-60137 | CVE-2026-64638 |
| 露出している場所 | REST API /batch/v1 |
wp-login.php(ログイン画面) |
| 管理者の関与 | 不要 | 必要(CVSS UI:A) |
| 攻撃条件の複雑さ | 低 | 高(CVSS AC:H) |
| 修正版 | 7.0.2 / 6.9.5 / 6.8.6 | 7.0.3 / 6.9.6 / 6.8.7 |
| batch/v1 の遮断 | 緩和策として有効 | 無効(経路が違う) |
緩和策を使い回さないでください。 本記事が後半で紹介している /wp-json/batch/v1 と ?rest_route=/batch/v1 の遮断は、wp2shell(CVE-2026-63030)に対する緩和策です。CVE-2026-64638にはまったく効きません。 今回露出しているのはログインフォームの処理であって、バッチ経路ではないためです。ログイン画面そのものを塞ぐことは通常できないので、この問題に対する実質的な対策は更新のみです。
仕組み:2つのサニタイザーが同じ文字列を別のものとして読む
技術的な核心は「パーサー差異(parser differential)」です。存在しないユーザー名でログインに失敗すると、WordPressはエラーメッセージを組み立てて画面に表示します。このとき文字列は2つの異なるサニタイザーを通過するのですが、その2つが「これはHTMLタグか?」の判定基準を共有していませんでした。
| 通過する処理 | 実体 | タグ判定の基準 |
|---|---|---|
wp_strip_all_tags() |
PHPの strip_tags() のラッパー |
< の直後が英字のときだけタグとみなす |
wp_kses_post() |
WordPress独自のKSESサニタイザー | より緩く解釈し、< とタグ名の間に空白があってもタグとみなす |
前段が「タグではない、ただの文字列だ」と判断して素通しした文字列を、後段が「これはタグだ」と解釈して要素として出力してしまう——これが今回の欠陥です。どちらの関数も単体では正しく動いており、組み合わせたときにだけ穴になるという点は、7月のwp2shell(CWE-436「解釈の不一致」)と同じ構造です。同じコードベースで、1か月のうちに2回、別の場所で同種の問題が顕在化したことになります。
本記事では、攻撃を成立させる具体的な文字列は扱いません。仕組みの理解と、自環境の判定・修正に必要な情報に絞ります。
修正の実体は esc_html() 3か所(tarball差分で確認)
公式配布物の 7.0.2 と 7.0.3 を両方ダウンロードして差分を取ったところ、今回のログイン画面XSSに対応する修正は wp-includes/user.php の3か所でした。
# 公式tarballを2つ落として差分を取る(読み取り専用・攻撃を含まない)
cd /tmp && for v in 7.0.2 7.0.3; do
curl -sO "https://wordpress.org/wordpress-$v.tar.gz" && tar xzf "wordpress-$v.tar.gz" && mv wordpress "wp-$v"
done
diff -u wp-7.0.2/wp-includes/user.php wp-7.0.3/wp-includes/user.php | grep -E "^[-+].*esc_html"
実測出力:
- $username
+ esc_html( $username )
- '<strong>' . $username . '</strong>'
+ '<strong>' . esc_html( $username ) . '</strong>'
- '<strong>' . $email . '</strong>'
+ '<strong>' . esc_html( $email ) . '</strong>'
いずれも wp_authenticate_username_password() および wp_authenticate_email_password() の中で、入力されたユーザー名/メールアドレスをエラーメッセージに埋め込んでいる箇所です。修正は「後段のサニタイザーがどう解釈するかに依存せず、埋め込む時点でHTMLとして無害化する」という方針になっています。
バージョンに依存しないパッチ判定
この修正内容が分かると、バージョン番号を見ずにパッチの有無を直接判定できます。 これはホスティング事業者やディストリビューションが独自にバックポートしている環境——wp core version の表示が古いままなのに修正は入っている、あるいはその逆——で効きます。
# WordPressのドキュメントルートで実行。修正が入っていれば PATCHED
grep -qF 'esc_html( $username )' wp-includes/user.php \
&& echo "PATCHED (CVE-2026-64638の修正あり)" \
|| echo "NOT PATCHED"
grep -F(固定文字列)を必ず付けてください。 パターンに $ と括弧が含まれるため、-F を省くと正規表現として解釈され、環境によって結果が変わります。検証中に実際に -F の有無で判定が食い違いました。
この判定が本当に全系統で機能するかを、公式tarballを7つ落として確認しました。
| バージョン | esc_html( $username ) の出現数 |
判定 |
|---|---|---|
| 4.7.34(最古のバックポート) | 1 | PATCHED |
| 6.8.6(wp2shellの修正版) | 0 | NOT PATCHED |
| 6.8.7 | 2 | PATCHED |
| 6.9.5(wp2shellの修正版) | 0 | NOT PATCHED |
| 6.9.6 | 2 | PATCHED |
| 7.0.2(wp2shellの修正版) | 0 | NOT PATCHED |
| 7.0.3 | 2 | PATCHED |
修正版と未修正版が例外なく分離できました。 4.7.34だけ出現数が1なのは、当時のコードには該当箇所が1か所しか無いためで、判定結果には影響しません。稼働中のコンテナに対して実行しても同じように動きます。
docker exec <コンテナ名> sh -c 'grep -qF "esc_html( \$username )" \
/var/www/html/wp-includes/user.php && echo PATCHED || echo "NOT PATCHED"'
wordpress:latest(中身は7.0.2)で実行した結果は NOT PATCHED でした。
PHPコード実行に至る条件は、wp2shellとは別物
ここは報道で最も圧縮されやすい部分なので、分けて書きます。XSS(ブラウザ上でのスクリプト実行)が成立する条件と、PHPコード実行に至る条件は違います。
・XSSまで:認証不要。ログインフォームを送信するだけで成立し、そのページ上で追加の操作は要らない
・PHPコード実行まで:ログイン中の管理者が、攻撃者の用意したページを開いて能動的に操作する必要がある。加えて下表の前提条件が揃っていること
この非対称性は、NVDのCVSS v4.0ベクタにそのまま表れています。
CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:A/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H
PR:N(権限不要)である一方、UI:A(User Interaction: Active=利用者の能動的な操作が必要)かつ AC:H(攻撃条件の複雑さが高い)です。採点した機関自身が「管理者の関与が要る」と認めていることになります。wp2shell(CVE-2026-63030)が管理者の関与なしに成立したのとは、危険度の性質が異なります。
とはいえ「管理者が1回リンクを踏めば成立しうる」は十分に危険で、優先度を下げる理由にはなりません。正確に理解したうえで、急いで更新するのが正しい対応です。
発見者(pwn.ai)が挙げている連鎖の前提条件は次の4つで、いずれもWordPressの既定値です。自環境の状態は以下のコマンドで確認できます(すべて読み取り専用)。
| 前提条件 | 既定 | 確認コマンド |
|---|---|---|
| 管理者がログイン中に攻撃者のページを操作する | — | (運用・教育の問題。技術的な確認は不可) |
| アプリケーションパスワードが利用可能 | HTTPSなら有効 | wp eval 'var_export( wp_is_application_passwords_available() );' |
管理者が unfiltered_html を持つ |
シングルサイトの管理者は保持 | 下記 |
管理者が install_plugins を持つ |
管理者ロールのみ保持 | 下記 |
# どのロールがプラグイン導入権限を持つか
wp eval 'foreach( wp_roles()->roles as $k=>$r ){
if( !empty($r["capabilities"]["install_plugins"]) ) echo $k, PHP_EOL; }'
検証環境(WordPress 7.0.2)での出力:
administrator
# 各ユーザーが実際に何を持っているか
wp eval 'foreach( get_users() as $u ){ echo $u->user_login, " => ",
( user_can($u,"unfiltered_html") ? "unfiltered_html YES" : "no" ), " / ",
( user_can($u,"install_plugins") ? "install_plugins YES" : "no" ), PHP_EOL; }'
admin => unfiltered_html YES / install_plugins YES
アプリケーションパスワードの確認結果は、サイトがHTTPSかどうかで変わります。 検証環境(HTTP)では false が返りましたが、これはコアの実装が is_ssl() || 'local' === wp_get_environment_type() を条件にしているためで、HTTPSで運用している通常のサイトでは true になります。「うちは false だったから前提条件を満たさない」と読み違えないでください。ローカル検証環境での結果は、本番の判断材料になりません。
前提条件を減らす方向の緩和策としては、次が現実的です。ただしいずれも更新の代わりにはなりません。
・日常作業を管理者アカウントで行わない。 記事執筆は編集者ロールで行い、管理者は必要なときだけ使う。連鎖は「管理者のセッションが生きていること」が起点になる
・wp-config.php に define( 'DISALLOW_FILE_MODS', true ); を入れる。 プラグイン・テーマの導入と編集を全面的に禁止する設定で、連鎖の最終段(ZIPアップロード)を塞ぐ。検証環境の既定値は false だった
・管理画面へのアクセス元IPを制限する(可能な環境のみ)
この問題には「遠隔から叩いて判定する方法」が無い
wp2shellでは curl -X OPTIONS でバッチ経路の到達性を確認できました。CVE-2026-64638には、これに相当する遠隔プローブがありません。 ログイン画面は修正版でも未修正版でも同じようにログインエラーを返すため、外形的な応答からは区別できないからです。
したがって判定手段は次の2つに限られます。どちらもサーバ上でファイルを読む必要があります。
wp core version(またはgrep wp_version wp-includes/version.php)で 7.0.3 / 6.9.6 / 6.8.7 以降かを見る- 上記の
grep -qF 'esc_html( $username )' wp-includes/user.phpでパッチそのものを見る
バックポート環境では 1 が誤解を招くことがあるため、2 を優先してください。
GETで叩いた404は「安全」の証明にならない
本記事をここから始めるのは、この誤読がいちばん多いと考えられるためです。 影響バージョンを知った人がまず試すのは、問題のエンドポイントに直接アクセスしてみることでしょう。ところが、その結果だけでは何も判定できません。
実機の影響範囲内であるWordPress 6.9.4に対してGETで叩いた結果:
curl -s -o /dev/null -w "%{http_code}\n" "https://example.com/?rest_route=/batch/v1"
404
そして、そもそも存在しない架空の経路に対して同じことをした結果:
curl -s "https://example.com/?rest_route=/nope/v1"
{"code":"rest_no_route","message":"No route was found matching the URL and request method.","data":{"status":404}}
区別がつきません。 バッチエンドポイントはPOSTのみを受け付けるため、GETでは「経路が無い」のと同じ応答を返します。ここで404を見て「うちにはbatch/v1が無い=安全」と結論づけるのが、最も危険な誤読です。
確実なのは OPTIONS です。
curl -s -X OPTIONS "https://example.com/?rest_route=/batch/v1"
影響範囲内の6.9.4での実測出力:
{"methods":["POST"],"endpoints":[...]}
HTTPステータスは200が返ります。methods に POST が含まれていれば、バッチエンドポイントは存在し、認証なしで到達できる状態です。
この200は「脆弱である」という判定ではありません。 ここで分かるのは「バッチAPIという経路が存在し、外部から到達できる」ことだけです。バッチAPIはWordPressの正規の機能なので、修正版の 7.0.2 や 6.9.5 でも、連鎖が成立しない 6.8系でも、同じように200を返します。 脆弱かどうかを決めるのは稼働バージョン(後述の「手順1:稼働バージョンを確定する」)であって、この応答ではありません。
このOPTIONSの使いどころは2つです。(a) GETの404を「安全」と誤読しないため。(b) WAFで遮断した場合に、その遮断が効いているかを確認するため(後述の「手順2:WAFでの遮断が効いているか確認する」)。「OPTIONSで200が返った=危ない」と早合点しないでください。 まず稼働バージョンを確認し、影響範囲に入っていなければ対応は不要です。
もう1つ、REST APIの経路一覧から確認する方法もあります。
curl -s "https://example.com/?rest_route=/" | \
python3 -c "import json,sys; print([r for r in json.load(sys.stdin).get('routes',{}) if 'batch' in r])"
['/batch/v1']
補足として、namespaces の一覧を見る方法は使えません。実機で確認したところ、6.9.4の namespaces は以下で、batch は含まれていませんでした。
['oembed/1.0', 'wp/v2', 'wp-site-health/v1', 'wp-block-editor/v1', 'wp-abilities/v1']
/batch/v1 はネームスペースではなくコアが直接登録する経路のため、ここには現れません。ネームスペース一覧を見て「batchが無いから安全」と判断するのも誤りです。
Docker公式イメージには、まだ修正版のタグが無い
Dockerで運用している場合、「イメージを最新にすれば直る」という前提が成り立たないことがあります。
本記事の検証時点(2026年7月20日 11:41 JST、および 同日 18:45 JST の再確認時点)で、Docker Hubの公式 wordpress イメージを確認した結果です。いずれの時点でも状況は同じでした。
for t in 7.0.2 7.0.1 6.9.5 6.9.4 6.8.6; do
code=$(curl -s -o /dev/null -w "%{http_code}" \
"https://hub.docker.com/v2/repositories/library/wordpress/tags/$t")
echo "$t -> HTTP $code"
done
実測出力:
7.0.2 -> HTTP 404
7.0.1 -> HTTP 200
6.9.5 -> HTTP 404
6.9.4 -> HTTP 200
6.8.6 -> HTTP 404
修正版のタグ(7.0.2 / 6.9.5 / 6.8.6)はいずれも存在せず、影響範囲内のタグだけが存在していました。 さらに latest を新規に取得して中身を確認すると:
docker pull wordpress:latest
docker run --rm --entrypoint grep wordpress:latest \
"wp_version =" /usr/src/wordpress/wp-includes/version.php
$wp_version = '7.0.1';
latest の中身は 7.0.1、すなわち影響範囲内でした。 docker pull wordpress:7.0.2 を試すと manifest unknown で失敗します。
この検証で分かったことが2つあります。1つは、Dockerユーザーは「イメージを引き直す」だけでは対処できないこと。当面はコンテナ内でWordPress自身の更新機能を使うか、バッチ経路の遮断で凌ぐ必要があります。もう1つは、ローカルにキャッシュされたイメージは平気で嘘をつくことです。筆者の環境では、キャッシュ済みの latest は 6.9.1 を指していました。docker rmi してから取得し直して初めて 7.0.1 と分かりました。必ず取得し直して中身を実測してください。
再確認(2026年7月20日 18:45 JST)では、タグの有無に加えてイメージの同一性も確かめました。latest と 7.0.1 の digest が一致していれば、latest の実体が 7.0.1 であることをpullせずに確認できます。
for t in latest 7.0.1; do
echo -n "$t -> "
curl -s "https://hub.docker.com/v2/repositories/library/wordpress/tags/$t" \
| python3 -c "import json,sys; d=json.load(sys.stdin); print(d['digest'], d['last_updated'])"
done
実測出力(digestが完全に一致しています):
latest -> sha256:d40b86dbdfcfad808a2029acf6543c670c4a61c29f70b9d24605e7d0b31ab83d 2026-07-16T21:19:25.712743Z
7.0.1 -> sha256:d40b86dbdfcfad808a2029acf6543c670c4a61c29f70b9d24605e7d0b31ab83d 2026-07-16T21:14:05.218064Z
last_updated が 2026年7月16日、つまり修正版がリリースされた7月17日より前である点にも注意してください。latest は修正版リリース後に一度も更新されていません。
この状況は公式イメージが更新されれば解消されます。この記事を読んだ時点では既に修正版タグが出ている可能性がありますので、上のコマンドで現在の状態をその場で確認してください。「記事にこう書いてあった」で判断しないことが、この種の確認の要点です。
3週間後に同じ検証をやり直した結果(2026年8月8日)
上の一文を書いた本人が、2026年8月8日 16:50 JST に同じコマンドを実行し直しました。 結果は「解消された」でも「変わらなかった」でもなく、その中間でした。
7.0.3 -> HTTP 404
7.0.2 -> HTTP 200
6.9.6 -> HTTP 404
6.9.5 -> HTTP 404
6.8.7 -> HTTP 404
6.8.6 -> HTTP 404
7.0.2 のタグは公開されました(7月20日時点では404でした)。一方で、8月6日に出たばかりの 7.0.3 はまだ存在せず、6.9系・6.8系の修正版タグは当時から一度も現れていません。
そして最も重要な点です。latest の中身をコンテナ内で実測しました。
docker pull wordpress:latest
docker run --rm --entrypoint grep wordpress:latest \
"wp_version =" /usr/src/wordpress/wp-includes/version.php
$wp_version = '7.0.2';
wordpress:latest の実体は 7.0.2、すなわち CVE-2026-64638 の影響範囲です。 前掲のパッチ判定を同じイメージに対して実行すると NOT PATCHED が返ります。docker pull wordpress:7.0.3 は manifest unknown で失敗します。
さらに、旧バージョン系統に固定している場合はもっと深刻です。
docker run --rm --entrypoint grep wordpress:6.9 \
"wp_version =" /usr/src/wordpress/wp-includes/version.php
$wp_version = '6.9.4';
wordpress:6.9 は 6.9.4 のままです。 これはwp2shell(事前認証RCE)の影響範囲そのものであり、修正版公開から3週間が経っても更新されていません。
この理由は、Docker公式イメージの定義ファイルを見ると分かります。
curl -s "https://raw.githubusercontent.com/docker-library/official-images/master/library/wordpress" \
| grep -cE "6\.9|6\.8"
0
定義ファイルに 6.9系・6.8系の記述はもう1つもありません。 現在ビルド対象となっているのは 7.0.2 系統(と cli、ベータの 7.1-RC1)だけです。つまり wordpress:6.9 や wordpress:6.8 は「古いが維持されているタグ」ではなく、過去のビルドが残っているだけで、今後修正版が来ることのないタグです。
wordpress:6.9 や wordpress:6.8 にピン留めしている場合、イメージの更新を待っても永久に直りません。 これらのタグはもう再ビルドされないため、docker pull を何度実行しても中身は変わりません。取れる選択肢は、①7.0系のイメージへ移行する、②コンテナ内でWordPress自身の更新機能を使う、③公式イメージをベースに自分で修正版を組み込んでビルドする、のいずれかです。
ここから引き出せる運用上の教訓は、7月時点の結論より一段強くなります。Docker公式イメージの更新は、WordPress本体のセキュリティリリースに連動していません。 7月20日は「修正版タグがまだ無い」でしたが、8月8日は「1つ前の修正版に追いついたところで、最新の修正版にはまた追いついていない」状態です。イメージの供給サイクルとセキュリティリリースのサイクルは別物であると前提を置いて運用設計してください。
wp2shellの時系列(Timeline)
日付は一次ソースの公表日に準拠しています。日本の読者向けに、日本時間(JST)とのズレが出る箇所は併記します。
| 日付 | 出来事 | 出典 |
|---|---|---|
| 2026-07-17 | WordPress 6.8.6 / 6.9.5 / 7.0.2 をセキュリティリリースとして公開 | WordPress公式(Releases・Security) |
| 2026-07-17 | CVE-2026-63030 / CVE-2026-60137 がNVDで公開 | NVD(Published 07/17/2026) |
| 2026-07-17 | Searchlight Cyber(Assetnote)が「wp2shell」として概要を公表。技術詳細は非公開と明言 | slcyber.io リサーチ記事 |
| 2026-07-18 | NVDのCVE情報が更新(Last Modified 07/18/2026)。海外メディアが一斉に報道 | NVD/BleepingComputer 他 |
| 2026-07-18以降 | GitHub上に複数のPoC(実証コード)が公開される | BleepingComputer |
| 2026-07-18以降 | watchTowrが「実環境での悪用の初期兆候」を報告 | BleepingComputer(watchTowr CEO Benjamin Harris のコメント) |
| 2026-07-19 | 日本のホスティング事業者(エックスサーバー等)がサーバ側対策と注意喚起を公表 | 各社の告知 |
| 2026-07-26 | pwn.ai がログイン画面のXSSを発見し、PHPコード実行までの連鎖を再現 | pwn.ai リサーチ記事 |
| 2026-07-27 | WordPressへ報告。同日リスクを承認 | pwn.ai リサーチ記事 |
| 2026-08-06 | WordPress 7.0.3 公開(セキュリティ修正12件)。同時に4.7系まで各系統へバックポート | WordPress公式リリースノート |
| 2026-08-07 | CVE-2026-64638 として協調公開。NVD登録(Published 08/07/2026 18:17 UTC) | NVD/pwn.ai |
| 2026-08-07 | 日本語では Security NEXT が速報を掲載(約200語の短報) | Security NEXT |
| 2026-08-08 | 本記事で再検証。wordpress:latest の実体が 7.0.2 のままであることを確認 |
本記事の実測 |
公表当初、Rapid7は「公に確認された実環境での悪用は把握していない」と記載していました。その後PoCが公開され、watchTowrが悪用の兆候を報告する、という順で状況が動いています。「最初は悪用なし」という初期の報道だけを見て判断すると、現状を読み違えます。
日付について1点補足します。リリースは米国時間7月17日ですが、日本時間では7月18日の未明〜午前にあたります。日本語記事によって「7月17日」と「7月18日」の表記が混在するのはこのためで、別の出来事ではありません。
「技術詳細は非公開」が意味していたこと
この時系列で運用上いちばん重要なのは、発見者が技術詳細を伏せていた期間が短かったという点です。
Searchlight Cyberは公表時に「脆弱性の深刻度を踏まえ、防御側が更新する時間を確保するため、現時点では技術的詳細を公開しない」と明記していました。これは責任ある開示として妥当な判断ですが、詳細非公開は「安全な猶予期間」ではありません。修正版のリリースそのものが手がかりになるためです。修正前後のコードの差分を比較すれば、どこが変わったかは誰でも確認できます。実際、パッチ公開の翌日以降にはGitHub上に複数のPoCが現れました。
つまり実務上の猶予は、公表からPoC出現までのごく短い期間しかなかったことになります。ここから引き出せる教訓は明快です。「詳細が出ていないから、悪用されるのはまだ先だろう」という判断は成り立ちません。深刻度の高いコアの脆弱性では、パッチ公開が実質的なカウントダウンの開始点だと考えて動く必要があります。
そしてこの記事を読んでいる時点は、既にPoCが出回った後です。更新の要否だけでなく、更新前に何かされていないかの確認までを一続きの作業として計画してください。
wp2shellで何が起きたのか(技術的要点)
技術的な核心は3行で言えます。
- WordPressのREST APIには
/batch/v1というバッチ処理用の経路がある。 複数のAPIリクエストを1回のPOSTでまとめて実行するための仕組みです。 - このバッチ経路が、リクエストの解釈を取り違える(CVE-2026-63030)。 NVDはこれをCWE-436「Interpretation Conflict(解釈の不一致)」に分類しています。バッチ経路の内側で組み立て直されたリクエストが、本来なら認証や権限チェックで弾かれるはずの経路に到達してしまう、という性質の欠陥です。
- その先に、
WP_Queryのauthor__not_inパラメータのSQLインジェクション(CVE-2026-60137)がある。 SQLインジェクションが成立すればデータベースの内容を操作でき、WordPressの構造上そこからコード実行につなげられます。
重要なのは「単体では大したことのない脆弱性が、経路の欠陥と繋がった瞬間に致命傷になる」という構図です。SQLインジェクション側は、単体では「プラグインやテーマが author__not_in に信頼できない入力を渡した場合に限り成立する」という条件付きの問題でした。NVDの記述もそうなっています。ところがバッチ経路の取り違えを経由すると、その条件を攻撃者側が自分で満たせてしまう。結果として、素のWordPressに対して認証なしで届くRCEになりました。
Searchlight Cyberは「攻撃には前提条件がなく、プラグインを入れていない素のWordPressに対して匿名ユーザーが実行できる」と記載しています。同時に、防御側が更新する時間を確保するため技術的詳細は公開しないとも明記しています。本記事も同じ方針で、攻撃の再現手順やPoCは扱いません。
(認証なし)"] --> B["POST /batch/v1
バッチ処理の入口"] B -->|"CVE-2026-63030
経路の取り違え
CWE-436"| C["本来は到達しないはずの
内部処理へ"] C -->|"CVE-2026-60137
author__not_in
CWE-89"| D["SQLインジェクション
成立"] D --> E["リモートコード実行
(RCE)"] style A fill:#7f1d1d,color:#fff style E fill:#7f1d1d,color:#fff style B fill:#1e3a5f,color:#fff style D fill:#78350f,color:#fff
深刻度スコアは評価機関で割れている
もう1つ、速報では触れられにくい点を挙げます。CVSSスコアは評価した機関によって食い違っており、しかも逆方向に割れています。
| CVE | WPScan(CNA)の評価 | CISA-ADPの評価 |
|---|---|---|
| CVE-2026-63030(バッチ経路) | 9.8 CRITICAL(AV:N/AC:L/PR:N/UI:N/C:H/I:H/A:H) | 7.5 HIGH(C:H/I:N/A:N) |
| CVE-2026-60137(SQLi) | 5.9 MEDIUM(AC:H/C:H/I:N/A:N) | 9.1 CRITICAL(AC:L/C:H/I:H/A:N) |
WPScanは「連鎖の入口である63030が致命的、SQLi単体は条件付きなので中程度」と評価し、CISA-ADPは逆に「SQLi側を高く、63030単体は情報漏洩どまり」と評価しています。どちらか一方の数字だけを引用すると印象が変わるため、本記事は両方を併記します。運用判断としては「連鎖として見れば緊急」で差し支えありません。
バッチAPIは繰り返し狙われる攻撃面
今回の欠陥をWordPress固有の不注意として片付けると、同じ種類の問題を次も見落とします。「複数のリクエストをまとめて処理する仕組み」は、構造的に取り違えを起こしやすいからです。
理由ははっきりしています。通常のWebリクエストは、Webサーバ → 認証 → 権限チェック → 処理という一本道を通ります。ところがバッチAPIは、1つの外側のリクエストの中身を、アプリケーション自身が複数の内側のリクエストへ組み立て直します。このとき内側のリクエストは、外側が通ったチェックを再現しなければなりません。再現の仕方が少しでもズレると、「外側では許可されない操作が、内側では許可される」という状態が生まれます。
NVDが今回の分類に選んだCWE-436「Interpretation Conflict」は、まさにこの種の欠陥を指す分類です。同じデータを、2つの層が違う意味に解釈することで生じる問題を表します。同種の攻撃面は、GraphQLのクエリバッチング、JSON-RPCのバッチリクエスト、各種APIゲートウェイの一括実行機能など、実装を問わず繰り返し現れています。
運用側の教訓としては、「普段誰も使っていない一括実行系のエンドポイントが、認証なしで外部に開いていないか」を棚卸ししておくことです。今回の /batch/v1 も、大半のサイトでは使われていないのに開いていました。使っていない入口は塞いでおくのが原則で、それは今回のような未知の欠陥に対する事前の緩和にもなります。
影響範囲マトリクス
自分がどの列に当てはまるかを確認してください。6.8系の扱いが、速報記事でいちばん誤解されやすい箇所です。
| 稼働バージョン | CVE-2026-63030 (バッチ経路) |
CVE-2026-60137 (SQLi) |
CVE-2026-64638 (ログイン画面XSS) |
必要な対応 |
|---|---|---|---|---|
| 7.0.0 – 7.0.1 | 影響あり | 影響あり | 影響あり | 7.0.3 へ更新(最優先) |
| 6.9.0 – 6.9.4 | 影響あり | 影響あり | 影響あり | 6.9.6 へ更新(最優先) |
| 6.8.0 – 6.8.5 | 影響なし | 影響あり | 影響あり | 6.8.7 へ更新 |
| 7.0.2(旧・修正版) | 修正済み | 修正済み | 影響あり | 7.0.3 へ更新 |
| 6.9.5(旧・修正版) | 修正済み | 修正済み | 影響あり | 6.9.6 へ更新 |
| 6.8.6(旧・修正版) | 該当なし | 修正済み | 影響あり | 6.8.7 へ更新 |
| 6.7以下(4.7まで) | 該当なし | — | 影響あり | 各系統の修正版へ更新(下記) |
| 6.8.7 / 6.9.6 / 7.0.3 以降 | 修正済み | 修正済み | 修正済み | 対応済み(更新の確認のみ) |
7.0.2 / 6.9.5 / 6.8.6 の3行が、7月時点の本記事では「対応済み」だった行です。 wp2shellへの対応としてこれらへ更新した場合、その対応自体は正しく、wp2shellの連鎖は成立しません。ただしCVE-2026-64638については未修正なので、もう一段の更新が必要です。
バックポートは4.7系まで24系統(公式APIで実測)
「4.7まで backport した」という公式の記述を、WordPress自身の更新APIで裏取りしました。
curl -s "https://api.wordpress.org/core/version-check/1.7/" \
| python3 -c "
import json,sys
offers=json.load(sys.stdin)['offers']
seen=[]
for o in offers:
c=o.get('current')
if c and c not in seen: seen.append(c)
print(len(seen), 'branches:', ', '.join(seen))
"
実測出力(見やすさのため改行を入れています):
24 branches: 7.0.3, 6.9.6, 6.8.7, 6.7.6, 6.6.6, 6.5.9, 6.4.9, 6.3.9,
6.2.10, 6.1.11, 6.0.13, 5.9.15, 5.8.14, 5.7.16, 5.6.18, 5.5.19,
5.4.20, 5.3.22, 5.2.25, 5.1.23, 5.0.26, 4.9.30, 4.8.29, 4.7.34
自分の系統の修正版がどれかは、この一覧で引けます。 6.4系なら 6.4.9、5.9系なら 5.9.15 です。
この24という数字の読み方に注意してください。 これは「今回のリリースで修正版が出た系統の数」であって、「CVE-2026-64638が24系統すべてに影響する」ことの直接の証明ではありません。今回のリリースは12件のセキュリティ問題をまとめて修正しており、公式は「WordPress 6.9 は12件のうち11件の影響を受ける」と系統ごとに影響が異なることを明記しています。CVE-2026-64638について公式・発見者がともに述べているのは「修正を4.7まで backport した」という点で、本記事の実測(4.7.34 に esc_html() が入っていること)はそれと整合します。
6.8系について正確に書きます。wp2shellの目玉である「事前認証RCE」は6.8系では成立しません。連鎖の入口であるバッチ経路の欠陥(63030)が6.8系には無いためです。ただしSQLインジェクション側(60137)は6.8.0〜6.8.5に影響し、修正版として6.8.6が出ています。この単体のSQLiは、NVDの記述どおり「プラグインやテーマが author__not_in に信頼できない入力を渡した場合」に成立するもので、素の構成でただちに悪用できるものではありません。
つまり6.8系の利用者にとっての正しい理解は、「話題の事前認証RCEの対象ではないが、放置してよいわけでもない」です。「6.9と7.0だけが危ない」という要約だけを読んで6.8系を安全と判断すると、SQLi側を取り逃します。そして2026年8月以降は、更新先が 6.8.6 ではなく 6.8.7 になっています(6.8.6はCVE-2026-64638の影響範囲です)。
なお、PHPのバージョンによって成立・不成立が変わるという情報は、確認した一次ソースには見当たりませんでした。PHPのバージョンで安全になるという判断はしないでください。
自システムでの確認方法(基本の手順)
ここまでの2点は「知らないと判定を誤る」落とし穴でした。ここからは、環境を問わず踏むことになる基本の確認手順をまとめます。すべて読み取り専用で、攻撃を含みません。自分が管理するサイトに対してのみ実行してください。
手順1:稼働バージョンを確定する
最も確実なのは、ファイルを直接読む方法です。管理画面の表示やキャッシュに影響されません。
# WordPressのドキュメントルートで実行
grep "wp_version =" wp-includes/version.php
実機での出力:
$wp_version = '6.9.4';
WP-CLIが使えるなら、こちらが簡潔です。
wp core version
6.9.4
複数サイトをまとめて確認する場合は、version.php を探して一覧にします。サーバに何十サイトも同居している環境ではこれが実用的です。
# /var/www 配下の全WordPressのバージョンを一覧化
find /var/www -name version.php -path "*wp-includes*" 2>/dev/null | \
while read f; do
v=$(grep -m1 "wp_version =" "$f" | sed "s/.*'\(.*\)'.*/\1/")
echo "$v ${f%/wp-includes/version.php}"
done | sort
出力例(バージョン順に並ぶので、古いものが上に来ます):
6.9.4 /var/www/site-a
7.0.1 /var/www/site-b
7.0.2 /var/www/site-c
同じ要領で、CVE-2026-64638のパッチ有無もまとめて一覧できます。バージョン番号ではなくパッチそのものを見るため、バックポート環境でも正しく判定できます。
find /var/www -name user.php -path "*wp-includes*" 2>/dev/null | \
while read f; do
if grep -qF 'esc_html( $username )' "$f"; then s=PATCHED; else s="NOT PATCHED"; fi
echo "$s ${f%/wp-includes/user.php}"
done | sort
出力例(未対応のサイトが上にまとまります):
NOT PATCHED /var/www/site-a
NOT PATCHED /var/www/site-c
PATCHED /var/www/site-b
「自動更新にしているから最新のはず」も実測で確かめてください。 WordPressはマイナー・セキュリティリリースを既定で自動更新しますが、wp-config.php で自動更新を明示的に無効化していると、その対象から外れます。次の2つが設定されていないかを確認してください。
grep -nE "AUTOMATIC_UPDATER_DISABLED|WP_AUTO_UPDATE_CORE" wp-config.php
AUTOMATIC_UPDATER_DISABLED が true、または WP_AUTO_UPDATE_CORE が false / 'minor' 以外に設定されている場合、コアの自動更新は期待どおりに動きません。設定を信用せず、上のバージョン実測を優先してください。
手順2:WAFでの遮断が効いているか確認する
緩和策としてWAFで遮断した場合、それが本当に効いているかは実際に叩いて確かめます。遮断が効いていれば、WAFのブロック応答(403など)が返るはずです。
# 2つの入口を両方確認する(片方だけ塞いでも回避される)
for u in "/wp-json/batch/v1" "/?rest_route=/batch/v1"; do
printf "%-28s -> " "$u"
curl -s -o /dev/null -w "%{http_code}\n" -X OPTIONS "https://example.com$u"
done
遮断前(影響範囲内の実機)は両方200が返ります。遮断後に403等へ変われば、ルールが効いています。/wp-json/... と ?rest_route=... は同じ経路への別の入口なので、必ず両方を確認してください。
手順3:ホスティング環境別の確認
環境ごとに手順が変わる部分をまとめます。
| 環境 | 確認方法 | 注意点 |
|---|---|---|
| 共用レンタルサーバ(エックスサーバー、さくら、ConoHa等) | 管理パネルのWordPress一覧、またはSSH/ファイルマネージャで wp-includes/version.php を確認 |
事業者側でWAF等のサーバ側対策が入っている場合がある。ただしそれは更新の代わりにはならない。各社の告知を確認のうえ、自分でもバージョンを実測する |
| マネージドWordPress(WP Engine等) | 事業者の管理画面のバージョン表示、または提供されているWP-CLI | 事業者が一括更新を実施している場合がある。実施済みかを実測で確認する |
| 自前サーバ/VPS | 上記の grep / wp core version / find 一括確認 |
複数サイト同居の取りこぼしに注意。find での一括確認を推奨 |
| Docker | コンテナ内で実測(イメージ更新では直らない場合がある) | 前掲の「Docker公式イメージには、まだ修正版のタグが無い」を必ず確認 |
侵害を受けていないかの確認
先に前提を明確にします。 公的機関やベンダーから、wp2shell固有のIoC(侵害指標)は公表されていません。日本語で最も詳しくまとまっているpiyolog氏の記事も、2026年7月19日時点で「侵害の兆候を確認するための具体的な指標は公表されていない」と明記しています。
したがって以下はwp2shell専用の検知手順ではなく、WordPressが乗っ取られた場合に一般的に残る痕跡を探す標準的な調査手法です。この区別は重要なので、そのまま書いておきます。
それでもこの章を置く理由は、公開エクスプロイトが出回り悪用の兆候も報告されている以上、「更新したから大丈夫」では不十分だからです。更新はこれ以降の侵入を止めますが、更新前に置かれたものは残ります。
以下のコマンドは、実際に侵害を模した環境(Webシェルを配置し、不正な管理者を作成した状態)に対して実行し、検知できることを確認済みです。
1. 想定外の管理者アカウントがないか
wp user list --role=administrator --fields=user_login,user_email,user_registered
検証環境での出力(eviluser が模擬的に追加した不正アカウント):
user_login user_email user_registered
admin [email protected] 2026-07-20 02:42:45
eviluser [email protected] 2026-07-20 02:43:50
user_registered の日付を必ず見てください。心当たりのない日付に作られた管理者は最優先で調査対象です。WP-CLIが無い場合はデータベースを直接参照します。
wp db query "SELECT u.user_login, u.user_email, u.user_registered
FROM wp_users u
JOIN wp_usermeta m ON u.ID = m.user_id
WHERE m.meta_key = 'wp_capabilities'
AND m.meta_value LIKE '%administrator%';"
2. uploads配下にPHPファイルが無いか
wp-content/uploads は本来、画像などのメディアだけが置かれる場所です。ここにPHPファイルがあるのは、正常な状態ではまずありません。
find wp-content/uploads -name "*.php" -mtime -30
検証環境での出力:
wp-content/uploads/.cache.php
先頭がドットの隠しファイルになっている点に注目してください。ls では見えません。find を使う理由がこれです。
3. コマンド実行系の関数を含むファイルを探す
grep -rlE 'system\(|passthru\(|shell_exec\(|eval\(|base64_decode\(' \
wp-content/uploads wp-content/themes --include="*.php"
検証環境での出力:
wp-content/uploads/.cache.php
正規のテーマやプラグインが base64_decode を使うことはあるため、検出=即座に悪性ではありません。uploads配下で引っかかった場合は、ほぼ確実に異常です。
4. コアファイルの改ざんを確認する
WordPressは公式のチェックサムと照合できます。コアファイルが書き換えられていないかを機械的に判定できるので、必ず実行してください。
wp core verify-checksums
検証環境での出力:
Warning: File should not exist: wp-config-docker.php
Success: WordPress installation verifies against checksums.
Success ならコアファイルは公式配布物と一致しています。この例の Warning はDocker公式イメージが同梱するファイルによるもので、異常ではありません。逆に File doesn't verify against checksum が出たら、そのファイルは改ざんされています。
5. 不審なスケジュール実行がないか
永続化のためにWP-Cronへ仕込まれることがあります。
wp cron event list --fields=hook,next_run_gmt
見慣れないフック名、特にランダムな文字列のようなフックがあれば調査対象です。
6. 最近更新されたPHPファイルを俯瞰する
find . -name "*.php" -mtime -14 -not -path "./wp-content/cache/*" -ls | sort -k8
更新作業をした日付以外に更新されているPHPがあれば、内容を確認してください。
7. アクセスログから侵入の有無と時期を絞り込む
ファイル側の痕跡が見つからなくても、ログには残っている場合があります。逆に、ログに batch/v1 へのPOSTが一切なければ、少なくともその期間は試行すらされていないという判断材料になります。
# batch/v1 へのPOSTを時系列で抽出(nginxの例)
zgrep -hE "batch/v1|rest_route=/batch/v1" /var/log/nginx/access.log* \
| grep '"POST' | awk '{print $4, $1, $9}' | sort
出力は「日時/送信元IP/ステータスコード」の並びになります。見るべき点は3つあります。
・ステータスコードが200番台のPOSTがあるか。 400や403ばかりなら、到達はしても処理は失敗している可能性が高い。200が並んでいる場合は要精査
・同一IPからの短時間の連続アクセスがあるか。 自動化されたスキャンや攻撃の典型的な形
・最初の記録がいつか。 これが侵害の可能性がある期間の起点になる
侵入の疑いがある時刻が絞れたら、その前後でファイルが作られていないかを突き合わせます。
# 特定日以降に作成・更新されたPHPファイル(例:7月17日以降)
find . -name "*.php" -newermt "2026-07-17" -not -path "./wp-content/cache/*" -ls
ログが残っていない場合:保存期間の短い共用サーバでは、この確認自体ができないことがあります。その場合は「侵入されていないことを証明できない」状態です。ファイル側の確認(管理者アカウント・uploads配下のPHP・チェックサム)をより丁寧に行い、それでも不安が残るなら、公表日より前のバックアップからの復旧を検討してください。確認できないことを「問題なし」と読み替えないことが、この段階でいちばん重要です。
調査で1つでも異常が見つかった場合:ファイルを消して終わりにしないでください。侵入経路と滞在期間が分からないままでは再侵入されます。バックアップからの復旧、全ユーザーのパスワードとソルト(wp-config.php の認証キー)の再発行、設置済みプラグイン・テーマの棚卸しまでを一続きで行うのが原則です。規模によっては専門事業者への相談を検討してください。
よくある誤解と、正しい理解
確認作業でつまずきやすい点を、実機で確かめた結果とあわせてまとめます。
| よくある誤解 | 正しい理解 |
|---|---|
「/batch/v1 にアクセスして404だから安全」 |
GETはPOST専用エンドポイントに対して常に404を返す。影響範囲内の6.9.4でも404だった。OPTIONS で判定する |
「REST APIのnamespacesに batch が無いから安全」 |
/batch/v1 はネームスペースではなくコアが直接登録する経路。6.9.4の実測でもnamespacesには現れなかった |
| 「Dockerイメージを最新にしたから安全」 | 検証時点で修正版タグは存在せず、latest の中身は7.0.1(影響範囲内)だった。中身をバージョンで実測する |
| 「6.8系なので無関係」 | 事前認証RCEの連鎖は成立しないが、SQLi側(CVE-2026-60137)の影響を受ける。6.8.6への更新が必要 |
| 「自動更新が有効だから対応済み」 | 自動更新を無効化した環境、ファイルシステムが書き込み不可の環境では適用されない。実測で確認する |
| 「WAFで塞いだから更新は不要」 | WAFは時間を稼ぐ応急処置。恒久対策は修正版への更新のみ |
| 「更新したので対応完了」 | 更新は以降の侵入を止めるだけ。更新前に置かれたWebシェルや作成された管理者は残る |
| 「プラグインを入れていないから安全」 | 発見者は「プラグインを入れていない素の構成で、前提条件なしに成立する」と明記している |
| 「7.0.2(6.9.5 / 6.8.6)に上げたので対応済み」 | wp2shellについては正しいが、2026年8月6日の 7.0.3 で公表されたCVE-2026-64638については未修正。7.0.3 / 6.9.6 / 6.8.7 が現在の安全ライン |
| 「batch/v1 を遮断したので新しいXSSも防げる」 | 防げない。CVE-2026-64638が露出しているのは wp-login.php であって、バッチ経路ではない |
| 「新しいXSSも認証不要でRCEまで行くらしい」 | XSSまでは認証不要だが、PHPコード実行にはログイン中の管理者の能動的な操作が要る。NVDのCVSSベクタも UI:A としている |
| 「バージョン表示が古いので未対応」 | ホスティング事業者がバックポートしている場合がある。grep -qF 'esc_html( $username )' wp-includes/user.php でパッチそのものを見るのが確実 |
wp2shellへの対策手順
優先度順に整理します。
緊急(今すぐ)
修正版へ更新する。 これが唯一の恒久対策です。更新先は2026年8月6日に変わっているので注意してください。
wp core update --version=7.0.3 # 7.0系の場合
wp core update --version=6.9.6 # 6.9系の場合
wp core update --version=6.8.7 # 6.8系の場合
それ以前の系統(6.7以下)を使っている場合の更新先は、前掲の「バックポートは4.7系まで24系統」の一覧で引いてください。
更新後は必ず実測で確認します。バージョンとパッチの両方を見るのが確実です。
wp core version
grep -qF 'esc_html( $username )' wp-includes/user.php && echo PATCHED || echo "NOT PATCHED"
更新先は、7.0系なら WordPress 7.0.3、6.9系なら WordPress 6.9.6、6.8系なら 6.8.7 です。メジャーバージョンを跨いで上げる必要はありません。 7.0系を使っているサイトが慌てて構成を変える必要はなく、同じ系列の修正版へ上げれば連鎖は成立しなくなります。テーマやプラグインの互換性リスクを避ける意味でも、まずは同系列の最新へ上げるのが定石です。
WordPressは自動更新が有効な環境に対して更新を強制配信しているとされていますが、自動更新を無効化している環境、Docker等でファイルシステムが書き込み不可の環境では適用されません。 「自動更新だから大丈夫」と考えず、必ずバージョンを実測してください。
更新が本当に反映されたかは、次の3点をセットで確認すると確実です。
wp core version # 1. バージョンが修正版になったか
wp core verify-checksums # 2. コアが公式配布物と一致するか
curl -s -X OPTIONS "https://example.com/?rest_route=/batch/v1" # 3. 経路の状態
1と2が通れば更新は成功です。3については、更新後も batch/v1 自体は存在し続けます(バッチAPIは正規の機能であり、削除されたわけではなく、経路の取り違えが修正されたためです)。したがって更新後にOPTIONSが200を返しても異常ではありません。ここを「まだ塞がっていない」と誤読しないでください。3を見るのは、WAFでの遮断を選んだ場合にその効きを確認するときです。
緊急(すぐ更新できない場合の緩和)
以下の緩和策は wp2shell(CVE-2026-63030)専用です。CVE-2026-64638 には効きません。 ログイン画面のXSSに対して発見者(pwn.ai)が示している対策は更新のみで、それ以外にできるのは連鎖の前提条件を減らすことだけです(前掲の「PHPコード実行に至る条件は、wp2shellとは別物」を参照)。2つの問題の緩和策を混同しないでください。
Searchlight Cyberが案内している緩和策は、バッチAPIへの匿名アクセスを遮断することです。遮断対象は次の2つで、必ず両方を塞ぎます。
・/wp-json/batch/v1
・?rest_route=/batch/v1
nginxでの例:
location ~ ^/wp-json/batch/v1 { return 403; }
if ($arg_rest_route ~* "^/batch/v1") { return 403; }
Apache(.htaccess)での例:
RewriteEngine On
RewriteRule ^wp-json/batch/v1 - [F,L]
RewriteCond %{QUERY_STRING} rest_route=/batch/v1 [NC]
RewriteRule ^ - [F,L]
適用後は、前掲の「手順2:WAFでの遮断が効いているか確認する」で実際に403が返ることを確認してください。設定しただけで確認しないのが、この種の緩和策で最も多い失敗です。
遮断による副作用も確認しておきます。バッチAPIはブロックエディタや一部のプラグイン・ヘッドレス構成が内部的に使う場合があります。遮断後は、管理画面での記事の保存・ブロックエディタの動作・連携アプリの挙動を一通り確認してください。 実際に不具合が出た場合は、遮断を解除するのではなく、更新を優先して実施し遮断を外すのが正しい順序です。
Cloudflareは自社WAFで対応ルールを提供していると告知しています。利用中のWAF・CDN・ホスティング事業者が対応ルールを出しているかを確認してください。ただしWAFは時間を稼ぐためのものであり、更新の代替ではありません。
推奨(更新後に実施)
・前章の「侵害を受けていないかの確認」を一通り実行する
・wp-config.php の認証キー・ソルトを再発行し、既存セッションを無効化する
・管理者アカウントの棚卸しとパスワード再設定
・アクセスログで batch/v1 へのPOSTを遡って確認する(下記)
grep -E "batch/v1|rest_route=/batch/v1" /var/log/nginx/access.log* | grep POST | head -50
公開エクスプロイトが出回る前後の日時に、心当たりのないPOSTが記録されていないかを確認します。ログの保存期間が短い環境では、この確認自体ができない点にも留意してください。
長期
・WordPressコアの自動更新を有効にする(マイナー・セキュリティリリースだけでも)
・wp-content/uploads でのPHP実行をWebサーバ設定で禁止する。Webシェルを置かれても実行させない多層防御になる
・稼働中の全WordPressのバージョンを定期的に一括取得する仕組みを持つ(前掲の find ワンライナーをcron化する等)
・REST APIの匿名アクセス範囲を把握しておく。今回のように、普段意識していない経路が攻撃面になる
uploads配下のPHP実行禁止(nginx):
location ~* ^/wp-content/uploads/.*\.php$ { deny all; }
他の情報源との使い分け
今回のような大型のWordPress 脆弱性では、日本語でも複数の解説が出ます。役割が違うので、併読を勧めます。
| 情報源 | 強み | この記事との関係 |
|---|---|---|
| WordPress公式・NVD | 一次情報。影響バージョンとCVEの正確な定義 | 本記事の事実関係はここで裏取りしている |
| Searchlight Cyber(発見者) | 発見者による説明。緩和策の根拠 | 攻撃の技術詳細は意図的に非公開 |
| piyolog | 出来事の整理と時系列。日本語で最も網羅的 | 実行コマンドは扱っていない。本記事が補完する |
| GMO Flatt Security | 脆弱性の仕組みと痕跡の技術解説 | 実行コマンドは扱っていない。本記事が補完する |
| ホスティング各社の告知 | 自社サービスでのサーバ側対策 | 自社環境限定。本記事は環境横断でまとめている |
| pwn.ai(CVE-2026-64638の発見者) | 発見の経緯とパーサー差異の解説 | 攻撃側の視点。本記事は防御・判定側に絞っている |
| Security NEXT | CVE-2026-64638の日本語速報(2026年8月7日) | 約200語の短報。CVE番号・CVSS・修正版の提示まで。判定手順やDocker運用は扱っていない |
本記事の役割は、「読んで理解する」ではなく「自分の環境で叩いて判定する」部分です。とくに以下は、実際に実行しなければ分からない情報でした。
・GETでの確認が当てにならないこと
・Docker公式イメージが7月時点で未更新であり、8月8日時点でも latest の中身が 7.0.2(CVE-2026-64638の影響範囲)のままであること
・wordpress:6.9 が6.9.4のまま二度と更新されないこと(定義ファイルからビルド対象が外れている)
・esc_html( $username ) の有無というパッチ判定が、4.7.34から7.0.3までの全系統で機能すること
今回のように「自分が影響範囲かを判定する」手順を重視した記事としては、nginx 2026年6月のCVE 3件解説|Critical 2件は条件付き、自分が対象か判定する手順も同じ構成で書いています。承認や信頼境界のズレが攻撃面になる事例としては、GhostApproval・SymJack解説|AIコーディング支援6種のsymlink承認ハイジャックが近い性質を扱っています。
まとめ
wp2shellは、単体では条件付きだったSQLインジェクションが、REST APIバッチ経路の解釈の取り違えと連鎖したことで事前認証RCEに化けたという事例です。プラグイン不要・前提条件なしで成立するため、影響範囲のバージョンを使っているなら最優先で更新してください。
そして2026年8月6日、その修正版だった 7.0.2 自体が、ログイン画面の事前認証XSS(CVE-2026-64638) の影響範囲であることが公表されました。原因は再び「2つの処理が同じ文字列を別のものとして解釈する」ことで、7月と同じ構造の問題が別の場所で顕在化した形です。
実務上の要点を4つに絞ります。
- 安全ラインは動く。 現在の更新先は 7.0.3 / 6.9.6 / 6.8.7。7月に 7.0.2 / 6.9.5 / 6.8.6 へ上げた対応は、wp2shellについては正しかったが、今回の問題については未対応
- バージョンよりパッチを見る。
grep -qF 'esc_html( $username )' wp-includes/user.phpが、4.7.34から7.0.3まで全系統で修正版と未修正版を分離した。バックポート環境ではこちらが確実 - 緩和策を使い回さない。
batch/v1の遮断はwp2shell専用で、CVE-2026-64638には効かない。今回露出しているのはwp-login.php。危険度の性質も違い、PHPコード実行には管理者の能動的な操作が要る(CVSSUI:A) - Dockerは供給サイクルが別物。 8月8日時点で
wordpress:latestの中身は 7.0.2、wordpress:6.9は 6.9.4(wp2shellの影響範囲)。しかも6.9系はもうビルド対象から外れており、待っても直らない
本記事の検証は2026年8月8日時点の実機・公式配布物・公式APIで行いました。Docker公式イメージの状況は時間とともに変わります——7月20日は「7.0.2のタグが無い」、8月8日は「7.0.2はあるが7.0.3が無い」でした。記事の記述をそのまま信じず、掲載したコマンドをその場で実行して現在の状態を確認してください。 それがこの記事のいちばんの使い方です。
参照ソース
・wp2shell: Pre Authentication RCE in WordPress Core(Searchlight Cyber/Assetnote・発見者による一次ソース)
・NVD — CVE-2026-63030(REST APIバッチ経路の取り違え・CWE-436)
・NVD — CVE-2026-60137(WP_Query author__not_in のSQLインジェクション・CWE-89)
・WordPress News(公式・2026年7月のセキュリティリリース)
・CVE-2026-63030: wp2shell a Critical Remote Code Execution Vulnerability in WordPress Core(Rapid7)
・WordPress Core “wp2shell” RCE flaws get public exploits, patch now(BleepingComputer)
・WordPress 7.0.3 Release(WordPress公式・2026年8月6日/セキュリティ修正12件と各報告者の一覧)
・NVD — CVE-2026-64638(ログイン画面の事前認証リフレクテッドXSS・CWE-79・CVSS v4.0 8.9)
・XSS2Shell: WordPress Preauth XSS to RCE Chain(pwn.ai・発見者による一次ソース)
・WordPress Core Update API(api.wordpress.org・バックポート系統の実測に使用)
・docker-library/official-images — library/wordpress(公式イメージのビルド定義)