
Proof-of-Concept-Exploit für CVE-2026-64638: reflektiertes XSS im WordPress-Login, verkettet mit DOM Clobbering, zur Übernahme des Admin-Kontos und Remote-Code-Ausführung.
Software: WordPress Core ≤ 7.0.2 (alle Versionen vor 7.0.3)
CVSS: 8.9 (Hoch)
CWE: CWE-79 — Unsachgemäße Neutralisierung von Eingaben während der Webseitengenerierung
Erforderliche Authentifizierung: Keine (Pre-Auth)
Benutzerinteraktion: Aktiv (Admin muss 1 Link anklicken)
Auswirkung: XSS → Kontoübernahme → Remote Code Execution
WordPress ist das beliebteste Content-Management-System der Welt und macht über 40 % aller Websites im Internet aus. Jede WordPress-Site hat eine Login-Seite unter /wp-login.php — dies ist ein öffentlicher Endpunkt, den jeder ohne Authentifizierung aufrufen kann.
Wenn ein Benutzer einen falschen Benutzernamen eingibt, zeigt WordPress eine Fehlermeldung an, die genau den Benutzernamen enthält, den der Benutzer gerade eingegeben hat: „Der Benutzername X ist auf dieser Site nicht registriert.“ Das Problem liegt darin, dass der Benutzerwert direkt in die HTML-Antwort eingefügt wird, ohne eine Escape-Funktion zu durchlaufen — ein Angreifer muss lediglich HTML/JavaScript anstelle eines echten Benutzernamens eingeben, und der Code wird im Browser ausgeführt.
Dies ist ein Reflected-XSS-Fehler — das Payload ist in der Anfrage enthalten und wird vom Server unverändert im HTML zurückgespiegelt. Gefährlich macht ihn, dass der Fehler auf der Login-Seite liegt — einem Ort, der häufig von Admins aufgerufen wird und an dem Admin-Session-Cookies gestohlen werden können.
Das Forschungsteam entdeckte außerdem, dass dieses XSS mit einer DOM-Clobbering-Schwachstelle im emoji-loader von WordPress verkettet werden kann, sodass JavaScript von einem externen Server geladen werden kann. Von dort aus kann ein Angreifer ein neues Admin-Konto erstellen → ein Plugin mit einer Webshell installieren → PHP-Code auf dem Server ausführen. Diese Exploit-Kette wird als XSS2Shell bezeichnet.
| Attribut | Wert |
|---|---|
| CVE-ID | CVE-2026-64638 |
| CVSS-Score | 8.9 (Hoch) |
| Software | WordPress Core ≤ 7.0.2 |
| Authentifizierung | Keine erforderlich (Pre-Auth) |
| Benutzerinteraktion | Erfordert 1 Klick (Admin klickt Link) |
| Angriffskomplexität | Hoch |
| Gepatcht | WordPress 7.0.3 (08/06/2026) |
| Reporter | pwn.ai-Team über HackerOne |
| HackerOne-Bericht | #3877102 |
WordPress lädt auf jeder Seite (einschließlich der Login-Seite) Emoji-Unterstützung über die Datei emoji-loader.js. Dieses Skript liest die Konfiguration aus einem Element mit id="wp-emoji-settings":
// Before patch (vulnerable)
const settings = JSON.parse(
document.getElementById('wp-emoji-settings').textContent
);
document.getElementById() gibt das erste Element im DOM mit passender ID zurück. Wenn ein Angreifer ein <div id="wp-emoji-settings"> vor dem ursprünglichen Skript-Tag einfügt, liest getElementById den Inhalt des Angreifers anstelle der echten Konfiguration. Diese Technik wird DOM Clobbering genannt — das Überschreiben des JavaScript-Verhaltens durch das Injizieren von HTML-Elementen.
Die Emoji-Konfiguration enthält eine URL zum Laden einer JavaScript-Datei (concatemoji). Der Angreifer kontrolliert diese URL → lädt eine JS-Datei von einem externen Server → führt innerhalb des Browser-Kontexts beliebigen Code aus.
Sobald die JavaScript-Ausführung im Admin-Kontext erreicht ist, verfügt der Angreifer über volle WordPress-Admin-Rechte:
/wp-admin/user-new.php mit der Admin-Session aufrufen/wp-admin/plugin-install.php hochladenJede der 3 oben genannten Methoden ermöglicht die Ausführung von PHP-Code auf dem Server — also RCE.
Aus dem Fix-Commit 0d6d42e auf wordpress-develop habe ich 3 Stellen in der Datei wp-includes/user.php identifiziert, an denen der Benutzername/die E-Mail direkt in die Fehlermeldung eingefügt wird:
# View diff between vulnerable and patched versions
git diff 7.0.2..7.0.3 -- src/wp-includes/user.php
Stelle 1 — Zeile 189 (Benutzername existiert nicht):
Vorher :
// BEFORE (vulnerable):
sprintf(
__( 'The username <strong>%s</strong> is not registered...' ),
$username // ← no escaping
)

