
Reproducer for CVE-2026-40860 — Apache Camel camel-jms/sjms/amqp JMS ObjectMessage unsafe deserialization (RCE)
Ce projet démontre une vulnérabilité de désérialisation Java dans le composant camel-jms d'Apache Camel (et transitivement dans camel-sjms, camel-sjms2, camel-amqp, camel-activemq, camel-activemq6), référencée sous l'identifiant CVE-2026-40860. JmsBinding.extractBodyFromJms() désérialise la charge utile d'un ObjectMessage JMS entrant via ObjectMessage.getObject() sans ObjectInputFilter, ni liste blanche ou liste noire de classes. Comme cette opération s'exécute chaque fois que mapJmsMessage=true (valeur par défaut) et que Camel est un consommateur JMS, un attaquant capable de publier un ObjectMessage malveillant dans une file d'attente ou un sujet consommé peut obtenir une exécution de code à distance si une chaîne de gadgets est présente dans le classpath.
Avis : https://camel.apache.org/security/CVE-2026-40860.html
// JmsBinding.extractBodyFromJms(Exchange, Message) - affected version
if (message instanceof ObjectMessage objectMessage) {
Object payload = objectMessage.getObject(); // <-- deserializes with no ObjectInputFilter
if (payload instanceof DefaultExchangeHolder holder) {
...
}
return payload;
}
getObject() exécute le ObjectInputStream.readObject() du fournisseur JMS sur le corps du message. Camel n'ajoute aucun filtrage de classe de son propre chef, donc une chaîne de gadgets présente dans le classpath s'exécute pendant la désérialisation.
Le correctif (4.14.7 / 4.18.2 / 4.20.0) ajoute une liste blanche par défaut via ObjectInputFilter (java.**;javax.**;org.apache.camel.**;!*), personnalisable via la nouvelle option de point de terminaison deserializationFilter ou via le paramètre JVM global -Djdk.serialFilter. Voici le message de validation de Camel concernant le correctif :
cette vérification s'effectue après que le fournisseur JMS a déjà désérialisé la charge utile. Elle empêche la propagation de classes inattendues vers la route, mais elle ne peut pas, à elle seule, arrêter les chaînes de gadgets dont le
readObject()est déclenché à l'intérieur duObjectInputStreamdu fournisseur. Une protection complète nécessite de configurer le filtre de désérialisation du fournisseur JMS lui-même et/ou le-Djdk.serialFilterglobal.
Donc la protection complète = mise à jour de Camel + restriction du fournisseur / du filtre JVM. Ce PoC utilise un client ActiveMQ avec trustAllPackages=true (un paramètre courant dans la réalité) afin que le fournisseur désérialise la charge utile ; sur une version affectée de Camel, rien d'autre ne s'interpose.
from("jms:queue:evil") // mapJmsMessage defaults to true
.log("Consumed: ${body.class.name}");
Le simple fait de recevoir le ObjectMessage déclenche la désérialisation — le corps de la route est sans importance.
La victime est le consommateur Camel JMS. L'attaquant est tout producteur capable de publier dans la file d'attente. Les deux communiquent avec un courtier Apache ActiveMQ Artemis réel tournant dans Docker.
CVE-2026-40860/
├── pom.xml # camel-jms 4.18.1 + activemq-client 6.2.4 + commons-collections 3.2.1 (gadget)
├── Dockerfile # runs the app (--add-opens only to build the gadget)
├── docker-compose.yml # Artemis broker (quay.io) + the reproducer app
├── README.md
└── src/main/
├── java/com/example/
│ ├── Application.java
│ ├── JmsConfig.java # OpenWire ConnectionFactory (trustAllPackages=true) + jms component
│ ├── VictimRoute.java # victim: from("jms:queue:evil")
│ ├── Gadget.java # CommonsCollections6 gadget, fires during getObject()
│ └── ExploitController.java # attacker: publishes ObjectMessage(gadget) to the queue
└── resources/
└── application.properties
Dans une attaque réelle, les octets sérialisés sont produits hors ligne par l'attaquant (par exemple avec ysoserial); seule la victime a besoin de la chaîne de gadgets dans son classpath. Ce PoC construit le gadget en cours d'exécution pour des raisons de commodité, c'est pourquoi la JVM est lancée avec
--add-opens java.base/java.util=ALL-UNNAMED— un détail de construction du gadget, sans rapport avec la vulnérabilité.
mvn clean package -DskipTests
docker compose up -d --build
Cela démarre un courtier Artemis (quay.io/artemiscloud/activemq-artemis-broker) et l'application de reproduction, qui s'y connecte via OpenWire.
curl -s http://localhost:8080/exploit/attack
# -> ObjectMessage published to queue 'evil'.
# camel-jms consumer called ObjectMessage.getObject() -> deserialization.
#
# >>> RCE proof — /tmp/pwned exists: true
docker exec cve-2026-40860 ls -la /tmp/pwned
docker compose down
Tout consommateur Camel JMS (camel-jms, camel-sjms, camel-sjms2, camel-amqp, camel-activemq, camel-activemq6) lisant depuis une destination vers laquelle un attaquant peut publier — un courtier partagé, un sujet avec des producteurs ouverts, une file d'attente alimentée par une source amont non fiable — avec mapJmsMessage=true (valeur par défaut).
mapJmsMessage=true (par défaut).ObjectMessage vers la destination consommée.trustAllPackages=true, ou un fournisseur sans filtre restrictif).commons-collections:3.2.1).Mettez à jour vers 4.14.7 / 4.18.2 / 4.20.0, et restreignez la désérialisation de bout en bout :
-Djdk.serialFilter=java.**;org.apache.camel.**;!* (ou la nouvelle option de point de terminaison deserializationFilter).trustedPackages, n'utilisez pas trustAllPackages=true).En attendant la mise à jour :
ObjectMessage ; définissez mapJmsMessage=false lorsque le message brut est acceptable.trustAllPackages=true sur des destinations non fiables.-Djdk.serialFilter.Ce reproducteur est fourni uniquement pour la recherche en sécurité et les tests autorisés, pour une vulnérabilité publiquement divulguée et corrigée. Ne l'utilisez pas contre des systèmes sans autorisation explicite.
| Propriété | Valeur |
|---|
| Composants | camel-jms (+ camel-sjms, camel-sjms2, camel-amqp, camel-activemq, camel-activemq6) |
| Classe affectée | org.apache.camel.component.jms.JmsBinding#extractBodyFromJms → jakarta.jms.ObjectMessage#getObject() |
| CWE | CWE-502 : Désérialisation de données non fiables |
| Impact | Exécution de code à distance (RCE) |
| Déclencheur | Consommateur Camel JMS + mapJmsMessage=true (défaut) + un ObjectMessage que l'attaquant peut mettre en file d'attente |
| Versions affectées | De 3.0.0 avant 4.14.7, de 4.15.0 avant 4.18.2, de 4.19.0 avant 4.20.0 |
| Versions corrigées | 4.14.7, 4.18.2, 4.20.0 |
| JIRA | CAMEL-23321 |
| Signaleur | Venkatraman Kumar (Securin) |