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
Tools/GitHubGitHub/snizi/cve-2026-18963-exploit
Authentication & AuthorizationVulnerability AnalysisExploitationWeb Application ExploitationPenetration Testing
GitHubsnizi/cve-2026-18963-exploit

CVE-2026-18963-Exploit

Exploit for Keycloak CVE-2026-18963 enabling unauthenticated account takeover via reset-credentials bypass. Includes safe detection, non-destructive proof, full takeover, username enumeration, and a lab with vulnerable and patched versions.

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share
View Repository
4312511 month agoReviewed by Kitploit

CVE-2026-18963 — Keycloak reset-credentials bypass → unauthenticated account takeover

CVE Affected Python Dependencies

Knowing only a username or e-mail address, an unauthenticated attacker sets an arbitrary password on any Keycloak account. The password-reset e-mail is delivered to the real victim and is never needed — the attacker never reads a mailbox, never clicks a link, and holds no prior credential or session.

Affected: Keycloak 26.0.0 – 26.7.1. Fixed in 26.7.2.


Am I vulnerable?

One command. No valid username required, and no side effects — it sends no e-mail, writes to no account, and stops before the exploitable step.

git clone https://github.com/Snizi/CVE-2026-18963-Exploit
cd CVE-2026-18963-Exploit

python3 cve_2026_18963_poc.py \
  --base https://sso.example.com --realm YOUR_REALM \
  --client-id account \
  --redirect-uri https://sso.example.com/realms/YOUR_REALM/account/ \
  --safe-check

Python 3.9+, standard library only. Nothing to install.

ExitVerdictMeaning
0🔴 VULNERABLEthe parked e-mail gate was served — the bug itself
2🟢 PATCHEDthe flow forked to login and stayed there (fix #51844 present)
2🟡 MITIGATEDreset-credentials unreachable — Forgot password is off. Not a patch.
3⚪ INCONCLUSIVEunrecognised response — do not read this as a pass

Run it per realm — Forgot password is a per-realm setting, and master counts. Details, and why the check needs no user and touches nothing, in §4a.

Already know you are exposed? Jump to remediation and detection / threat hunting.

Try it without a target

The repo ships a lab that boots a vulnerable 26.7.1 and a patched 26.7.2 side by side against an identical realm, plus a mailbox to watch the reset e-mail arrive and stay unread while the account is taken over:

cd lab && docker compose up -d

python3 ../cve_2026_18963_poc.py --base http://localhost:8080 \
  --realm poc --client-id poc-app --safe-check   # VULNERABLE
python3 ../cve_2026_18963_poc.py --base http://localhost:8100 \
  --realm poc --client-id poc-app --safe-check   # PATCHED

⚠️ Authorised testing only

This repository exists for defenders, incident responders and authorised penetration testers. Run it against systems you own or hold written permission to test. Everything here ships with a self-contained vulnerable lab (lab/), so nothing external needs to be touched to learn how the bug works. Pointing it at third-party infrastructure without authorisation is illegal in most jurisdictions and is not something this project supports.

References: keycloak#51833 · GHSA-4gv3-mc9p-5wqc · fix keycloak#51844


Contents

  • 1. Root cause
  • 2. Affected versions (including legacy lines)
  • 3. The lab
  • 4. Usage
    • 4a. Safe detection (--safe-check) — start here
    • 4b. Non-destructive proof (--check)
    • 4c. Full takeover
    • 4d. Username enumeration (--enum)
  • 5. Custom login themes
  • 6. Known gap — PKCE
  • 7. Validation performed
  • 8. Remediation
  • 9. Detection
  • Author

1. Root cause

Two defects chained. Neither is exploitable alone.

Defect 1 — an unscoped, sticky flag

services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java

processAction() — any POST carrying the form key tryAnotherWay:

processor.getAuthenticationSession().setAuthNote(
    AuthenticationProcessor.AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED, "true");
return createSelectAuthenticatorsScreen(model);

The note is a bare boolean with no record of which execution set it. It is cleared only in the branch that handles a submitted authenticationExecution parameter. Omit that parameter — as this PoC does throughout — and the flag stays set for the life of the authentication session.

processFlow() — while the flag is truthy, normal flow evaluation is skipped:

if (Boolean.parseBoolean(authSession.getAuthNote(AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED))) {
    String lastExecutionId = authSession.getAuthNote(CURRENT_AUTHENTICATION_EXECUTION);
    if (lastExecutionId != null) {
        AuthenticationExecutionModel executionModel =
            realm.getAuthenticationExecutionById(lastExecutionId);
        if (executionModel != null)
            return createSelectAuthenticatorsScreen(executionModel);   // <-- attacker-usable form
    }
}

It renders a submittable form aimed at whatever execution is currently parked, instead of keeping the session pinned on "waiting for the e-mail".

The glue is processResult() case FORK: — when Send Reset Email fires it stamps CURRENT_AUTHENTICATION_EXECUTION = <reset-credential-email execution id> and forks the browser to the login page. The parked execution is precisely the e-mail gate.

Defect 2 — the e-mail gate never checks the action token

services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialEmail.java

@Override
public void action(AuthenticationFlowContext context) {
    context.getUser().setEmailVerified(true);
    context.success();
}

Unconditional. Nothing verifies that the flow was resumed by a valid action token, so reaching action() is treated as equivalent to proving mailbox control.

The chain

tryAnotherWay POST            → sticky AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED="true"
submit victim identifier      → mail sent to victim, e-mail execution parked (FORK)
re-enter reset-credentials    → sticky flag serves a form targeting the parked e-mail execution
POST that form                → ResetCredentialEmail.action() → success() → gate bypassed
                              → flow advances to UPDATE_PASSWORD → attacker sets the password

Six HTTP requests, no authentication, no authenticationExecution parameter at any point.

The fix (PR #51844)

  • The note now stores model.getId(), and processFlow() honours it only when it equals CURRENT_AUTHENTICATION_EXECUTION, otherwise removes it. In the attack the two differ (choose-user id vs. e-mail-gate id) — exactly what the patch detects, and exactly the signal --safe-check keys on.
  • ResetCredentialEmail.action() now requires context.getUser().getId().equals(authNote(ACTION_TOKEN_USER_ID)) and otherwise fails with INVALID_USER.

2. Affected versions (including legacy lines)

LineVulnerableCommunity fix
Legacy (WildFly-based Keycloak, ≤ 17)not affected—
Quarkus 17 – 25.xnot affected—
26.026.0.0 – 26.0.17none
26.126.1.0 – 26.1.5none
26.226.2.0 – 26.2.16none
26.326.3.0 – 26.3.5none
26.426.4.0 – 26.4.1426.4.15 (vendor backport tag)
26.526.5.0 – 26.5.7none
26.626.6.0 – 26.6.526.6.6 (vendor backport tag)
26.726.7.0 – 26.7.126.7.2

What "legacy" means for this CVE

Download Tool