Share
## 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.