Skip to content

CVE-2026-11800: Verified Reproduction

CVE-2026-11800: Keycloak JWT algorithm confusion privilege escalation

CVE-2026-11800 is verified against keycloak/keycloak · github. Affected versions: Keycloak < 26.6.4 (26.6.x line). Fixed in 26.6.4. Vulnerability class: Privilege Escalation. This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00243.

REPRO-2026-00243 keycloak/keycloak · github Privilege Escalation Jul 6, 2026 CVE entry .txt
Severity
HIGH
CVSS
8.1
Confidence
HIGH
Reproduced in
46m 0s
Tool calls
281
Spend
$7.18
01 · Overview

What Is CVE-2026-11800?

CVE-2026-11800 is a high-severity JWT algorithm confusion vulnerability (CWE-347) in Keycloak's JWT Authorization Grant flow that allows privilege escalation and impersonation. Pruva reproduced it (reproduction REPRO-2026-00243).

02 · Severity & CVSS

CVE-2026-11800 Severity & CVSS Score

CVE-2026-11800 is rated high severity, with a CVSS base score of 8.1 out of 10.

HIGH threat level
8.1 / 10 CVSS base
Weakness CWE-347 (Improper Verification of Cryptographic Signature)

High — serious impact or readily exploitable. Prioritize remediation.

03 · Affected Versions

Affected keycloak/keycloak Versions

keycloak/keycloak · github versions Keycloak < 26.6.4 (26.6.x line) are affected.

How to Reproduce CVE-2026-11800

$ pruva-verify REPRO-2026-00243
or curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00243/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for CVE-2026-11800

Authorization bypass — reproduced
  • reached the target end-to-end
  • full exploit chain demonstrated
  • on the real production code path
  • high confidence
  • the upstream fix blocks the same trigger
Trigger

Forged HS256 JWT assertion signed with RSA public key DER bytes as HMAC secret, submitted via grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer

Attack chain
  1. POST /realms/{realm}/protocol/openid-connect/token with grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer and assertion=<forged HS256 JWT>
Runnable proof: reproduction_steps.sh
Captured evidence: fixed keycloak
How the agent worked 711 events · 281 tool calls · 46 min
46 minDuration
281Tool calls
201Reasoning steps
711Events
2Dead-ends
Agent activity over 46 min
Support
22
Hypothesis
2
Repro
351
Judge
31
Variant
301
0:0046:00

Root Cause and Exploit Chain for CVE-2026-11800

Versions: Red Hat Build of Keycloak 26.6.0–26.6.3; upstream Keycloak versions prior to the fix in 26.6.4/26.7.0Fixed: Keycloak 26.6.4, 26.7.0

