
Pre-auth RCE PoC für WordPress Core – verknüpft CVE-2026-63030 (REST /batch/v1 route-confusion desync) mit CVE-2026-60137 (author__not_in SQLi) zu einer unauthentifizierten Shell. Nur für autorisierte Tests.
Nicht authentifizierte WordPress Core RCE PoC, die CVE-2026-63030 + CVE-2026-60137 verkettet. Nur für autorisierte Tests.
wp2shell.py ist ein Proof-of-Concept, bestehend aus einer einzigen Datei, das ausschließlich die Standardbibliothek nutzt und zwei WordPress-Core-Sicherheitslücken zu einer nicht authentifizierten Remote-Code-Ausführung verkettet:
| CVE | Komponente | Fehler |
|---|---|---|
| CVE-2026-63030 | REST /batch/v1-Handler | Route-Confusion Desync – der Batch-Handler verliert die Synchronisation zwischen seiner Handler-Tabelle und seiner Validierungs-Tabelle, sobald ein Sub-Request-Pfad nicht geparst werden kann, sodass ein Sub-Request durch einen Handler ausgeführt wird, für den er nie validiert wurde. |
| CVE-2026-60137 | WP_Query | skalare SQL-Injection – der Wert author__not_in wird direkt in SQL eingefügt, sodass ein String, der ihn erreicht, injizierbar ist. |
Keiner der beiden Fehler allein führt zu RCE. Der Desync ist die Zustellung (er schmuggelt eine Angreiferzeichenfolge in eine Abfrage, die sie nie erreichen sollte); die Injection ist die Grundlage. Zusammen ergeben sie für einen nicht authentifizierten Angreifer ein vollständiges SQL-Lese-/Schreib-Orakel und daraus eine funktionierende Shell.
⚠️ Nur für autorisierte Tests. Dieses Tool nutzt Live-WordPress-Installationen aus. Verwenden Sie es nur gegen Systeme, die Sie besitzen oder für die Sie eine explizite schriftliche Genehmigung zum Testen haben.
Verwundbar: 6.8.0–6.8.5, 6.9.0–6.9.4, 7.0.0–7.0.1 Gepatched: 6.8.6, 6.9.5, 7.0.2 (und später) Gemeldet von Adam Kues (Assetnote / Searchlight Cyber); SQLi auch TF1T, dtro, haongo zugeschrieben.
Nur 6.9.0–7.0.1 sind RCE-fähig. In der 6.8.x-Reihe tritt der Desync immer noch auf, aber der Zweig, der den Handler verschieben würde, trifft auf einen WP_Error::get_method()-Fatal – daher ist 6.8.x ein Denial-of-Service/Absturz, keine Shell. Das Tool erkennt dies und gibt an, welche Grundlage verfügbar ist.
Der Exploit ist schichtweise aufgebaut; jede Schicht ist eine Brücke zur nächsten.
Desync (CVE-2026-63030). Ein Batch-Request verschachtelt einen zweiten Batch. Ein Sub-Request trägt einen absichtlich fehlerhaften Pfad (http://:, den wp_parse_url() ablehnt). Ab 6.9.0+ führt der fehlgeschlagene Parse dazu, dass die Handler- und Validierungstabellen um einen Eintrag abweichen, sodass der nächste Sub-Request unter einem Handler läuft, für den er nie validiert wurde – der öffentliche Beitragssammlung.
Injection (CVE-2026-60137). Dieser fehlgeleitete Aufruf erreicht WP_Query mit einem vom Angreifer kontrollierten author__not_in. Ein UNION ALL SELECT dort liest keine Zeilen – es erfindet sie, sodass wir beliebige Beiträge im Abfrageergebnis fälschen können.
SQL → Schreibbrücke. Die gefälschten Beiträge enthalten [embed]-Markup. WordPress löst diese getreulich auf und speichert sie als echte oembed_cache-Beitrags-Meta-Zeilen zwischen – eine schreibgeschützte Injection wird so zu einer beliebigen Schreib-Grundlage.
Rechteerweiterung. Mit dieser Schreibbrücke fälschen wir (a) ein Customizer-Changeset, das als echter Administrator verfasst ist, und (b) einen selbstreferenziellen request-Beitrag. Zusammen zwingen sie WordPress, seine eigene Request-Pipeline als dieser Admin erneut auszuführen – ein Fenster, das gerade lang genug ist, damit ein in die Warteschlange gestellter POST /wp/v2/users-Aufruf einen neuen Administrator erstellt.
Shell. Melden Sie sich als neuer Admin an, laden Sie ein Plugin hoch, führen Sie den Befehl aus. Das Konto, seine Meta, die oembed-Zeilen und die Webshell werden anschließend bereinigt – es bleibt kein bleibender Fußabdruck zurück.
Für die Lese-Seite (--dump, Prefix-Auflösung, gecachte IDs) wählt das Tool automatisch das schnellste Orakel aus, das funktioniert, und hält beim ersten Treffer an:
union – In-Band-UNION: Der post_title des gefälschten Beitrags enthält |||HEX(wert)|||, was unverändert im /wp/v2/posts-Antwortkörper zurückgespiegelt wird. Liest einen gesamten Wert (beliebige Länge) in einer Anfrage. Wenn UNION reflektiert, werden die booleschen Orakel nie getestet.bool – Boolesches X-WP-Total-Orakel: Der X-WP-Total-Header der verwirrten Sub-Antwort ist count>0 für true / 0 für false. Ein Bit pro Request (Bisektion).time – Zeitbasiertes SLEEP(): Der Fallback, wenn der Körper entfernt und die Antwort ein 500 ist (z. B. ein Post-Dispatch-Fatal von einem Caching-mu-Plugin). Funktioniert auch bei Zielen, die bei jedem REST-Aufruf abstürzen.--dump verwendet GROUP_CONCAT, um den gesamten Berechtigungssatz auf einmal zu ziehen, wenn UNION verfügbar ist.
Keine. Nur Python 3.7+ Standardbibliothek – kein pip install.
chmod +x wp2shell.py
# 1. Ist es verwundbar? (keine Injection, keine Schreibvorgänge, kein Konto erstellt)
./wp2shell.py http://ziel --check
# 2. Schreibgeschützter Credential-Dump – ERSTER Administrator + Options-Geheimnisse
./wp2shell.py http://ziel --dump
# …jeder Administrator
./wp2shell.py http://ziel --dump all
# 3. Vollständiges RCE – temporären Admin erstellen, Befehl ausführen, bereinigen
./wp2shell.py http://ziel --exec "id; uname -a"
./wp2shell.py http://ziel --user-list # vollständige Benutzertabelle (Logins/E-Mails/Rollen)
./wp2shell.py http://ziel --content # jeden Beitragstyp bei status=any (privat/entwurf)
./wp2shell.py http://ziel --content full # …einschließlich des rohen Texts jedes Elements
./wp2shell.py -f ziele.txt --check --target-threads 8
| Flag | Zweck |
|---|---|
url / -f DATEI | Einzelnes Ziel oder eine Datei mit Zielen (eine URL pro Zeile) |
--check | Nur Sicherheitsüberprüfung – keine Injection, Schreibvorgänge oder Konto |
--dump [all] | Dump des Hashs + Geheimnisse des ersten Admins; all = jeder Admin |
--exec BEFEHL | Temporären Admin erstellen → BEFEHL ausführen → bereinigen |
--user-list | Erweiterter In-Band-Lesezugriff auf die gesamte Benutzertabelle (kein Login, umgeht 2FA) |
--content [full] | Erweiterter Lesezugriff auf jeden Beitragstyp bei status=any; full fügt Körper hinzu |
--oracle {auto,union,bool,time} | Extraktionsorakel erzwingen (Standard auto) |
--prefix PREFIX | Tabellenprefix erzwingen anstatt automatisch aufzulösen |
--impersonate UID | Diese Benutzer-ID anstelle des ersten Admins imitieren |
--threads N | Gleichzeitige Extraktionssonden innerhalb eines Ziels (Standard 5) |
--target-threads N | N Ziele aus -f gleichzeitig scannen (Standard 1) |
--proxy URL | Über einen Proxy leiten (z. B. Burp http://127.0.0.1:8080) |
--timeout SEK | Timeout pro Anfrage (Standard 30) |
--verbose / -v | Rohe Batch-Sub-Responses ausgeben (Debugging) |