
CLI Python qui détecte et exploite CVE-2026-90907, un contournement d'autorisation dans Joomla! com_users permettant la création de comptes non authentifiés ; mode vérification non destructif plus PoC.
POUR DES TESTS DE SÉCURITÉ AUTORISÉS UNIQUEMENT. Exécuter le mode exploit contre des systèmes que vous ne possédez pas ou pour lesquels vous ne disposez pas d'une autorisation écrite explicite de test est illégal. Le mode par défaut (
CHECK) est non destructif et ne crée rien.
Outil en ligne de commande qui détecte et (facultativement, avec -U) exploite
CVE-2026-90907, un
contournement d'autorisation de sévérité moyenne (CVSS 6.9) dans le cœur de Joomla! CMS
qui permet à un attaquant distant non authentifié de déclencher la création
non autorisée de comptes utilisateur via le contrôleur com_users → profile.save —
même lorsque l'inscription publique est désactivée dans la Configuration globale.
| Champ | Valeur |
|---|---|
| ID CVE | CVE-2026-90907 |
| Sévérité | Moyenne — CVSS 6.9 |
| Type | Contournement d'autorisation / contrôle d'accès inapproprié (CWE-862) |
| Composant | Composant com_users, tâche profile.save |
| Vecteur d'attaque | Distant, non authentifié, POST en deux étapes dans la même session |
| Corrigé dans | Joomla! 5.4.9 et 6.1.4 (version de sécurité, 2026-09-29) |
Selon le bulletin de sécurité Joomla! (2026-09-29) et les sources d'avis publics :
Joomla! 3.x est également affecté mais en fin de vie : il ne recevra pas de correctif officiel — prévoyez une migration. La vérification de la bannière de version de l'outil est informative ; le verdict est comportemental (voir ci-dessous).
Le compte contrôlé par l'attaquant est un compte front-end à faibles privilèges sans accès administratif ni back-end. Néanmoins, cela permet à un attaquant de :
ProfileModel::save() ne fait unset() de $data['username'] que lorsque le nom d'utilisateur
est considéré comme conforme. isUsernameCompliant est dérivé de l'état utilisateur
com_users.edit.profile.data, que le contrôleur remplit avec la
requête complète lorsque la validation du formulaire échoue. Le contrôleur ne parvient pas non plus à
vérifier l'état de connexion de la requête entrante. Par conséquent, au sein d'une même session
invité :
privacyconsent → la validation échoue → le nom d'utilisateur non nettoyé est conservé dans
l'état utilisateur de la session (rien n'est encore créé).jform[privacyconsent][privacy]=1 → la garde unset() est contournée →
INSERT INTO #__users → redirection 303 avec user_id > 0.user.login (le nom du champ est
password, pas passwd) avec le mot de passe correct vs. un mot de passe incorrect et
lecture du message flash : correct → « you cannot access the private section »
(auth OK, ACL bloque) / incorrect → « do not match ». Le différentiel
constitue la preuve.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
| Option | Description |
|---|---|
TARGET | URL de base (normalisée ; sans barre oblique finale) |
-U/--username | Active le mode EXPLOIT. Sans -U → CHECK non destructif |
-P/--password | Par défaut : généré avec secrets, 16 caractères alphanumériques + symboles, toujours affiché |
-E/--email | Par défaut : <username>@example.invalid |
-N/--name | Par défaut : CVE Audit |
-o/--output | Répertoire de preuves (par défaut evidence_cve90907_<ts> sous le cwd) |
--proxy URL | Proxy pour les requêtes (Burp : http://127.0.0.1:8080) |
--timeout | Par défaut 20 s |
--ua | User-Agent (par défaut cve-2026-90907-audit/1.0) |
--no-verify-login | Ignore le différentiel de connexion (mode exploit) |
--verify-ssl | Vérifier TLS (par défaut : ne pas vérifier) |
--no-color | Pas de couleurs ANSI |
-v/--verbose | Journal complet req/resp sur stderr |
| Code | Signification |
|---|---|
0 | Ok / non vulnérable / inconnu |
1 | Vulnérable (LIKELY en check, CONFIRMED en exploit) |
2 | Erreur opérationnelle (réseau, dépendance, etc.) |
profile dans Location, pas de redirection vers la connexion) + flash
d'erreur de validation (par ex. message de politique de confidentialité requise). Confirmation définitive =
mode exploit.summary.json.Location avec user_id > 0 après W2.Chaque exécution écrit un répertoire -o horodaté contenant 00_token.txt,
01_version.txt, 02_w1_seed.txt, 03_w2_exploit.txt,
04_login_correct.txt, 05_login_wrong.txt et summary.json. Chaque fichier
enregistre la REQUEST complète (méthode/URL/en-têtes clés/corps), la RESPONSE (statut,
Location, Server, flash extrait, 500 premiers octets du corps) et les horodatages
ISO — prêt à être joint à un rapport de pentest.
privacyconsent et utilise un nom d'utilisateur
non conforme (probe()) : l'enregistrement échoue sur les règles du formulaire et rien n'est persisté.user_id > 0 (ce qui ne devrait pas arriver), le mode
exploit s'abandonne avant W2.user_usergroup_map).