Pwning ManageEngine — From Endpoint to Exploit(CVE-2021–42847)

Note: This post is the second in a series of articles I’m writing on vulnerabilities / exploit vectors in ManageEngine products. This is a standalone article, but if you’re interested, you can read the first part here.


In October of 2021, ManageEngine released ADAudit Plus build 7006. According to the release notes, this version included a patch for a critical unauthenticated arbitrary file write vulnerability tracked as CVE-2021–42847(CSV3 base score: 9.8). The disclosure was credited to Moon (I have been unable to find a single social media account, GitHub page, or website for this security researcher. If you know where they hang out on the interwebs, please let me know and I’ll add a link to this article).

After coming across a vulnerable instance during a pentest, and discovering that no root cause analysis or PoC has ever been made available for this vulnerability, I decided to have a closer look myself. This article details my findings, including the potential exploitation vectors and the limitations thereof.

First things first: what is ADAudit Plus

ManageEngine ADAudit Plus is an IT auditing solution that allows users to monitor their Active Directory environment, file servers, Windows servers and Windows workstations in order to identify suspicious activity such as unusual logons, account lockouts, and changes to users, files, groups, security policies and more.

1. Vulnerable endpoint: agentGPOWatcherData

Patch diffing ADAudit Plus builds 7005 and 7006 (available for download here) reveals that the /api/agent/tabs/agentGPOWatcherData endpoint has been removed in 7006 via the highly advanced patching technique known as “commenting out”. Specifically, the following line in \conf\adap\rest-api.xmlis commented out in build 7006:

<ADAPRestApiMapping UNIQUE_ID="ADAPRestApiMapping:UNIQUE_ID:717" TAB_NAME="agentData" URL_PATH="/tabs/agentGPOWatcherData" CLASS_NAME="com.adventnet.sym.adsm.auditing.webclient.ember.api.agent.AgentDataHandler" METHOD_NAME="receiveGPOWatcherData" DESCRIPTION="Receives GPO File watcher data from ADAuditPlus Server Agents"/>

This corresponds with the /api/agent/tabs/agentGPOWatcherData endpoint mentioned in\webapps\adap\WEB-INF\security\security.xml :

<url path="/api/agent/tabs/agentGPOWatcherData" dynamic-params="true" csrf="false" method="post">
   <inputstream max-len="-1" type="String"/>

The original purpose of this endpoint has to do with the fact that ADAudit Plus can be configured to monitor Active Directory computers by installing agents on those machines. Those agents can interact with ADAudit Plus via HTTP requests to the API, and the /api/agent/tabs/agentGPOWatcherData endpoint allowed agents to send updates to ADAudit Plus regarding Changes to Group Policy Objects (GPOs) and/or configuration changes for the agents themselves.

The entry-point for the agentGPOWatcherData endpoint is the receiveGPOWatcherData function in the AgentDataHandler class inside \lib\AdventNetADAPClient.jar. As we will see, CVE-2021–42847 actually comprises two vulnerabilities originating in the same endpoint:

  • Unauthenticated arbitrary file write (with serious limitations)
  • Unauthenticated XML external entity injection (XXE)

2. Unauthenticated arbitrary file write

The unauthenticated arbitrary file write vector originates in the first part ofreceiveGPOWatcherData. The first few lines show that HTTP POST requests to /api/agent/tabs/agentGPOWatcherDataare expected to contain an HTTP body in JSON format, which should include a boolean parameter isGPOData.

Pwning ManageEngine — From Endpoint to Exploit(CVE-2021–42847)

If the value for isGPOData is true, we hit the following code:

Pwning ManageEngine — From Endpoint to Exploit(CVE-2021–42847)

