
Exploit pour Keycloak CVE-2026-18963 permettant la prise de contrôle de compte sans authentification via le contournement de reset-credentials. Inclut une détection sûre, une preuve non destructive, une prise de contrôle complète, l'énumération de noms d'utilisateur et un laboratoire avec des versions vulnérables et corrigées.
En connaissant seulement un nom d'utilisateur ou une adresse e-mail, un attaquant non authentifié définit un mot de passe arbitraire sur n'importe quel compte Keycloak. L'e-mail de réinitialisation de mot de passe est envoyé à la vraie victime et n'est jamais nécessaire — l'attaquant ne lit jamais une boîte mail, ne clique jamais sur un lien et ne détient aucun identifiant ni session préalables.
Affecté : Keycloak 26.0.0 – 26.7.1. Corrigé dans 26.7.2.
Une seule commande. Aucun nom d'utilisateur valide requis, et aucun effet secondaire — elle n'envoie aucun e-mail, n'écrit sur aucun compte et s'arrête avant l'étape exploitable.```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
| Sortie | Verdict | Signification |
|:---:|---|---|
| `0` | 🔴 **VULNÉRABLE** | la passerelle e-mail en attente a été servie — le bug lui-même |
| `2` | 🟢 **CORRIGÉ** | le flux a bifurqué vers la connexion et y est resté (correctif #51844 présent) |
| `2` | 🟡 **ATTÉNUÉ** | reset-credentials inaccessible — *Mot de passe oublié* est désactivé. **Pas un correctif.** |
| `3` | ⚪ **NON CONCLUSIF** | réponse non reconnue — **ne le prenez pas pour un succès** |
Exécutez-le par realm — *Mot de passe oublié* est un paramètre par realm, et `master` compte.
Détails, et pourquoi la vérification n'a besoin d'aucun utilisateur et ne touche à rien, dans
[§4a](#4a-safe-detection---safe-check--start-here).
**Vous savez déjà que vous êtes exposé ?** Passez à [correction](#8-remediation) et à
[détection / recherche de menaces](#9-detection).
### Essayez sans cible
Le dépôt fournit un laboratoire qui démarre une version vulnérable **26.7.1** et une version corrigée **26.7.2**
côte à côte contre un realm identique, ainsi qu'une boîte aux lettres pour observer l'e-mail de réinitialisation
arriver et rester non lu pendant que le compte est détourné :```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
⚠️ Tests autorisés uniquement
Ce dépôt existe pour les défenseurs, les intervenants en gestion d'incidents et les testeurs d'intrusion autorisés. Exécutez-le sur des systèmes que vous possédez ou pour lesquels vous disposez d'une autorisation écrite de tester. Tout ce qui se trouve ici est fourni avec un laboratoire vulnérable autonome (
lab/), donc rien d'externe n'a besoin d'être touché pour comprendre le fonctionnement du bug. L'utiliser contre une infrastructure tierce sans autorisation est illégal dans la plupart des juridictions et n'est pas soutenu par ce projet.
Références: keycloak#51833 · GHSA-4gv3-mc9p-5wqc · correction keycloak#51844
Deux défauts enchaînés. Aucun des deux n'est exploitable seul.
services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
processAction() — toute requête POST contenant la clé de formulaire tryAnotherWay:```java
processor.getAuthenticationSession().setAuthNote(
AuthenticationProcessor.AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED, "true");
return createSelectAuthenticatorsScreen(model);
La note est un **booléen brut sans aucune trace de l'exécution qui l'a définie**. Elle n'est
effacée que dans la branche qui traite un paramètre `authenticationExecution`
soumis. Omettez ce paramètre — comme le fait ce PoC tout au long — et le flag reste
défini pour la durée de la session d'authentification.
`processFlow()` — tant que le flag est truthy, l'évaluation normale du flux est ignorée :```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
}
}
Cela rend un formulaire à soumettre destiné à n'importe quelle exécution actuellement en attente, au lieu de maintenir la session bloquée sur « en attente de l'e-mail ».
Le ciment est processResult() case FORK: — lorsque Envoyer l'e-mail de réinitialisation est déclenché, cela définit
CURRENT_AUTHENTICATION_EXECUTION = <reset-credential-email execution id> et redirige
le navigateur vers la page de connexion. L'exécution en attente est précisément la passerelle e-mail.
`services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialEmail.java````java @Override public void action(AuthenticationFlowContext context) { context.getUser().setEmailVerified(true); context.success(); }
Inconditionnel. Rien ne vérifie que le flux a été repris par un jeton d'action valide,
donc *atteindre* `action()` est considéré comme équivalent à prouver le contrôle de la boîte mail.
### La chaîne```
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
Six requêtes HTTP, aucune authentification, aucun paramètre authenticationExecution à aucun
moment.