
Analisi della causa principale, verifica passiva della versione e PoC di laboratorio per CVE-2026-18322, un'escalation di privilegi non autenticata nel plugin WordPress Smart Popup di Supsystic.
Ricerca di sicurezza su CVE-2026-18322, una vulnerabilità di privilege-escalation
non autenticata nel plugin WordPress Smart Popup by Supsystic
(popup-by-supsystic). Questo repository contiene un'analisi della causa radice derivata dal
diff del codice sorgente upstream, le patch estratte, uno strumento di rilevamento (fingerprinter passivo di versione) per identificare le installazioni affette e un laboratorio per il PoC di sfruttamento.
La vulnerabilità è pubblica e corretta. Questo lavoro è pubblicato per uso difensivo: aiutare gli operatori a trovare e correggere gli host affetti.
| Intervallo di versioni | Stato |
|---|---|
< 1.13.0 | Vulnerabile |
>= 1.13.0 | Corretta |
La 1.13.0 (rilasciata il 31.07.2026) è la prima release corretta; ripara tutti e tre gli anelli della catena descritta di seguito. Vedi §1 dell'analisi.
Tre debolezze indipendenti si compongono nella creazione non autenticata di un amministratore:
1. Una collisione nella mappa dei permessi — havePermissions() combinava due mappe di permessi con
array_merge(). Entrambe usano la chiave stringa PPS_USERLEVELS ('userlevels'), e
array_merge() sovrascrive le chiavi stringa, quindi la lista breve predefinita del controller di base
sostituiva in blocco la lista del modulo popup — rimuovendo silenziosamente la restrizione
di amministratore da save e da altri nove metodi:
$permissions = $mod->getController()->getPermissions(); // [... 'save' ...]
$permissionsBase = $mod->getController()->getBasePermissions(); // ['getListForTbl','removeGroup','clear']
$permissions = array_merge($permissions, $permissionsBase); // base vince — 'save' è sparito
Il controllo fallisce aperto: un'azione assente dalla mappa non viene mai negata.
2. Un nonce riutilizzabile inviato a estranei — save richiedeva comunque un pps_nonce, ma l'email
di conferma dell'iscrizione incorporava esattamente quell'azione nonce. Per gli utenti non autenticati un
nonce di WordPress è legato a uid=0, quindi il token generato per un iscritto anonimo
risulta valido per qualsiasi attaccante anonimo.
3. Nessuna allowlist di ruoli lato server — createWpSubscriber() passava il ruolo configurato
direttamente a WP_User::set_role(). La lista di ruoli sicuri del plugin (che esclude
administrator) era applicata solo durante il rendering del menu a tendina di amministrazione — un controllo solo di UI.
Concatenate: iscriviti per ottenere un nonce → riproducilo contro popup::save per impostare
sub_wp_create_user_role=administrator → attiva il flusso di iscrizione → account amministratore
persistente.
Procedura completa con riferimenti a file/righe: docs/ANALYSIS.md.
poc/cve_2026_18322_check.py determina se un sito esegue una versione affetta. Non tenta mai
lo sfruttamento: emette semplici GET HTTP e legge la versione che l'installazione
pubblica su se stessa.
git clone <this-repo> && cd CVE-2026-18322
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
requests è consigliato ma opzionale — lo strumento ripiega sulla libreria standard.
# Singolo target
python3 poc/cve_2026_18322_check.py https://example.com
# Con evidenze di supporto
python3 poc/cve_2026_18322_check.py https://example.com -v
# Molti target, in concorrenza, esportando i risultati
python3 poc/cve_2026_18322_check.py -f targets.txt -t 20 --json results.json
python3 poc/cve_2026_18322_check.py -f targets.txt --csv results.csv --only-vulnerable
# Attraverso un proxy, ignorando gli errori TLS (uso in laboratorio)
python3 poc/cve_2026_18322_check.py https://staging.internal -k --proxy http://127.0.0.1:8080
I target possono essere forniti come argomenti o tramite -f (- legge da stdin). Un hostname nudo è
assunto come https://.
$ python3 poc/cve_2026_18322_check.py https://example.com -v
[VULNERABLE] https://example.com/ (Smart Popup by Supsystic 1.11.2) < 1.13.0 affected by CVE-2026-18322; upgrade to 1.13.0+
- asset-version: plugin asset enqueued with ?ver=1.11.2 (https://example.com/)
- homepage-reference: references /plugins/popup-by-supsystic/ (https://example.com/)
- readme: plugin readme.txt is publicly readable (https://example.com/wp-content/plugins/popup-by-supsystic/readme.txt)
- readme-stable-tag: Stable tag: 1.11.2 (.../readme.txt)
| Stato | Significato |
|---|---|
VULNERABLE | Plugin rilevato a < 1.13.0 |
NOT_VULNERABLE | Plugin rilevato a >= 1.13.0 |
PLUGIN_DETECTED_VERSION_UNKNOWN | Plugin presente, versione non determinabile — verificare manualmente |
PLUGIN_NOT_DETECTED | Nessuna evidenza del plugin (non prova dell'assenza) |
ERROR | Target irraggiungibile o malformato |
Codici di uscita: 0 = nessun target vulnerabile, 1 = almeno uno vulnerabile, 2 = errore di utilizzo.
Due segnali passivi, entrambi semplici GET HTTP di risorse pubbliche:
?ver=PPS_VERSION
(classes/frame.php:410,473), quindi la homepage spesso rivela la versione esatta.readme.txt — Stable tag: è autorevole e ha la precedenza su un asset in cache
potenzialmente obsoleto; il changelog funge da fallback.La scansione della homepage scopre anche directory wp-content rinominate (es. /app/ di Bedrock)
così la ricerca del readme segue il layout reale del sito.
Lo strumento di rilevamento non tenta lo sfruttamento. Nessun exploit weaponizzato per questa CVE è pubblicato qui: l'unico codice che esegue la catena completa è rigidamente vincolato al laboratorio locale (vedi sotto).
Il checker passivo esegue solo il fingerprinting della versione. Il suo HttpClient non espone nessun
verbo HTTP diverso da GET — imposto per costruzione e verificato nella suite di test, così
non può emettere una richiesta che modifica lo stato nemmeno per errore. Non chiama mai popup::save,
non invia un parametro sub_wp_create_user_role, non invia un modulo di iscrizione, non attiva un'email
di conferma, né crea o modifica alcun utente, popup o impostazione. Legge due risorse pubbliche — la homepage e readme.txt — e nient'altro.
Questa sicurezza ha un costo, dichiarato apertamente: il verdetto vale solo quanto i metadati di versione che l'host pubblica. Compromessi e modalità di fallimento: §9.