
# CVE-2026-33701 का तकनीकी विश्लेषण OpenTelemetry Java Agent RMI इंस्ट्रुमेंटेशन में असुरक्षित डिसीरियलाइज़ेशन भेद्यता, जिसमें शोषण की स्थितियाँ, पैच विवरण और शमन रणनीतियाँ शामिल हैं।
गंभीरता: क्रिटिकल (CVSS v4.0: 9.3)
प्रभावित संस्करण: opentelemetry-javaagent < 2.26.1
पैच किया गया: 2.26.1 (23 मार्च 2026 को जारी)
OpenTelemetry वर्तमान में Java इकोसिस्टम में सबसे व्यापक रूप से अपनाया गया ऑब्ज़र्वेबिलिटी फ्रेमवर्क है। विशेष रूप से Java एजेंट का उपयोग हजारों प्रोडक्शन सेवाओं द्वारा ट्रेसिंग, मेट्रिक्स और लॉगिंग के लिए एप्लिकेशन को स्वचालित रूप से इंस्ट्रूमेंट करने के लिए किया जाता है, बिना डेवलपर से किसी कोड परिवर्तन की आवश्यकता के। आप इसे केवल javaagent फ्लैग के रूप में अटैच करते हैं और यह पर्दे के पीछे सब कुछ करता है।
CVE-2026-33701 को दिलचस्प बनाने वाली बात सिर्फ़ यह भेद्यता नहीं है, बल्कि यह है कि इसे कैसे पेश किया गया। एजेंट अपने RMI इंस्ट्रूमेंटेशन के साइड इफेक्ट के रूप में चुपचाप एक कस्टम RMI एंडपॉइंट पंजीकृत करता है। डेवलपर्स को पता नहीं होता कि यह वहाँ है। यह किसी एप्लिकेशन कोड में नहीं है। यह कुछ ऐसा नहीं है जिसे उन्होंने कॉन्फ़िगर किया हो। एजेंट ने इसे स्वचालित रूप से वहाँ रखा, और 2.26.1 से नीचे के संस्करणों के लिए वह एंडपॉइंट बिना किसी फ़िल्टर के आने वाले डेटा को डिसीरियलाइज़ कर रहा था।
यदि एप्लिकेशन में पहले से ही RMI या JMX पोर्ट खुला था, तो उस पोर्ट तक नेटवर्क एक्सेस रखने वाला हमलावर एजेंट के कस्टम एंडपॉइंट पर एक क्राफ्टेड सीरियलाइज़्ड पेलोड भेज सकता था और संभावित रूप से JVM प्रक्रिया के विशेषाधिकारों के साथ रिमोट कोड निष्पादन प्राप्त कर सकता था। शर्त यह है कि RCE के लिए एप्लिकेशन क्लासपाथ पर एक संगत गैजेट चेन मौजूद होनी चाहिए। उस पर बाद में और विस्तार से।
जब OTel Java एजेंट JVM से अटैच होता है, तो यह संदर्भ प्रसार के लिए RMI कॉल्स को इंस्ट्रूमेंट करता है। विचार यह है कि जब कोई RMI क्लाइंट किसी रिमोट मेथड को कॉल करता है, तो एजेंट को वर्तमान ट्रेस संदर्भ को सर्वर साइड तक प्रचारित करने की आवश्यकता होती है ताकि स्पैन सेवा सीमाओं के पार सही ढंग से जुड़ सकें।
ऐसा करने के लिए एजेंट एक हार्डकोडेड ObjID का उपयोग करके अपना स्वयं का कस्टम RMI ऑब्जेक्ट पंजीकृत करता है। ContextPropagator.java में:
public static final ObjID CONTEXT_CALL_ID =
new ObjID("io.opentelemetry.javaagent.context-call".hashCode());
यह ObjID वह तरीका है जिससे एजेंट RMI रनटाइम के भीतर अपने स्वयं के एंडपॉइंट की पहचान करता है। जब एक इंस्ट्रूमेंटेड RMI क्लाइंट किसी सर्वर से कनेक्ट होता है, तो यह पहले जाँचता है कि सर्वर के पास यह ObjID पंजीकृत है या नहीं। यदि है, तो यह उसे एक संदर्भ प्रसार पेलोड भेजता है। यदि नहीं, तो यह संदर्भ प्रसार को छोड़ देता है और केवल नियमित कॉल करता है।
समस्या यह है कि यह एंडपॉइंट JVM RMI रनटाइम के अंदर पंजीकृत है, जिसका अर्थ है कि यह उसी ट्रांसपोर्ट को साझा करता है जो एप्लिकेशन के खुले RMI या JMX पोर्ट के पास है। यदि एप्लिकेशन में -Dcom.sun.management.jmxremote.port सेट है, या यदि यह अपनी स्वयं की RMI सेवाओं को एक्सपोर्ट करता है, तो एजेंट का एंडपॉइंट उसी पोर्ट पर किसी भी व्यक्ति के लिए पहुंच योग्य है जो उससे कनेक्ट कर सकता है।
डिसीरियलाइज़ेशन ContextPayload.java में होता है। 2.26.1 से नीचे के संस्करणों में read() मेथड इस तरह दिखती थी:
@SuppressWarnings("BanSerializableRead") // fine
public static ContextPayload read(ObjectInput oi) throws IOException {
try {
Object object = oi.readObject();
if (object instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, String> map = (Map<String, String>) object;
return new ContextPayload(map);
}
} catch (ClassCastException | ClassNotFoundException ex) {
logger.log(FINE, "Error reading object", ex);
}
return null;
}
@SuppressWarnings("BanSerializableRead") एनोटेशन // fine टिप्पणी के साथ वास्तव में कुछ कहता है। टीम के किसी व्यक्ति ने किसी बिंदु पर इसे एक चिंता के रूप में फ्लैग किया था, सप्रेशन जोड़ा गया था, और टिप्पणी इसे उचित ठहराने के लिए थी। स्पष्ट रूप से यह ठीक नहीं था।
बिना किसी सीरियलाइज़ेशन फ़िल्टर के oi.readObject() क्लासिक असुरक्षित डिसीरियलाइज़ेशन पैटर्न है। Java सीरियलाइज़ेशन डिसीरियलाइज़ेशन के दौरान क्लासपाथ पर किसी भी क्लास को खुशी से इंस्टैंशिएट करेगा, और यदि हमलावर सीरियलाइज़्ड स्ट्रीम को नियंत्रित कर सकता है, तो वे चुन सकते हैं कि कौन सी क्लासें इंस्टैंशिएट हों और किस क्रम में। यही गैजेट चेन हमलों का आधार है।
कोड डिसीरियलाइज़ेशन के बाद instanceof Map की जाँच करता है, लेकिन उस बिंदु तक नुकसान पहले ही हो चुका होता है। गैजेट चेन readObject() कॉल के दौरान ही निष्पादित होती है, किसी भी टाइप जाँच से पहले। सुरक्षा के दृष्टिकोण से instanceof जाँच पूरी तरह से अप्रासंगिक है।
2.26.1 में फिक्स डिसीरियलाइज़ेशन दृष्टिकोण को पूरी तरह से बदल देता है। Map ऑब्जेक्ट को सीरियलाइज़ करने और readObject() को कॉल करने के बजाय, नया कार्यान्वयन संदर्भ प्रविष्टियों को प्रिमिटिव प्रकारों के रूप में मैन्युअल रूप से पढ़ता है:
@Nullable
public static ContextPayload read(ObjectInput oi) throws IOException {
int size = oi.readInt();
if (size > MAX_CONTEXT_ENTRIES) {
logger.log(
FINE,
"RMI context propagation payload size {0} exceeds maximum allowed of {1}, skipping context propagation.",
new Object[] {size, MAX_CONTEXT_ENTRIES});
return null;
}
Map<String, String> map = new HashMap<>();
for (int i = 0; i < size; i++) {
String key = oi.readUTF();
String value = oi.readUTF();
map.put(key, value);
}
return new ContextPayload(map);
}
और राइट साइड को मैच करने के लिए अपडेट किया गया:
public void write(ObjectOutput out) throws IOException {
int size = context.size();
if (size > MAX_CONTEXT_ENTRIES) {
out.writeInt(0);
return;
}
out.writeInt(size);
for (Map.Entry<String, String> entry : context.entrySet()) {
out.writeUTF(entry.getKey());
out.writeUTF(entry.getValue());
}
}
यह दृष्टिकोण स्ट्रीम से केवल प्रिमिटिव प्रकार (ints और UTF स्ट्रिंग्स) पढ़ता है। कोई ऑब्जेक्ट इंस्टैंशिएशन नहीं हो रहा है। readInt() या readUTF() के माध्यम से कोई गैजेट चेन निष्पादित नहीं हो सकती। फिक्स सही और पूर्ण है।
एक दूसरा परिवर्तन भी ध्यान देने योग्य है। एजेंट के कस्टम एंडपॉइंट की पहचान करने के लिए उपयोग किया जाने वाला ObjID संस्करणित किया गया था:
// पहले (2.26.0)
new ObjID("io.opentelemetry.javaagent.context-call".hashCode());
// बाद में (2.26.1)
new ObjID("io.opentelemetry.javaagent.context-call-v2".hashCode());
इसका मतलब है कि एक पैच किया गया एजेंट पुराने ObjID पर बिल्कुल भी प्रतिक्रिया नहीं देगा। पुराने एंडपॉइंट को लक्षित करने वाले हमलावर को पैच किए गए सर्वर से कोई प्रतिक्रिया नहीं मिलेगी। यह डिसीरियलाइज़ेशन फिक्स के शीर्ष पर एक अच्छा अतिरिक्त हार्डनिंग उपाय है, यह प्रभावी रूप से किसी भी एक्सप्लॉइट को तोड़ देता है जो v1 एंडपॉइंट के खिलाफ बनाया गया था, भले ही किसी तरह डिसीरियलाइज़ेशन अभी भी पहुंच योग्य हो।
इसके शोषण योग्य होने के लिए निम्नलिखित तीनों शर्तें एक साथ सत्य होनी चाहिए:
1. OpenTelemetry Java एजेंट JDK 16 या उससे नीचे पर अटैच किया गया
यह सबसे महत्वपूर्ण बाधा है। JDK 17 ने RMI इंटर्नल्स में महत्वपूर्ण परिवर्तन पेश किए और Java Platform Module System के माध्यम से बहुत सख्त मॉड्यूल एनकैप्सुलेशन लागू करता है। Java डिसीरियलाइज़ेशन के खिलाफ काम करने वाली अधिकांश गैजेट चेन आंतरिक JDK क्लासेस तक पहुंचने या उन मेथड्स को इनवोक करने के लिए रिफ्लेक्शन पर निर्भर करती हैं जो सामान्य रूप से सुलभ नहीं होतीं। JDK 17 मजबूत एनकैप्सुलेशन के कारण डिफ़ॉल्ट रूप से उन रिफ्लेक्शन पथों को तोड़ देता है, जिसका अर्थ है कि अधिकांश ज्ञात गैजेट चेन JDK 17 और उससे ऊपर पर काम नहीं करतीं।
हालाँकि, JDK 8, 11 और 16 पर, रिफ्लेक्शन एक्सेस बहुत अधिक अनुमेय है और गैजेट चेन अपेक्षा के अनुरूप काम करती हैं। ये JDK संस्करण अभी भी प्रोडक्शन Java डिप्लॉयमेंट्स का एक महत्वपूर्ण हिस्सा दर्शाते हैं, विशेष रूप से पुराने एंटरप्राइज़ वातावरण में।
2. एक RMI या JMX पोर्ट हमलावर के लिए नेटवर्क-पहुंच योग्य है