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.
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).
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 — serious impact or readily exploitable. Prioritize remediation.
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 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 Proof of Reproduction for CVE-2026-11800
- 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
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
- POST /realms/{realm}/protocol/openid-connect/token with grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer and assertion=<forged HS256 JWT>
reproduction_steps.sh How the agent worked
Root Cause and Exploit Chain for CVE-2026-11800
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-services—AbstractBaseJWTValidator,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 clienttest-app. The fixed version (26.6.4) rejected the same assertion with HTTP 400invalid_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
expectedSignatureAlgif 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
nonealgorithm - A check via
ClientSignatureVerifierProvider.isAsymmetricAlgorithm()that rejects symmetric algorithms unlessisSymmetricAlgorithmAllowed()returnstrue(onlyJWTClientSecretValidatorfor 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
- The attacker obtains the Identity Provider's RSA public key (publicly available)
- The attacker encodes it as base64 (the raw content, without PEM headers)
- 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 - The attacker signs the assertion with HMAC-SHA256 using the RSA public key's DER-encoded bytes as the secret
- The attacker submits the assertion to Keycloak's token endpoint with
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer - 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
- PR: https://github.com/keycloak/keycloak/pull/50374
- Commit:
d343c7373dcc7bfeb9dc14de2309836299c81837 - Title: "Remove support for symmetric algorithms in JWT public key validators"
Reproduction Steps
- Reference:
bundle/repro/reproduction_steps.sh - What the script does:
- Pulls
quay.io/keycloak/keycloak:26.6.3(vulnerable) andquay.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
requestsandcryptographyin 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-grantIdentity Provider with the RSA public key's base64 content (no PEM headers) aspublicKeySignatureVerifier,useJwksUrl=false,jwtAuthorizationGrantEnabled=true, and nojwtAuthorizationGrantAssertionSignatureAlgset - Creates a confidential client
test-appwithoauth2.jwt.authorization.grant.enabled=trueandoauth2.jwt.authorization.grant.idppointing to the IdP - Creates a federated user
basic-userlinked to the IdP with subjectbasic-user-id - Forges a JWT assertion with
alg: HS256signed 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.jsonand cleans up containers
- Pulls
- Expected evidence of reproduction:
logs/vulnerable/response_status.txt:HTTP 200logs/vulnerable/access_token.json: Contains a valid Bearer access token forbasic-userissued fortest-applogs/fixed/response_status.txt:HTTP 400logs/fixed/response_body.json:{"error":"invalid_grant","error_description":"Invalid signature algorithm"}
Evidence
Log file locations
bundle/logs/reproduction_steps.log— Main script logbundle/logs/vulnerable/exploit.log— Exploit log for vulnerable versionbundle/logs/vulnerable/forged_jwt.txt— The forged HS256 JWT assertionbundle/logs/vulnerable/request.txt— HTTP request detailsbundle/logs/vulnerable/response_status.txt— HTTP response statusbundle/logs/vulnerable/response_body.json— Full HTTP response bodybundle/logs/vulnerable/access_token.json— Issued access token (decoded)bundle/logs/fixed/exploit.log— Exploit log for fixed versionbundle/logs/fixed/response_status.txt— HTTP response statusbundle/logs/fixed/response_body.json— Full HTTP response bodybundle/logs/vuln_keycloak.log— Vulnerable Keycloak server logsbundle/logs/fixed_keycloak.log— Fixed Keycloak server logsbundle/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-slimwithrequestsandcryptography - RSA key: 2048-bit, generated per-run
- JWT signing: HMAC-SHA256 with RSA public key DER bytes (294 bytes) as secret
Recommendations / Next Steps
- Upgrade to Keycloak 26.6.4 or later — The fix adds algorithm-type validation and removes the HMAC branch from
HardcodedPublicKeyLoader. - Set
jwtAuthorizationGrantAssertionSignatureAlgexplicitly on all JWT Authorization Grant Identity Providers to pin the expected algorithm (e.g.,RS256), which provides defense-in-depth even on older versions. - Use JWKS URL instead of hardcoded keys where possible — the JWKS-based key loading path does not have the HMAC branch vulnerability.
- Audit Identity Provider configurations — check if any IdPs have
publicKeySignatureVerifierset to base64 content (without PEM headers), which would make them exploitable on vulnerable versions. - 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
publicKeySignatureVerifierto be base64 content (not PEM with headers) becauseBase64Url.decode()cannot handle PEM headers (the spaces in-----BEGIN PUBLIC KEY-----causeIllegal 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.
Artifacts and Evidence for CVE-2026-11800
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-11800
Upgrade keycloak/keycloak · github to 26.6.4 or later.
FAQ: CVE-2026-11800
How does the CVE-2026-11800 JWT algorithm-confusion attack work?
Which Keycloak versions are affected by CVE-2026-11800, and where is it fixed?
How severe is CVE-2026-11800?
How can I reproduce CVE-2026-11800?
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.