
Hunt for CVE-2026-18963 exploitation traces (Keycloak unauthenticated account takeover) in the Keycloak database
A psql script that searches a Keycloak PostgreSQL database for traces of
CVE-2026-18963 exploitation (unauthenticated account takeover through the
reset-credentials flow).
Published by KYOS. We wrote it while patching the Keycloak deployments we operate, to verify that nobody had been taken over during the exposure window.
CVE-2026-18963 is a flaw in the reset-credentials flow of keycloak-services
(keycloak#51833). An
unauthenticated attacker can complete the password reset process for any user
without clicking the mail verification link, then set a new password on the
account. No user interaction is required.
Severity: Critical, CVSS v3.1 9.1 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N),
per Red Hat and
NVD. Root cause is
improper state validation in the authentication flow, fixed in
keycloak#51844.
| Stream | Fixed in |
|---|---|
| 26.7.x | 26.7.2 (release notes) |
| 26.6.x | 26.6.6 |
| 26.4.x (LTS) | 26.4.15 |
| 26.8 | 26.8.0 |
Everything below those versions in the supported streams is vulnerable. Out-of-support releases (26.5.x, 26.3 and older) get no fix: assume exposure and upgrade to a fixed stream. Red Hat lists legacy RH-SSO 7 as not affected; the Red Hat Build of Keycloak 26.4/26.6 is fixed in 26.4.15-1 / 26.6.6-1.
Disabling self-service password reset removes the vulnerable entry point:
Admin console > Realm settings > Login > "Forgot password" off, for every
realm. Via the admin API: PUT /admin/realms/{realm} with
{"resetPasswordAllowed": false}.
This blocks the login-actions/reset-credentials endpoint but also prevents
legitimate users from resetting their own password, so treat it as a stopgap
until you upgrade. Note that Red Hat does not list an endorsed mitigation for
this CVE; patching is the only real fix.
cve-2026-18963-keycloak-hunt.sql runs four read-only queries:
| Query | What it finds |
|---|---|
| Q0 | Whether the realm stores login/admin events at all, and their TTL. If events are off or expired, empty results in Q2/Q3 prove nothing. |
| Q1 | Every password credential set inside the exposure window (credential.created_date). This is the takeover itself and works even if event logging was off. |
| Q2 | Reset/credential login events, flagging any completed reset with no SEND_RESET_PASSWORD in the prior 24h (no_email_before = t). That flag is the CVE signature: the attacker never triggered the mail. |
| Q3 | Admin-API credential resets and execute-actions operations, to rule out the admin-driven route. |
# defaults: realm 'master', window since 2026-06-01
psql -U keycloak -d keycloak -f cve-2026-18963-keycloak-hunt.sql
# explicit realm and window (run once per realm, quote values exactly like this)
psql -U keycloak -d keycloak \
-v realm="'myrealm'" -v since="'2026-05-01'" \
-f cve-2026-18963-keycloak-hunt.sql
Set since to just before your vulnerable version went live. On Kubernetes:
kubectl exec -it my-postgres-pod -- \
psql -U keycloak -d keycloak -v realm="'myrealm'" -v since="'2026-05-01'" \
-f - < cve-2026-18963-keycloak-hunt.sql
Rows in Q1 are not automatically compromises. Match each against a known legitimate cause (self-service reset, helpdesk action, new enrolment) and treat anything unexplained as a takeover candidate.
Q2 rows with no_email_before = t on RESET_PASSWORD, UPDATE_CREDENTIAL
or UPDATE_PASSWORD are the strongest indicator of this CVE being exploited.
Correlate the ip_address column with your access logs.
Check Q0 first: login events expire (events_expiration) and may be disabled
entirely. Q1 does not expire, so it is the most reliable check.
MIT, see LICENSE. Provided as-is, without warranty.