Pre-reading Requirements In this post, I assume you have a basic background in software and cybersecurity engineering. However, even if you're not highly technical, don't worry, I will ensure that you can grasp and understand the intricacies of the vulnerability and the exploit, as well as
In this post, I assume you have a basic background in software and cybersecurity engineering. However, even if you're not highly technical, don't worry, I will ensure that you can grasp and understand the intricacies of the vulnerability and the exploit, as well as how it could have been prevented (Yes, I am aware it is a zero-day vulnerability, but as you will see later, prevention is still possible :) ).
Exploit can be found at https://github.com/mouadk/application-security/tree/main/activemq-cve-2023-46604.
Let's dive in.
Apache ActiveMQ is a widely-used open source message broker written in Java, known for its multi-protocol compatibility. It offers clients the flexibility of choosing from a variety of programming languages and platforms, with support for JavaScript, C, C++, Python, .Net, and others.
In Java, any object from a class that directly or indirectly (i.e. through inheritance) implements the Serializable interface can be serialized and deserialized using Java native serialization.
When an object is serialized in Java, the entire object graph reachable from that object is serialized into a byte stream. This includes not only the immediate members of the object but also any other objects those members reference, and so on.
When deserializing, Java starts by reading the class information of the object. This is to ensure that the class definition is available and compatible with the serialized data. If the class isn’t available, a ClassNotFoundException will be thrown.
Introduced in JDK 9 , deserialization filters were added to Java as a way to enhance the security of the deserialization process. Given the history of security vulnerabilities associated with Java’s serialization mechanism, filters are a way to provide fine-grained control over the classes that are allowed or denied during deserialization. When deserializing data from an untrusted source, the application may be vulnerable to attacks such as remote code execution (RCE).
OWASP has identified deserialization vulnerabilities as one of the top 10 security risks for web applications (now A08:2021-Software and Data Integrity Failures). Vulnerabilities in the deserialization process can arise from a variety of sources, both from the main application and from its dependencies.
Many popular libraries and frameworks have been found to have unsafe deserialization mechanisms in the past. Java’s native serialization has been particularly notorious, but other languages and frameworks aren’t immune. This widespread presence makes deserialization bugs attractive to attackers.
A Gadget is a method that use objects or fields that can be controlled by an attacker. Gadget chains are malicious sequences of methods (gadgets) created by attackers. Deserialization Attacks require a set of gadgets to be present on the classpath of a vulnerable application.
An attacker could craft a byte stream that, when deserialized on the remote host, could control the execution flow of the Java code by chaining sequences of Java code, that is, gadgets. Several libraries with gadgets can be combined to perform a deserialization attack.
Not all classes are inherently dangerous when deserialized, but some classes can be leveraged to trigger unintended side effects. For instance, some classes may execute code when they are instantiated or when certain methods are called during the deserialization process. Popular libraries and frameworks may inadvertently introduce such gadget classes, which become vectors for attacks, as we will see in the next section.
As said earlier, the main cause of CVE-2023-46604 is a lack of validation in Deserialisation Process.
Essentially, when a stream of input data is received, the OpenWireFormat#doUnmarshal
method is called to unmarshal the ByteSequence
. The BaseDataStreamMarshaller
plays a key role in this process, handling both marshalling and unmarshalling operations.
There are various types of DataStreamMarshaller
s, and routing to the appropriate marshaller is determined by inspecting the surfaced DATA_STRUCTURE_TYPE
. For example, the ExceptionResponseMarshaller
specifically handles all inputs where DATA_STRUCTURE_TYPE
equals 31.
The issue with ExceptionResponseMarshaller
is that it instantiates throwable classes (presented within the ExceptionResponse
) without performing any validation. This means an attacker can craft any throwable class (essentially tricking the system into treating any class as if it were throwable), and then, when the payload is deserialized, the system will execute the attacker's chosen class.
One possible and interesting gadget is ClassPathXmlApplicationContext
(credits to x1r0z), a standalone XML application context and an implementation of Spring's ApplicationContext
, which acts as a container for various objects that support the application.
An attacker is typically uninterested in gadgets legitimate use. ClassPathXmlApplicationContext
allows for the configuration of a Spring application through an XML file. The location of the XML configuration file is specified as a single string parameter when creating a new instance of ClassPathXmlApplicationContext
. This parameter can point to XML files containing Spring bean definitions which, in a malicious scenario, may be hosted and served by the attacker.
However, there is a problem: ClassPathXmlApplicationContext
is not inherently throwable, but an attacker could potentially override the local class definition with a malicious version and marshal the harmful payload.
Once the attacker gains control over ClassPathXmlApplicationContext
and can ensure its delivery, they can define a bean that instantiates a process and open the calculator as follows:
<?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>open</value>
<value>-a</value>
<value>calculator</value>
</list>
</constructor-arg>
</bean>
</beans>
When deserializing, the server does not verify whether the class to be instantiated inherits from Throwable
.
This oversight lead to a Remote Code Execution (RCE) vulnerability. Essentially, an attacker could execute shell commands available within the system's context, which could have dire consequences if there are no runtime safeguards in place. For example, they could open a calculator application, or more damaging operations such as delivering ransomware.
Here what the final gadget chain looks like:
ExceptionResponse#throwable
ExceptionResponseMarshaller#tightUnmarshal
BaseDataStreamMarshaller#tightUnmarsalThrowable
Constructor#newInstance
ClassPathXmlApplicationContext#init
Spring Context Refreshed (now the bean will be initialized)
java.lang.ProcessBuilder#start
RCE
The issue has been resolved by adding a check to ensure that the class in question is indeed Throwable
. Classes derived from Throwable
are not supposed to produce such side effects.
It is crucial to ensure that you have updated to the latest versions. The versions containing the fix are:
I've observed that many people focus a lot on the use of Endpoint Detection and Response (EDR) to prevent such security issues. Although EDR can intervene during process creation, it's better to catch issues earlier if possible.
A Runtime Application Self-Protection (RASP) solution, which is integrated within the application itself, can add protective measures against misuse. In this case, RASP could have potentially prevented the problem by monitoring and blocking malicious activity.
RASP is capable of defending against zero-day exploits through instrumentation, and could have been effective against this particular zero-day, as well as other notable vulnerabilities like Spring4Shell and Log4Shell, given proper implementation. A proof of concept can be found at https://github.com/mouadk/application-security/tree/main/activemq-cve-2023-46604:
As shown above, it effectively blocks the PoC exploit.
Additionally, it's important to ensure that only necessary services are exposed publicly and are only accessible from required locations. Implementing measures to control service exposure is critical (for instance, it's quite unusual to expose a message broker directly without a clear reason). As a best practice, one might expose an HTTPS endpoint, secured with OAuth 2.0 or similar protocols, and then connect it to the broker. This is not overly complex and can be seamlessly achieved with modern frameworks.
Of course, if an attacker has already penetrated the network, they could execute commands with the server's privileges. In such cases, additional security measures could include broker-level authentication, port randomization, firewall rules, and TLS encryption.
Supply chain attacks are on the rise. Even if you adhere to the best practices and coding standards, there's still a risk of attacks originating from external dependencies, such as Spring Boot, and others.
However, we cannot afford to be passive. Instead, we should be proactive by implementing runtime solutions like RASP, which could potentially prevent such attacks. While RASP and EDR solutions are not foolproof, they can still offer significant protection.
Doing what we can with the resources we have is crucial, and most JDK internal classes can be patched to mitigate these kinds of issues, either through instrumentation (for example, due to licensing issues) or directly.