This part of the function allows ADAudit Plus agents to use agentGPOWatcherData in order to send GPO details to ADAudit Plus in the form of an XML document, possibly supplemented with an HTML document. The logic here is as follows:

  1. The values of the DOMAIN_NAME and GPO_GUID parameters, if provided, are passed in a call to theGPOUtil.getGPOXMLDir method, which will create a directory at \webapps\adap\GPODetails\<DOMAIN_NAME>\<GPO_GUID>\ if that directory does not exist yet. The name of the directory is returned and assigned to the fileLocation string.
  2. The value of the VER_FILE_NAME parameter is appended to fileLocation and then used as the path to a file that is about to be created. This is achieved via .toPath() .
  3. The value of the xmlReport parameter is written to the aforementioned file using UTF16LE encoding. It should be noted here that zero checks are performed to validate the contents of xmlReport.
  4. If the htmlReport parameter is also passed along, the Html_fileNameparameter is also expected, the value for which is used to create a file just like VER_FILE_NAME. In this case, the value of htmlReport is written to said file using UTF16 encoding. Again, no validation is performed for the value of htmlReport.

The above logic contains at least three notable security issues that can be exploited in order to achieve unauthenticated arbitrary file write via /api/agent/tabs/agentGPOWatcherData:

  1. The .toPath() call is vulnerable to directory traversal, meaning that if VER_FILE_NAME and/or Html_fileName includes instances of ..\\ , those will be interpreted as valid Windows path navigators ..\, making it possible to let ADAudit Plus write the output files outside of the intended directory.
  2. No file extension checks are performed for VER_FILE_NAME and Html_fileName, so these parameters can be used to create any type of file.
  3. Since no validation is performed for xmlReport and htmlReport, the created files can have whatever content can be passed via JSON.

For instance, to create a file at C:\pwned.txt with the contents “wynter was here”, we can send the following request:

