
Exploit für Keycloak CVE-2026-18963, der eine nicht authentifizierte Übernahme von Konten über einen Reset-Credentials-Bypass ermöglicht. Enthält sichere Erkennung, nicht-destruktiven Nachweis, vollständige Übernahme, Benutzernamen-Enumeration sowie ein Labor mit verwundbaren und gepatchten Versionen.
Mit nur einem Benutzernamen oder einer E-Mail-Adresse setzt ein nicht authentifizierter Angreifer ein beliebiges Passwort auf einem beliebigen Keycloak-Konto. Die E-Mail zum Zurücksetzen des Passworts wird an das echte Opfer zugestellt und wird nie benötigt — der Angreifer liest nie ein Postfach, klickt nie auf einen Link und besitzt weder vorherige Anmeldedaten noch eine Sitzung.
Betroffen: Keycloak 26.0.0 – 26.7.1. Behoben in 26.7.2.
Ein Befehl. Kein gültiger Benutzername erforderlich und keine Nebenwirkungen — es sendet keine E-Mail, schreibt in kein Konto und stoppt vor dem ausnutzbaren Schritt.```bash git clone https://github.com/Snizi/CVE-2026-18963-Exploit cd CVE-2026-18963-Exploit
python3 cve_2026_18963_poc.py
--base https://sso.example.com --realm YOUR_REALM
--client-id account
--redirect-uri https://sso.example.com/realms/YOUR_REALM/account/
--safe-check
Python 3.9+, nur Standardbibliothek. Nichts zu installieren.
| Exit | Befund | Bedeutung |
|:---:|---|---|
| `0` | 🔴 **VERWUNDBAR** | das geparkte E-Mail-Gate wurde ausgeliefert — der Bug selbst |
| `2` | 🟢 **GEPATCHT** | der Ablauf verzweigte zum Login und blieb dort (Fix #51844 vorhanden) |
| `2` | 🟡 **ENTSCHÄRFT** | Reset-Anmeldedaten nicht erreichbar — *Passwort vergessen* ist deaktiviert. **Kein Patch.** |
| `3` | ⚪ **UNKLAR** | nicht erkannte Antwort — **nicht als bestanden werten** |
Pro Realm ausführen — *Passwort vergessen* ist eine Einstellung pro Realm, und `master` zählt.
Details und warum der Check keinen Benutzer benötigt und nichts verändert, in
[§4a](#4a-safe-detection---safe-check--start-here).
**Wissen Sie bereits, dass Sie exponiert sind?** Springen Sie zu [Behebung](#8-remediation) und
[Erkennung / Threat Hunting](#9-detection).
### Ohne Ziel ausprobieren
Das Repo enthält ein Labor, das eine verwundbare **26.7.1** und eine gepatchte **26.7.2**
nebeneinander gegen einen identischen Realm startet, plus ein Postfach, um die Reset-E-Mail
ankommen und ungelesen bleiben zu sehen, während das Konto übernommen wird:```bash
cd lab && docker compose up -d
python3 ../cve_2026_18963_poc.py --base http://localhost:8080 \
--realm poc --client-id poc-app --safe-check # VULNERABLE
python3 ../cve_2026_18963_poc.py --base http://localhost:8100 \
--realm poc --client-id poc-app --safe-check # PATCHED
⚠️ Nur für autorisierte Tests
Dieses Repository existiert für Verteidiger, Incident Responder und autorisierte Penetrationstester. Führen Sie es nur gegen Systeme aus, die Ihnen gehören oder für die Sie eine schriftliche Genehmigung zum Testen besitzen. Alles hier wird mit einem eigenständigen verwundbaren Labor (
lab/) geliefert, sodass keine externen Systeme angefasst werden müssen, um zu lernen, wie der Fehler funktioniert. Die Ausrichtung auf Infrastruktur Dritter ohne Autorisierung ist in den meisten Rechtsordnungen illegal und wird von diesem Projekt nicht unterstützt.
Referenzen: keycloak#51833 · GHSA-4gv3-mc9p-5wqc · Fix keycloak#51844
Zwei Defekte, die verkettet sind. Keiner ist allein ausnutzbar.
services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
processAction() — jedes POST, das den Formularschlüssel tryAnotherWay trägt:```java
processor.getAuthenticationSession().setAuthNote(
AuthenticationProcessor.AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED, "true");
return createSelectAuthenticatorsScreen(model);
Die Notiz ist ein **nackter boolescher Wert ohne Aufzeichnung darüber, zu welchem Ausführungssatz sie gehört**. Sie wird
nur in dem Zweig gelöscht, der einen übermittelten `authenticationExecution`-Parameter verarbeitet. Lässt man diesen Parameter weg — wie es dieser PoC durchgängig tut — bleibt das Flag für die gesamte Lebensdauer der Authentifizierungssitzung gesetzt.
`processFlow()` — solange das Flag wahr ist, wird die normale Flussauswertung übersprungen:```java
if (Boolean.parseBoolean(authSession.getAuthNote(AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED))) {
String lastExecutionId = authSession.getAuthNote(CURRENT_AUTHENTICATION_EXECUTION);
if (lastExecutionId != null) {
AuthenticationExecutionModel executionModel =
realm.getAuthenticationExecutionById(lastExecutionId);
if (executionModel != null)
return createSelectAuthenticatorsScreen(executionModel); // <-- attacker-usable form
}
}
Es rendert ein absendbares Formular, das auf die aktuell geparkte Ausführung abzielt, anstatt die Sitzung auf „Warten auf die E-Mail" festzunageln.
Der Klebstoff ist processResult() case FORK: — wenn Send Reset Email ausgelöst wird, stempelt es
CURRENT_AUTHENTICATION_EXECUTION = <reset-credential-email execution id> und verzweigt
den Browser zur Anmeldeseite. Die geparkte Ausführung ist genau das E-Mail-Gate.
`services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialEmail.java````java @Override public void action(AuthenticationFlowContext context) { context.getUser().setEmailVerified(true); context.success(); }
Unconditional. Nothing verifies that the flow was resumed by a valid action token,
so *reaching* `action()` is treated as equivalent to proving mailbox control.
### The chain```
tryAnotherWay POST → sticky AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED="true"
submit victim identifier → mail sent to victim, e-mail execution parked (FORK)
re-enter reset-credentials → sticky flag serves a form targeting the parked e-mail execution
POST that form → ResetCredentialEmail.action() → success() → gate bypassed
→ flow advances to UPDATE_PASSWORD → attacker sets the password
Sechs HTTP-Anfragen, keine Authentifizierung, an keiner Stelle ein authenticationExecution-Parameter.