
wp2shell (CVE-2026-63030 & CVE-2026-60137) - 完全なRCEチェーン
Searchlight Cyberのwp2shellアドバイザリに関連する、認証不要のWordPress RESTバッチルート混乱SQLインジェクションの独立した概念実証です。
このリポジトリはSearchlight Cyberの公式チェッカーではありません。checkはSQLiパスを確認し、readはデータベース読み取りを実行し、shellは管理者認証情報を指定するか、最初にSQLi-to-adminブリッジを実行して、プラグインベースのコマンドシェルを開きます。
Searchlight Cyberのアドバイザリは、以下のwp2shell RCE対象バージョン範囲を示しています:
| バージョン範囲 | ステータス |
|---|---|
| <= 6.8.5 | 影響なし |
| 6.9.0 – 6.9.4 | 影響あり |
| 7.0.0 – 7.0.1 | 影響あり |
RESTバッチエンドポイント(/batch/v1)は認証不要で、1回の呼び出しで複数のサブリクエストを実行し、各サブリクエストが個別に検証され、パーミッションチェックされることを前提としています。
serve_batch_request_v1()は2つの並列配列($matches(各サブリクエストの一致したハンドラ)と$validation(各サブリクエストの検証結果))を構築し、ディスパッチ時に両方を同じオフセットでインデックスします。パスがwp_parse_url()に失敗したサブリクエストは$validationに追加されますが、$matchesには追加されないため、配列が同期しなくなり、サブリクエストが別のサブリクエストのハンドラの下でディスパッチされます。これがルート混乱です。
このPoCはプリミティブを2回ネストします:
POST /wp/v2/postsリクエストでrequestsボディを持つものが、バッチハンドラ自身の下でディスパッチされます。投稿リクエストとして検証されたため、そのrequestsリストはバッチスキーマに対してチェックされず、そのサブリクエストはGETを使用できるため、メソッド許可リストがバイパスされます。GET /wp/v2/posts/999999アイテムルートリクエストが、author_exclude、orderby、per_pageなどの投稿コレクションクエリパラメータを運びます。999999 IDは存在する必要はなく、コレクション専用パラメータを検証しないアイテムルートにマッチするための可能性の低い投稿IDです。その後、デシンクにより同じリクエストがposts get_items()の下でディスパッチされ、author_excludeがWP_Queryのauthor__not_inクエリ変数にマッピングされ、脆弱なビルドがそれをSQLに文字列として補間します。その結果、認証前に到達可能なブール型および時間ベースのブラインドSQLインジェクションが実現します。このPoCには、SQLi-to-adminチェーンで使用されるUNION偽装投稿プリミティブも含まれています。
ここで実装されているRCEパスは次のとおりです:
wp_posts行を使用して、投稿コレクションを通じて攻撃者が制御するコンテンツをレンダリングします。レンダリングブリッジは、SQLi readがget_items()に到達するために使用するのと同じルートである/wp/v2/posts/999999アイテムルートソースを使用します。POST /wp/v2/usersに到達させ、生成された管理者を作成します。手順1〜5は認証前です。コマンド実行ステップは認証済みの管理者プラグインアップロードです。
Python 3.8以上と標準ライブラリ。サードパーティの依存関係はありません。
リポジトリディレクトリから実行します:
./wp2shell.py <command> <url> [options]
または、pip install .を実行してwp2shellコマンドをPATHに追加します。
最初にパッシブなWordPressマーカーと公開バージョンヒントを表示し、次に良性のバッチマーカープローブを送信します。脆弱なバッチ実装は、ルート混乱マーカーパターンparse_path_failed、block_cannot_read、rest_batch_not_allowedを含むHTTP 207を返します。
マーカープローブはWordPressコア修正に基づいています。不正な///リクエストはparse_path_failedを生成します。/wp/v2/postsリクエストはバッチ許可スペーサーとして機能します。/wp/v2/block-renderer/...ルートはバッチ許可されていませんが、匿名でそのハンドラに到達するとblock_cannot_readを返します。/batch/v1はrest_batch_not_allowedを提供します。脆弱なビルドでは、パースエラーによりバッチハンドラ配列が同期しなくなり、スペーサーリクエストがブロックレンダラーハンドラの下でディスパッチされます。修正済みビルドでは配列が同期しているため、この正確な3つすべてのパターンは細工されたプローブでは出現しません。
デフォルトでは、checkはそこで停止し、SQLiペイロードを送信しません。アクティブなSQLi確認も必要な場合は、--confirm-sqliを使用します。確認は最初にUNION読み取りプリミティブを試み、UNIONリフレクションが利用できない場合はペアリングタイミングプローブにフォールバックします。
これらのシグナルは独立しています。バージョンヒントは単なるヒントであり、マーカーパターンはルート混乱を示し、--confirm-sqliはペイロードがデータベースに到達したことを示します。WAFがペイロードをブロックする可能性があるため、確認が失敗してもバグがないとは証明されません。
./wp2shell.py check http://target
./wp2shell.py check targets.txt # scan every URL in the file
./wp2shell.py read http://target # server fingerprint
./wp2shell.py read http://target --preset users # user logins and password hashes
./wp2shell.py read http://target --query "SELECT @@version"
デフォルトの抽出方法は--technique autoで、以下の順序で利用可能なメソッドを試みます:
UNIONを介して偽のWP_Post行を偽造し、REST応答からそのタイトルを||HEX(value)||として読み戻します。ペイロードは同じ/wp/v2/posts/999999ソースルートを使用し、orderby=noneとper_page=500を指定するため、偽の行はレンダリングされた投稿として残ります。値ごとに1リクエスト。WP_DEBUG_DISPLAYオン)、EXTRACTVALUE/UPDATEXMLでリクエストあたり約15バイトを漏洩します。X-WP-Totalヘッダーを真偽シグナルとして読み取り、反映された値を必要としません。--technique union|error|blindで1つを強制します。これらの読み取りパスはデータベース行を書き込みません。
--userと--passwordを指定すると、shellは指定された管理者認証情報でログインし、WordPressのプラグインアップロード動作を使用します。
認証情報がない場合、shellは最初に事前認証SQLi-to-adminブリッジを実行し、生成された管理者としてログインし、その後プラグインシェルをアップロードします。
./wp2shell.py shell http://target --user admin --password '<recovered>' --cmd id
./wp2shell.py shell http://target --user admin --password '<recovered>' -i # interactive shell
./wp2shell.py shell http://target --cmd id # pre-auth bridge
./wp2shell.py shell http://target -i # pre-auth interactive
shellはプラグインのウェブシェルをアップロードし(ランダムパスと実行ごとのトークンでロック)、そのパスを表示します。アップロードされたウェブシェルは自動的に削除されます。事前認証ブリッジが管理者を作成した場合、その生成されたアカウントはシェルセッション終了後に自動的に削除されます。
WordPress 7.0.2にアップデートするか、サイトが6.9ブランチの場合は6.9.5にアップデートしてください。それまでは、エッジで/wp-json/batch/v1とrest_route=/batch/v1クエリパラメータの両方をブロックするか、rest_pre_dispatchフィルタを介してバッチエンドポイントに認証を要求してください。
許可されたセキュリティテストのみに使用してください。あなたが所有する、またはテストする明示的な書面による許可を得たシステムに対してのみ使用してください。保証は一切提供されず、誤用に対する責任も負いません。
| オプション | 適用対象 | 説明 |
|---|
--proxy URL | all | HTTPプロキシ経由でトラフィックをルーティングします(例:Burp)。 |
--timeout N | all | リクエストのタイムアウト(秒)。 |
--sleep N | check | --confirm-sqliのタイミングフォールバックで使用される遅延。 |
--samples N | check | --confirm-sqliのタイミングフォールバックで使用されるタイミングペア数。 |
--confirm-sqli | check | アクティブなSQLi確認ペイロードも送信します。 |
--preset | read | fingerprintまたはusers。 |
--technique | read | auto(デフォルト)、union(インバンド、偽の投稿を偽造)、error(インバンド、データベースエラーの表示が必要)、またはblind。 |
--query | read | 読み取るスカラーSQL式。 |
--prefix | read | データベーステーブルプレフィックス(デフォルト wp_)。 |
--max-length N | read | 値ごとに読み取る最大文字数(デフォルト128)。 |
--user / --password | shell | オプションの管理者認証情報。両方を省略すると事前認証ブリッジを使用します。 |
--cmd | shell | 実行するコマンド(-i使用時は省略)。 |
-i / --interactive | shell | デプロイ後に対話型シェルを開きます。 |