Oct 30, 2026·8 min read·2 visits
A loose substring check (String.contains) in Keycloak Policy Enforcer's PolicyEnforcer.isDefaultAccessDeniedUri method allows any authenticated user to bypass all authorization checks by appending the configured access-denied URI (defaulting to /access-denied) as a URL path segment or query parameter.
CVE-2026-9800 is an authorization bypass vulnerability affecting the Keycloak Policy Enforcer (org.keycloak:keycloak-policy-enforcer), which is integrated into Red Hat build of Keycloak, Red Hat build of Quarkus, Red Hat Single Sign-On 7, and Red Hat JBoss Enterprise Application Platform Expansion Pack. The flaw stems from improper comparison logic in the PolicyEnforcer.isDefaultAccessDeniedUri method, which uses loose substring matching on the full raw request URI rather than exact, normalized path comparisons against the configured access-denied landing page. Consequently, an authenticated attacker possessing any valid low-privileged token can bypass role-based, scope-based, and User-Managed Access (UMA) policy evaluations by embedding the access-denied path segment into the URL path or query parameters.
The Keycloak Policy Enforcer (org.keycloak:keycloak-policy-enforcer and servlet-policy-enforcer) functions as a security interceptor for Java servlet containers and Quarkus applications. Its primary architectural role is to intercept incoming HTTP requests targeting protected resource servers, inspect bearer access tokens, and enforce fine-grained authorization policies configured within Keycloak Authorization Services. This enforcement encompasses Role-Based Access Control (RBAC), OAuth2 scope validation, and User-Managed Access (UMA) permission ticket verification.
When a request fails policy evaluation in web applications, the Policy Enforcer can redirect the client to a designated error or landing page defined by the on-deny-redirect-to configuration property (which commonly defaults to /access-denied). To prevent infinite redirection loops when the client subsequently requests this error page, the enforcer checks whether the incoming request targets the configured access-denied URI. If a match occurs, the enforcer exempts the request from policy evaluation and permits it to proceed through the filter chain.
Classified under CWE-1025 (Comparison Using Wrong Factors), CVE-2026-9800 occurs because the exemption check in PolicyEnforcer.isDefaultAccessDeniedUri performs an unanchored substring search (String.contains) across the entire raw URI string instead of comparing the canonicalized request path against the configured redirect path. Because the raw URI includes subpaths and query parameters, an attacker can inject the access-denied string anywhere into the request target.
This flaw exposes a critical attack surface across any microservice or web application relying on the Keycloak Policy Enforcer with on-deny-redirect-to enabled. Any authenticated user holding a valid bearer token—regardless of their assigned roles or permissions—can completely bypass authorization enforcement, resulting in unauthorized read and write access to protected APIs and administrative endpoints.
The root cause of CVE-2026-9800 resides in the implementation of the isDefaultAccessDeniedUri method within the PolicyEnforcer class. During the request interception lifecycle, the enforcer invokes this method to determine whether the current HTTP request is attempting to load the access-denied landing page configured via getOnDenyRedirectTo().
Two distinct design flaws converge in this method to create the vulnerability. First, the method retrieves the target identifier via request.getUri(), which returns the complete, unparsed URI string—including the scheme, authority, path, and raw query string (?param=value)—rather than isolating the path component via a dedicated path extraction method. Second, the method compares this raw string against the configured accessDeniedUri using uri.contains(accessDeniedUri).
When isDefaultAccessDeniedUri evaluates to true, the Policy Enforcer immediately short-circuits the authorization pipeline and treats the request as authorized. This premature exit bypasses PolicyEvaluator.evaluate(), skipping all configured role checks (hasRole()), scope validations (hasScope()), and UMA ticket negotiations.
Consequently, there is a parser differential between how the Keycloak Policy Enforcer evaluates exemptions and how the downstream servlet container or JAX-RS router dispatches the request. While the enforcer sees the substring /access-denied inside a query parameter and grants access, the downstream web framework ignores the unknown query parameter and routes the request directly to the restricted controller method.
In vulnerable versions of org.keycloak:keycloak-policy-enforcer (versions <= 26.0.5), the isDefaultAccessDeniedUri method inspects the incoming HttpRequest object without parsing or normalizing the URI structure.
// Vulnerable implementation in PolicyEnforcer.java
public boolean isDefaultAccessDeniedUri(HttpRequest request) {
// Flaw 1: Retrieves the full raw URI, including query parameters
String uri = request.getUri();
String accessDeniedUri = getConfiguration().getOnDenyRedirectTo();
if (accessDeniedUri != null) {
// Flaw 2: Uses loose substring matching instead of exact path equality
return uri.contains(accessDeniedUri);
}
return false;
}To remediate this vulnerability, the comparison logic must be restricted to the normalized path component of the URI, and the substring check must be replaced with an exact match or canonicalized boundary check.
// Patched implementation pattern
public boolean isDefaultAccessDeniedUri(HttpRequest request) {
String accessDeniedUri = getConfiguration().getOnDenyRedirectTo();
if (accessDeniedUri != null) {
// Fix 1: Parse the URI, normalize path traversal segments, and extract strictly the path
String requestPath = URI.create(request.getUri()).normalize().getPath();
// Fix 2: Enforce exact equality between the canonical request path and the configured path
return requestPath.equals(accessDeniedUri);
}
return false;
}By invoking URI.create(request.getUri()).normalize().getPath(), the patched logic strips all query strings (?...) and fragments (#...), while also resolving relative path segments (such as /../ or /./). Replacing String.contains() with String.equals() ensures that requests to subpaths like /api/resource/access-denied do not falsely match a root-level /access-denied configuration.
This fix effectively eliminates both query parameter injection and subpath appending variants. However, application developers must still ensure that downstream servlet containers and reverse proxies normalize URIs consistently (for example, handling matrix parameters like ;jsessionid= or encoded slashes %2F) so that the path evaluated by URI.normalize().getPath() strictly matches the path dispatched by the application router.
Exploiting CVE-2026-9800 requires three conditions: the target application must protect its endpoints using the Keycloak Policy Enforcer, the on-deny-redirect-to configuration property must be defined, and the attacker must possess a valid authentication token for any user account recognized by the Keycloak realm. Because the Policy Enforcer validates token authenticity prior to evaluating authorization exemptions, unauthenticated requests without a valid bearer token will still be rejected.
An attacker can trivially discover the configured on-deny-redirect-to value by sending a standard request with a low-privileged token to a restricted endpoint and inspecting the Location header in the resulting HTTP 302 Found or 403 Forbidden response. Once the path (typically /access-denied) is known, the attacker can execute the bypass using either query parameter injection or path appending.
Query parameter injection is the most reliable exploitation vector because downstream REST controllers generally ignore unrecognized query parameters. The attacker appends a dummy query key containing the access-denied path to the restricted API endpoint:
GET /api/v1/admin/users?bypass=/access-denied HTTP/1.1
Host: api.target-domain.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
Accept: application/jsonWhen the Policy Enforcer intercepts this request, request.getUri() returns /api/v1/admin/users?bypass=/access-denied. The check uri.contains("/access-denied") returns true, bypassing authorization. The underlying JAX-RS or Spring controller receives the request at /api/v1/admin/users, ignores the bypass parameter, and returns the sensitive administrative user list.
For endpoints configured with wildcard routing or hierarchical path parameters, an attacker can append the access-denied string directly as a trailing path segment:
GET /api/v1/confidential-records/access-denied HTTP/1.1
Host: api.target-domain.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...In this scenario, uri.contains("/access-denied") again evaluates to true. If the backend endpoint maps routes using patterns such as /api/v1/confidential-records/*, the application processes the request and exposes the protected resource.
CVE-2026-9800 carries a CVSS v3.1 base score of 8.1 (High) with the vector CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N. The attack complexity is low (AC:L) and requires no user interaction (UI:N), as exploitation involves simple URL string manipulation over the network (AV:N). Privileges required is rated low (PR:L) because the attacker must authenticate as a standard user before reaching the policy enforcement stage.
The confidentiality and integrity impacts are both rated high (C:H/I:H). Because the vulnerability disables the entire policy evaluation engine for matching requests, an attacker can read any protected resource across the application (GET) and execute state-changing administrative operations (POST, PUT, DELETE). Multi-tenant applications that rely on Keycloak UMA permissions or fine-grained scopes to isolate tenant data are exposed to cross-tenant data access and manipulation.
The availability impact is rated none (A:N) in the standard CVSS vector because the flaw in the URI check does not directly trigger memory exhaustion, application crashes, or thread deadlocks. However, if an application exposes administrative endpoints capable of deleting system resources or shutting down services, an attacker could leverage the integrity bypass to cause secondary service disruption.
According to threat intelligence telemetry, CVE-2026-9800 has an EPSS score of 0.00651 (51.09th percentile), indicating a moderate baseline probability of exploitation. While a functional proof-of-concept is publicly documented via Red Hat Bugzilla #2482472, the vulnerability is not currently listed in the CISA Known Exploited Vulnerabilities (KEV) catalog.
Organizations utilizing the Keycloak Policy Enforcer within Java servlet applications or Quarkus microservices should upgrade to the patched component releases provided by Red Hat and the upstream Keycloak project. Specifically, deployments of Red Hat build of Keycloak must be upgraded to versions 26.4.13+ (for the 26.4 stream) or 26.6.4-2+ (for the 26.6 stream). Applications built on Red Hat build of Quarkus should update their platform dependencies to incorporate org.keycloak:keycloak-policy-enforcer version 26.0.10.redhat-00001 or later.
> [!NOTE]
> Updating the central Keycloak identity server alone does not remediate this vulnerability. Because keycloak-policy-enforcer runs as an adapter library embedded within client applications and resource servers, every affected microservice and servlet application must be rebuilt and redeployed with the patched library.
For environments where immediate application rebuilding is not feasible, administrators can disable the on-deny-redirect-to configuration property in keycloak.json or application.properties if custom redirect pages are not strictly required (for example, in pure REST APIs where returning a raw HTTP 403 Forbidden status code is sufficient). When on-deny-redirect-to is null or unset, isDefaultAccessDeniedUri returns false and the vulnerable comparison is never executed.
Alternatively, security teams can deploy Web Application Firewall (WAF) or reverse proxy rules (such as NGINX or ModSecurity) in front of vulnerable services to inspect and block incoming requests that contain the access-denied path segment within query strings or non-root URI paths.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
org.keycloak:keycloak-policy-enforcer Keycloak | <= 26.0.5 | 26.0.10.redhat-00001 |
Red Hat build of Keycloak 26.4 Red Hat | >= 26.4.0, <= 26.4.12 | 26.4.13-1 |
Red Hat build of Keycloak 26.6 Red Hat | >= 26.6.0, <= 26.6.4 | 26.6.4-2 |
Red Hat build of Quarkus 3.27 Red Hat | 3.27.4.SP2 | 26.0.10.redhat-00001 |
Red Hat build of Quarkus 3.33 Red Hat | 3.33.2.SP2 | 26.0.10.redhat-00001 |
| Attribute | Detail |
|---|---|
| Vulnerability Type | Authorization Bypass via Improper URI Comparison |
| CWE ID | CWE-1025 (Comparison Using Wrong Factors) |
| CVSS v3.1 Score | 8.1 High (AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N) |
| Affected Method | PolicyEnforcer.isDefaultAccessDeniedUri |
| Attack Vector | Network (HTTP Query Parameter or Subpath Injection) |
| EPSS Score | 0.651% (51.09th Percentile) |
| Exploit Status | Proof-of-Concept Documented (Not in CISA KEV) |
The software performs a comparison between entities, but the comparison uses the wrong factors or characteristics of the entities, which can lead to incorrect results and security boundary bypasses.
CVE-2026-9705 is an insufficient session expiration and authorization bypass vulnerability in Keycloak and Red Hat build of Keycloak. The vulnerability occurs in the OAuth 2.0 Dynamic Client Registration Management Protocol implementation, where the authentication handler fails to verify whether a target client is enabled before accepting requests authenticated with a previously issued Registration Access Token (RAT). Consequently, an actor possessing a valid RAT for an administratively disabled client can query, update, and re-enable the client configuration, restoring revoked access to realm resources.
CVE-2026-9799 is an authorization bypass vulnerability in the User-Managed Access (UMA) subsystem of Keycloak (org.keycloak.authorization). When a resource server is configured with the non-default PERMISSIVE policy enforcement mode, an authenticated user holding a valid UMA permission ticket for a single typed resource can obtain an authorization Requesting Party Token (RPT) granting access to all resources of the same type across the resource server. By submitting an authorization request with the type selector prefix 'resource-type-any:<type>', the attacker bypasses instance-level owner approval checks for unshared peer resources that lack explicit static administrative policies.
CVE-2026-9083 is a path traversal and information disclosure vulnerability (CWE-22) affecting Keycloak and the Red Hat build of Keycloak. The vulnerability resides in the Java Keystore key provider component (JavaKeystoreKeyProviderFactory), which fails to restrict the user-supplied keystore file path to a designated directory. An authenticated realm administrator holding the 'manage-realm' role can supply arbitrary absolute or relative filesystem paths when configuring a key provider. Because the application returns distinct error messages depending on whether the target path does not exist, lacks read permissions, or fails keystore parsing, an attacker can use this component as an oracle to probe the underlying host filesystem.
CVE-2026-9086 is a stored Cross-Site Scripting (XSS) vulnerability in Keycloak and the Red Hat build of Keycloak. The vulnerability stems from a case-sensitive URI scheme validation check in DefaultClientValidationProvider, allowing attackers with client registration or management permissions to inject javascript: or data: URIs using mixed-case characters.
CVE-2026-62290 is an incorrect authorization vulnerability (CWE-863) in cert-manager that allows low-privileged Kubernetes namespace tenants to bypass ClusterIssuer DNS01 solver policies and exfiltrate cluster-scoped DNS credentials. By directly creating standalone ACME Challenge custom resources rather than going through the standard Certificate and Order workflow, an attacker can specify arbitrary solver provider configurations while referencing a privileged ClusterIssuer. The cert-manager ACME challenge controller reconciles these directly instantiated Challenge resources without verifying their parentage or validating the user-supplied solver parameters against the referenced ClusterIssuer's selector policies.
CVE-2026-118662 (also tracked as GHSA-8g5g-mxqh-grgj) is an improper access control and path canonicalization vulnerability in the Vite frontend build and development tool. In version 6.4.4, the Vite development server fails to strip transform and loader query parameters before evaluating file paths against the `server.fs.deny` configuration directive. When the development server is exposed to a network and sensitive files located within `server.fs.allow` match patterns in `server.fs.deny`, an unauthenticated remote attacker can bypass file access restrictions by appending specific query strings such as `?raw` or `?inline` to HTTP requests. This allows arbitrary retrieval of restricted files, including environment configuration files and private cryptographic keys.