Nachher :
// fixed:
sprintf(
__( 'The username <strong>%s</strong> is not registered...' ),
esc_html( $username ) // ← escaped
)
Stelle 2 — Zeile 216 (falsches Passwort):
Vorher :

// BEFORE:
'<strong>' . $username . '</strong>' // ← no escaping
Nachher :
// AFTER:
'<strong>' . esc_html( $username ) . '</strong>'
Stelle 3 — Zeile 299 (falsches Passwort für E-Mail):
Vorher :

// BEFORE:
'<strong>' . $email . '</strong>' // ← no escaping
Nachher :
// AFTER:
'<strong>' . esc_html( $email ) . '</strong>'
Datenfluss von der POST-Anfrage bis zur Fehlermeldung:
$_POST['log'] ← Benutzereingabe aus dem Login-Formular
↓
wp_signon() [user.php:51]
$credentials['user_login'] = wp_unslash($_POST['log']) ← entfernt nur Backslashes
↓
wp_authenticate($username, $password) [pluggable.php:689]
$username = sanitize_user($username) ← entfernt HTML-Tags, hat aber einen Bypass
↓
wp_authenticate_username_password() [user.php:153]
get_user_by('login', $username) ← Benutzer nicht gefunden
↓
sprintf('The username <strong>%s</strong>...', $username) ← XSS!
↓
WP_Error → login_header() → wp_admin_notice()
wp_kses_post(...) ← filtert HTML, lässt aber <div>, <a>, durch
↓
HTML-Antwort → Browser-Rendering → JavaScript-Ausführung
WordPress hat 2 Filterebenen, bevor der Benutzername das HTML erreicht:
Ebene 1: sanitize_user() — Ruft strip_tags() auf, um HTML-Tags zu entfernen. Allerdings hat PHP's strip_tags() bekannte Einschränkungen: nicht standardkonforme Tag-Formatierung kann den Filter umgehen.
Ebene 2: wp_kses_post() — Lässt eine sichere Teilmenge von HTML durch, einschließlich <div>, <a>, `` mit bestimmten Attributen (entfernt aber Event-Handler wie onerror, onload). Entscheidend: wp_kses_post erlaubt <div id="wp-emoji-settings"> — genau das Element, das für DOM Clobbering benötigt wird.
Das pwn.ai-Team fand einen Weg, beide Ebenen zu umgehen, um ein nutzbares Payload zu injizieren. Spezifische technische Details wurden nicht öffentlich veröffentlicht.
Um visuell zu bestätigen, dass der Benutzername ohne Escaping direkt ins HTML gelangt, habe ich Xdebug + VS Code verwendet, um Haltepunkte an Schlüsselstellen der Ausführungskette zu setzen.
Schritt 1 — XSS-Payload in das Login-Formular eingeben:
Rufe http://localhost:8282/wp-login.php auf, gib als Benutzernamen `` ein und klicke dann auf „Anmelden“. Ein Alert-Popup erscheint — XSS funktioniert.