
Caccia alle tracce di sfruttamento di CVE-2026-18963 (acquisizione non autenticata di account Keycloak) nel database di Keycloak.
Uno script psql che cerca in un database PostgreSQL di Keycloak le tracce
dello sfruttamento di CVE-2026-18963 (takeover dell'account non autenticato
attraverso il flusso di reset delle credenziali).
Pubblicato da KYOS. L'abbiamo scritto mentre applicavamo le patch alle istanze Keycloak che gestiamo, per verificare che nessuno fosse stato preso di mira durante la finestra di esposizione.
CVE-2026-18963 è una falla nel flusso di reset delle credenziali di keycloak-services
(keycloak#51833). Un
attaccante non autenticato può completare la procedura di reset della password per qualsiasi utente
senza cliccare sul link di verifica via email, e poi impostare una nuova password
sull'account. Non è richiesta alcuna interazione da parte dell'utente.
Verificato su Keycloak 26.7.1 su un'istanza usa e getta. Non pubblichiamo i passaggi di riproduzione; i dettagli seguenti sono quelli necessari per leggere le query.
Il flusso tiene traccia della schermata dell'autenticatore su cui si trova in un modo che non è legato
al passaggio che l'ha impostata. Questo permette di rientrare nel flusso di reset delle credenziali
al passaggio di verifica via email, dove il vulnerabile ResetCredentialEmail.action()
restituisce successo senza mai verificare la presenza di un action token. L'esecuzione
prosegue poi fino al modulo di aggiornamento della password.
Due conseguenze plasmano ogni query in questo repo:
SEND_RESET_PASSWORD. L'assenza di tale evento non è una firma.action() contrassegna l'indirizzo email dell'account come verificato
prima di restituire. Il percorso legittimo non raggiunge mai action(), quindi
questa è una modifica di stato che solo il bypass produce. La Q3 si basa su questo.La correzione (keycloak#51844)
lega lo stato della schermata all'esecuzione che l'ha impostato, e consente ad action()
di avere successo solo quando è presente un action token per quello specifico utente.
Gravità: Critica, CVSS v3.1 9.1 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N),
secondo Red Hat e
NVD. La causa principale è
una convalida impropria dello stato nel flusso di autenticazione, corretta in
keycloak#51844.
| Stream | Corretta in |
|---|---|
| 26.7.x | 26.7.2 (note di rilascio) |
| 26.6.x | 26.6.6 |
| 26.4.x (LTS) | 26.4.15 |
| 26.8 | 26.8.0 |
Tutto ciò che è al di sotto di queste versioni negli stream supportati è vulnerabile. Le release fuori supporto (26.5.x, 26.3 e precedenti) non ricevono correzioni: presumi l'esposizione e aggiorna a uno stream corretto. Red Hat elenca il legacy RH-SSO 7 come non interessato; il Red Hat Build of Keycloak 26.4/26.6 è corretto in 26.4.15-1 / 26.6.6-1.
Disabilitare il reset della password self-service rimuove il punto di ingresso vulnerabile:
Console di amministrazione > Impostazioni realm > Login > "Password dimenticata" disattivato, per ogni
realm. Tramite l'API di amministrazione: PUT /admin/realms/{realm} con
{"resetPasswordAllowed": false}.
Questo blocca l'endpoint login-actions/reset-credentials ma impedisce anche agli
utenti legittimi di reimpostare la propria password, quindi trattalo come una soluzione temporanea
finché non aggiorni. Nota che Red Hat non elenca una mitigazione approvata per
questa CVE; l'applicazione della patch è l'unica vera correzione.
La prima versione di questo script segnalava un reset completato che non aveva
un SEND_RESET_PASSWORD poco prima.
Sbagliato, e
vilmosnagy l'ha fatto notare
dopo aver riprodotto la CVE sulla propria istanza e aver visto l'evento comparire
comunque. Il motivo è la prima conseguenza di cui sopra: l'autenticatore mail viene eseguito
normalmente, quindi la vittima riceve una vera mail di reset e Keycloak registra un normale
SEND_RESET_PASSWORD. Il controllo era invertito. Avrebbe cancellato ogni
account compromesso che avesse esaminato. È stato rimosso.
Gli schemi basati sul timing hanno un problema più piccolo ma reale. L'approccio nello
script di caccia di thomasdelorge
correla SEND_RESET_PASSWORD con UPDATE_PASSWORD su un code_id condiviso
e segnala un intervallo breve. La chiave di correlazione è corretta, e l'autore è esplicito
sul fatto che la soglia è euristica. I nostri test dicono lo stesso: un reset aggirato
e uno legittimo completato nello stesso browser producono tipi di evento identici, chiavi di dettaglio identiche e lo stesso code_id. Solo il ritardo differisce,
il che lascia alla soglia l'intero verdetto. Chi non ha fretta resta
sotto di essa. Gli utenti reali la superano, perché il code_id rimane lo stesso solo quando il
link viene aperto nel browser che ha avviato il flusso, che è anche il caso
in cui la mail è probabilmente già aperta.
Non abbiamo trovato alcun evento, alcun dettaglio di evento e alcun errore che separi i due. Non c'è alcun evento "passaggio mail saltato" da cercare. Ciò che esiste è un effetto collaterale di stato, ed è quello che usa Q3.
cve-2026-18963-keycloak-hunt.sql esegue sei query in sola lettura:
| Query | Cosa trova |
|---|---|
| Q0 | Se il realm memorizza gli eventi, il loro TTL e se i dettagli degli eventi vengono persistiti. Q2 correla sul dettaglio code_id, quindi se i dettagli mancano Q2 è cieca e il suo silenzio non significa nulla. |
| Q1 | Ogni impostazione di credenziale password all'interno della finestra di esposizione (credential.created_date). Questo è il takeover stesso, e sopravvive alla scadenza degli eventi. |
| Q2 | Ogni aggiornamento di credenziale completato, correlato con la mail di reset della stessa sessione di autenticazione, e classificato. Vedi sotto. |
| Q3 | L'effetto collaterale deterministico: utenti con email_verified senza una causa legittima. |
| Q4 | Reset di credenziali e scritture utente tramite Admin-API, per spiegare le righe di Q1 e Q3 ed escludere la via guidata dall'admin. |
| Q5 | Timeline grezza degli eventi di reset/credenziali, per leggere un singolo caso manualmente. |
| Verdetto | Significato |
|---|---|
NO_MAIL_WAS_SENT | L'aggiornamento della credenziale condivide un code_id con un invio fallito: nessun indirizzo sull'account, o SMTP lo ha rifiutato. Nessun link ha mai lasciato il server, quindi nessuno avrebbe potuto aprirne uno, eppure il flusso ha comunque raggiunto il modulo della password. Legittimamente impossibile; trattare come sfruttamento. |
CLEARED_TOKEN_CLICKED | Il reset è stato completato con un code_id diverso da qualsiasi SEND_RESET_PASSWORD per quell'utente. Solo un action token consumato in un altro browser o sessione produce ciò, quindi il link della mail è stato davvero cliccato. Deterministicamente legittimo. |
CANDIDATE | Stesso code_id dell'invio. Questo è l'aspetto del bypass, e anche l'aspetto di un reset legittimo nello stesso browser. Non è un riscontro di per sé. |
NOT_RESET_FLOW | Nessun SEND_RESET_PASSWORD per quell'utente nella finestra; la password è cambiata in qualche altro modo (account console, admin API, registrazione). Verificare con Q4. |