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):

BitNameWho has it here
0x80000TRUSTED_FOR_DELEGATIONPRINT01$
0x1000000TRUSTED_TO_AUTH_FOR_DELEGATIONnobody in this lab (good)
(attribute)msDS-AllowedToDelegateToWEB01$CIFS/fs01, HTTP/api
(attribute)msDS-AllowedToActOnBehalfOfOtherIdentityon 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:

  1. PRINT01 unconstrained after the print role moved to a server OS that no longer needs it. Flag leftover. Finding.
  2. 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.
  3. FS01 RBCD grants WEB01$. Same hop, other control plane. If both classic and RBCD describe the same pair, IR should not count two incidents.
  4. TrustedToAuthForDelegation is False. If a change window flips it, 5136 on that attribute is the event, not a new CVE.
  5. Computer and user service accounts both take these flags. Filtering objectClass=computer misses svc-web unconstrained 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.
  • TrustedToAuthForDelegation default off. Exception list with expiry.
  • Who can write msDS-AllowedToActOnBehalfOfOtherIdentity on 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 -L WEB01 / PRINT01 / FS01 as above
  • Flags: PRINT01 unconstrained; WEB01 constrained to CIFS/fs01,HTTP/api, no protocol transition; FS01 RBCD inbound WEB01$
  • klist as labuser: TGT + cifs/fs01 + HTTP/web01, AES-256, forwardable (default)
  • SYSTEM klist on 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