Unconstrained Delegation is an existing feature in Active Directory environments that allows, so to speak, “blindly trusting” a domain computer (or user, although the latter is not the most common). When we talk about “trusting,” I’m referring to, for example, allowing that computer or user to carry out actions on my behalf.

If you haven’t read it, I recommend that before continuing you read Humble Attempt to Explain Kerberos

In Kerberos, the way to be able to say “I am me” is through a TGT, this is how we prove who we are in this type of environment. Therefore, for a computer or user to be able to perform actions on my behalf, they will need my TGT. And this is somewhat the basic idea of delegation.

In conclusion, and in very brief terms, the idea is that I provide my TGT to the computer that has Unconstrained Delegation enabled, so that it can perform actions on my behalf. The problem with this is that if I have access to a computer which has this feature enabled, I can look in memory if there are TGT tickets from users other than mine, which would allow me to perform lateral movements or escalate privileges in the active directory.

Let’s see everything that has been discussed in more detail and step by step.

Let’s imagine the situation where I authenticate with my domain user to an SMB resource on another computer, what would happen is simple, a Kerberos authentication process would begin step by step. However, in one of these steps, specifically in KRB_TGS_REP, the authentication behavior will change depending on whether the computer where the service we are trying to authenticate to is located has Unconstrained Delegation enabled or not.

  • If the computer does NOT have Unconstrained Delegation, the KDC’s response in KRB_TGS_REP will be as follows:

KRB_TGS_REP response without Unconstrained Delegation

A normal and “typical” response. However:

  • If the computer has Unconstrained Delegation enabled, the response will be like this:

KRB_TGS_REP response with Unconstrained Delegation

The KDC will include, within the TGS, the user’s TGT.

Remember that the TGS is encrypted with the password hash of the user running the service. So they will be the only one who can decrypt the TGS to obtain the TGT.

Continuing with the Kerberos authentication process, we already know that what comes now is KRB_AP_REQ, the step where the client (us) sends the TGS to the service so that it can finally decide whether to authorize us or not:

KRB_AP_REQ process with Unconstrained Delegation

The client will send the TGS and when the service receives it, it will be able to decrypt it and among other things, obtain the TGT, which will remain in memory so that it can be used by the service (or an attacker…).

When Unconstrained Delegation is enabled, what is being enabled is the TRUSTED_FOR_DELEGATION flag on the service account.

For example, if we enable Unconstrained Delegation on the following computer:

Enabling Unconstrained Delegation on computer

When enumerating UserAccountControl, we will be able to see the TRUSTED_FOR_DELEGATION flag enabled:

TRUSTED_FOR_DELEGATION flag in Get-DomainComputer

Get-DomainComputer

So if this flag is not found in the account running the service, the KDC will never include the TGT when we try to authenticate to that service. The KDC will also not include the TGT if the user running the delegation service belongs to the Protected Users group, or has the NOT_DELEGATED flag, the delegation will not work even if you have the TRUSTED_FOR_DELEGATION flag.

In conclusion, the complete process is:

  1. A user Y requests a TGS for a service X that runs under user X (either a computer account or a normal user account).
  2. The KDC checks if the TRUSTED_FOR_DELEGATION flag is enabled on user X and if they belong to the Protected Users group or have the NOT_DELEGATED flag.
  3. If they only have TRUSTED_FOR_DELEGATION, the KDC will include a TGT from user Y within the TGS for service X.
  4. Finally, service X will receive the TGS and obtain the TGT from user Y.
  5. From here, since service X already has the user’s TGT, each time it wants to authenticate to a service on behalf of user Y, it will perform the corresponding KRB_TGS_REQ and KRB_TGS_REP steps to obtain a TGS for the service in question where it wants to authenticate

The following image from adsecurity.org illustrates this complete process very well:

Kerberos Unconstrained Delegation communication flow diagram

Kerberos Unconstrained Delegation Communication Flow

Knowing how the entire process works and what Unconstrained Delegation is about, let’s see it in a practical way.

Let’s put ourselves in a situation, we have just entered the corporate network and we have a domain user, so we proceed to enumerate the active directory, specifically, we want to check if there is any computer or user that has Unconstrained Delegation enabled, in other words, the TRUSTED_FOR_DELEGATION flag.

We can enumerate this information in different ways:

Get-DomainComputer -UnConstrained

Enumeration with PowerView Get-DomainComputer

Enumeration result with PowerView

Get-ADComputer -Filter {TrustedForDelegation -eq $True}

Enumeration with Get-ADComputer

Get-ADComputer -Filter {TrustedForDelegation -eq $true -and primarygroupid -eq 515} -Properties trustedfordelegation,serviceprincipalname,description

Detailed enumeration with Get-ADComputer

Get-ADUser -Filter {TrustedForDelegation -eq $True}

Enumerate if any normal user has the TrustedForDelegation flag.

User enumeration with Get-ADUser

In this case there is no “normal” user that has the flag enabled.

At this point, we have already enumerated the active directory and we have seen that there is a machine that has Unconstrained Delegation enabled. Therefore, let’s imagine that in one way or another, we manage to compromise this machine.

The idea now would be to observe if there are TGTs stored in memory, for this we can use Mimikatz or Rubeus.

For example, let’s try first with Mimikatz:

Invoke-Mimikatz –Command '"sekurlsa::tickets /export"'

Ticket export with Mimikatz

Once the tickets are exported, we can view them in the current directory:

Exported tickets in directory

In this case, if we look closely, all tickets belong to the user we already have from having compromised this machine, so there is no relevant TGT. However, if I now force an authentication of the Domain Administrator on this computer, for example using PSSession:

Force authentication with PSSession

Exported tickets after authentication

Administrator ticket details

When dumping the tickets again, this time we can observe how there are some belonging to the Administrator user and the krbtgt service. If it belongs to the krbtgt service it means it is a TGT ticket. Additionally, in the Mimikatz output we can view the details in a more organized way, in order to confirm that it is a TGT ticket from the Administrator user.

This same procedure can be carried out with Rubeus:

.\Rubeus.exe monitor /interval:5 /nowrap

Ticket monitoring with Rubeus

With Rubeus we can specify how often we want to monitor if there are tickets in memory using the /interval argument, in this case, every 5 seconds. When executing this, Rubeus will show us both the tickets that are already in memory, as well as the new ones that arrive.

To avoid noise, you can specify that only tickets from the specific user you specify are shown, for example, Administrator:

Specific monitoring of Administrator user

In this way, it will wait for any TGT from this user to arrive. If we now force an authentication of the Administrator user:

Force Administrator authentication

Rubeus will detect the TGT and display it on screen in base64:

TGT detected by Rubeus in base64

With these two tools seen, we can dump and view the tickets that may be in a computer’s memory.

Now, what would be done now?

At this point and having TGTs, we would proceed to perform Pass The Ticket. We will see this in another post, however, here is an example for the TGT collected with Rubeus:

Convert the ticket with ticketConverter.py from Impacket

Using Pass The Ticket with CrackMapExec

Important to specify the FQDN in CrackMapExec, the IP would not work

In this way, the DC is compromised, and with it, the active directory. It will not always be as simple as obtaining a TGT from a domain administrator directly, sometimes we will obtain TGTs from other domain users that will allow us to perform lateral movements on the network.

Finally, if you haven’t noticed yet, Unconstrained Delegation can be combined very well with Coerce attacks.

References