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

FactorWhat it looks likeFirst thing to check
Vulnerable versionBroker on any 5.x build older than 5.15.16/5.16.7/5.17.6/5.18.3Version in the startup banner of activemq.log, or jar filenames in the ActiveMQ lib directory
OpenWire port reachable from untrusted network61616 bound to 0.0.0.0, no firewall restriction, or exposed through a load balancer or security groupss -tlnp for the bind address, plus the actual network path (cloud SG, LB, DNAT)
Default or no broker authenticationadmin/admin works, or no authentication plugin configuredBroker config for the authentication plugin
Legacy OpenWire Module5.8.0 through 5.15.15 module in use on otherwise newer installsCheck whether the legacy module is deployed
Java OpenWire clients unpatchedClient applications embed an older activemq-client jar and connect to brokers you do not fully controlDependency 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 under org.apache.commons.collections.functors or org.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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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

SignalWhy it mattersWarning sign
Deserialization errors in broker logPrimary application-layer exploit fingerprint for this CVE classAny repeat occurrence; any match on gadget-style package names
Transport connection failuresExploit attempts and scanning both show up as abnormal connection churn and socket errors on 61616WARN lines with unfamiliar source IPs, especially followed by aborts
Connection count anomaliesExploitation and scanning appear as connections from IPs outside your known client setEstablished 61616 sessions from addresses not in your client inventory
Authentication failure ratePre-attack reconnaissance and brute force against default credentialsSustained failures from a single source, or broad failures after a credential change
Web console / JMX access sourcesManagement interfaces should only see your management networkAny session to 8161 or 1099 from a non-allowlisted source
Unexpected child processes or outbound connections from the broker hostPost-exploitation behavior: payload fetch, miner, C2Broker 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.