POST /api/agent/tabs/agentGPOWatcherData HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0
Content-Length: 186{
  "DOMAIN_NAME" : "doesnotexist.domain" ,
  "xmlReport":"wynter was here"

This will create the desired file at the desired location:

Pwning ManageEngine — From Endpoint to Exploit(CVE-2021–42847)

In addition, it will create an empty directory at \webapps\adap\GPODetails\doesnotexist.domain\somethingrandom

2.1 File write limitations

Unfortunately, the file write vector has serious limitations:

  • Most importantly, the intended file contents need to be JSON compatible, which means only UTF-8 characters are supported. In other words, the vector is incompatible with binary payloads.
  • In addition, the UTF16LE and UTF16LE encoding for, respectively, VER_FILE_NAME/xmlReport and Html_fileName/htmlReport, further reduces the types of files we can write to the target. For instance, VER_FILE_NAME/xmlReport cannot be used for creating executable PowerShell scripts. While it is possible to create .ps1 files this way, executing those files will result in PowerShell throwing encoding-related errors. Fortunately, Html_fileName/htmlReport can be used to create PowerShell scripts without issues. This, as is shown later on, is the most potent use I could find for this vector. I also tried creating .bat files, but this failed due to encoding-related issues for both VER_FILE_NAME/xmlReportand Html_fileName/htmlReport.

3. XXE

The second part of the isGpoData code path in receiveGPOWatcherDatais shown below:

Pwning ManageEngine — From Endpoint to Exploit(CVE-2021–42847)

The following happens here:

  • The names and values of several attributes required for a valid request to the agentGPOWatcherData endpoint are used to populate the Hashtable object result, which itself is then used to populate the HashMap queueMaptogether with a few more attributes.
  • If all required attributes have been provided, which includes the not yet mentioned integer GPO_VERSION, the server sends add the following JSON body to the response:
  • Next, the queueMap object is passed in a call to GPOManagementListener.getInstance().process()

The process() function is defined in the GPOManagementListener class inside \lib\AdventNetADAPServer.jar. Quite a lot happens in this function, but the only things we care about here are three function calls in the below code snippet. All three function calls will be triggered when an agent sends an HTTP POST request similar to the previous “wynter was here” example, where isGPOData is set to true. However, the GPO_VERSION parameter should now be added to the JSON hash. The value for this doesn’t seem to matter as long as it’s an Integer.

Pwning ManageEngine — From Endpoint to Exploit(CVE-2021–42847)

In the above code, absoluteFileName is the absolute path to the file we created via VER_FILE_NAME. The three relevant function calls are:

  • XMLParserUtil.getExtnErrors(absoluteFileName)
  • GPOComparator.getInstance().compareGPO(absoluteFileName, previousVersionFile, domainName)
  • GPOComparator.getInstance().compareGPOForLink(absoluteFileName, previousVersionFile)

As it turns out, all three function calls will lead to XXE if xmlReport contained a valid XXE payload.

3.1 XXE via XMLParserUtil.getExtnErrors()

The XMLParserUtil class is also part of AdventNetADAPServer.jar. The top of its getExtnErrors() function deserves a closer look:

Pwning ManageEngine — From Endpoint to Exploit(CVE-2021–42847)

The important part here is the call to the overloaded method getExtnErrors()via getExtnErrors(getElement(fileName, elementName)). This call is performed twice:

  • once with elementName set to “Computer”
  • once with elementName set to “User”.

In both cases, fileName and elementName are first passed to getElement(), which is defined in the same class, and which passes fileName in a call to a function called parseXml:

Pwning ManageEngine — From Endpoint to Exploit(CVE-2021–42847)

What follows is another case of method overloading, which eventually brings is to the below version of parseXml():

Pwning ManageEngine — From Endpoint to Exploit(CVE-2021–42847)

Seeing this method for the first time was a “holy shit” moment for me because I had recently studied the excellent writeup for CVE-2022–28219 by Naveen Sunkavally of This detailed a more recent vulnerability affecting ManageEngine ADAudit Plus that could be leveraged to achieve unauthenticated remote code execution (RCE). CVE-2022–28219 combines an untrusted Java deserialization code execution vector with an XXE vector originating in the insecure use of Java’sDocumentBuilderFactory class, which is vulnerable to XXE by default.

The implementation of DocumentBuilderFactory in parseXml() is almost identical to how it is implemented in the vulnerable function covered by CVE-2022–28219. Most importantly, external entity resolution is not disabled before the parse() method in theDocumentBuilderFactory class is run on the file contents that, as shown above, any unauthenticated user can control. (For details on how to implement DocumentBuilderFactory securely, please check out this excellent blog post).

3.2 XXE via GPOComparator.getInstance().compareGPO()

The GPOComparator class can be found inAdventNetADAPServer.jar as well. The path from its compareGPO function to XXE includes two steps:

  1. CompareGPO passes newFileName (the file we control) in a call to the XMLParserUtil.getElementList function
  2. XMLParserUtil.getElementList passes newFile to our good friendparseXml:
Pwning ManageEngine — From Endpoint to Exploit(CVE-2021–42847)

3.3 XXE via GPOComparator.getInstance().compareGPOForLink()

The compareGPOForLink function in the GPOComparator class completes our quartet of potential XXE triggers by making a direct call to parseXml:

Pwning ManageEngine — From Endpoint to Exploit(CVE-2021–42847)

3.4 Triggering XXE

As we saw above, the /api/agent/tabs/agentGPOWatcherData endpoint provides us with (at least) four XXE triggers:

  • Two via XMLParserUtil.getExtnErrors()
  • One via GPOComparator.getInstance().compareGPO()
  • One via GPOComparator.getInstance().compareGPOForLink()

In order to test if I could actually trigger XXE via the xmlReport parameter in a request to the agentGPOWatcherData endpoint, I started Python’s default HTTP server on my test system and then used Naveen Sunkavally’s example XML payload for triggering NTLM authentication from the target to a provided HTTP server. My initial test failed, but after retracing my steps I noticed once again that UTF-16LE encoding is used when writing the contents of xmlReportto the file that is being created via VER_FILE_NAME. I then changed the encoding in the XML payload to UTF-16, resulting in the following request (I’m using an image here because Medium doesn’t like plaintext XXE payloads, who would’ve thought?):

Pwning ManageEngine — From Endpoint to Exploit(CVE-2021–42847)

I sent the request, checked my ever-patient Python server, and saw this:

Pwning ManageEngine — From Endpoint to Exploit(CVE-2021–42847)

Yay, all four XXE triggers were activated at once!

4. The road to pwnage (is full of disappointments)

I tried a variety of techniques to build an unauthenticated RCE exploit chain using the two vectors I identified for CVE-2021–42847. The short version of this story is that I failed because I am n00b. The long version is outlined in this section.

4.1 Failing to get unauthenticated RCE via the file write vector

For the file write vector, I pursued several potential paths to unauthenticated RCE, including the following:

  • Writing a JSP payload to the web root: This seemed like the most obvious route to take, but I quickly discovered it wasn’t going to work because ADAudit refuses to process requests to JSP files and I was unable to find a way to edit the web server configuration without having to restart ADAudit Plus, which is something unauthenticated users cannot control as far as I know.
  • Overwriting one of the bundled PowerShell scripts: ADAudit Plus comes bundled with a few PowerShell scripts. While it is possible to overwrite those using CVE-2021–42847, I did not find a way to force ADAudit Plus to load any of these files at runtime and thereby execute my payload. From what I could tell, these scripts are only run during startup, so once again a restart of ADAudit Plus would be required.

4.2 Failing to get unauthenticated RCE via the XXE vector

After failing to get what I wanted out of the file write vector, I turned to the delicate art of ripping off other people’s work. More specifically, I tried to use the exploit chain for CVE-2022–28219 while replacing the XXE vector used there with the one I identified for CVE-2021–42847. This *almost* worked. In fact, it was so close that it still hurts. Allow me to elaborate.

CVE-2022–28219 combines an untrusted Java deserialization code execution vector with an XXE vector that can be leveraged to upload a serialized payload to the target via

a really nice Java-specific XXE technique disclosed by Timothy Morgan in 2013 to upload a file using the jar file protocol and a “blocking” server that doesn’t close the connection after the upload

A simplified version of the exploit chain looks like this:

  1. Attacker W generates a serialized Java payload using a tool like ysoserialand launches two servers: a “blocking” web server and a special XXE FTP server.
  2. W sends an XXE payload to vulnerable endpoint. The payload leverages the jar file protocol to trick the web server into attempting to load a jar file from the blocking server. The payload is temporarily stored as temp file in the designated directory on the target system (typically c:\users\<username>\appdata\local\temp\). The blocking server holds the connection open, which prevents Java from unpacking the temp file and deleting it.
  3. Because the temp file containing the payload has a random name, W sends additional XXE payloads that trick the target into sending directory listings to the target via their XXE FTP server.
  4. Once W obtains the name of and full path to the temp file containing their payload, they force ADAudit Plus to execute it by sending an HTTP GET request to the /cewolf endpoint, which represents the untrusted Java deserialization code execution vector.

This exploit chain has been automated in a python PoC script by the good folks at and in a Metasploit module by Ron Bowes of Rapid7.

In order to test if this exploit chain could also work with the XXE vector from CVE-2021–42847, I created a copy of the Metasploit module and edited the targeted endpoint as well as the JSON and XML payloads. When I ran this new version of the module, it managed to do the following:

  • Identify the appdata\local\temp\ directory of the only configured local user on the target, which was the location where Java would store the temp file containing the payload
  • Trick the target into downloading the payload from the blocking server and storing it in the identified temp directory
  • Hang and eventually timeout like a coward

As it turns out, the ADAudit Plus /api/agent/tabs/agentGPOWatcherDataendpoint becomes unresponsive for as long as the blocking server remains open when using this exploit chain with CVE-2021–42847. As a result, my module was unable to perform step 3 to obtain the exact name of the temp file containing the payload. And without that, it was impossible to execute the payload, even though it did reside on the target for as long as the blocking server remained open.

In fact, when I ran the module while keeping an eye on the c:\users\<username>\appdata\local\temp\ folder, I could see the payload being uploaded there as a temp file. It was right there, staring me in the face. So close, and yet forever out of reach of my module, since nuking the blocking server in order to render the endpoint responsive again, would prompt the instant deletion of the payload. Tears were shed, chairs were thrown, career shifts were considered, but somehow I made it through.

I then actually took the extra step of also creating a custom version of the python PoC script for CVE-2022–28219 and testing that. However, the result was exactly the same, and was followed by more tears, more broken chairs, and another resignation email saved in drafts .

I did confirm that it is possible to use the CVE-2021–42847 XXE vector for step 2 of the exploit chain, and subsequently switch to CVE-2022–28219 for step 3, but this approach doesn’t really provide any added value to the original exploit for CVE-2022–28219.

FYI, I also briefly looked into the possibility of using the file write vector to simply write a serialized payload to the target and executing that via /cewolf, but this wasn’t possible due to the UTF-8 limitation for payloads.

5. Authenticated RCE

Once I had accepted that I wasn’t going to get unauthenticated RCE out of this (while realizing that someone 1337 might still be able to), I adjusted my objectives from unauthenticated to authenticated RCE. This proved to be very simple to achieve thanks to a feature called “alert scripts”.

ADAudit Plus lets users configure “alerts” for suspicious behavior affecting Active Directory or the file servers and other systems being monitored. When creating an alert, users can specify the type of behavior that will trigger it, and how ADAudit Plus should respond to the alert. It is possible to have ADAudit Plus send custom emails and, more interestingly, execute a custom script.

Rather unsurprisingly, the “alert script” functionality can be easily abused in combination with the file write vector for CVE-2021–42847. This is most relevant for builds 7004 and 7005 because in builds 7003 and prior, “alert scripts” can be used by authenticated users to execute arbitrary payloads without even needing a file write vector.

5.1 Authenticated RCE on builds 7003 and prior

When looking into the alert scripts functionality, I noticed that this is implemented differently in builds 7003 and prior than it is in later builds. As specified in the release notes for build 7004:

Security hardening has been performed on custom alert script execution.

The phrase security “hardening” is misleading here, because builds 7003 and prior include zero security checks to prevent abuse of this functionality. In fact, when specifying the custom script to be executed for an alert, users can simply enter a CMD payload instead of the name of a script, because their input will be passed directly to a Runtime.exec() call. This happens in the runAlertScriptAction function that is part of the AlertProfileManager class inside AdventNetADAPServer.jar. All an authenticated user has to do in order to get RCE out of this is:

  1. Go to the Alerts tab to either create a new alert profile, or modify an existing one.
  2. Under the category section of the alert profile, click the + on the side of the Report Profiles field, then tick the box next to All Users Logon in the popup and confirm by clicking OK .
  3. Scroll down to Alert Actions and tick the Execute Script box. Enter your CMD payload in the Script Location field and hit Save at the bottom of the page to save the alert profile.
  4. Perform a failed active directory authentication attempt. You can actually do this via ADAudit Plus itself by logging out (or opening the app again in a private window), and then trying to log back in using incorrect AD credentials. To do this, select one of the configured AD domains under the Logon To dropdown, enter a random username and password, and hit LOGIN.

The demo video below covers the full attack:

5.2 Authenticated RCE via CVE-2021–42847 on builds 7004 and 7005

The “security hardening” performed for build 7004, introduced an /alert_scripts directory in the ADAudit Plus installation directory. From now on, users are required to use this directory to store any custom scripts they want ADAudit Plus to execute as part of the alert scripts functionality. To enforce this, any value entered in the Script Location field during the configuration of an alert script (see step 3 above), should correspond with the name of a PowerShell or BAT script that actually exists in the /alert_scriptsdirectory.

Because CVE-2021–42847 allows us to write PowerShell scripts to any location on the target, including /alert_scritpts, the alert scripts functionality can still be used to achieve authenticated RCE on builds 7004 and 7005. In order to achieve this, you can follow the below steps:

  1. Leverage the agentGPOWatcherData to write a PowerShell script to /alert_scripts via a request like the one below:
POST /api/agent/tabs/agentGPOWatcherData HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0
Content-Length: 186{
  "DOMAIN_NAME":"doesnotexist.domain" ,
  "xmlReport":"wynter was here",
  "htmlReport":"<POWERSHELL PAYLOAD>",

2. Follow step 1 in section 5.1.

3. Follow step 2 in section 5.1.

4. Follow step 3 in section 5.1, but enter the name of the PowerShell script you created (in the above example this is evil.ps1) in the Script Location field instead.

5. Follow step 4 in section 5.1.

5.3 Metasploit module: Authenticated RCE in ADAudit Plus builds 7005 and prior

I have written a Metasploit module to automate the attacks described in sections 5.1 and 5.2.

5.4 MSF module demo for ADAudit Plus build 7005

6 A conditional path to unauthenticated RCE

To my amazement, my research into the alert scripts feature of AD Audit Plus actually revealed a way in which CVE-2021–42847 could still be used to achieve unauthenticated RCE on builds 7005 and prior. However, this is only true if certain non-default conditions are present on the target. It’s possible to imagine quite a few different scenarios that would allow this attack and the required conditions will change a bit between scenarios, but one of the more plausible scenarios I could come up with goes like this:

  1. The target is ADAudit Plus build 7004 or 7005.
  2. ADAudit Plus is configured with at least one alert profile that leverages the alert script functionality. The script used for this alert is a PowerShell script.
  3. The name of at least one PowerShell script in <install_dir>/alert_scripts/reveals the type of activity that will trigger the alert.
  4. The activity or activities required to trigger the alert include behavior that an unauthenticated user can control, such as failed active directory login attempts, AD account lockouts (if an account lockout is set), or even the modification of files in a location accessible to an unauthenticated attacker, such as an SMB share that allows SMB NULL authentication.

In the above scenario, unauthenticated RCE would possible like this:

  1. Attacker W uses the XXE vector in CVE-2021–42847 to obtain the directory listing of the alert_scripts folder. This reveals one or multiple PowerShell scripts. At least one of the PowerShell scripts has a name that reveals the type of activity that will trigger the alert, and this activity can be controlled by W. For instance, one script is called user_lockout.ps1
  2. W uses the file write vector in CVE-2021–42847 in order to overwrite the interesting PowerShell script with a custom PowerShell payload.
  3. W starts a listener for their PowerShell payload
  4. W performs the action that is likely to trigger the alert that will execute the PowerShell script. In this example, W performs several incorrect login attempts for one Active Directory user until the lockout threshold is hit. Once ADAudit Plus registers the account lockout event, it loads all alert profiles that are configured for this type of activity, including the one that executes user_lockout.ps1. As a result, W receives a connection to their listener.

6.1 Metasploit module: Leveraging CVE-2021–42847 to read and write files, mess with alert scripts, and more.

Because CVE-2021–42847 does allow unauthenticated users to do some cool stuff, and even offers a conditional path to unauthenticated RCE, I wrote a second Metasploit module that can be used to take advantage of this. The module I wrote is an auxiliary module that supports the following five actions:

  1. READ_FILE_OR_DIR: Obtain directory listings and reading files via XXE
  2. WRITE_FILE: Leverage the file write vector to create arbitrary files
  3. LIST_ALERT_SCRIPTS: List the contents of alert_scripts via XXE
  4. OVERWRITE_ALERT_SCRIPT: Overwrite an existing PowerShell scripts in alert_scripts
  5. TRIGGER_NTLM_AUTH: Trigger NTLM authentication from the host via XXE

For actions 1, 3 and 5, the XXE vectors for both CVE-2021–42847 and CVE-2022–28219 are supported. The default XXE vector is CVE-2022–28219 because this affects more versions and is significantly faster to exploit than CVE-2021–42847. The only benefit of CVE-2021–42847 over CVE-2022–28219 when it comes to XXE is that the former does not require a valid domain name to be entered, although this will normally be very easy to obtain. In the example videos below the XXE vector for CVE-2021–42847 is being used for demonstration purposes.

6.1.1 MSF module demo for READ_FILE_OR_DIR

6.1.2 MSF module demo for WRITE_FILE

6.1.3 MSF module demo for TRIGGER_NTLM_AUTH

6.1.4 MSF module demo for unauthenticated RCE via LIST_ALERT_SCRIPTS and OVERWRITE_ALERT_SCRIPT


The most effective way to mitigate against CVE-2021–42847 is to update your ManageEngine ADAudit Plus instances to the latest versions using the available service packs.

A few final words regarding additional research

While I wasn’t able to come up with an unconditional exploit chain for unauthenticated RCE, this may nevertheless be possible for CVE-2021–42847. If you have an idea for how to achieve this, please don’t hesitate to try. If you do find a way, I’d be happy to mention you here and write a new Metasploit module 🙂



原文始发于Erik Wynter:Pwning ManageEngine — From Endpoint to Exploit(CVE-2021–42847)

版权声明:admin 发表于 2022年11月8日 下午2:44。
转载请注明:Pwning ManageEngine — From Endpoint to Exploit(CVE-2021–42847) | CTF导航