CVE-2026-11800 is a JWT algorithm confusion vulnerability in Keycloak's JWT Authorization Grant flow (grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer). When an Identity Provider is configured with a hardcoded public key (not via JWKS URL) and the jwtAuthorizationGrantAssertionSignatureAlg setting is left unset (the default), the AbstractBaseJWTValidator.validateSignatureAlgorithm() method does not reject symmetric algorithms (HS256/HS384/HS512). Additionally, the HardcodedPublicKeyLoader contains an HMAC branch that loads the configured publicKeySignatureVerifier value as an HMAC secret key using Base64Url.decode(). An attacker who knows the Identity Provider's RSA public key (which is inherently public) can forge a JWT assertion with alg: HS256, sign it using the RSA public key's DER-encoded bytes as the HMAC secret, and submit it to Keycloak's token endpoint. The vulnerable Keycloak version accepts this forged assertion and issues a valid access token for any federated user linked to the affected Identity Provider, enabling privilege escalation and impersonation. The fix (PR #50374, commit d343c7373dcc7bfeb9dc14de2309836299c81837) adds explicit algorithm-type validation that rejects symmetric algorithms in JWT public key validators and removes the HMAC branch from HardcodedPublicKeyLoader.

  • Package/component affected: org.keycloak:keycloak-servicesAbstractBaseJWTValidator, HardcodedPublicKeyLoader, JWTAuthorizationGrantValidator, JWTAuthorizationGrantIdentityProvider
  • Affected versions: Red Hat Build of Keycloak 26.6.0–26.6.3; upstream Keycloak versions prior to the fix in 26.6.4/26.7.0
  • Fixed versions: Keycloak 26.6.4, 26.7.0
  • Risk level: High
  • Consequences: An attacker with valid client credentials can bypass JWT signature verification in the JWT Authorization Grant flow. By forging an assertion signed with the Identity Provider's public key (used as an HMAC secret), the attacker can obtain unauthorized access tokens and impersonate any federated user linked to the affected Identity Provider, leading to unauthorized access and privilege escalation.

Impact Parity

  • Disclosed/claimed maximum impact: Authentication bypass via JWT algorithm confusion; attacker can create unauthorized access tokens and impersonate any federated user, leading to unauthorized access and potential privilege escalation.
  • Reproduced impact from this run: Full end-to-end exploitation demonstrated. A forged HS256 JWT assertion signed with the RSA public key DER bytes as the HMAC secret was submitted to the Keycloak token endpoint. The vulnerable version (26.6.3) accepted the assertion (HTTP 200) and issued a valid Bearer access token for the federated user basic-user, issued for client test-app. The fixed version (26.6.4) rejected the same assertion with HTTP 400 invalid_grant / Invalid signature algorithm.
  • Parity: full — the claimed authentication bypass and unauthorized access token issuance were demonstrated through the real Keycloak API endpoint.

Root Cause

The vulnerability has two contributing factors in the vulnerable code:

1. Missing algorithm-type validation in AbstractBaseJWTValidator.validateSignatureAlgorithm()

The vulnerable version's validateSignatureAlgorithm(String expectedSignatureAlg) method only checks:

  • That the algorithm is not null
  • That it matches expectedSignatureAlg if that parameter is non-null

When expectedSignatureAlg is null (which occurs when the IdP config does not set jwtAuthorizationGrantAssertionSignatureAlg — the default), ANY algorithm passes validation, including symmetric algorithms like HS256 and the none algorithm. The fix adds:

  • Explicit rejection of the none algorithm
  • A check via ClientSignatureVerifierProvider.isAsymmetricAlgorithm() that rejects symmetric algorithms unless isSymmetricAlgorithmAllowed() returns true (only JWTClientSecretValidator for legitimate client-secret JWT auth allows this)
2. HMAC branch in HardcodedPublicKeyLoader

When the IdP uses useJwksUrl=false with a hardcoded key, the HardcodedPublicKeyLoader is instantiated with the alg parameter taken from the JWT header. The vulnerable version includes an HMAC branch:

} else if (JavaAlgorithm.isHMACJavaAlgorithm(algorithm)) {
    keyWrapper.setType(KeyType.OCT);
    keyWrapper.setSecretKey(KeyUtils.loadSecretKey(Base64Url.decode(encodedKey), algorithm));
}

When the JWT header specifies alg: HS256, this branch is taken. Base64Url.decode(encodedKey) decodes the publicKeySignatureVerifier config value as base64url. If the config value is the base64 content of the RSA public key (without PEM headers), this produces the exact DER-encoded SubjectPublicKeyInfo bytes — the same bytes as RSAPublicKey.getEncoded() in Java. These bytes become the HMAC secret key. The MacSignatureVerifierContext then uses this secret to verify the HS256 signature, which matches because the attacker signed with the same DER bytes.

The fix removes this HMAC branch entirely, replacing it with a warning and null key:

} else {
    logger.warnf("Unrecognized or invalid algorithm %s for hardcoded public key", algorithm);
    kw = null;
}
Attack flow
  1. The attacker obtains the Identity Provider's RSA public key (publicly available)
  2. The attacker encodes it as base64 (the raw content, without PEM headers)
  3. The attacker forges a JWT assertion with header {"alg":"HS256","typ":"JWT"} and payload containing the target federated user's subject, the IdP's issuer, and the Keycloak realm's issuer as audience
  4. The attacker signs the assertion with HMAC-SHA256 using the RSA public key's DER-encoded bytes as the secret
  5. The attacker submits the assertion to Keycloak's token endpoint with grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
  6. The vulnerable Keycloak version:
    • Passes algorithm validation (no expected algorithm configured)
    • Loads the base64 public key content as an HMAC secret via HardcodedPublicKeyLoader's HMAC branch
    • Verifies the HS256 signature successfully (same bytes used for signing and verification)
    • Issues a valid access token for the impersonated federated user
