
Proof-of-concept exploit for CVE-2026-18963, an authentication bypass in Keycloak's forgot-password flow, allowing password reset without proper verification. Includes non-destructive proof mode.
Proof of Concept for CVE-2026-18963, a flaw in Keycloak's "Forgot Password" /
credential-selection flow that allows an attacker to reach the
UPDATE_PASSWORD step — and reset a target account's password — without
properly completing the required identity verification (e.g. without a valid
OTP/second factor).
The script drives the flow purely over HTTP (no browser/Selenium required) by:
tryAnotherWay.startSessionPolling() restart URL used by
the Keycloak UI to keep the auth session alive.UPDATE_PASSWORD.This tool is provided for authorized security testing and research only (penetration tests you are engaged to perform, CTFs, your own lab environments, or coordinated vulnerability disclosure). Running it against systems you do not own or do not have explicit written authorization to test is illegal. The author and contributors assume no liability for misuse or damage caused by this tool.
Use --stop-at-update-password to produce a non-destructive proof (reaching
UPDATE_PASSWORD without submitting a new password) whenever that is
sufficient to demonstrate the issue.
requestsbeautifulsoup4git clone <this-repo-url>
cd POC
python -m venv .venv
source .venv/bin/activate # on Windows: .venv\Scripts\activate
pip install -r requirements.txt
python poc_cve_2026_18963_terminal.py \
--base-url http://localhost:8080 \
--realm master \
--client-id account \
--username admin \
--new-password "NewPassword123!"
| Flag | Default | Description |
|---|---|---|
--base-url | http://localhost:8080 | Base URL of the target Keycloak instance |
--realm | master | Target realm |
--client-id | account | OIDC client ID used to initiate the login flow |
--username | admin | Target username |
--new-password | (required) | Password to set for the target account |
--stop-at-update-password | off | Stop once UPDATE_PASSWORD is reached, without submitting a new password (non-weaponized proof) |
--timeout | 10.0 | HTTP request timeout in seconds |
--output-dir | debug_http | Directory where each intermediate HTML response is saved for debugging |
--insecure | off | Disable TLS certificate verification |
python poc_cve_2026_18963_terminal.py \
--base-url https://target.example.com \
--realm myrealm \
--username victim \
--new-password unused \
--stop-at-update-password
Every intermediate HTTP response is saved to --output-dir (debug_http by
default) as 01_login.html, 02_reset.html, ... for offline review. The
script exits 0 on success and 1 on failure, printing progress and
diagnostic messages to stdout.
Aurélien LOIE