0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【緊急対応】Plesk + WordPressのWP2Shell(CVE-2026-60137 / CVE-2026-63030)対応手順まとめ

0
Posted at

この記事でわかること

  • 2026/7/17公開のWordPress本体(プラグイン無関係)の脆弱性2件。合わせて「WP2Shell」と通称。
  • REST APIのバッチ処理機能が悪用され、未認証でDB内容の読み取り/管理者アカウントの勝手な作成が可能。
  • 対象:Plesk環境でWordPressを複数運用していて、SSHでサーバーにログインできる人。
  • 「アップデートすれば終わり」ではなく、証拠保全→侵入痕跡調査→復旧の順で対応するのが本記事の主眼です。

アップデートだけでは、すでに侵入されていた場合の被害は消えません。4章以降の調査手順まで必ず実施してください。


0. 対象読者

  • Plesk上で複数のWordPressサイトを管理している
  • SSHでサーバーにログインし、WP-CLI・mysqldump・zgrep等が使える
  • 「とりあえずコアを更新すれば安全」ではなく、侵害有無まで確認したい人

1. 何が起きているか

  • WordPress本体(プラグイン一切不要)の不具合
  • 未認証の第三者
  • REST APIの「バッチ処理」機能を悪用し、DBの中身を読み取る/勝手に管理者アカウントを作成してサイトを乗っ取れる

素のWordPressでも発生します。CVE番号(CVE-2026-60137, CVE-2026-63030)は覚えなくてよく、「WP2Shell」とだけ覚えておけばOKです。


2. バージョンチェック

cd /var/www/vhosts/<ドメイン>/httpdocs
wp core version
バージョン 状態 対応
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以上へ
それ以外(新しい) 対応済み 念のため3章以降も実施推奨

複数サイトを一括チェックする場合:

for dir in /var/www/vhosts/*/httpdocs; do
  echo "=== $dir ==="
  wp core version --path="$dir" --allow-root 2>/dev/null
done

ハマりどころ①:WordPressの設置場所は httpdocs 直下とは限りません(httpdocs/wp 等のサブディレクトリ設置あり)。以下で機械的に洗い出すのが確実です。

find /var/www/vhosts -maxdepth 4 -name wp-config.php

把握していなかった検証用の複製サイトや旧サイトが出てくることがあるので、Pleskのドメイン一覧と突き合わせておくと安心です。


3. 先にコアを更新する

Plesk管理画面:「WordPress」→対象サイト行の「アップデート」

CLIの場合:

cd /var/www/vhosts/<ドメイン>/httpdocs
wp core update
wp core version

wp core version で更新後バージョンを必ず確認(自動更新されている前提で確認をサボらない)。


4. 更新前に証拠を保全する

ここが今回の一番の勘所です。 不審なユーザーやファイルを見つけて即削除すると、「いつ・どこから・何をされたか」を後から説明できなくなります。パスワード変更やアカウント削除の前に、以下2つを先に保全してください。

4-1. DBバックアップ

mysqldump -u<DBユーザー> -p'<DBパスワード>' -h 127.0.0.1 -P <ポート番号> '<DB名>' > /root/evidence_$(date +%Y%m%d)_db.sql

DB接続情報は wp-config.php から確認可能。mysqldump は必ずroot権限のシェルから直接実行すること(wp db export はサイト実行ユーザー権限で動くため /root のような保護ディレクトリへの保存ができない)。

4-2. アクセスログの保全

cp -a /var/www/vhosts/system/<ドメイン>/logs /root/evidence_$(date +%Y%m%d)_logs

Pleskのログはローテーションで数日〜2週間程度で消える設定になっていることが多いです。侵入時期を確定できるのはログが残っている今だけ、という前提で先に確保してください。


5. 侵入済みかどうかの調査

更新だけでは既存の侵害は消えません。 以下を順に確認します。

5-1. 不審な管理者アカウント(最重要)

wp user list --fields=ID,user_login,user_email,user_registered,roles --orderby=user_registered --order=desc

チェック項目:

  • user_registered2026-07-17以降 のアカウントがないか
  • 見覚えのない user_login / user_email
  • rolesadministrator が入っていて心当たりがないもの

ハマりどころ②:偽装アカウントは巧妙です。実案件では日本人名を装ったログイン名、wordpress.org風・wpenginebot風のメールアドレスなど、公式を装うパターンが確認されています。目視の「怪しさ」判定に頼らず、登録日時で機械的に切り分けるのが確実です。

削除前にID・情報をメモ。削除コマンド:

wp user delete 999 --reassign=1

5-2. DB直書きの不正ユーザー

wp db query "SELECT ID, user_login, user_email, user_registered FROM wp_users ORDER BY user_registered DESC LIMIT 20;"
wp db query "SELECT user_id, meta_value FROM wp_usermeta WHERE meta_key = 'wp_capabilities' ORDER BY user_id;"

(テーブルプレフィックスは wp-config.php$table_prefix に合わせて読み替え)

users にIDがあるのに usermeta に権限情報がないアカウントは、管理画面の一覧に出てこない「隠しユーザー」の可能性があります。

5-3. アクセスログの痕跡確認

zgrep -h "batch/v1" /var/www/vhosts/system/<ドメイン>/logs/access*log* | awk '{print $9}' | sort | uniq -c
ステータス 意味
301/400/404/503のみ 攻撃対象の入口に未到達。無害
207(応答数KB程度) 探索・拒否応答。単独では黒と言えない
207で応答サイズが極端に大 DB抜き取りの可能性が高い

応答サイズ順で全体分布を確認:

zgrep -h "batch/v1" /var/www/vhosts/system/<ドメイン>/logs/access*log* | awk '{print $10, $9, $1, $4}' | sort -rn | head -40

ハマりどころ③:固定閾値でawkを組んで判定しないこと。DB規模はサイトごとに全く違うため「抽出成功」とみなせる応答サイズは数十KB〜数MBまで幅があります。閾値固定で「該当なし」だった場合でも即安全と判断せず、sort -rn | head で分布全体を見てから段階的に閾値を下げて確認し直してください。

5-4. ログイン試行(ステータスコードの読み方注意)

zgrep -h "POST /<WordPress設置パス>/wp-login.php" /var/www/vhosts/system/<ドメイン>/logs/access*log* | awk '{print $9, $1, $4}' | sort

ハマりどころ④wp-login.php へのPOSTは 302=ログイン成功、200=ログイン失敗 です。直感と逆なので注意してください(失敗時はログイン画面が200で再表示、成功時はダッシュボードへ302リダイレクト)。

302が見つかっても即断せず、以下3段階で実害まで確認:

  1. POST /wp-login.php が302(成功)
  2. 同一IPが直後に GET /wp-admin/ へアクセスし、200かつ応答サイズが大(フルロード。リダイレクトのみなら302で小サイズ)
  3. さらにその後、plugin-install.phptheme-editor.phpxmlrpc.php 等への正しいパスでのアクセスがあるか

2で止まっていれば「ログイン成功・閲覧のみ」、3まで到達していれば「実害の疑い濃厚」。CDN経由の場合、同一攻撃者でも数秒でIPが入れ替わることがあるため、直後の別IPアクセスも同一人物の可能性として見ておく。

5-5. 誤検知しやすいパターン(無害なことが多い)

  • .js/.css/画像/フォントへの200応答:認証不要で誰でも200が返るのが正常
  • install.php への大量アクセス:インストール済みサイトでも200やリダイレクトを返すことがあり、ボットの機械的探索
  • UAが1秒ごとに変わる分散アクセス:403や空振りで終わっているなら単なる分散スキャン

6. 不審ファイルの確認

mu-plugins は管理画面のプラグイン一覧に表示されないため見落とされがちです。

ls -la wp-content/mu-plugins/

最近作成・変更されたPHPファイルの一覧:

find wp-content/ -name "*.php" -mtime -14 -ls
find wp-content/uploads -type f -name "*.php*" -ls

コア・プラグインの改ざんチェック:

wp core verify-checksums
wp plugin verify-checksums --all

ハマりどころ⑤:チェックサム不一致=今回の攻撃とは限りません。有料プラグインのライセンスカスタマイズや制作時点のファイル差し替えが原因のケースも多いです。不一致ファイルは、5-3〜5-4で確認した攻撃露出期間中の変更かを find ... -mtime の結果と突き合わせてから判断してください。期間外の変更なら今回の攻撃と無関係な場合が多いです。

フォーム保存機能をDB保存設定にしている場合、wp-content/uploads 以下へのPHP混入は特に念入りに確認。


7. 漏えい範囲の判定(個人情報の有無)

wp db query "SELECT table_name, ROUND((data_length+index_length)/1024/1024,2) AS mb, table_rows FROM information_schema.tables WHERE table_schema=DATABASE() ORDER BY (data_length+index_length) DESC;"
wp db query "SELECT post_type, post_status, COUNT(*) FROM wp_posts GROUP BY post_type, post_status ORDER BY COUNT(*) DESC;"
  • wp_users が数件(管理者のみ)なら会員情報漏えいはなし
  • お問い合わせフォーム系プラグインがDB保存設定になっていないか確認(メール送信のみ完結ならDBには残らない)

APIキー・ライセンスキー等の平文保存は漏えい前提で扱う:

wp db query "SELECT option_name, LENGTH(option_value) FROM wp_options WHERE option_name LIKE '%key%' OR option_name LIKE '%token%' OR option_name LIKE '%secret%' OR option_name LIKE '%license%' ORDER BY LENGTH(option_value) DESC;"

ハマりどころ⑥:応答サイズの大小からDB中身の漏えい量を逆算することはできません。応答が小さくてもDB自体が大きい場合「特定範囲のみ抜かれた」可能性が残ります。判断がつかない場合は「特定できないため、DB内の情報は漏えいしたものとして扱う」を安全側の前提にしてください。


8. 侵害時の進行パターン(参考)

  1. DB読み取り/勝手な管理者アカウント作成(5章で検出できる段階)
  2. そのアカウントでログインし、テーマ編集等から不正コード書き込み・バックドア設置
  3. 迷惑メール送信・他サイトへの攻撃踏み台・検索エンジンスパム埋め込み
  4. wp-config.php のDBパスワード等が盗み見られる可能性

放置してもサイトの見た目は普段通りのまま進行します。表面化する頃には検索エンジンからの警告や送信メールのブラックリスト入りとして実害が出ていることが多いです。同一サーバーに複数サイトが同居している場合、他サイトへの横展開リスクもあります。


9. 侵害確認後の復旧手順

証拠保全(4章)が未実施なら先に実施。

  1. サイトを一時非公開に(Pleskメンテナンスモード or 一時停止)
  2. コアを最新版に更新(3章)
  3. セッション無効化(パスワード変更より先に)
wp config shuffle-salts

パスワードだけ変えてもログイン中Cookieは生き残るため、必ずこのコマンドを先に実行。

  1. パスワード全変更

    • WP管理者:wp user update <ユーザー名> --user_pass="新パスワード"
    • DBパスワード(Plesk「データベース」から変更):ファイル改ざん・不正ログインの実害が確認された場合は必須。DB読み取りのみで実害なし、かつMySQLがbind-addressでlocalhost限定と確認できていれば省略可(grep -i bind-addressで確認)
    • Plesk・FTP・SSHパスワード:実害確認時 or パスワード使い回しがある場合は必須。実害なしでも念のため変更推奨
  2. 不正管理者アカウントの削除(5-1で特定したもの)

  3. 削除後、数時間〜翌日に再度ユーザー一覧確認

wp user list --fields=ID,user_login,user_email,user_registered,roles

数が増えていたらバックドア経由の自動再作成の疑い。

  1. 不審ファイル削除。心配な場合はwp-content/themeswp-content/pluginsを公式配布元から再取得して上書き
wp theme install <テーマ名> --force
wp plugin install <プラグイン名> --force
  1. 外部連携キー(reCAPTCHA、有料プラグインライセンス等)の再発行。複数サイトで使い回している場合は関連サイト全てで同時に差し替え
  2. 可能であれば 2026-07-17より前のクリーンなバックアップから復元が最も確実。復元直後に必ず3章の更新を再実行
  3. 同一Pleskサーバー上の他サイトも5・6章の手順で確認
  4. 個人情報漏えいの可能性がある場合、7章の判定結果をもとに関係者へ状況共有

10. 報告書の書き方の注意

  • ログが既に消えて確認不能な場合:「侵入がなかった」ではなく「確認できた範囲では痕跡がなかった
  • DB抜き取り内容が特定不能な場合:「漏えいはない」ではなく「特定できないため、DB内の情報は漏えいしたものとして扱う
  • ログイン成功の確認だけで「侵害あり」と断定せず、5-4のステップ3(実害到達の有無)まで見てから最終評価

11. 再発防止

  • Plesk「WordPress」画面でコア・プラグイン・テーマの自動更新を有効化
  • 管理者アカウントの定期棚卸し(不要アカウント削除)
  • 管理画面ログインへの2FA導入
  • 外部連携キーの複数サイト使い回しを解消(サイトごとに分離)
  • バックアップアーカイブ(.wpress等)をサーバー上に放置しない
  • find /var/www/vhosts -maxdepth 4 -name wp-config.php で未把握の複製・旧サイトを定期棚卸し

同じような対応に追われている方の参考になれば幸いです。気づいた点や補足があればコメントいただけると助かります。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?