CVE-2026-11800: Verified Repro With Script Download
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://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 Alternate trigger for CVE-2026-11800: JWT algorithm confusion (HS256 with RSA public key DER bytes as HMAC secret) against a hardcoded-public-key OIDC Identity Provider, reached via the OIDC external-internal token exchange (RFC 8693, grant_type=urn:ietf:params:oauth:grant-type:token-exchange, subject_token_type=urn:i…
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.
Variant Analysis & Alternative Triggers for CVE-2026-11800
A distinct alternate trigger (not a bypass) for CVE-2026-11800 was found and
reproduced end-to-end. The original CVE is JWT algorithm confusion in the JWT
Authorization Grant flow (grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer +
assertion), gated by AbstractBaseJWTValidator.validateSignatureAlgorithm() (the fix's
Layer 1) and HardcodedPublicKeyLoader (Layer 2). The variant reaches the same root
cause / sink — HardcodedPublicKeyLoader's HMAC branch on a hardcoded RSA public key —
from a different entry point that the fix's Layer 1 does not cover: the OIDC
external-internal token exchange (RFC 8693,
grant_type=urn:ietf:params:oauth:grant-type:token-exchange with
subject_token_type=urn:ietf:params:oauth:token-type:jwt and subject_issuer=<oidc-idp>).
An attacker who knows an OIDC Identity Provider's RSA public key (inherently public) forges
an HS256 subject_token signed with the RSA public key DER bytes as the HMAC secret and
exchanges it for a Keycloak access token for any federated user linked to that IdP. Tested
against quay.io/keycloak/keycloak:26.6.3 (vulnerable — HTTP 200, access token issued for
the impersonated federated user basic-user) and quay.io/keycloak/keycloak:26.6.4
(fixed — HTTP 400 invalid_token). The variant is covered by the fix's Layer 2 only
(the HardcodedPublicKeyLoader HMAC-branch removal); the fix's primary algorithm-type gate
(Layer 1) is never invoked on this path. This is therefore an alternate trigger, not a
bypass: the released fix (26.6.4) rejects the forged token.
Fix Coverage / Assumptions
- Invariant the fix relies on: attacker-controlled JWTs verified against an IdP's
hardcoded public key either (a) pass through
AbstractBaseJWTValidator.validateSignatureAlgorithm()which rejects symmetric/nonealgorithms, or (b) are verified by a key loader that can no longer produce an HMAC secret from a public-key config. - Paths explicitly covered (Layer 1): JWT Authorization Grant
(
DefaultJWTAuthorizationGrantValidator) and all JWT client authenticators (JWTClientValidator,JWTClientSecretValidator,FederatedJWTClientValidator,IDJWTAuthorizationGrantValidator) — everyAbstractBaseJWTValidatorsubclass. - What the fix does NOT cover (Layer 1):
OIDCIdentityProvider.verifySignature()and the external token-exchange / broker ID-token validation paths, which callPublicKeyStorageManager.getIdentityProviderKeyWrapper()→HardcodedPublicKeyLoaderdirectly with the JWT header's algorithm and perform no algorithm-type allow-list check. These paths are defended only by Layer 2 (HMAC-branch removal).
Variant / Alternate Trigger
Entry point: POST /realms/{realm}/protocol/openid-connect/token with
grant_type=urn:ietf:params:oauth:grant-type:token-exchange,
subject_token_type=urn:ietf:params:oauth:token-type:jwt,
subject_issuer=<oidc-idp-alias>, requested_token_type=urn:ietf:params:oauth:token-type:access_token,
subject_token=<forged HS256 JWT>, plus client_id/client_secret of a client authorized
(via v1 FGAP token-exchange permission) to exchange to that IdP.
Code path:
TokenExchangeGrantType.process() → V1TokenExchangeProvider.tokenExchange() →
isExternalInternalTokenExchangeRequest()/exchangeExternalToken() →
OIDCIdentityProvider.exchangeExternal() → exchangeExternalImpl() →
validateJwt() → validateToken(subjectToken, ignoreAudience=true) → verify(jws) →
verifySignature(jws) → PublicKeyStorageManager.getIdentityProviderKeyWrapper() →
HardcodedPublicKeyLoader(kid, publicKeySignatureVerifier, alg=HS256) →
(on vulnerable) HMAC branch: Base64Url.decode(base64PubKey) → RSA public key DER bytes →
HMAC SecretKey → MacSignatureVerifierContext.verify() succeeds.
Files / functions:
services/.../protocol/oidc/tokenexchange/V1TokenExchangeProvider.java(tokenExchange,exchangeExternalToken)services/.../broker/oidc/OIDCIdentityProvider.java(exchangeExternalImpl,validateJwt,validateToken,verify,verifySignature)services/.../keys/loader/PublicKeyStorageManager.java(getIdentityProviderKeyWrapper)services/.../keys/loader/HardcodedPublicKeyLoader.java(removed HMAC branch = the fix)
Other candidates considered and ruled out:
Federated JWT client authentication (
clientAuthenticatorType=federated-jwt,CLIENT_AUTH_FEDERATEDfeature, default-enabled): same sink, but it goes throughFederatedJWTClientValidator→AbstractBaseJWTValidator.validateSignatureAlgorithm()(Layer 1 covers it) and additionally itsDefaultClientAssertionStrategyissuer/client lookup did not resolve in 26.6.3 dev mode in our test environment, so it could not be exercised end-to-end; regardless it is Layer-1-covered.OIDC broker ID-token validation with a hardcoded key: same
verifySignaturesink, but the ID token arrives server-to-server from the upstream IdP's token endpoint (not directly attacker-controlled), so it does not cross the relevant trust boundary for an external attacker and is not a practical variant.private_key_jwtclient auth (ClientPublicKeyLoader): no HMAC branch ever existed, so not vulnerable to this confusion even pre-fix; and Layer 1 covers it post-fix.JWKS-URL / JWKS-string key configs (
OIDCIdentityProviderPublicKeyLoader): the JWKS is admin/IdP-controlled, not attacker-controlled, so anoctkey cannot be injected by the attacker; HS256 against an RSA JWKS key fails to verify on both versions.Package/component affected:
org.keycloak:keycloak-services—OIDCIdentityProvider(external token exchange),HardcodedPublicKeyLoader,PublicKeyStorageManager; same vulnerable sink as the original CVE.Affected versions (as tested): Red Hat Build of Keycloak / upstream Keycloak 26.6.3 (vulnerable). 26.6.4 rejects (fixed via Layer 2).
Risk level: High (same class as the parent CVE; CVSS 8.1 base per GHSA-gqj5-2xp5-3qmp).
Consequences: An attacker with valid client credentials for a client authorized to exchange to a vulnerable OIDC IdP (hardcoded public key,
validateSignature=true) can forge an HS256subject_tokenand obtain a valid Keycloak access token for any federated user linked to that IdP — federated-user impersonation / privilege escalation, identical impact class to the JWT Authorization Grant variant.
Impact Parity
- Disclosed/claimed maximum impact (parent CVE): impersonate any federated user linked to the affected Identity Provider; unauthorized access / privilege escalation.
- Reproduced impact from this variant run: Full end-to-end — forged HS256
subject_tokensubmitted via external token exchange; vulnerable 26.6.3 returned HTTP 200 with a Beareraccess_tokenwhose decoded payload issub=<basic-user uuid>,azp=te-client,iss=http://localhost:8080/realms/test-realm,preferred_username=basic-user— i.e. a token minted for the impersonated federated userbasic-user, issued for the attacker's clientte-client. - Parity:
full— same authentication-bypass / federated-user-impersonation impact as the parent CVE, demonstrated through the real Keycloak token endpoint. - Not demonstrated: No RCE/memory-corruption (not applicable to this class); no demonstration of post-impersonation lateral movement beyond token issuance.
Root Cause
HardcodedPublicKeyLoader (vulnerable versions) contains an HMAC branch keyed on the JWT
header's attacker-controlled alg: when alg is HS256/HS384/HS512 and the IdP is
configured with useJwksUrl=false + publicKeySignatureVerifier set to the base64
content of an RSA public key (no PEM headers), Base64Url.decode(encodedKey) yields the
exact DER-encoded SubjectPublicKeyInfo bytes — the same bytes as
RSAPublicKey.getEncoded() — which become the HMAC SecretKey. Because the RSA public key
is inherently public, an attacker can sign an HS256 JWT with those bytes and the signature
verifies. The OIDC external token-exchange path feeds the attacker's subject_token
directly into OIDCIdentityProvider.verifySignature() → HardcodedPublicKeyLoader with
no algorithm-type allow-list check (Layer 1 is absent on this path), so on the vulnerable
version the forged token is accepted and a federated-user access token is minted.
Fix commit: d343c7373dcc7bfeb9dc14de2309836299c81837
(https://github.com/keycloak/keycloak/pull/50374). The fix removes the HMAC branch (Layer 2)
and adds the algorithm-type gate to AbstractBaseJWTValidator (Layer 1). Layer 2 closes
this variant on 26.6.4 (the loader returns null for HS256 → verifySignature fails →
invalid_token); Layer 1 does not run on this path.
Reproduction Steps
- Reference:
bundle/vuln_variant/reproduction_steps.sh(driver) +bundle/vuln_variant/exploit_variant.py(embedded exploit). - What the script does:
- Pulls
quay.io/keycloak/keycloak:26.6.3(vulnerable) and:26.6.4(fixed). - Starts both Keycloak instances in dev mode with
--features=token-exchange,admin-fine-grained-authz:v1 --hostname=localhost --http-enabled=true(token-exchangev1 enables external-internal exchange;admin-fine-grained-authz:v1enables the v1 FGAP token-exchange permission used to authorize the test client — a legitimate admin config, not a bypass). - Starts a Python client container (
requests+cryptography). - For each Keycloak instance:
- Creates
test-realm; generates a 2048-bit RSA key pair; creates an OIDC IdP (providerId=oidc) withuseJwksUrl=false,publicKeySignatureVerifier=<base64 of RSA public key DER>,validateSignature=true,disableTypeClaimCheck=true,syncMode=FORCE. - Creates a federated user
basic-userlinked to the IdP (userId=basic-user-id). - Creates a confidential client
te-clientand grants it the v1 FGAPtoken-exchangepermission to the IdP (enable IdP management permissions → create aclientpolicy listingte-client→ bind it to the IdPtoken-exchangescope permission). - Forges an HS256
subject_token(iss=https://external-idp-issuer,sub=basic-user-id,aud=https://external-idp-issuer,typ=Bearer) signed with HMAC-SHA256 using the RSA public key DER bytes (294 bytes) as the secret. - POSTs the external token exchange and records the HTTP status/body and any
access_token.
- Creates
- Captures Keycloak server logs, image build metadata, and writes
runtime_manifest.json; cleans up containers/network.
- Pulls
- Expected evidence:
logs/vuln_variant/vulnerable/response_status.txt=HTTP 200;logs/vuln_variant/vulnerable/access_token.jsoncontains a Bearer access token whose decoded payload haspreferred_username=basic-user,azp=te-client.logs/vuln_variant/fixed/response_status.txt=HTTP 400;logs/vuln_variant/fixed/response_body.json={"error":"invalid_token","error_description":"invalid token"}.logs/vuln_variant/verdict.txt=alternate_trigger.
Evidence
bundle/logs/vuln_variant/reproduction_steps.log— driver logbundle/logs/vuln_variant/exploit_run.log— exploit stdout (both versions)bundle/logs/vuln_variant/vulnerable/{exploit.log,forged_subject_token.txt,request.txt,response_status.txt,response_body.json,access_token.json}bundle/logs/vuln_variant/fixed/{exploit.log,forged_subject_token.txt,request.txt,response_status.txt,response_body.json}bundle/logs/vuln_variant/{vuln_keycloak.log,fixed_keycloak.log,image_metadata.txt}
Key excerpts (run 1):
Vulnerable (26.6.3):
Forged HS256 subject_token payload: {"iss":"https://external-idp-issuer","sub":"basic-user-id","aud":"https://external-idp-issuer","typ":"Bearer",...}
HMAC secret = RSA public key DER bytes (294 bytes)
Response status: 200
*** vulnerable: forged HS256 subject_token ACCEPTED! Access token received. ***
Token sub: 16391078-5822-47ec-8f53-d15bf771c5f6
Token azp (client): te-client
Token preferred_username: basic-user
Fixed (26.6.4):
Response status: 400
fixed: forged HS256 subject_token REJECTED. Error: invalid_token, Description: invalid token
VERDICT: alternate_trigger
Environment: Keycloak 26.6.3 / 26.6.4 Docker images (Quarkus, JVM 21),
--hostname=localhost, features token-exchange,admin-fine-grained-authz:v1; Python 3.12
client with requests + cryptography; RSA 2048-bit key generated per run; HMAC-SHA256
with RSA public key DER bytes (294) as secret. Docker-in-Docker with a custom bridge network
(container-to-container via container names).
Recommendations / Next Steps
- Extend the algorithm-type gate to the broker
verifySignaturepath. Add an explicit allow-list check inOIDCIdentityProvider.verifySignature()(and the sharedAbstractOAuth2IdentityProvider/broker verify path) mirroringAbstractBaseJWTValidator.validateSignatureAlgorithm(): rejectnoneand reject symmetric algorithms (HS256/HS384/HS512) unless the IdP is explicitly configured to allow symmetric verification. This makes external token exchange and broker ID-token validation two-layer-defended (Layer 1 + Layer 2) like the JWT Authorization Grant, instead of relying solely onHardcodedPublicKeyLoadernot reintroducing an HMAC branch. - Defense in depth: consider rejecting symmetric algorithms for any hardcoded-public-key
IdP at the
PublicKeyStorageManager.getIdentityProviderKeyWrapper()dispatch level, independent of the loader. - Pin
jwtAuthorizationGrantAssertionSignatureAlg/ equivalent on IdPs and prefer JWKS URLs over hardcoded keys where possible. - The fix is effective for this variant on 26.6.4 (Layer 2); the recommendation is about structural completeness / future-proofing, not an open bypass.
Additional Notes
- Bypass vs alternate trigger: This is an alternate trigger (distinct entry point, same root cause/sink), not a bypass. The released fix (26.6.4) rejects the forged token (Layer 2). Per the stage spec, the reproduction script exits 1 (alternate trigger), not 0 (bypass).
- Idempotency: Verified by running
reproduction_steps.shtwice consecutively; both runs produced identical results (vulnerable HTTP 200 + token; fixed HTTP 400invalid_token; verdictalternate_trigger). - Feature prerequisites: The variant requires
token-exchange(v1) andadmin-fine-grained-authz:v1to be enabled. In the default 26.6.x image,TOKEN_EXCHANGE(v1) andADMIN_FINE_GRAINED_AUTHZ(v1) are disabled by default (v2 FGAP is default-on and does not support token-exchange permission configuration); the script enables them explicitly. This is a deployment-config prerequisite (an admin who has turned on external token exchange for an IdP), analogous to the original CVE'soauth2.jwt.authorization.grant.enabledprerequisite — not a weakening of the fix. - Audience/type-claim handling: the forged
subject_tokensets payloadtyp=Bearer(one ofOIDCIdentityProvider.SUPPORTED_TOKEN_TYPES) and the IdP setsdisableTypeClaimCheck=trueto pass the post-signature type check; these are payload controls, not signature controls, and do not affect the algorithm-confusion root cause.
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.