Fix reference

Reproduction Steps

  1. Reference: bundle/repro/reproduction_steps.sh
  2. What the script does:
    • Pulls quay.io/keycloak/keycloak:26.6.3 (vulnerable) and quay.io/keycloak/keycloak:26.6.4 (fixed) Docker images
    • Creates a Docker network and starts both Keycloak instances in dev mode plus a Python client container
    • Installs requests and cryptography in the Python client
    • Waits for both Keycloak instances to become healthy
    • Runs a Python exploit script that, for each Keycloak instance:
      • Creates a test realm via the Admin REST API
      • Generates an RSA 2048-bit key pair
      • Creates a jwt-authorization-grant Identity Provider with the RSA public key's base64 content (no PEM headers) as publicKeySignatureVerifier, useJwksUrl=false, jwtAuthorizationGrantEnabled=true, and no jwtAuthorizationGrantAssertionSignatureAlg set
      • Creates a confidential client test-app with oauth2.jwt.authorization.grant.enabled=true and oauth2.jwt.authorization.grant.idp pointing to the IdP
      • Creates a federated user basic-user linked to the IdP with subject basic-user-id
      • Forges a JWT assertion with alg: HS256 signed with HMAC-SHA256 using the RSA public key DER bytes as the secret
      • Submits the assertion to the token endpoint with grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
      • Records the HTTP response and any access token
    • Compares results: vulnerable accepts (HTTP 200 + access token), fixed rejects (HTTP 400 + "Invalid signature algorithm")
    • Captures Keycloak server logs, exploit logs, forged JWT, request/response artifacts
    • Writes runtime_manifest.json and cleans up containers
  3. Expected evidence of reproduction:
    • logs/vulnerable/response_status.txt: HTTP 200
    • logs/vulnerable/access_token.json: Contains a valid Bearer access token for basic-user issued for test-app
    • logs/fixed/response_status.txt: HTTP 400
    • logs/fixed/response_body.json: {"error":"invalid_grant","error_description":"Invalid signature algorithm"}

Evidence

Log file locations
  • bundle/logs/reproduction_steps.log — Main script log
  • bundle/logs/vulnerable/exploit.log — Exploit log for vulnerable version
  • bundle/logs/vulnerable/forged_jwt.txt — The forged HS256 JWT assertion
  • bundle/logs/vulnerable/request.txt — HTTP request details
  • bundle/logs/vulnerable/response_status.txt — HTTP response status
  • bundle/logs/vulnerable/response_body.json — Full HTTP response body
  • bundle/logs/vulnerable/access_token.json — Issued access token (decoded)
  • bundle/logs/fixed/exploit.log — Exploit log for fixed version
  • bundle/logs/fixed/response_status.txt — HTTP response status
  • bundle/logs/fixed/response_body.json — Full HTTP response body
  • bundle/logs/vuln_keycloak.log — Vulnerable Keycloak server logs
  • bundle/logs/fixed_keycloak.log — Fixed Keycloak server logs
  • bundle/repro/runtime_manifest.json — Runtime evidence manifest
Key excerpts

Vulnerable version (26.6.3) — HS256 assertion ACCEPTED:

Forged JWT header: {"alg": "HS256", "typ": "JWT"}
Forged JWT payload: {"jti": "...", "iss": "https://authorization-grant-issuer", "sub": "basic-user-id", "aud": "http://localhost:8080/realms/test-realm", "exp": ..., "iat": ...}
HMAC secret = RSA public key DER bytes (294 bytes)
Response status: 200
*** vulnerable: HS256 assertion ACCEPTED! Access token received. ***
Token preferred_username: basic-user
Token azp (client): test-app

Fixed version (26.6.4) — HS256 assertion REJECTED:

Response status: 400
fixed: HS256 assertion REJECTED. Error: invalid_grant, Description: Invalid signature algorithm
Environment details
  • Keycloak vulnerable: quay.io/keycloak/keycloak:26.6.3 (Quarkus 3.33.2, JVM 21)
  • Keycloak fixed: quay.io/keycloak/keycloak:26.6.4
  • Docker network: custom bridge network
  • Python client: python:3.12-slim with requests and cryptography
  • RSA key: 2048-bit, generated per-run
  • JWT signing: HMAC-SHA256 with RSA public key DER bytes (294 bytes) as secret

