
Lauffähiger Spring-Boot- und Quarkus-Reproducer für CVE-2026-94386, der die scope-übergreifende Wiederverwendung von OAuth-Tokens im statischen Token-Cache von Apache Camel camel-oauth demonstriert.
Lauffähiger Proof-of-Concept-Reproduzierer für die Apache-Camel-Schwachstelle, bei der
OAuthClientCredentialsTokenResolver die von ihm bezogenen Client-Credentials-Tokens in einem statischen Cache
ablegte, der von jeder Resolver-Instanz und jedem CamelContext in der JVM gemeinsam genutzt wurde, und diesen Cache
nur auf den Token-Endpunkt und die Client-ID schlüsselte — obwohl die Token-Anfrage auch das Client-Secret und
den angeforderten Scope enthält.
// affected
record TokenCacheKey(String tokenEndpoint, String clientId) { }
| Laufzeitumgebung | Verzeichnis | Stack |
|---|---|---|
| Camel Spring Boot | camel-spring-boot/ | Spring Boot 3.5.13 + camel-oauth 4.22.1 |
| Camel Quarkus | camel-quarkus/ | Camel Quarkus 3.39.0 (Quarkus 3.39.0, Camel 4.22.0) |
Zwei Profile, die einen Token-Endpunkt und eine Client-ID gemeinsam nutzen, aber unterschiedliche Scopes anfordern oder sich mit unterschiedlichen Client-Secrets authentifizieren, werden auf denselben Cache-Eintrag abgebildet. Das Profil, das zuerst aufgelöst wurde, hat ihn befüllt; dem anderen wurde dann dieses Token ausgehändigt, ohne dass unter seiner eigenen Konfiguration eine Token-Anfrage gestellt wurde, und zwar solange das Token gültig blieb.
Token-Caching ist standardmäßig aktiviert, und mehrere Profile auf einen einzigen Identity-Provider-Client zu richten, ist eine gewöhnliche Nutzung der Profilunterstützung und keine bewusste Entscheidung für eine unsichere Einstellung.
cd camel-spring-boot # or: cd camel-quarkus
mvn clean package
docker compose up -d --build
until curl -sf http://localhost:8080/exploit/ready >/dev/null; do sleep 1; done
curl -s http://localhost:8080/exploit/attack
docker compose down
Ausgabe auf einem betroffenen Build (identisch auf beiden Runtimes):
1) Profile 'invoices' resolves first
token handed to it: TOKEN-FOR[scope=invoice.read;secret=...aaaa]
token requests the IdP has received: 1
2) Profile 'admin' resolves next, through a DIFFERENT resolver instance
token handed to it: TOKEN-FOR[scope=invoice.read;secret=...aaaa]
token requests the IdP has received: 1 (unchanged - no request was made under its own config)
it was handed the token issued for the OTHER profile's scope: true
3) Profile 'partner' - same endpoint and clientId, DIFFERENT client secret (...-zzzz)
token handed to it: TOKEN-FOR[scope=invoice.read;secret=...aaaa]
it was served the token obtained with the other secret, without ever
authenticating with its own: true
4) CONTROL - profile 'admin' again with cache-tokens=false
token handed to it: TOKEN-FOR[scope=admin.write;secret=...aaaa]
it got a token for its own scope: true
Everything the IdP was actually asked for:
- client_id=camel-integration-client scope=invoice.read;secret=...aaaa
- client_id=camel-integration-client scope=admin.write;secret=...aaaa
>>> PROVEN: ...
Der Token-Wert benennt den Scope und das Secret, für die er ausgestellt wurde, sodass Sie sehen können, aus welcher Profilkonfiguration jeder Token tatsächlich stammt. Das Request-Log am Ende ist der harte Beweis: Bei aktiviertem Caching wurde der Identity Provider überhaupt nie nach dem Token des admin-Profils gefragt — nur die Anfrage des ersten Profils und die der Kontrolle erscheinen.
Probe 2 verwendet bewusst eine zweite Resolver-Instanz: Der Cache ist static, sodass eine separate Instanz — und ein separater CamelContext — ihn gemeinsam nutzt.
Der Resolver bezieht Tokens im Auftrag der Komponenten, die ihre ausgehenden Aufrufe mit einem benannten OAuth-Profil authentifizieren — camel-openai, camel-langchain4j-agent, camel-huggingface, camel-docling, camel-a2a, camel-spring-ai-chat und die IBM-watsonx-Komponenten — sowie von Anwendungen, die das OAuthClientAuthenticationFactory-SPI direkt aufrufen.
Um das Problem zu erreichen, ist ein Deployment erforderlich, das mehr als ein Profil definiert, die sich einen Token-Endpunkt und eine Client-ID teilen, sich aber in Scope oder Client-Secret unterscheiden.
Die Profile werden durch das Deployment konfiguriert, das vertrauenswürdig ist, sodass keine nicht vertrauenswürdige Eingabe den Cache erreicht. Der Nutznießer ist der Dienst, der einen Token erhält, der nicht für ihn bestimmt war, und der, falls feindlich gesinnt oder kompromittiert, diesen Token innerhalb seines Scopes wiederverwenden kann. Das Ergebnis ist eine ausgehende Anfrage, die einen Token trägt, der für den Scope eines anderen Profils ausgestellt wurde — eine umfassendere Berechtigung, als das aufrufende Profil konfiguriert hatte, oder ein Dienst, für den der Token nicht ausgestellt wurde. Bei Identity Providern, die die Audience eines Tokens aus dem angeforderten Scope ableiten, kann ein anderer Scope einen Token für einen gänzlich anderen Dienst bedeuten.
Kein Identity Provider und kein Netzwerk. Die App hostet einen Stellvertreter für den Token-Endpunkt des Providers, der den Client-Credentials-Grant bedient, einen Token ausstellt, dessen Wert den Scope und das Client-Secret benennt, für die er ausgestellt wurde, und jede Anfrage aufzeichnet. Der echte OAuthClientCredentialsTokenResolver und der echte statische Cache laufen.
Der Stellvertreter liest die Client-Credentials aus dem HTTP-Basic-Authorization-Header, nicht aus Formularparametern, denn so sendet
camel-oauthsie.
| Eigenschaft | Wert |
|---|---|
| Komponente | camel-oauth (OAuthClientCredentialsTokenResolver) |
| CWE | CWE-524 (Use of Cache Containing Sensitive Information) |
| Schweregrad | Mittel |
| Angriffsvektor | Zwei oder mehr OAuth-Profile, die sich einen Token-Endpunkt und eine Client-ID teilen, sich aber in Scope oder Client-Secret unterscheiden |
| Auswirkung | Eine ausgehende Anfrage, die einen Token trägt, der für den Scope eines anderen Profils ausgestellt oder mit dem Secret eines anderen Profils bezogen wurde |
| Betroffene Versionen | Ab 4.19.0 vor 4.22.2 |
| Behobene Versionen | 4.22.2, 4.23.0 |
| JIRA | CAMEL-24872 |
| Danksagung | Andrea Cosentino von der Apache Software Foundation |
Advisory: https://camel.apache.org/security/CVE-2026-94386.html
Der Cache-Schlüssel enthält nun jede Eingabe der Token-Anfrage: den Token-Endpunkt, die Client-ID, einen SHA-256-Fingerprint des Client-Secrets — sodass das rohe Secret nicht im statischen Cache gehalten wird — und den angeforderten Scope. Ein fehlender, leerer oder nur aus Leerzeichen bestehender Scope zählt als ein Wert, da jeder von ihnen den scope-Parameter aus der Anfrage weglässt; jeder andere Scope wird exakt abgeglichen. Profile, die in allen vier Punkten übereinstimmen, teilen sich weiterhin einen Token über Resolver-Instanzen hinweg, während Profile, die sich in Credentials oder Scope unterscheiden, separate Tokens beziehen.
Behoben auf main in apache/camel#26681 (5e896b2d), zurückportiert auf camel-4.22.x in #27779 (b2d9957b).
Setzen Sie camel.version auf 4.22.2 und führen Sie es erneut aus. Verifiziert gegen 4.22.1 → 4.22.2: