Constrained Delegation¶
Introduction¶
We talked about Unconstrained Delegation, well Constrained Delegation is just the other one wearing a mask. Back in 2003 Microsoft realized how crazy Unconstrained Delegation can be, if used with a malicious purpose in mind. And guess what? Instead of handing empty signed checks, they started handing signed checks with a pre-defined purpose... Not so big of a change really. It serves the same purpose of letting a service access a resource under user-context.
Discovery¶
Similar to Unconstrained Delegation it was ~2014 when the attack became more and more popular.
Is Constrained Delegation still a thing?¶
As you might've already guessed, it is. Why? You may ask... Well the reasons are similar as for Unconstrained Delegation, there will be legimitate scenarios and there will be lazy SysAdmins.
We all know how Sys Admins act like :D: - "It's more secure than Unconstrained, so it must be safe!" (SPOILER: It's not!) - Service accounts that "need" to access backend services on behalf of users - Web apps connecting to SQL databases with user context - "The vendor said we need this for their app to work!" (Classic vendor excuse) - Modern multi-tier applications that were designed with delegation in mind
Modern assessments regularly find constrained delegation on IIS servers, SharePoint farms, SQL Servers, Custom Applications. It's like finding locked treasure chests where someone forgot the lock is pickable!
Summing it all up¶
To wrap it all up, Constrained Delegation needs a few more steps than Unconstrained Delegation. It's like the "Diet Coke" version of it. The thing is that we do not even need to compromise a machine, we just need to compromise the service and then we can become any user, except the protected ones and from there to expand ourselves.
Constrained Delegation, the S4U Extension Party¶
The attack initially is based on: 1. The service has it's SPN list that is allowed to delegate to. 2. S4U2Self can request a service ticket to itself on any user behalf. 3. S4U2Proxy can request service tickets to any service on behalf of any user. 4. Transition Protocol, if this is ON, you can be anyone at anytime. 5. Service uses the forged tickets to access backend resources.
How we abuse it: 1. Compromise a service account. 2. S4U2Self Request ticket for ourselves as any user. 3. Use that ticket to request access to allowed services. 4. Abuse the fact that Service Names can be changed. 5. Lateral Movement to other resources.
S4USelf: "Trust Me BRO!"¶
The thing with this is that if the TRUSTED_AUTH_FOR_DELEGATION is enabled for that service, this is like a golden mine.
The S4U2Self Flow:
WebApp -> DC: "I need a ticket to myself for Alice (S4U2Self)"
DC: "Do you have TRUSTED_TO_AUTH_FOR_DELEGATION?"
WebApp: "Yes!"
DC: "Cool, here's a ticket for Alice -> WebApp (forwardable)"
The INSANE part: Alice never authenticated! The service just CLAIMED she did!
S4UProxy: "Give me the access!"¶
Now, WebApp has a TGT for Alice. Guess what's next? Well let's access stuff as Alice! :D
The S4U2Proxy Flow:
WebApp -> DC: "I have this ticket from Alice, give me one for SQLServer"
DC: "Is SQLServer in your msDS-AllowedToDelegateTo list?"
WebApp: "Yes, check my account!"
DC: "Alright, here's Alice -> SQLServer ticket"
WebApp -> SQLServer: "Hi, I'm Alice!" (presents ticket)
The Critical Vulnerability: Protocol Transition¶
Here's where it gets REALLY broken. If the service account has the flag TRUSTED_TO_AUTH_FOR_DELEGATION (Protocol Transition), it can:
Step 1: Create Tickets from NOTHING¶
Normal Flow (Without Protocol Transition):
- User must authenticate first
- Service gets delegated TGT or ticket
- Service can only delegate authenticated users
Protocol Transition Flow (The Broken Way):
- Service just DECIDES user "authenticated"
- Service creates ticket for ANY user
- No actual authentication required!
- User doesn't even need to be online!
Step 2: The Attack Chain¶
Here's how we exploit this as attackers: Attacker's Dream Scenario:
- Compromise service account password (via Kerberoasting, password spray, etc.)
- Check if account has TRUSTED_TO_AUTH_FOR_DELEGATION
- Use S4U2Self to request ticket as Domain Admin to our service
- Use S4U2Proxy to request ticket to allowed services as Domain Admin
- Pass-the-Ticket to access those services with DA privileges!
Step 3: Why This is Worse Than It Looks¶
The "constraints" are a joke because:
1. Service Name Confusion:
- Configured: MSSQLSvc/SQL01.corp.local
- Also works: CIFS/SQL01.corp.local (DIFFERENT SERVICE!)
- Why? They're the same machine! Host-based SPNs are interchangeable!
2. Delegation Chain:
- Compromise ServiceA (delegates to ServiceB)
- ServiceB also has delegation to ServiceC
- Chain them together for expanded access!
3. Cross-Protocol Abuse:
- HTTP service can become CIFS (file access)
- MSSQL can become LDAP (directory access)
- As long as it's the same host!
Let's understand it deeper:¶
The Admin's Perspective:
1. Opens Active Directory Users and Computers (ADUC)
2. Finds the service account (let's say WEB_SVC)
3. Goes to Properties → Delegation tab
4. Selects "Trust this user for delegation to specified services only"
5. Adds specific services like:
- MSSQLSvc/SQL01.corp.local:1433
- MSSQLSvc/SQL01.corp.local
- HTTP/API.corp.local
What This ACTUALLY Looks Like in AD:¶
Service Account: WEB_SVC
├── userAccountControl: 16781312 (includes TRUSTED_TO_AUTH_FOR_DELEGATION)
├── msDS-AllowedToDelegateTo: [
│ ├── "MSSQLSvc/SQL01.corp.local:1433"
│ ├── "MSSQLSvc/SQL01.corp.local"
│ └── "HTTP/API.corp.local"
│ ]
└── servicePrincipalName: [
├── "HTTP/WEB01.corp.local"
└── "HTTP/WEB01"
]
Breaking Down SPNs (Service Principal Names)¶
An SPN has this format, and each part matters:
SERVICE_CLASS/HOST:PORT/SERVICE_NAME
Examples:
MSSQLSvc/SQL01.corp.local:1433 <- SQL Server on specific port
│ │ │
│ │ └── Port (optional)
│ └── Hostname (FQDN)
└── Service Class (the service type)
HTTP/API.corp.local <- Web service, default port
CIFS/FS01.corp.local <- File sharing service
LDAP/DC01.corp.local <- Domain Controller LDAP
The Configuration Process - What Really Happens¶
Here's the beautiful part that many don't realize:
Step 1: Admin Configures Delegation
PowerShell: Set-ADUser WEB_SVC -Add @{'msDS-AllowedToDelegateTo'=@('MSSQLSvc/SQL01.corp.local:1433','HTTP/API.corp.local')}
Step 2: What Gets Set in AD
- The msDS-AllowedToDelegateTo attribute gets populated
- If Protocol Transition is enabled: TRUSTED_TO_AUTH_FOR_DELEGATION flag
- If not: Just TRUSTED_FOR_DELEGATION flag
Step 3: The Service Can Now:
- Request tickets to ONLY those specific SPNs
- Can't randomly request tickets to LDAP/DC01.corp.local
- OR CAN IT?! (Spoiler: Service name abuse says hello!)
Why This Configuration is Broken¶
Here's where the "specific SPNs" becomes a joke: The Abuse Scenarios:
1. Alternative Service Names on Same Host:
Configured: MSSQLSvc/SQL01.corp.local
Also Works:
- CIFS/SQL01.corp.local (File access!)
- HOST/SQL01.corp.local (Remote access!)
- RPCSS/SQL01.corp.local (RPC!)
Why? Same machine = interchangeable services!
2. Port Stripping:
Configured: MSSQLSvc/SQL01.corp.local:1433
Also Works: MSSQLSvc/SQL01.corp.local (no port)
3. Name Variations:
If SQL01.corp.local = 10.10.10.5
Sometimes works with IP-based SPNs!
Real-World Configuration Example¶
Let me show you what this looks like in an actual enterprise:
Typical SharePoint Setup:
Account: SP_WebApp
msDS-AllowedToDelegateTo:
├── MSSQLSvc/SPSQL01.corp.local:1433 (Content DB)
├── MSSQLSvc/SPSQL02.corp.local:1433 (Config DB)
├── HTTP/SP-API.corp.local (Web services)
└── HTTP/SP-Search.corp.local (Search service)
What SP_WebApp can ACTUALLY access:
├── CIFS/SPSQL01.corp.local (C$ share - pwn the SQL server!)
├── CIFS/SPSQL02.corp.local (More file access!)
├── HOST/SPSQL01.corp.local (scheduled tasks!)
└── Any service on those 4 machines!
How to Enumerate This Configuration¶
As an attacker, here's how you find and abuse these configurations:
powershell# PowerView Method:
Get-DomainUser WEB_SVC | Select msds-allowedtodelegateto
Get-DomainComputer WEB01$ | Select msds-allowedtodelegateto
# Native AD PowerShell:
Get-ADUser -Filter {msDS-AllowedToDelegateTo -like "*"} -Properties msDS-AllowedToDelegateTo
# LDAP Query (for when you're being sneaky):
(&(objectCategory=user)(msDS-AllowedToDelegateTo=*))
# Rubeus will even enumerate for you:
Rubeus.exe triage /showall
Normal Constrained Delegation:¶
Normal Constrained Delegation:
────────────────────────────────
Flag Check: Actually NO special userAccountControl flag!
Just needs: msDS-AllowedToDelegateTo attribute populated
userAccountControl: Standard values (no delegation-specific flags)
msDS-AllowedToDelegateTo: ["MSSQLSvc/SQL01.corp.local"]
THE WEIRD PART: Constrained Delegation without Protocol Transition
doesn't need any special UAC flags, just the SPN list!
The Complete Delegation Flag Breakdown¶
Let me show you ALL the combinations and what they mean:
UNCONSTRAINED DELEGATION:
─────────────────────────
userAccountControl: TRUSTED_FOR_DELEGATION (524288 / 0x80000)
msDS-AllowedToDelegateTo: [empty]
Result: Can impersonate ANY user to ANY service (nuclear option)
CONSTRAINED DELEGATION (Kerberos Only):
───────────────────────────────────────
userAccountControl: [no special delegation flag needed]
msDS-AllowedToDelegateTo: ["MSSQLSvc/SQL01.corp.local", ...]
Result: Can forward existing Kerberos tickets to specified services
CONSTRAINED WITH PROTOCOL TRANSITION:
─────────────────────────────────────
userAccountControl: TRUSTED_TO_AUTH_FOR_DELEGATION (16777216 / 0x1000000)
msDS-AllowedToDelegateTo: ["MSSQLSvc/SQL01.corp.local", ...]
Result: Can CREATE tickets for any user to specified services
The Protocol Transition Checklist:¶
□ Confirm TRUSTED_TO_AUTH_FOR_DELEGATION flag
□ Enumerate msDS-AllowedToDelegateTo targets
□ Obtain service account credentials:
└── Kerberoasting (if SPN is set)
└── Password in config files
└── Password spraying
└── AS-REP Roasting (if no preauth)
□ Choose impersonation target (go big!)
□ Execute S4U2Self + S4U2Proxy chain
□ Abuse service name confusion
□ Maintain persistence
□ Look for delegation chains
□ Document for report: "Protocol Transition = Game Over"