Recommendations / Next Steps

  1. Upgrade to Keycloak 26.6.4 or later — The fix adds algorithm-type validation and removes the HMAC branch from HardcodedPublicKeyLoader.
  2. Set jwtAuthorizationGrantAssertionSignatureAlg explicitly on all JWT Authorization Grant Identity Providers to pin the expected algorithm (e.g., RS256), which provides defense-in-depth even on older versions.
  3. Use JWKS URL instead of hardcoded keys where possible — the JWKS-based key loading path does not have the HMAC branch vulnerability.
  4. Audit Identity Provider configurations — check if any IdPs have publicKeySignatureVerifier set to base64 content (without PEM headers), which would make them exploitable on vulnerable versions.
  5. Revoke and rotate tokens — any access tokens issued via the JWT Authorization Grant flow on vulnerable versions should be considered potentially forged and revoked.

Additional Notes

  • Idempotency: The script is fully idempotent. It cleans up previous containers at the start and end. Each run creates fresh containers, realm, IdP, client, and user. The realm is deleted before creation if it exists.
  • Reproducibility: Verified by running the script twice consecutively — both runs produced identical results (vulnerable accepts, fixed rejects).
  • Key insight: The exploit requires the publicKeySignatureVerifier to be base64 content (not PEM with headers) because Base64Url.decode() cannot handle PEM headers (the spaces in -----BEGIN PUBLIC KEY----- cause Illegal base64 character 20). When the value is raw base64, Base64Url.decode() produces the exact DER bytes of the RSA public key, which become the HMAC secret. This is a realistic configuration scenario — an administrator may paste the base64 content of a public key without PEM headers, or import it from a source that provides base64 rather than PEM.
  • Docker networking: This environment uses Docker-in-Docker where port mapping to the host does not work. The script uses a custom Docker network with container-to-container communication via container names.

CVE-2026-11800 Reproduction Transcript

The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.

Full session Replay every step — scrub the timeline or play it back.

Event 1/40
0:001:00
0:00
session startedaccounts/fireworks/routers/glm-5p2-fast · CVE-2026-11800 · REPRO-20
0:02
0:04
web search
0:05
web search
0:07
0:08
0:11
web search
0:14
0:15
web search
0:27
0:29
0:30
web search
0:31
0:44
0:44
extract_facts
no facts extracted
0:45
0:45
0:45
supportrepro
0:52
0:54
0:54
0:54
0:55
0:55
0:55
0:55
0:58
0:58
0:58
1:00
08 · How to Fix

How to Fix CVE-2026-11800

Upgrade keycloak/keycloak · github to 26.6.4 or later.

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-11800 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-11800

How does the CVE-2026-11800 JWT algorithm-confusion attack work?

An attacker who knows the Identity Provider's RSA public key (which is inherently public) forges a JWT assertion with alg: HS256, signing it using the RSA public key's DER-encoded bytes as the HMAC secret, and submits it via grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer to Keycloak's token endpoint. The vulnerable HardcodedPublicKeyLoader HMAC branch accepts this forged assertion and issues a valid access token for any federated user linked to that Identity Provider.

Which Keycloak versions are affected by CVE-2026-11800, and where is it fixed?

Keycloak versions before 26.6.4 in the 26.6.x line are affected. It is fixed in 26.6.4, which adds explicit algorithm-type validation rejecting symmetric algorithms in JWT public key validators and removes the HMAC branch from HardcodedPublicKeyLoader.

How severe is CVE-2026-11800?

It is rated high severity. An attacker with only public knowledge of the Identity Provider's RSA public key can forge tokens and impersonate any federated user, escalating privileges without needing the private key.

How can I reproduce CVE-2026-11800?

Download the verified script from this page and run it in an isolated environment against Keycloak before 26.6.4 with an Identity Provider configured with a hardcoded RSA public key. It forges an HS256-signed JWT assertion using that public key as the HMAC secret, submits it via the jwt-bearer grant, and shows Keycloak issuing a valid access token, then confirms 26.6.4 rejects it.
11 · References

References for CVE-2026-11800

Authoritative sources for CVE-2026-11800 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.