Like discovering the bank's PIN-reset kiosk lets anyone walk up and change the vault combination without showing ID
CVE-2026-18963 is a logic flaw in the keycloak-services component — the core engine of every Keycloak and Red Hat Build of Keycloak (RHBK) deployment. The reset-credentials flow, triggered by the "Forgot password" link, is supposed to send an email with a signed action token, and only let the requester set a new password after proving they control the mailbox. Two chained defects break that contract: a boolean flag (AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED) persists across the authentication session without being scoped to the execution that set it, and ResetCredentialEmail.action() reaches a success state without validating the action token. Together, an unauthenticated attacker who knows (or can guess) a target username or email can reset any account's password — including the Keycloak admin — in six HTTP requests, with no email access, no user interaction, and zero dependencies. Affected versions span Keycloak 26.0.0 – 26.4.14, 26.5.0 – 26.6.5, and 26.7.0 – 26.7.1. Fixes landed in 26.4.15, 26.6.6, 26.7.2, and 26.8.0.
Red Hat's CRITICAL 9.1 rating is dead-on. Every friction factor in this chain works in the attacker's favor: no authentication, no user interaction, low complexity, default-enabled feature, and the target component is the identity provider itself — not something that *runs alongside* an IdP, but the IdP. The CVSS vector (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N) accurately captures the access profile. The only reason the score isn't 10.0 is the absence of an availability impact (A:N) and unchanged scope (S:U). Multiple working Python PoCs appeared within days of disclosure, and the exploit is trivially reproducible. If anything, a vendor score of 9.1 is *generous to the attacker* — the real-world blast radius of an IdP admin takeover can cascade far beyond what CVSS models.
5 steps from start to impact.
Target the Keycloak login page
inurl:/realms/ inurl:/protocol/openid-connect). Keycloak login pages are designed to be publicly accessible — they are the front door for all federated authentication. The attacker identifies a target username or email address through OSINT, directory harvesting, or the application itself.- Network access to the Keycloak login page
- Knowledge of a valid username or email address
- None — Keycloak is internet-facing by design and usernames/emails are often enumerable or publicly known (corporate email = username)
http.title:"Sign in" http.html:keycloak or Censys equivalent identifies exposed instances. GreyNoise may tag scanning activity targeting /realms/*/login-actions/reset-credentials.Initiate the reset-credentials flow
/realms/{realm}/login-actions/reset-credentials) and submits the target's username or email. This triggers the normal first stage: Keycloak creates an authentication session and sets the AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED flag. In a legitimate flow, the server would send an email with a signed action token and wait.- Forgot-password feature enabled (the default)
- Only blocked if an administrator has explicitly disabled forgot-password in every realm — this breaks a core user-facing feature and most deployments leave it on
SEND_RESET_PASSWORD event, but this is identical to a legitimate reset request.Re-enter the flow to exploit the sticky flag
AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED persists without being scoped to the execution that created it, the flow evaluator incorrectly evaluates the state as having already completed the selector screen, allowing the attacker to skip forward. The attacker posts a crafted form submission that advances the session past the email-verification gate.- Active authentication session from Step 2
- None — requires only standard HTTP requests; implemented in the Snizi, T0w0T, and atiilla PoC tools
Bypass action-token validation
ResetCredentialEmail.action() method executes and reaches a success state without validating that the inbound request carries the signed action token that would prove mailbox control. The code also incorrectly marks the user's email as verified — a side effect that becomes the strongest forensic indicator. The attacker is now transitioned to the UPDATE_PASSWORD form.- Successful state manipulation from Step 3
- None — this is the core logic bug; no runtime or configuration setting prevents it on vulnerable versions
Set new password and take over the account
UPDATE_PASSWORD action, completing the takeover. They can now log in as the victim — including as realm admin. From an admin account, the attacker controls all realms, all users, all client configurations, all federated applications, and can issue arbitrary tokens. This is full identity-plane compromise.- Successful bypass from Step 4
- MFA/2FA on the account would block *login* after the password change, but the password is still reset (DoS to legitimate user). Passkeys/passwordless eliminates the password vector entirely.
RESET_PASSWORD and UPDATE_CREDENTIAL events, but these are indistinguishable from legitimate resets without cross-referencing email provider delivery logs or the database forensics script.PUT /admin/realms/{realm} with resetPasswordAllowed: false). This completely eliminates the vulnerable code path — the reset-credentials flow cannot be initiated. Deploy within 3 days per the noisgate mitigation SLA for CRITICAL findings. Note: this disables self-service password recovery for all users. Route password reset requests through admin-initiated resets until the patch is applied./admin/ and /realms/master/ paths behind a reverse proxy with IP allowlisting or VPN. This does not block the exploit itself (which targets the user-facing login page), but limits the blast radius by preventing the attacker from using a compromised admin account remotely. Deploy within 3 days.- WAF generic rules — the exploit uses standard HTTP form POST requests identical to legitimate password resets; there are no payload signatures, SQL injection patterns, or anomalous encodings for a WAF to match without custom rules that would also block legitimate resets
- Rate limiting — the complete exploit chain requires only six HTTP requests, which falls far below any practical rate-limit threshold that wouldn't also block legitimate users
- CAPTCHA on the login page — the attack targets the reset-credentials flow, not the login form; unless a CAPTCHA is specifically wired into the reset flow (non-default Keycloak configuration), login-page CAPTCHA provides zero protection
- Session timeout reduction — the six-request chain completes in under two seconds; no realistic session timeout value would interrupt it
The supporting signals.
| In-the-Wild Exploitation | No confirmed exploitation as of 2026-10-08. Not listed in CISA KEV. No campaigns attributed. However, the trivial exploit chain and multiple public PoCs make exploitation a matter of *when*, not *if*, for exposed unpatched instances. |
|---|---|
| Proof-of-Concept Availability | Multiple public PoCs. Snizi/CVE-2026-18963-Exploit — Python, zero dependencies, includes --safe-check mode and Docker lab. T0w0T/POC-CVE-2026-18963 — Python 3.8+, uses requests+beautifulsoup4. atiilla/CVE-2026-18963-Exploit and kc-resetforge tool also documented. BlackHatExploitation/Exploit-For-CVE-2026-18963 — additional repo. Forensic detection via kyos-public/keycloak-cve-2026-18963-hunt. |
| EPSS | 3.177% probability of exploitation in the next 30 days — 87th percentile. This places it well above the median and in the top tier of actionable vulnerabilities. |
| CISA KEV Status | Not listed as of 2026-10-08. Given the public PoCs and trivial chain, KEV listing is plausible if ITW exploitation is confirmed. |
| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N — Network-accessible, low complexity, no privileges, no user interaction. High confidentiality and integrity impact. No availability impact (accounts are taken over, not destroyed). This is the worst-case access profile for an authentication bypass. |
| Affected Versions | Keycloak 26.0.0 – 26.4.14, 26.5.0 – 26.6.5, 26.7.0 – 26.7.1. RHBK container images before 26.4-23 and 26.6-12. RHBK operator bundles before 26.4.15-1 and 26.6.6-1. |
| Fixed Versions | Upstream: 26.4.15, 26.6.6, 26.7.2, 26.8.0+. RHBK: advisories RHSA-2026:56519, RHSA-2026:56520, RHSA-2026:56523, RHSA-2026:56524. |
| Internet Exposure | Keycloak is an identity provider designed to be internet-facing. Shodan query http.title:"Sign in" http.html:keycloak returns significant exposure. Censys scanning identifies instances across major cloud providers. The login page (and forgot-password flow) is accessible to any network client by design. |
| Disclosure Timeline | Disclosed 2026-08-18. Upstream patch (26.7.2) released 2026-08-19. RHBK advisories published 2026-08-24. Researcher: James Paremain (credited by Red Hat). Bugzilla: 2511595. |
| Related CVEs | Keycloak 26.7.2 also fixed CVE-2026-15571 (predictable account-linking hash enabling OIDC-based account takeover). Twelve additional CVEs were addressed in the 26.7.1 release window. |
Sources.
Why this verdict
- Unauthenticated, zero-interaction, low-complexity remote attack. The CVSS vector is AV:N/AC:L/PR:N/UI:N — the worst possible access profile. An attacker needs nothing more than network access to the Keycloak login page and a target username or email address. No credentials, no phishing, no social engineering.
- Trivial weaponization with public PoCs. At least five GitHub repositories implement the full chain in Python with zero or minimal dependencies. The Snizi exploit includes a Docker lab, safe-check mode, and completes in six HTTP requests. The exploitation barrier is effectively zero for any attacker who can reach the endpoint.
- Forgot-password is enabled by default. The vulnerable code path is active in every stock Keycloak deployment. Disabling it breaks a core user-facing feature (self-service password recovery), so the vast majority of production instances have it enabled.
- Internet-facing by design. Unlike internal-only services, Keycloak must be reachable by every user who authenticates — including remote workers, mobile users, and federated partners. Restricting access to an internal network defeats the product's purpose for most deployments. There is no architectural friction limiting reachability.
- Role multiplier: Keycloak IS the identity provider. This is not a vulnerability in software that *happens* to run alongside an IdP — this IS the IdP. 100% of Keycloak installations serve the identity-provider role by definition. Compromising an admin account yields full control of the identity plane: all realms, all user accounts, all client configurations, all federated applications, all token issuance. Blast radius is identity-scale → fleet-scale → potentially supply-chain-scale depending on downstream integrations. Per the high-value role catalog (Identity & Trust), the floor is CRITICAL for canonical IdP software, and this chain succeeds trivially in that canonical role.
Why not higher?
The CVSS caps at 9.1 because there is no direct availability impact (A:N) and the scope is unchanged (S:U). A 10.0 would require scope change or availability destruction. The attacker takes over accounts but does not crash the Keycloak service itself. The 9.1 accurately reflects the ceiling for this class of vulnerability.
Why not lower?
Every friction factor works in the attacker's favor: unauthenticated remote access, no user interaction, low complexity (six HTTP requests), default-enabled feature, internet-facing component, and the affected software is canonically a high-value identity provider where 100% of installations occupy the critical role. Multiple working PoCs are public. EPSS is 87th percentile. There is no credible argument for any downgrade.
Crowdsourced verification payload.
Run from any auditor workstation with curl installed. No authentication or special privileges required — the script makes outbound HTTPS requests only. Invoke as: bash check_cve_2026_18963.sh https://sso.example.com master where the first argument is the Keycloak base URL and the optional second argument is the realm name (defaults to master).
#!/usr/bin/env bash
# CVE-2026-18963 Verification — Keycloak Reset-Credentials Account Takeover
# Checks Keycloak version and forgot-password reachability.
# Usage: bash check_cve_2026_18963.sh <KEYCLOAK_BASE_URL> [REALM]
# Exit: 0 = PATCHED | 1 = VULNERABLE | 2 = UNKNOWN
# Needs: curl (outbound HTTPS only, no privileges)
set -uo pipefail
KC="${1:?Usage: $0 <KEYCLOAK_BASE_URL> [REALM]}"
REALM="${2:-master}"
KC="${KC%/}"
echo "[*] CVE-2026-18963 check against ${KC} (realm: ${REALM})"
# --- 1. Detect version ---
VER=""
# Method A: welcome / root page
BODY=$(curl -sk --max-time 10 "${KC}/" 2>/dev/null || true)
VER=$(echo "$BODY" | sed -n 's/.*[Kk]eycloak[[:space:]]*\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*/\1/p' | head -1)
# Method B: X-Keycloak-Version header on OIDC discovery
if [ -z "$VER" ]; then
VER=$(curl -skI --max-time 10 \
"${KC}/realms/${REALM}/.well-known/openid-configuration" 2>/dev/null \
| tr -d '\r' \
| sed -n 's/^[Xx]-[Kk]eycloak-[Vv]ersion:[[:space:]]*//p' | head -1)
fi
# --- 2. Check forgot-password reachability ---
RESET_CODE=$(curl -sk -o /dev/null -w "%{http_code}" --max-time 10 \
"${KC}/realms/${REALM}/login-actions/reset-credentials" 2>/dev/null || echo "000")
if [ "$RESET_CODE" -ge 200 ] && [ "$RESET_CODE" -lt 400 ]; then
FORGOT="reachable"
else
FORGOT="blocked_or_disabled (HTTP ${RESET_CODE})"
fi
echo "[i] Forgot-password endpoint: ${FORGOT}"
# --- 3. Evaluate version ---
if [ -z "$VER" ]; then
echo "[!] Could not detect Keycloak version remotely."
echo "[!] Verify on host: /opt/keycloak/bin/kc.sh show-config or container image tag."
if [ "$FORGOT" = "reachable" ]; then
echo "VULNERABLE (version unknown, reset endpoint reachable — treat as vulnerable)"
exit 1
fi
echo "UNKNOWN"
exit 2
fi
echo "[i] Detected version: ${VER}"
IFS='.' read -r MAJ MIN PAT <<< "$VER"
VULN=0
if [ "$MAJ" -eq 26 ]; then
if [ "$MIN" -le 3 ]; then VULN=1
elif [ "$MIN" -eq 4 ] && [ "$PAT" -lt 15 ]; then VULN=1
elif [ "$MIN" -eq 5 ]; then VULN=1
elif [ "$MIN" -eq 6 ] && [ "$PAT" -lt 6 ]; then VULN=1
elif [ "$MIN" -eq 7 ] && [ "$PAT" -lt 2 ]; then VULN=1
fi
fi
if [ "$VULN" -eq 1 ]; then
echo "VULNERABLE (Keycloak ${VER}, forgot-password: ${FORGOT})"
exit 1
else
echo "PATCHED (Keycloak ${VER})"
exit 0
fi