Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-18963-PoC — Proof of concept and vulnerability checker for CVE-2026-18963, an unauthenticated account takeover in the Keycloak reset-credentials flow. Single-file Python, optional Playwright browser component. | Kitploit
Tools/GitHubGitHub/orevim/cve-2026-18963-poc
Vulnerability AnalysisExploitationWeb Application ExploitationWeb SecurityPenetration TestingAuthentication
GitHuborevim/cve-2026-18963-poc

CVE-2026-18963-PoC

Proof of concept and vulnerability checker for CVE-2026-18963, an unauthenticated account takeover in the Keycloak reset-credentials flow. Single-file Python, optional Playwright browser component.

View Repository
16 days agoNot yet reviewed
Website

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-18963

A proof of concept and vulnerability checker for the Keycloak reset-credentials flow bypass, with an optional browser component that renders the resulting state in a real Chrome window.

The tool is a single file, cve_2026_18963_poc.py, written against the Python standard library. Playwright is the only third-party dependency and it is imported lazily, so the HTTP side runs on a stock interpreter with nothing installed.

cve_2026_18963_poc.py     1009 lines, Python 3.10 or newer

Credit. CVE-2026-18963 was reported to Red Hat by James Paremain, who is credited in the vendor advisory. Orevim did not discover or report this vulnerability. This repository is an independent proof of concept written from the published advisory.


1. The Vulnerability

CVE-2026-18963 is an authentication bypass in Keycloak's forgot-password flow. It is two defects acting together.

The unscoped note. When a user opens the authenticator selection screen, Keycloak writes an AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED note into the authentication session. The note is a bare boolean. It records that a selection screen was displayed, not which execution displayed it. The note outlives the execution that set it.

The unguarded handler. ResetCredentialEmail.action() sets emailVerified on the resolved user and calls success() without checking the action token that the emailed link is supposed to carry. The method assumes it can only be reached by following that link.

Chained, an unauthenticated caller sets the note against the username execution, advances the flow so the email execution becomes current, then re-enters the reset URL. The stale note causes the selection screen to render bound to the email execution. Posting an empty body to that execution's loginAction invokes the unguarded handler directly. The flow advances to the UPDATE_PASSWORD required action with no email ever read.

IdentifierCVE-2026-18963, GHSA-4gv3-mc9p-5wqc
ClassCWE-640, weak password recovery mechanism for forgotten password
CVSS v3.19.1 AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
Affected>= 26.0.0, < 26.4.15, >= 26.5.0, < 26.6.6, >= 26.7.0, < 26.7.2
Patched in26.4.15, 26.6.6, 26.7.2. The 26.8.0 line ships with the fix
Packageorg.keycloak:keycloak-services (Maven)
PublishedAugust 18, 2026
Reported byJames Paremain, credited in the Red Hat advisory

The only precondition is that the realm offers password recovery to unauthenticated users. No credential, token, or prior session is required.

The Request Chain

Eight requests, in order. Tags match the step labels the tool prints and the filenames it writes under --out.

#TagMethodWhat it does
1loginGETAuthorization URL. Establishes the authentication session and yields the reset link.
2resetGETReset-credentials page. Carries the username form.
3tryanotherPOSTtryAnotherWay=on. Writes the unscoped note.
4usernamePOSTThe identifier. Advances the flow so the email execution becomes current. Sends recovery mail if the identifier resolves.
5reenterGETThe reset URL again. The stale note renders the selector bound to the email execution.
6actionPOSTEmpty body to that execution's loginAction. Invokes the unguarded handler.
7required-actionGETFollows the Location to the UPDATE_PASSWORD page.
8setpasswordPOSTThe new password. Only sent with --set-password.

Step 6 is the exploit, and the empty body is load-bearing. Posting authenticationExecution=<id> instead is the ordinary selector interaction: it switches execution and re-sends the recovery mail, which never reaches the defect. The empty POST answers no challenge, so Keycloak calls the handler for whichever execution the stale note left current.


2. Part One: The HTTP Chain

Modes

# Decide whether the instance is affected. Changes nothing, sends no mail.
./cve_2026_18963_poc.py --url https://sso.example.com --realm demo \
    --client-id account-console --check

# Additionally report which identifiers resolve to a real account.
# Resolving accounts receive a recovery email. See the note below.
./cve_2026_18963_poc.py --url https://sso.example.com --realm demo \
    --client-id account-console --check --users-file names.txt --rate 0.25

# Reach the set-password form for one account and stop there.
./cve_2026_18963_poc.py --url https://sso.example.com --realm demo \
    --client-id account-console --username [email protected]

# Complete the takeover. This locks the account holder out.
./cve_2026_18963_poc.py --url https://sso.example.com --realm demo \
    --client-id account-console --username [email protected] \
    --set-password 'N3w-passw0rd!'

--check probes with a random identifier of the form <16 hex>@<8 hex>.invalid. The .invalid TLD is reserved by RFC 2606 and cannot resolve, so the probe cannot collide with a real account and cannot cause mail to be sent. It establishes the build's status without touching anyone.

Adding --users changes that. Step 4 is the ordinary forgot-password submission. On a realm with SMTP configured, an identifier that resolves to a user with an email address receives a genuine password-recovery email, on affected and patched builds alike. The bare probe does not, because ResetCredentialEmail.authenticate() sends nothing when the user is null. Treat --check --users as a flow that notifies real people and confirm it is within your authorization before running it.

Verdicts

The tool classifies on the response to step 6, the empty POST.

ResponseOutcomeMeaning
400account-absentThe handler ran with no action token and resolved no user. The build is affected.
302 with Locationform-reachedThe handler ran and the account resolves. Affected, and this identifier is real.
Anything elsenot-vulnerableThe empty POST did not advance.
No selector at step 5not-vulnerableThe stale note was not honored.
302 after step 8password-setThe password changed and the realm issued a session.
429 past the retry budgetrate-limitedNo verdict. The run stopped.
A negative reached after any throttlinginconclusiveSee below.

The 400 is the important one. It separates the build is affected from this account exists, which lets --check establish exposure against an instance without enumerating a single real user. Resolving identifiers are then a separate, opt-in question.

A negative is only sound if nothing was throttled. Keycloak expires the authentication session on its own clock. A 429 mid-chain costs a backoff, and a chain that resumes after a long sleep returns exactly the page a patched build returns. Reporting that as not affected is the one failure mode a checker must not have, so any negative reached after a throttle event is downgraded to inconclusive with the throttle count and the advice to re-run slower.

Target Derivation

Nothing about the deployment is hardcoded. The tool reads what it needs:

  • Endpoints come from the realm's OIDC discovery document, derived from --url and --realm, or supplied directly with --discovery-url. Pass --auth-url to skip discovery entirely.
  • The reset URL is the first link on the login page whose path contains login-actions/reset-credentials.
  • Form actions and field names are parsed out of each page as it arrives, so renamed fields and non-default paths need no flags.
  • The redirect URI defaults to the account console of the discovered issuer.
  • PKCE parameters are generated per run: a random 32-byte verifier and its S256 challenge.
Download Tool