This is a flag-and-ticket lab, not a coerce-and-forward cookbook. Target is a fake lab forest LAB.INTERNAL with three deliberately boring service accounts I created: unconstrained print leftover, classic constrained web, RBCD on a file server. Goal: klist the tickets a user actually holds after hitting those services, setspn -L the accounts, and draw the three control planes in text. I do not coerce authentication, I do not write msDS-AllowedToActOnBehalfOfOtherIdentity from a low-priv principal, I do not S4U2Proxy toward a DC.
1Figure 1. Three control planes. Direction of the allow-list is the whole confusion.
2Unconstrained: client TGT may be forwarded to a compromised mid-tier
3Constrained: mid-tier account lists target SPNs (optional protocol transition)
4RBCD: target computer lists who may inbound-delegate to it
Lab layout
1labs/deleg_lab/
2 klist-unconst.txt
3 klist-const.txt
4 setspn.txt
5 getad.txt
Accounts (fake): LAB\labuser (person), LAB\WEB01$ (IIS, classic constrained), LAB\PRINT01$ (unconstrained leftover), LAB\FS01$ (file server, RBCD attribute present but empty-ish). SIDs [REDACTED].
Artifact: setspn (who has which SPN)
1C:\lab> setspn -L WEB01
2Registered ServicePrincipalNames for CN=WEB01,OU=servers,DC=lab,DC=internal:
3 HTTP/web01.lab.internal
4 HTTP/web01
5 WSMAN/web01.lab.internal
6 TERMSRV/web01
7 HOST/web01.lab.internal
8 HOST/web01
9
10C:\lab> setspn -L PRINT01
11Registered ServicePrincipalNames for CN=PRINT01,OU=servers,DC=lab,DC=internal:
12 HOST/print01.lab.internal
13 HOST/print01
14
15C:\lab> setspn -L FS01
16Registered ServicePrincipalNames for CN=FS01,OU=servers,DC=lab,DC=internal:
17 CIFS/fs01.lab.internal
18 CIFS/fs01
19 HOST/fs01.lab.internal
20 HOST/fs01
21
22C:\lab> setspn -Q HTTP/web01.lab.internal
23Checking domain DC=lab,DC=internal
24CN=WEB01,OU=servers,DC=lab,DC=internal
25 HTTP/web01.lab.internal
26 HTTP/web01
27Existing SPN found!
setspn -Q is the collision check. Two computer objects with the same HTTP SPN is a finding of its own. This lab is clean.
Artifact: directory flags (the three planes)
1PS C:\lab> Get-ADComputer WEB01,PRINT01,FS01 -Properties TrustedForDelegation,
2 TrustedToAuthForDelegation, msDS-AllowedToDelegateTo,
3 msDS-AllowedToActOnBehalfOfOtherIdentity |
4 fl Name, TrustedForDelegation, TrustedToAuthForDelegation,
5 'msDS-AllowedToDelegateTo', 'msDS-AllowedToActOnBehalfOfOtherIdentity'
1Name : PRINT01
2TrustedForDelegation : True # unconstrained
3TrustedToAuthForDelegation : False
4msDS-AllowedToDelegateTo : {}
5msDS-AllowedToActOnBehalfOfOtherIdentity :
6
7Name : WEB01
8TrustedForDelegation : False
9TrustedToAuthForDelegation : False # no protocol transition
10msDS-AllowedToDelegateTo : {CIFS/fs01.lab.internal, HTTP/api.lab.internal}
11msDS-AllowedToActOnBehalfOfOtherIdentity :
12
13Name : FS01
14TrustedForDelegation : False
15TrustedToAuthForDelegation : False
16msDS-AllowedToDelegateTo : {}
17msDS-AllowedToActOnBehalfOfOtherIdentity : # descriptor present, lab: WEB01$ allowed
UserAccountControl bits I match against the dump (so a 4662/replication viewer can decode without PowerShell):
| Bit | Name | Who has it here |
|---|---|---|
| 0x80000 | TRUSTED_FOR_DELEGATION | PRINT01$ |
| 0x1000000 | TRUSTED_TO_AUTH_FOR_DELEGATION | nobody in this lab (good) |
| (attribute) | msDS-AllowedToDelegateTo | WEB01$ → CIFS/fs01, HTTP/api |
| (attribute) | msDS-AllowedToActOnBehalfOfOtherIdentity | on FS01$, grant to WEB01$ |
Protocol transition (TrustedToAuthForDelegation) is off. That is the edge case I want people to look for: a constrained allow-list with that bit on means the front-end can S4U2Self without an inbound Kerberos TGS from the user. I do not turn it on to demonstrate.
Artifact: klist after a normal user hits the services
Interactive logon as labuser, then dir \\fs01.lab.internal\labshare and a browser GET to https://web01.lab.internal/ (lab app). Tickets on LABPC01:
1C:\lab> klist
2Current LogonId is 0:0x[REDACTED]
3
4Cached Tickets: (3)
5
6#0> Client: labuser @ LAB.INTERNAL
7 Server: krbtgt/LAB.INTERNAL @ LAB.INTERNAL
8 KerbTicket Encryption Type: AES-256-CTS-HMAC-SHA1-96
9 Ticket Flags 0x40e10000 -> forwardable renewable initial pre_authent
10 Start Time: 5/17/2021 14:02:11 (local)
11 End Time: 5/18/2021 00:02:11 (local)
12 Renew Time: 5/24/2021 14:02:11 (local)
13 Session Key Type: AES-256-CTS-HMAC-SHA1-96
14 Cache Flags: 0x1 -> PRIMARY
15 Kdc Called: dc01.lab.internal
16
17#1> Client: labuser @ LAB.INTERNAL
18 Server: cifs/fs01.lab.internal @ LAB.INTERNAL
19 KerbTicket Encryption Type: AES-256-CTS-HMAC-SHA1-96
20 Ticket Flags 0x40a10000 -> forwardable renewable pre_authent
21 Kdc Called: dc01.lab.internal
22
23#2> Client: labuser @ LAB.INTERNAL
24 Server: HTTP/web01.lab.internal @ LAB.INTERNAL
25 KerbTicket Encryption Type: AES-256-CTS-HMAC-SHA1-96
26 Ticket Flags 0x40a10000 -> forwardable renewable pre_authent
27 Kdc Called: dc01.lab.internal
forwardable on the TGT is default in this domain. It is not proof of unconstrained abuse. The unconstrained edge is on the service: PRINT01$ with TrustedForDelegation=True may receive a forwarded TGT when a client talks to it with delegation requested. I do not print a ok_as_delegate / forwarded TGT from a coercion. I print the flag on the computer object and stop. A ticket whose flags include forwarded (past tense) sitting in a mid-tier cache, for a user who never logged on to that host, is the IR artifact; this snapshot does not have one.
On PRINT01 itself, after a legitimate admin console logon (not a coerce), klist as the computer is empty of other users’ TGTs in this capture. That is what I want in IR: presence of someone else’s TGT in a mid-tier cache is the unconstrained smoking gun. Absence is not a proof the flag is off — check the directory.
1C:\lab> klist -li 0:0x3e7
2# SYSTEM logon on PRINT01 after idle
3Cached Tickets: (0)
4# good for this snapshot. the flag is still True — that is the finding, not a ticket
Text diagrams (the overlay)
1 [ user: labuser ]
2 |
3 TGT krbtgt/LAB.INTERNAL flags: forwardable, initial
4 |
5 +---------------+----------------+
6 | |
7 v v
8 HTTP/web01 HOST/print01
9 WEB01$ PRINT01$
10 constrained unconstrained
11 allow: CIFS/fs01, HTTP/api (any service, if TGT forwarded)
12 |
13 v
14 CIFS/fs01
15 FS01$
16 RBCD inbound: WEB01$
1Who configures whom
2--------------------
3Unconstrained : bit on the *mid-tier* computer/user
4Classic const.: msDS-AllowedToDelegateTo on the *mid-tier*
5RBCD : security descriptor on the *target* computer
6Protocol trans: TrustedToAuthForDelegation on the *mid-tier* (off here)
7Who can write RBCD / AllowedToDelegateTo : treat as tier-0 adjacent ACL
Edge cases I record from this forest, still without a chain:
- PRINT01 unconstrained after the print role moved to a server OS that no longer needs it. Flag leftover. Finding.
- WEB01 constrained allow-list includes
CIFS/fs01. That is a file-server hop, not a DC hop. Rank by target sensitivity; do not dump 200 SPNs unranked. - FS01 RBCD grants WEB01$. Same hop, other control plane. If both classic and RBCD describe the same pair, IR should not count two incidents.
TrustedToAuthForDelegationis False. If a change window flips it, 5136 on that attribute is the event, not a new CVE.- Computer and user service accounts both take these flags. Filtering
objectClass=computermissessvc-webunconstrained leftovers.
Events I keep, not a playbook
1Event 5136 A directory object was modified.
2Object: CN=PRINT01,OU=servers,DC=lab,DC=internal
3Attribute: userAccountControl
4Value: [includes TRUSTED_FOR_DELEGATION] # or the reverse, a hardening
5
6Event 4769 A Kerberos service ticket was requested.
7Account Name: labuser@LAB.INTERNAL
8Service Name: HTTP/web01.lab.internal
9Ticket Encryption Type: 0x12
10# a storm of 4769 for many users' TGSes from WEB01$ is the S4U pattern
11# I do not generate that storm
Mitigation
- Inventory all three planes quarterly; diff
TrustedForDelegation,msDS-AllowedToDelegateTo, RBCD DACL. - Remove unconstrained everywhere it is not a named exception with an owner. Print servers first.
TrustedToAuthForDelegationdefault off. Exception list with expiry.- Who can write
msDS-AllowedToActOnBehalfOfOtherIdentityon servers in T0/T1 OUs: not Account Operators folklore, not “Authenticated Users”. - Protected Users / Authentication Policy silos for people whose TGT must not be forwarded. Test before enforcing.
1PS C:\lab> Get-ADComputer -Filter { TrustedForDelegation -eq $true } |
2 select Name
3PRINT01
4DC01
5# DC01 unconstrained is historic default for DCs. PRINT01 is the leftover.
What I file after this lab
setspn -LWEB01 / PRINT01 / FS01 as above- Flags: PRINT01 unconstrained; WEB01 constrained to
CIFS/fs01,HTTP/api, no protocol transition; FS01 RBCD inbound WEB01$ klistas labuser: TGT +cifs/fs01+HTTP/web01, AES-256,forwardable(default)- SYSTEM
kliston PRINT01: 0 tickets this snapshot; flag still True - Out of scope: coercion, S4U2Proxy to a DC, writing RBCD from a workstation account
Commands appendix
1setspn -L WEB01
2setspn -Q HTTP/web01.lab.internal
3klist
4Get-ADComputer PRINT01,WEB01,FS01 -Properties TrustedForDelegation,
5 TrustedToAuthForDelegation, msDS-AllowedToDelegateTo,
6 msDS-AllowedToActOnBehalfOfOtherIdentity
7Get-ADComputer -Filter { TrustedForDelegation -eq $true } | select Name