
CLI em Python que detecta e explora a CVE-2026-90907, um bypass de autorização no com_users do Joomla! que permite a criação de contas sem autenticação; modo de verificação não destrutivo, além de PoC.
APENAS PARA TESTES DE SEGURANÇA AUTORIZADOS. Executar o modo de exploit contra sistemas que você não possui ou para os quais não tem permissão escrita explícita para testar é ilegal. O modo padrão (
CHECK) é não destrutivo e não cria nada.
Ferramenta de CLI que detecta e (opcionalmente, com -U) explora
CVE-2026-90907, uma
bypass de autorização de severidade média (CVSS 6.9) no núcleo do Joomla! CMS
que permite a um atacante remoto não autenticado disparar a criação não autorizada
de conta de usuário através do controlador com_users → profile.save —
mesmo quando o registro público está desabilitado na Configuração Global.
| Campo | Valor |
|---|---|
| ID CVE | CVE-2026-90907 |
| Severidade | Média — CVSS 6.9 |
| Tipo | Bypass de autorização / controle de acesso inadequado (CWE-862) |
| Componente | Componente com_users, tarefa profile.save |
| Vetor de ataque | Remoto, não autenticado, POST em duas etapas na mesma sessão |
| Corrigido em | Joomla! 5.4.9 e 6.1.4 (versão de segurança, 2026-09-29) |
Conforme o boletim de segurança do Joomla! (2026-09-29) e fontes de advisories públicos:
O Joomla! 3.x também é afetado, mas está em Fim de Vida (End-of-Life): não receberá um patch oficial — planeje uma migração. A verificação do banner de versão da ferramenta é informativa; o veredito é comportamental (veja abaixo).
A conta controlada pelo atacante é uma conta de front-end com baixos privilégios, sem acesso administrativo ou de backend. Ainda assim, isso permite que um atacante:
ProfileModel::save() apenas faz unset() em $data['username'] quando o nome de usuário
é considerado compatível. isUsernameCompliant é derivado do estado do usuário
com_users.edit.profile.data, que o controlador preenche com a
requisição completa quando a validação do formulário falha. O controlador também falha em
verificar o estado de login da requisição recebida. Portanto, dentro de uma sessão de convidado:
privacyconsent → a validação falha → o nome de usuário sujo é persistido no
estado do usuário da sessão (nada é criado ainda).jform[privacyconsent][privacy]=1 → a guarda unset() é ignorada →
INSERT INTO #__users → redirecionamento 303 com user_id > 0.user.login (o nome do campo é
password, não passwd) com a senha correta vs. uma incorreta e
leia a mensagem flash: correta → "you cannot access the private section"
(autenticação OK, ACL bloqueia) / incorreta → "do not match". O diferencial é
a evidência.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 | Descrição |
|---|---|
TARGET | URL base (normalizada; sem barra final) |
-U/--username | Ativa o modo EXPLOIT. Sem -U → CHECK não destrutivo |
-P/--password | Padrão: gerada com secrets, 16 caracteres alfanuméricos+símbolos, sempre exibida |
-E/--email | Padrão: <username>@example.invalid |
-N/--name | Padrão: CVE Audit |
-o/--output | Diretório de evidências (padrão evidence_cve90907_<ts> sob o cwd) |
--proxy URL | Proxy para requisições (Burp: http://127.0.0.1:8080) |
--timeout | Padrão 20 s |
--ua | User-Agent (padrão cve-2026-90907-audit/1.0) |
--no-verify-login | Ignora o diferencial de login (modo exploit) |
--verify-ssl | Verifica TLS (padrão: não verificar) |
--no-color | Sem cores ANSI |
-v/--verbose | Log completo de req/resp no stderr |
| Código | Significado |
|---|---|
0 | Ok / não vulnerável / desconhecido |
1 | Vulnerável (LIKELY no check, CONFIRMED no exploit) |
2 | Erro operacional (rede, dependência, etc.) |
profile em Location, sem redirecionamento para login) + flash
de erro de validação (ex.: mensagem de política de privacidade obrigatória).
Confirmação definitiva = modo exploit.summary.json.Location com user_id > 0 após W2.Cada execução grava um diretório -o com timestamp contendo 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. Cada arquivo
registra a REQUEST completa (método/URL/cabeçalhos-chave/corpo), a RESPONSE
(status, Location, Server, flash extraído, primeiros 500 bytes do corpo) e
timestamps ISO — pronto para anexar a um relatório de pentest.
privacyconsent e usa um nome de usuário
não conforme (probe()): o salvamento falha nas regras do formulário e nada
é persistido.user_id > 0 (o que não deveria
acontecer), o modo exploit aborta antes de W2.user_usergroup_map).MIT — consulte LICENSE.