CVE-2023-46604 is a CVSS 10.0 unauthenticated remote code execution vulnerability in the ActiveMQ Classic OpenWire protocol marshaller. If an attacker can open a TCP connection to your OpenWire transport connector (default port 61616), they can send a crafted packet that causes the broker to instantiate an arbitrary class on the classpath. No credentials are required. It was exploited in the wild before and immediately after disclosure in October 2023, and unpatched, internet-reachable brokers are still being found.
This article covers two situations: you just found out you might be running a vulnerable broker and need to triage fast, or you are doing a scheduled exposure audit. The sequence is the same: determine the version, determine network reachability, look for exploitation evidence, then patch and restrict.
The fix shipped in ActiveMQ 5.15.16, 5.16.7, 5.17.6, and 5.18.3. Anything older on those branches is vulnerable. Per the Apache advisory, the affected ranges are 5.18.0 through 5.18.2, 5.17.0 through 5.17.5, 5.16.0 through 5.16.6, all versions before 5.15.16, and the Legacy OpenWire Module 5.8.0 through 5.15.15.
What this means
The vulnerability is in the OpenWire marshaller’s handling of the ClassInfo structure inside an ExceptionResponse command. The broker (or client) unmarshals a class name supplied by the remote peer without validating that it is actually a Throwable. The known exploit chain uses a Spring configuration class to load a malicious XML file from an attacker-controlled HTTP server, which yields command execution as the broker’s OS user. Two properties make this unusually dangerous:
- It is pre-authentication. The attacker only needs TCP reachability to the OpenWire port. No valid JMS credentials, no web console access, nothing else.
- It hits both sides of the connection. Brokers are vulnerable when they receive the crafted packet, and Java-based OpenWire clients are vulnerable when a malicious or compromised broker sends it to them. Patching only your brokers leaves client-side exposure in place.
Multiple incident response firms documented exploitation delivering cryptominers, ransomware, and remote access tooling against exposed brokers, with forensic evidence of exploitation weeks before public disclosure. An unpatched broker reachable from an untrusted network should be treated as potentially compromised, not just potentially vulnerable.
flowchart TD
A[Broker on 61616] --> B{Version patched?}
B -- "5.15.16+ / 5.16.7+ / 5.17.6+ / 5.18.3+" --> C{61616 reachable from untrusted networks?}
B -- "older / unknown" --> D[Treat as exposed]
D --> E[Isolate network access now]
D --> F[Hunt logs for exploit evidence]
D --> G[Patch broker and OpenWire clients]
C -- "yes" --> H[Still restrict: management and transport ports off untrusted networks]
C -- "no" --> I[Patch on schedule, verify bind addresses]Exposure factors
| Factor | What it looks like | First thing to check |
|---|---|---|
| Vulnerable version | Broker on any 5.x build older than 5.15.16/5.16.7/5.17.6/5.18.3 | Version in the startup banner of activemq.log, or jar filenames in the ActiveMQ lib directory |
| OpenWire port reachable from untrusted network | 61616 bound to 0.0.0.0, no firewall restriction, or exposed through a load balancer or security group | ss -tlnp for the bind address, plus the actual network path (cloud SG, LB, DNAT) |
| Default or no broker authentication | admin/admin works, or no authentication plugin configured | Broker config for the authentication plugin |
| Legacy OpenWire Module | 5.8.0 through 5.15.15 module in use on otherwise newer installs | Check whether the legacy module is deployed |
| Java OpenWire clients unpatched | Client applications embed an older activemq-client jar and connect to brokers you do not fully control | Dependency tree of client applications |
Quick checks
Run these read-only commands on the broker host. Nothing here restarts or modifies the broker.
# 1. Confirm the OpenWire listener, its bind address, and the owning PID
ss -tlnp | grep 61616
# 0.0.0.0:61616 or :::61616 means every interface. 127.0.0.1:61616 means local only.
# The -p flag shows the PID/process name; confirm it is the broker JVM.
# 2. Count current established OpenWire connections and note source IPs
ss -tn state established '( sport = :61616 )' | wc -l
ss -tn state established '( sport = :61616 )' | awk 'NR>1 {print $6}' | sort | uniq -c | sort -rn | head -20
# 3. Check the web console and JMX exposure while you are here
ss -tlnp | grep -E '8161|1099'
# 4. Determine the running version from the broker log startup banner
grep -i "Apache ActiveMQ" /opt/activemq/data/activemq.log | tail -5
# 5. Cross-check jar versions on disk
ls /opt/activemq/lib/ | grep -i activemq | head -20
# 6. Hunt the broker log for deserialization and transport-failure signatures
grep -ci "ClassNotFoundException\|InvalidClassException\|StreamCorruptedException\|deserialization" /opt/activemq/data/activemq.log
grep -i "Transport Connection to" /opt/activemq/data/activemq.log | grep -i "SocketException" | tail -20
# 7. Authentication failure noise (brute force or scanning pressure)
grep -ci "authentication failed\|invalid credentials\|login failed" /opt/activemq/data/activemq.log
Two notes on the log hunt:
- Rapid7’s incident response team reported that successful exploitation produced a single WARN line of the form
Transport Connection to: tcp://<attacker_ip>:<port> failed: java.net.SocketException, followed by a connection-abort message. Treat matching lines as a strong signal, but treat their absence as inconclusive. Logging configuration and attacker behavior vary, and a quiet log does not prove the broker was not compromised. - The deserialization-error patterns (
ClassNotFoundException,InvalidClassException) are useful both for exploit detection and for distinguishing a client version mismatch from an attack. Repeated deserialization errors naming gadget-style class paths (for example underorg.apache.commons.collections.functorsororg.springframework.beans.factory) are a page-worthy event, not a ticket.
Also check reachability from outside the host, not just the bind address. A broker bound to 0.0.0.0 behind a firewall that blocks 61616 is a different risk from one behind a permissive cloud security group or an accidental load balancer listener. Scan from a host on each network segment that should NOT have access.
How to diagnose it
- Establish the version and patch state. Use the startup banner and jar listing above. Map it against the fixed versions: 5.15.16, 5.16.7, 5.17.6, 5.18.3. If you cannot determine the version, assume vulnerable.
- Map the network exposure. Record the bind address, the firewall or security group rules, and any NAT or load balancer path that can deliver TCP 61616 from an untrusted network. Do the same for 8161 (web console) and 1099 (JMX), since default credentials plus an exposed management interface is the second classic ActiveMQ compromise path.
- Hunt for exploitation evidence. Grep for the socket-abort WARN signature and deserialization errors as above. If you find attacker IPs or suspicious ClassNotFound patterns, pivot to host forensics: unexpected child processes of the broker JVM, outbound HTTP from the broker host to unknown servers (the Spring XML fetch), new files written by the broker user, and persistence mechanisms. At that point this is an incident response, not a patching exercise.
- Check client-side exposure. Inventory applications embedding the ActiveMQ OpenWire client. Any client on a vulnerable version that connects to brokers outside your full control inherits the same RCE risk in the other direction.
- Decide containment. If the broker is vulnerable AND reachable from an untrusted network, restrict network access to 61616 first (firewall, security group, or binding the transport connector to a trusted interface). Do not wait for the patch window. If exploitation evidence exists, isolate the host before anything else.
Signals to monitor going forward
| Signal | Why it matters | Warning sign |
|---|---|---|
| Deserialization errors in broker log | Primary application-layer exploit fingerprint for this CVE class | Any repeat occurrence; any match on gadget-style package names |
| Transport connection failures | Exploit attempts and scanning both show up as abnormal connection churn and socket errors on 61616 | WARN lines with unfamiliar source IPs, especially followed by aborts |
| Connection count anomalies | Exploitation and scanning appear as connections from IPs outside your known client set | Established 61616 sessions from addresses not in your client inventory |
| Authentication failure rate | Pre-attack reconnaissance and brute force against default credentials | Sustained failures from a single source, or broad failures after a credential change |
| Web console / JMX access sources | Management interfaces should only see your management network | Any session to 8161 or 1099 from a non-allowlisted source |
| Unexpected child processes or outbound connections from the broker host | Post-exploitation behavior: payload fetch, miner, C2 | Broker JVM spawning shells, or egress to unknown HTTP servers |
Fixes
Patch the broker
Upgrade to 5.15.16, 5.16.7, 5.17.6, 5.18.3, or any later release on a supported line. The fix adds class-type validation in the OpenWire marshaller so only Throwable types can be instantiated through the vulnerable path. This is the only complete fix. Check the Java version requirement of your target release line before upgrading: the 5.17 line and later require Java 11, and the 6.x line requires Java 17.
Patch Java OpenWire clients
Client applications embedding the vulnerable marshaller are exploitable by a malicious broker. Upgrade the client dependency in lockstep. This is the part most teams miss because advisories emphasize the broker.
Restrict network access to the OpenWire port
Regardless of patch state, 61616 should only be reachable from networks that legitimately host your JMS clients. Use host firewall rules, cloud security groups, or bind the transport connector to a specific interface. Apply the same discipline to the web console (8161) and the remote JMX connector (disabled by default in current configs via createConnector="false"). If your deployment has re-enabled either for remote management, audit the source restriction and credentials.
Set the SERIALIZABLE_PACKAGES whitelist, with the right expectations
The org.apache.activemq.SERIALIZABLE_PACKAGES system property whitelists which Java packages may be deserialized in JMS ObjectMessage bodies. Configure it to only the packages your applications genuinely serialize. This hardens you against the ObjectMessage deserialization class of attacks (CVE-2015-5254 and similar). Important caveat: it controls the ObjectMessage path, not the OpenWire ExceptionResponse marshaller path that CVE-2023-46604 exploits. It is defense in depth, not a substitute for the patch.
One upgrade gotcha reported on newer releases: the 5.19.7 / 6.2.6 security-hardening releases removed java.lang from the default serializable packages list. If your applications send ObjectMessages carrying java.lang types, they will fail deserialization after that upgrade until you explicitly add java.lang back. Plan for this when jumping from an old 5.x line straight to current.
Replace default credentials
The broker ships with well-known credentials (admin/admin). Change them on the broker transports and the web console, and use the authorization plugin to restrict which users can create and access which destinations. Auto-created destinations plus weak credentials turn a messaging bug into a network foothold.
Prevention
- Never expose 61616 to untrusted networks. Patch state changes; architecture should not depend on it. The OpenWire port is for your applications only.
- Track broker version as an inventory item. You should be able to answer “what ActiveMQ version runs where” without SSHing into anything.
- Alert on the security signals above, especially deserialization errors and management-interface access from unexpected sources. These are cheap log-based checks.
- Audit bind addresses after every config change. Exposure regressions usually come from someone “temporarily” binding to 0.0.0.0 or opening a security group for a test.
- Watch for scanning pressure. Connection-count anomalies and authentication failure spikes on 61616 are early indicators that your broker is on someone’s target list.
How Netdata helps
- Per-second connection visibility: Netdata charts TCP connection counts and rates on the broker host, so a sudden burst of new sessions on 61616 from scanning or exploit attempts stands out against your client baseline instead of hiding in a 5-minute average.
- Correlation across signals: Connection anomalies on 61616 can be viewed next to broker process CPU, thread count, and network egress. The post-exploitation shape (odd child activity, unexpected outbound traffic, CPU spike from a miner) becomes visible in one place rather than across three tools.
- JMX and JVM context: Broker MBean metrics (connections, destinations, memory) alongside JVM heap and GC give you a behavioral baseline, which makes deviations during and after an incident easier to spot and document.
- Log-based signal groundwork: Pairing Netdata’s system metrics with your centralized log alerts on the deserialization and transport-failure patterns above covers both the network layer and the application layer of this CVE class.
Related guides
- ActiveMQ broker down: telling a crashed broker from a hung one
- ActiveMQ broker won’t start: port conflicts, store recovery, and lock contention
- ActiveMQ InactivityIOException: Channel was inactive for too long
- ActiveMQ connection and session leak: clients that never close
- ActiveMQ consumers connected but not acknowledging: the zombie consumer
- ActiveMQ destination explosion: dynamic destinations, MBean bloat, and GC pressure
- ActiveMQ disk full on the KahaDB partition: write failures and store corruption risk
- ActiveMQ.DLQ growing: dead letter queue accumulation and poison messages
- ActiveMQ DLQ never expires: setting TTL so the dead-letter queue stops leaking storage
- ActiveMQ offline durable subscriber pending messages: the silent storage leak
- ActiveMQ enqueue outpacing dequeue: reading the rate imbalance before the backlog
- ActiveMQ expired message count climbing: TTL expiry and silent correctness loss






