
Procure por rastros de exploração do CVE-2026-18963 (tomada de conta não autenticada do Keycloak) no banco de dados do Keycloak.
Um script psql que procura numa base de dados PostgreSQL do Keycloak por rastros da exploração da CVE-2026-18963 (account takeover não autenticado através do fluxo de reset-credentials).
Publicado pela KYOS. Escrevemo-lo enquanto aplicávamos patches às implementações do Keycloak que operamos, para verificar que ninguém tinha sido comprometido durante a janela de exposição.
A CVE-2026-18963 é uma falha no fluxo de reset-credentials do keycloak-services (keycloak#51833). Um atacante não autenticado consegue concluir o processo de reposição de palavra-passe de qualquer utilizador sem clicar no link de verificação enviado por email, e depois definir uma nova palavra-passe na conta. Não é necessária qualquer interação do utilizador.
Verificado contra o Keycloak 26.7.1 numa instância descartável. Não estamos a publicar passos de reprodução; os detalhes abaixo são os necessários para ler as consultas.
O fluxo regista em que ecrã de autenticação se encontra de uma forma que não está ligada ao passo que o definiu. Isso permite reentrar no fluxo de reset-credentials no passo de verificação por email, onde o vulnerável ResetCredentialEmail.action() devolve sucesso sem nunca verificar a existência de um action token. A execução prossegue então para o formulário de atualização de palavra-passe.
Duas consequências moldam todas as consultas neste repositório:
SEND_RESET_PASSWORD normal. A ausência desse evento não é uma assinatura.action() vulnerável marca o endereço de email da conta como verificado antes de retornar. O caminho legítimo nunca chega a action(), pelo que esta é uma alteração de estado que só o bypass produz. A Q3 assenta nisto.A correção (keycloak#51844) liga o estado do ecrã à execução que o definiu, e só permite que action() tenha sucesso quando está presente um action token para esse utilizador exato.
Severidade: Crítica, CVSS v3.1 9.1 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N), segundo a Red Hat e a NVD. A causa raiz é uma validação de estado inadequada no fluxo de autenticação, corrigida em keycloak#51844.
| Linha | Corrigida em |
|---|---|
| 26.7.x | 26.7.2 (notas de lançamento) |
| 26.6.x | 26.6.6 |
| 26.4.x (LTS) | 26.4.15 |
| 26.8 | 26.8.0 |
Tudo abaixo dessas versões nas linhas suportadas está vulnerável. As versões fora de suporte (26.5.x, 26.3 e anteriores) não recebem correção: assuma a exposição e atualize para uma linha corrigida. A Red Hat indica o legado RH-SSO 7 como não afetado; o Red Hat Build of Keycloak 26.4/26.6 está corrigido em 26.4.15-1 / 26.6.6-1.
Desativar a reposição de palavra-passe self-service remove o ponto de entrada vulnerável: Consola de administração > Realm settings > Login > "Forgot password" desligado, para todos os realms. Através da API de administração: PUT /admin/realms/{realm} com {"resetPasswordAllowed": false}.
Isto bloqueia o endpoint login-actions/reset-credentials, mas também impede os utilizadores legítimos de repor a sua própria palavra-passe, pelo que deve ser encarado como uma medida provisória até atualizar. Note que a Red Hat não indica uma mitigação aprovada para esta CVE; aplicar o patch é a única correção real.
A primeira versão deste script sinalizava uma reposição concluída que não tinha um SEND_RESET_PASSWORD pouco antes.
Isso estava errado, e
vilmosnagy apontou isso
depois de reproduzir o CVE na própria instância e observar o evento aparecer
mesmo assim. O motivo é a primeira consequência acima: o autenticador de e-mail
é executado normalmente, então a vítima recebe um e-mail de redefinição real e o Keycloak registra um
SEND_RESET_PASSWORD comum. A verificação estava invertida. Ela teria limpado todas as
contas comprometidas que analisasse. Ela foi removida.
Esquemas baseados em tempo têm um problema menor, mas real. A abordagem no
script de caça do thomasdelorge
correlaciona SEND_RESET_PASSWORD com UPDATE_PASSWORD em um code_id compartilhado
e sinaliza um intervalo curto. A chave de correlação está correta, e o autor é explícito
de que o limiar é heurístico. Nossos testes dizem o mesmo: uma redefinição contornada
e uma legítima concluída no mesmo navegador produzem tipos de evento idênticos, chaves de detalhe idênticas e o mesmo code_id. Apenas o atraso difere,
o que deixa o limiar carregando todo o veredito. Quem não tem pressa fica
abaixo dele. Usuários reais o ultrapassam, porque o code_id só permanece o mesmo quando o
link é aberto no navegador que iniciou o fluxo, que também é o caso
em que o e-mail provavelmente já está aberto.
Não encontramos nenhum evento, nenhum detalhe de evento e nenhum erro que separe os dois. Não há um evento de "etapa de e-mail ignorada" para procurar. O que existe é um efeito colateral de estado, e é isso que o Q3 usa.
cve-2026-18963-keycloak-hunt.sql executa seis consultas somente leitura:
| Consulta | O que ela encontra |
|---|---|
| Q0 | Se o realm armazena eventos, seu TTL e se os detalhes dos eventos estão sendo persistidos. O Q2 correlaciona pelo detalhe code_id, então se os detalhes estiverem ausentes o Q2 fica cego e seu silêncio não significa nada. |
| Q1 | Toda credencial de senha definida dentro da janela de exposição (credential.created_date). Este é o próprio takeover, e ele sobrevive à expiração dos eventos. |
| Q2 | Toda atualização de credencial concluída, correlacionada com o e-mail de redefinição da mesma sessão de autenticação, e classificada. Veja abaixo. |
| Q3 | O efeito colateral determinístico: usuários que estão email_verified sem causa legítima. |
| Q4 | Redefinições de credencial e gravações de usuário via Admin-API, para explicar as linhas do Q1 e do Q3 e descartar a rota conduzida pelo admin. |
| Q5 | Linha do tempo bruta de eventos de redefinição/credencial, para ler um caso individual manualmente. |
| Veredito | Significado |
|---|---|
NO_MAIL_WAS_SENT | A atualização de credencial compartilha um code_id com um envio falho: nenhum endereço na conta, ou o SMTP recusou. Nenhum link saiu do servidor, então ninguém poderia ter aberto um, e ainda assim o fluxo chegou ao formulário de senha. Legitimamente impossível; trate como exploração. |
CLEARED_TOKEN_CLICKED | A redefinição foi concluída sob um code_id diferente de qualquer SEND_RESET_PASSWORD para aquele usuário. Apenas um token de ação consumido em outro navegador ou sessão produz isso, então o link do e-mail realmente foi clicado. Deterministicamente legítimo. |
CANDIDATE | Mesmo code_id do envio. É assim que o bypass se parece, e também como uma redefinição legítima no mesmo navegador se parece. Não é um achado por si só. |
NOT_RESET_FLOW | Nenhum SEND_RESET_PASSWORD para aquele usuário na janela; a senha mudou de alguma outra forma (console da conta, admin API, cadastro). Verifique o Q4. |
secs_after_send é impresso para contexto. Não o use como veredito.