
Prova de conceito e verificador de vulnerabilidade para CVE-2026-18963, uma tomada de conta não autenticada no fluxo de redefinição de credenciais do Keycloak. Python em arquivo único, componente de navegador Playwright opcional.
Uma prova de conceito e verificador de vulnerabilidade para o bypass do fluxo de redefinição de credenciais do Keycloak, com um componente de navegador opcional que renderiza o estado resultante em uma janela real do Chrome.
A ferramenta é um único arquivo, cve_2026_18963_poc.py, escrito com base na biblioteca padrão do Python. O Playwright é a única dependência de terceiros e é importado de forma preguiçosa, de modo que o lado HTTP é executado em um interpretador padrão sem nada instalado.
cve_2026_18963_poc.py 1009 lines, Python 3.10 or newer
Crédito. A CVE-2026-18963 foi reportada à Red Hat por James Paremain, que é creditado no aviso do fornecedor. A Orevim não descobriu nem reportou esta vulnerabilidade. Este repositório é uma prova de conceito independente escrita a partir do aviso publicado.
A CVE-2026-18963 é um bypass de autenticação no fluxo de recuperação de palavra-passe do Keycloak. São dois defeitos a atuar em conjunto.
A nota sem âmbito. Quando um utilizador abre o ecrã de seleção de autenticador,
o Keycloak escreve uma nota AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED na
sessão de autenticação. A nota é um booleano simples. Regista que um ecrã de
seleção foi apresentado, não qual execução o apresentou. A nota sobrevive à
execução que a definiu.
O handler sem proteção. ResetCredentialEmail.action() define emailVerified
no utilizador resolvido e chama success() sem verificar o token de ação
que o link enviado por email deveria transportar. O método assume que só pode ser
alcançado seguindo esse link.
Encadeados, um chamador não autenticado define a nota contra a execução de
username, avança o fluxo para que a execução de email se torne atual e depois
volta a entrar no URL de reposição. A nota obsoleta faz com que o ecrã de seleção
seja renderizado associado à execução de email. Enviar um corpo vazio para o
loginAction dessa execução invoca diretamente o handler sem proteção. O fluxo
avança para a ação obrigatória UPDATE_PASSWORD sem que nenhum email tenha sido lido.
| Identificador | CVE-2026-18963, GHSA-4gv3-mc9p-5wqc |
| Classe | CWE-640, mecanismo fraco de recuperação de palavra-passe para palavra-passe esquecida |
| CVSS v3.1 | 9.1 AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N |
| Afetado | >= 26.0.0, < 26.4.15, >= 26.5.0, < 26.6.6, >= 26.7.0, < 26.7.2 |
| Corrigido em | 26.4.15, 26.6.6, 26.7.2. A linha 26.8.0 é distribuída com a correção |
| Pacote | org.keycloak:keycloak-services (Maven) |
| Publicado | 18 de agosto de 2026 |
| Reportado por | James Paremain, creditado no aviso da Red Hat |
A única pré-condição é que o realm ofereça recuperação de palavra-passe a utilizadores não autenticados. Não é necessária qualquer credencial, token ou sessão prévia.
Oito pedidos, por ordem. As etiquetas correspondem aos rótulos dos passos que a ferramenta imprime e aos
nomes de ficheiro que escreve em --out.
| # | Etiqueta | Método | O que faz |
|---|---|---|---|
| 1 | login | GET | URL de autorização. Estabelece a sessão de autenticação e devolve o link de reposição. |
| 2 | reset | GET | Página de reposição de credenciais. Contém o formulário de username. |
| 3 | tryanother | POST | tryAnotherWay=on. Escreve a nota sem âmbito. |
| 4 | username | POST | O identificador. Avança o fluxo para que a execução de email se torne atual. Envia email de recuperação se o identificador for resolvido. |
| 5 | reenter | GET | O URL de reposição novamente. A nota obsoleta faz com que o seletor seja renderizado associado à execução de email. |
| 6 | action | POST | Corpo vazio para o loginAction dessa execução. Invoca o handler sem proteção. |
| 7 | required-action | GET | Segue o Location para a página UPDATE_PASSWORD. |
| 8 | setpassword | POST | A nova palavra-passe. Só é enviada com --set-password. |
O passo 6 é o exploit, e o corpo vazio é essencial. Enviar
authenticationExecution=<id> em vez disso é a interação normal do seletor: muda
de execução e reenvia o email de recuperação, o que nunca chega ao
defeito. O POST vazio não responde a qualquer desafio, pelo que o Keycloak chama o handler para
a execução que a nota obsoleta deixou como atual.
# Decide whether the instance is affected. Changes nothing, sends no mail.
./cve_2026_18963_poc.py --url https://sso.example.com --realm demo \
--client-id account-console --check
# Additionally report which identifiers resolve to a real account.
# Resolving accounts receive a recovery email. See the note below.
./cve_2026_18963_poc.py --url https://sso.example.com --realm demo \
--client-id account-console --check --users-file names.txt --rate 0.25
# Reach the set-password form for one account and stop there.
./cve_2026_18963_poc.py --url https://sso.example.com --realm demo \
--client-id account-console --username [email protected]
# Complete the takeover. This locks the account holder out.
./cve_2026_18963_poc.py --url https://sso.example.com --realm demo \
--client-id account-console --username [email protected] \
--set-password 'N3w-passw0rd!'
--check sonda com um identificador aleatório da forma
<16 hex>@<8 hex>.invalid. O TLD .invalid é reservado pela RFC 2606 e
não pode ser resolvido, portanto a sonda não pode colidir com uma conta real e não pode
fazer com que um e-mail seja enviado. Ela estabelece o status da build sem tocar em
ninguém.
Adicionar
--usersmuda isso. O passo 4 é o envio comum de senha esquecida. Em um realm com SMTP configurado, um identificador que resolve para um usuário com endereço de e-mail recebe um e-mail genuíno de recuperação de senha, tanto em builds afetadas quanto corrigidas. A sonda simples não recebe, porqueResetCredentialEmail.authenticate()não envia nada quando o usuário é nulo. Trate--check --userscomo um fluxo que notifica pessoas reais e confirme que está dentro da sua autorização antes de executá-lo.
A ferramenta classifica com base na resposta ao passo 6, o POST vazio.
| Resposta | Resultado | Significado |
|---|---|---|
| 400 | account-absent | O handler foi executado sem token de ação e não resolveu nenhum usuário. A build está afetada. |
302 com Location | form-reached | O handler foi executado e a conta é resolvida. Afetada, e este identificador é real. |
| Qualquer outra coisa | not-vulnerable | O POST vazio não avançou. |
| Nenhum seletor no passo 5 | not-vulnerable | A nota obsoleta não foi honrada. |
| 302 após o passo 8 | password-set | A senha foi alterada e o realm emitiu uma sessão. |
| 429 além do orçamento de tentativas | rate-limited | Sem veredicto. A execução foi interrompida. |
| Um negativo alcançado após qualquer limitação de taxa | inconclusive | Veja abaixo. |
O 400 é o importante. Ele separa a build está afetada de esta
conta existe, o que permite que --check estabeleça a exposição contra uma instância
sem enumerar um único usuário real. Resolver identificadores é então uma
questão separada e opcional.
Um negativo só é confiável se nada foi limitado por taxa. O Keycloak expira a
sessão de autenticação pelo seu próprio relógio. Um 429 no meio da cadeia custa um backoff, e uma
cadeia que retoma após uma longa pausa retorna exatamente a página que uma build corrigida
retorna. Relatar isso como não afetada é o único modo de falha que um verificador não
deve ter, então qualquer negativo alcançado após um evento de limitação de taxa é rebaixado para
inconclusive com a contagem de limitações e a recomendação de reexecutar mais devagar.
Nada sobre a implantação é codificado de forma fixa. A ferramenta lê o que precisa: