## https://sploitus.com/exploit?id=0AA05A02-0F2B-5FD7-B8ED-75132927E78E
# camel-jms Forged DefaultExchangeHolder Filter-Bypass Reproducer (CVE-2026-43866)
This project demonstrates **CVE-2026-43866**, a **bypass of the CVE-2026-40860 fix** in Apache Camel's
`camel-jms` (and `camel-sjms` / JMS-family) components. CVE-2026-40860 added a post-deserialization class
allow-list (`java.**;javax.**;org.apache.camel.**;!*`) to incoming JMS `ObjectMessage` payloads. But
`org.apache.camel.support.DefaultExchangeHolder` lives in the allow-listed `org.apache.camel.**` namespace, so
an `ObjectMessage` whose top-level object **is** a `DefaultExchangeHolder` passes the check. The receiving side
then calls `DefaultExchangeHolder.unmarshal()` on it **without** requiring `transferExchange` โ writing every
non-null field of the holder into the routed Exchange (body, IN/OUT headers, exchange properties, variables,
exchange id, exception). An attacker who can publish an `ObjectMessage` can thus **inject arbitrary Exchange
state using only universally-trusted `java.*` types โ no deserialization gadget chain required** โ to
manipulate routing, headers, properties and error handling.
Advisory: https://camel.apache.org/security/CVE-2026-43866.html
## Vulnerability Summary
| Property | Value |
|----------|-------|
| **Components** | `camel-jms` (+ `camel-sjms`, `camel-sjms2`, `camel-amqp`, `camel-activemq`, `camel-activemq6`) |
| **Affected Class** | `org.apache.camel.component.jms.JmsBinding#extractBodyFromJms` โ `DefaultExchangeHolder.unmarshal` |
| **CWE** | CWE-502 (Deserialization of Untrusted Data) + CWE-20 (Improper Input Validation) |
| **Impact** | Exchange-state injection: attacker-controlled body, headers, properties, variables, exception |
| **Nature** | **Bypass** of the CVE-2026-40860 class-filter fix (not a flaw in it) |
| **Affected Versions** | From 3.0.0 before 4.14.8, from 4.15.0 before 4.18.3, from 4.19.0 before 4.21.0 |
| **Fixed Versions** | 4.14.8, 4.18.3, 4.21.0 |
| **JIRA** | CAMEL-23373 (jms), CAMEL-23409 (sjms) |
| **Reporter** | gaorenyusi |
## Technical Details
```java
// JmsBinding.extractBodyFromJms(...) - affected 4.18.2
if (message instanceof ObjectMessage objectMessage) {
Object payload = objectMessage.getObject();
checkDeserializedClass(payload); // CVE-2026-40860 allow-list: java.**;javax.**;org.apache.camel.**;!*
if (payload instanceof DefaultExchangeHolder holder) { // passes
DefaultExchangeHolder.unmarshal(exchange, holder); // jmsHeaders = extractHeadersFromJms(message, exchange);
exchange.getIn().getHeaders().putAll(jmsHeaders);
return exchange.getIn().getBody();
} else {
return payload;
}
}
```
The asymmetry: the **sending** side gates `ObjectMessage`/`transferExchange` creation, but the **receiving**
side unmarshals any `DefaultExchangeHolder` it deserializes. The fix (4.14.8 / 4.18.3 / 4.21.0) adds a new
`objectMessageEnabled` option (`security = "insecure:serialization"`, default **false**) โ an incoming
`ObjectMessage` is no longer deserialized at all unless explicitly enabled, so a forged holder can never reach
`unmarshal()`. (This is a breaking change for routes that rely on `ObjectMessage` / `transferExchange`.)
> **A JMS-provider allow-list does not help.** This PoC configures the ActiveMQ client with a realistic,
> locked-down `trustedPackages = [java, javax, org.apache.camel]` โ the `org.apache.camel` entry is exactly
> what a deployment using legitimate `transferExchange` must trust. The forged holder still passes, because it
> is itself a `DefaultExchangeHolder` whose fields are all trusted `java.*` types, indistinguishable from a
> legitimate one.
## The victim route
```java
from("jms:queue:cve") // mapJmsMessage defaults to true; transferExchange NOT set
.process(exchange -> { /* observes the injected body / headers / properties */ });
```
## Repository layout โ attacker vs. victim
The **victim** is the Camel JMS consumer. The **attacker** is any producer that can publish to the queue. Both
talk to a real **Apache ActiveMQ Artemis** broker in Docker.
```
CVE-2026-43866/
โโโ pom.xml # camel-jms 4.18.2 + activemq-client 6.2.4 (NO gadget library)
โโโ Dockerfile # runs the app (no --add-opens; no gadget)
โโโ docker-compose.yml # Artemis broker (quay.io) + the reproducer app
โโโ README.md
โโโ src/main/
โโโ java/com/example/
โ โโโ Application.java
โ โโโ JmsConfig.java # OpenWire ConnectionFactory (trustedPackages incl. org.apache.camel) + jms component
โ โโโ VictimRoute.java # victim: from("jms:queue:cve"); records what the route observed
โ โโโ CapturedState.java
โ โโโ ForgedHolderFactory.java # builds a DefaultExchangeHolder via the public marshal() API
โ โโโ ExploitController.java # attacker: publishes the forged holder as an ObjectMessage
โโโ resources/
โโโ application.properties
```
## Prerequisites
- Java 17+ and Maven 3.8+
- Docker (runs the broker and the app)
## Reproduction Steps
### Step 1: Build and start everything
```bash
mvn clean package -DskipTests
docker compose up -d --build
```
### Step 2: Trigger the bypass
```bash
curl -s http://localhost:8080/exploit/attack
# -> Published a forged DefaultExchangeHolder as a JMS ObjectMessage to queue 'cve'.
# ...
# body = INJECTED-BODY-... (injected: true)
# header = pwned-header-... (injected: true)
# property = pwned-property-... (injected: true)
#
# >>> Exchange-state injection proof โ attacker controlled body+header+property: true
```
The route ran with a body, header and property it never set โ all supplied by the attacker's forged holder.
### Cleanup
```bash
docker compose down
```
## Attack Vectors
Any Camel JMS **consumer** (`camel-jms`, `camel-sjms`, `camel-sjms2`, `camel-amqp`, `camel-activemq`,
`camel-activemq6`) with `mapJmsMessage=true` (the default), reading from a destination an attacker can publish
to. `transferExchange` does **not** need to be enabled on the consumer.
## Exploit Conditions
1. A Camel JMS consumer with `mapJmsMessage=true` (default), on an affected version.
2. The attacker can enqueue an `ObjectMessage` whose payload is a `DefaultExchangeHolder`.
3. The JMS provider deserializes the payload โ which, for any deployment using `transferExchange`, means the
provider already trusts `org.apache.camel`. **No gadget library is required.**
## Recommended Fix
Upgrade to **4.14.8 / 4.18.3 / 4.21.0** (CAMEL-23373 / CAMEL-23409). JMS `ObjectMessage` handling is disabled by
default via the new `objectMessageEnabled` option; only enable it against destinations fed exclusively by
trusted producers.
## Mitigation
Until upgrading:
1. Restrict publish access to Camel-consumed queues/topics to **trusted producers** via JMS broker authorization.
2. Do not expose JMS consumers that map `ObjectMessage` bodies to untrusted networks.
3. Note: a JMS-provider deserialization allow-list does **not** mitigate this specific bypass (the payload uses
only universally-trusted classes plus `DefaultExchangeHolder`).
## 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.