
CVE-2026-95663 के लिए रन करने योग्य प्रूफ-ऑफ-कॉन्सेप्ट रिप्रोड्यूसर, जिसमें Apache Camel camel-keycloak ने टोकन समाप्ति के बाद भी कैश किए गए टोकन इंट्रोस्पेक्शन परिणाम प्रदान किए, साथ में Spring Boot और Quarkus डेमो।
Apache Camel की उस भेद्यता के लिए चलाने योग्य प्रूफ-ऑफ-कॉन्सेप्ट पुनरुत्पादक, जिसमें KeycloakSecurityPolicy ने
टोकन इंट्रोस्पेक्शन सक्षम (useTokenIntrospection=true) होने पर एक्सेस टोकन के समाप्त होने के बाद भी उसे मान्य मानना जारी रखा,
क्योंकि कैश्ड इंट्रोस्पेक्शन परिणाम टोकन से अधिक समय तक जीवित रहा।
| रनटाइम | निर्देशिका | स्टैक |
|---|---|---|
| Camel Spring Boot | camel-spring-boot/ | Spring Boot 3.5.13 + camel-keycloak 4.22.1 |
| Camel Quarkus | camel-quarkus/ | Camel Quarkus 3.39.0 (Quarkus 3.39.0, Camel 4.22.0) |
इंट्रोस्पेक्शन पथ पर, Keycloak किसी टोकन के exp बीत जाने के बाद उसे inactive बताता है — इसलिए
active फ़्लैग ही समाप्ति को लागू करता है। इंट्रोस्पेक्शन परिणाम डिफ़ॉल्ट रूप से कैश किए जाते हैं
(introspectionCacheEnabled=true, introspectionCacheTtl=60), और दोनों शिप्ड कैश
(ConcurrentMapTokenCache, CaffeineTokenCache) किसी प्रविष्टि को उसके डालने के बाद एक निश्चित समय पर समाप्त करते थे,
बिना उस जीवनकाल को उस exp क्लेम से सीमित किए जो कैश्ड प्रतिक्रिया में पहले से मौजूद था।
बदले में KeycloakSecurityProcessor ने केवल active की जाँच की और कभी exp की वर्तमान समय से तुलना नहीं की।
इसलिए टोकन के समाप्त होने से कुछ समय पहले कैश की गई प्रतिक्रिया, उसी टोकन को ले जाने वाले बाद के अनुरोधों को प्रस्तुत की जाती थी,
जिन्हें उन भूमिकाओं और अनुमतियों के साथ स्वीकार किया जाता था जो समाप्त टोकन में थीं — यद्यपि RFC 7662 कहता है
कि exp वाली प्रतिक्रिया को उस समय से आगे कैश नहीं किया जाना चाहिए।
cd camel-spring-boot # or: cd camel-quarkus
mvn clean package
docker compose up -d --build
until [ "$(curl -s http://localhost:8080/exploit/ready)" = "Started" ]; do sleep 1; done
curl -s http://localhost:8080/exploit/attack # takes ~4s: it waits for the token to expire
docker compose down
प्रभावित बिल्ड पर आउटपुट (दोनों रनटाइम पर समान):
1) Request while the token is still valid
introspection calls so far: 1
outcome: [ADMITTED - the protected route ran]
... the token's exp has now passed. The cache entry is still inside its 60s TTL.
2) The same token again, now expired, against the SAME route
introspection calls so far: 1 (unchanged - the endpoint was never asked again)
outcome: [ADMITTED - the protected route ran]
an expired token was admitted: true
3) CONTROL - the same expired token against a route with introspectionCacheEnabled=false
introspection calls so far: 2 (the endpoint was asked)
outcome: [REJECTED - CamelAuthorizationException: Token is not active (may be revoked or expired)]
rejected once the endpoint was actually consulted: true
>>> PROVEN: ...
इंट्रोस्पेक्शन कॉल काउंटर ही प्रमाण है: यह प्रोब 1 और प्रोब 2 के बीच नहीं बदलता। पहचान प्रदाता से दूसरी बार कभी नहीं पूछा गया, इसलिए उसका निर्णय — जो तब तक "inactive" हो चुका है — कभी पॉलिसी तक नहीं पहुँचा। प्रोब 3 सिद्ध करता है कि वह निर्णय मौजूद है: कैशिंग बंद वाले रूट के विरुद्ध वही समाप्त टोकन अस्वीकार कर दिया जाता है, जो उन डिप्लॉयमेंट्स के लिए भी वर्कअराउंड है जो अभी अपग्रेड नहीं कर सकते। introspectionCacheTtl को कम करना केवल विंडो को संकीर्ण करता है; यह उसे बंद नहीं करता।
कोई Keycloak सर्वर नहीं और कोई नेटवर्क नहीं। ऐप Keycloak के introspection endpoint के लिए एक स्टैंड-इन होस्ट करता है, ठीक उसी पथ पर जो KeycloakTokenIntrospector बनाता है
({serverUrl}/realms/{realm}/protocol/openid-connect/token/introspect), जो Keycloak की तरह व्यवहार करता है:
exp बीतने तक active, उसके बाद inactive, प्रतिक्रिया में exp, iss और realm_access.roles के साथ। वास्तविक KeycloakSecurityPolicy, वास्तविक KeycloakTokenIntrospector और वास्तविक कैश चलते हैं।
Introspection के लिए टोकन का JWT होना आवश्यक नहीं है, इसलिए यहाँ टोकन एक अपारदर्शी स्ट्रिंग है — जो exp मायने रखता है वह introspection प्रतिक्रिया में रहता है, जिसका सम्मान करने में कैश विफल हो रहा था।
यह समस्या टोकन के पहले से धारण किए गए अधिकारों से अधिक कुछ नहीं देती: न टोकन जालसाज़ी और न ही विशेषाधिकार वृद्धि। विंडो introspectionCacheTtl से सीमित है — डिफ़ॉल्ट रूप से 60 सेकंड, 300 से 600 जहाँ आंतरिक सेवाओं के लिए दस्तावेज़ की सिफ़ारिश का पालन किया गया — और यह केवल उसी टोकन के लिए खुलती है जिसे उसी Camel प्रक्रिया ने introspect किया और कैश किया था जब वह अभी भी active था। Token introspection डिफ़ॉल्ट रूप से सक्षम नहीं है। इसलिए मध्यम गंभीरता।
स्थानीय JWT सत्यापन पथ, जो introspection अक्षम होने पर उपयोग होता है, प्रभावित नहीं है।
| गुण | मान |
|---|---|
| घटक | camel-keycloak (KeycloakSecurityPolicy with useTokenIntrospection=true) |
| CWE | CWE-613 (Insufficient Session Expiration) |
| गंभीरता | मध्यम |
| आक्रमण वेक्टर | एक कॉलर द्वारा bearer टोकन को फिर से प्रस्तुत करना जो कुछ क्षण पहले वैध था, introspection कैश के TTL के भीतर |
| प्रभाव | टोकन की समाप्ति के बाद अनुमत किए गए अनुरोध, उस टोकन द्वारा धारण की गई भूमिकाओं और अनुमतियों के साथ |
| प्रभावित संस्करण | 4.16.0 से 4.18.5 से पहले तक, 4.19.0 से 4.22.2 से पहले तक |
| ठीक किए गए संस्करण | 4.18.5, 4.22.2, 4.23.0 |
| JIRA | CAMEL-24902 |
| श्रेय | Andrea Cosentino from Apache Software Foundation |
Advisory: https://camel.apache.org/security/CVE-2026-95663.html
दो स्वतंत्र परिवर्तन:
introspectionCacheTtl और टोकन के exp तक शेष समय में से जो छोटा हो उससे सीमित करते हैं, और जिस परिणाम का टोकन पहले ही समाप्त हो चुका है वह कभी कैश से नहीं दिया जाता।KeycloakSecurityProcessor अब introspection का उपयोग करने वाले प्रत्येक पथ पर उस टोकन को अस्वीकार करता है जिसका exp अतीत में है, इसलिए समाप्ति वहाँ भी लागू होती है जैसे वह पहले से स्थानीय JWT सत्यापन पथ पर थी।जिस प्रतिक्रिया में कोई exp दावा नहीं होता, वह कॉन्फ़िगर किए गए time-to-live को बनाए रखती है।
main पर apache/camel#26747 (7bfacb70) में ठीक किया गया, camel-4.22.x में #27182 (66fd7034) में और camel-4.18.x में #27183 (6be02c19) में बैकपोर्ट किया गया।
camel.version को 4.22.2 पर सेट करें और इसे फिर से चलाएँ। 4.22.1 → 4.22.2 के विरुद्ध सत्यापित:
2) The same token again, now expired, against the SAME route
introspection calls so far: 2
outcome: [REJECTED - CamelAuthorizationException: Token is not active (may be revoked or expired)]
an expired token was admitted: false
>>> NOT reproduced: the expired token was rejected.
ध्यान दें कि काउंटर 1 से 2 तक बढ़ रहा है: कैश एंट्री का जीवनकाल टोकन के exp द्वारा सीमित था, इसलिए
दूसरे अनुरोध ने पुनः इंट्रोस्पेक्शन किया और एंडपॉइंट का वास्तविक निर्णय प्राप्त किया।
यह रिपॉज़िटरी शैक्षिक और रक्षात्मक उद्देश्यों के लिए प्रकाशित की गई है: Apache Camel उपयोगकर्ताओं को इस भेद्यता को समझने, यह सत्यापित करने में मदद करने के लिए कि क्या वे प्रभावित हैं, और यह पुष्टि करने के लिए कि अपग्रेड करने से यह हल हो जाती है। इस सामग्री का उपयोग उन सिस्टमों के विरुद्ध न करें जिनके आप स्वामी या संचालक नहीं हैं।