Unconstrained Delegation

One of the weaknesses that I configured in my lab environment was “Unconstrained Delegation.” Systems in an Active Directory (AD) environment can be configured for unconstrained delegation. This means that a system can “delegate”, or impersonate users that authenticate to it.

Normally, when a user authenticates to a service running on a system, the user provides the service with a kerberos Ticket Granting Service (TGS) ticket. A TGS ticket allows the user to authenticate to the specific service the user requested access to. The service is able to open the TGS ticket and verify that the user is authenticated to the domain and has the privileges to access the service.

When a user authenticates to a service running a system configured for unconstrained delegation, the TGS ticket also contains a copy of the user’s kerberos Ticket Granting Ticket (TGT). The TGT is used by users to request TGS’s from a domain controller. When the service on an unconstrained delegation system opens the user’s TGS, it will extract the embedded TGT and place it into lsass.exe’s memory. If the system needs to “delegate” for the user, or request access to another service, the user’s TGT is extracted from memory, and the unconstrained delegation performs the TGS request on the user’s behalf.

The attack, then, is to compromise a system configured for unconstrained delegation, extract the memory contents of lsass.exe, and look for other user’s TGT tickets that can be used to request access for other services. If you are able to obtain a user’s TGT that hasn’t expired, you can access domain services as that user.

A better and more coherent explanation of Unconstrained Delegation can be found from adsecurity.

Finding Unconstrained Delegation Systems

PowerView can be used to find systems configured for unconstrained delegation. In a previous post I discussed how to get Covenant C2 running and get a Grunt/agent running on a system in the lab. In the grunt, I imported the PowerView.ps1 script and then used the “Get-DomainComputer” command to discover systems with unconstrained delegation.

Get-DomainComputer -Unconstrained | select -exp dnshostname

The first system listed is the domain controller for the domain. All domain controllers will have unconstrained delegation configured, and there are potential attacks against domain controllers for cross-forest trust attacks, but that is out of scope for this post. The system to focus on is the server, srv02.

To extract srv02’s lsass.exe process memory to access user’s TGTs, we will need elevated privileges on that system. One way to accomplish that is to access srv02 as a user who is in the local Administrators group. PowerView has the “Get-DomainGPOLocalGroup” function that searches through group policies to find any that use “RestrictedGroups” to add users to the local Administrators group on systems.

Get-DomainGPOLocalGroup

This shows that a GPO called “Server-admins” adds the “ServerAdmins” group to the local Administrators group on systems. To find out what systems are affected by this GPO, “Get-DomainOU” from PowerView can be used. I found this command from the PowerView Tips and Tricks gist created by HarmJ0y.

Get-DomainOU -GPLink '<GPP_GUID>' | % {Get-DomainComputer -SearchBase $_.distinguishedname -Properties dnshostname}

So, the GPO applies to both srv01 and srv02. To find out who is a member of the ServerAdmins group, “Get-DomainGroupMember” from PowerView can be used.

Get-DomainGroupMember -identity 'ServerAdmins' -recurse | select -exp membername

To access srv02 with elevated privileges, we should try and compromise the serveradmin user, who is a member of the ServerAdmins group. “Find-DomainUserLocation” from PowerView can be used to determine if the serveradmin user has any sessions on or is logged into a system on the domain.

Find-DomainUserLocation -userIdentity 'serveradmin'

The user has a session on srv01. If we can compromise srv01, it’s possible to get either credential or kerberos information from the system to authenticate as serveradmin to the domain.

Compromising the User

In a previous post, I described how the kerberoast attack can be used to compromise a service account. The kerberoast attack allowed me to crack the credentials for the serviceAccount user, who has local admin privileges on srv01. I was able to launch a grunt on srv01 as the serviceAccount user in high integrity.

With a Grunt running on srv01 as the serviceAccount user, I used LogonPasswords to dump credential information from lsass using Mimikatz. This recovered the NTLM password hash of the serveradmin user.

The serveradmin user was logged into srv01 through an interactive RDP session that was still connected. This meant that serveradmin’s TGT was still in memory as well. Rubeus can be used through a grunt to discover what kerberos tickets are currently in memory. The klist command in Rubeus will return all tickets for all logon sessions if you have elevated privileges. If you do not have elevated privileges, it will display only the tickets from your current logon session.

Rubeus klist

The TGT is the ticket for the service “krbtgt/MURPH.COOP.” You should also see all TGS tickets for specific services in klist, such as “ldap/dc01.murph.coop” to perform LDAP queries against the domain controller. Note the “LogonId” of 0x99908. Rubeus allows you to dump a base64 encoded ticket by logon id.

After this is executed, you should see a base64 encoded blob of the kerberos ticket in your Grunt.

With either serveradmin’s NTLM password hash or the base64 encoded TGT, you should be able to authenticate to the domain as serveradmin through kerberos.

Authenticating as the User and Access SRV02

The following is not performed through a Covenant grunt. For some reason that I was unable to resolve, I could not use kerberos tickets created through my grunt to access srv02 over remote management services like WMI or psremoting. I’m not sure if it is a Covenant issue, or more likely, an issue with my lab setup. The following section will be performed through an RDP session on srv01 running as the serviceaccount user.

Using the NTLM Hash

Rubeus allows for you to perform a pass-the-ticket (PTT) attack using a user’s NTLM password hash. More information on how this works under the hood can be found on HarmJ0y’s blog. This is done using the “asktgt” functionality of Rubeus.

Rubeus.exe asktgt /user:serveradmin /domain:murph.coop /rc4:<NTLM hash> /ptt

The “/ptt” flag inserts the new TGT into your current logon session. This can be viewed with klist to confirm you now have a TGT for the servadmin user.

Using the Ticket

Alternatively, you can use Rubeus’s “ptt” functionality and supply the base64 encoded ticket to inject serveradmin’s TGT into your logon session.

Again, klist can be used to confirm that you have serveradmin’s TGT in your logon session.

Accessing SRV02

With the valid TGT for serveradmin, srv02 can be accessed using WMI and psremoting. You could use Rubeus’s asktgs to request TGS tickets for services on srv02 (rpcss/srv02.murph.coop and HTTP/srv02.murph.coop should give you access to WMI and psremoting respectively). While doing this through an RDP session, the requests for the TGS tickets were performed in the backgroun when I attempted to use WMI and psremoting, as it would if you had authenticated as serveradmin with a username and password.

Accessing the srv02 with WMI:

Using psremoting to launch a new grunt on srv02:

This launched a new grunt on srv02 as the serveradmin user.

Failures in Covenant

I had attempted all of the above in a Covenant grunt as well. Here, I created a TGT using the NTLM password hash:

And here I tried using ptt with the encoded ticket:

Both of these appeared to be successful, and klist showed the TGT in my session.

When I tried to access services on the domain, such as getting a directory listing of SYSVOL on the domain controller, I would get “Access Denied”, indicating that I was not getting a TGS for that service.

I used asktgs to request a TGS for the cifs/dc01.murph.coop service, and then I was able to access SYSVOL.

I then tried to request TGS tickets for services on srv02. Requesting a ticket for cifs/srv02.murph.coop to access the file system worked.

However, requesting TGS tickets for other services such as rpcss or HTTP or whatever to provide remote management access, everything failed. The built in WMIGrunt command failed, as well as trying to run the WMI command manually through shell, shellcmd, and powershell. The same was true of using psremoting. I’m not sure what the cause of this was. I think it had something to do with my session being bonkered by launching the grunt on srv01 through WMI. Something about that and the way my lab was configured caused the grunt not to get kerberos tickets properly through the session. Later, when I was on srv02, I didn’t have these issues, and it worked when done manually through the RDP session on srv01 as the serviceaccount user. I still haven’t figured out why. It’s possible I have a critical misunderstanding of how this is supposed to work. ¯\_(ツ)_/¯

Delegation Without Constraint

Once on the srv02 system, I ran Rubeus klist to check for TGT tickets. Earlier I had manually did a directory listing of \\srv02.murph.coop\c$ as the murphDA user to create a service authentication to srv02. Rubeus klist showed murphDA’s TGT in memory.

I then used Rubeus dump to get the base64 encoded TGT.

With the base64 encoded ticket, I used Rubeus ptt to pass-the-ticket for murphDA into my session.

Klist showed that the TGT was successfully injected into my session.

Now, for the moment of truth, I attempted to get a directory listing of the C$ share of the domain controller. Only a domain admin user should have the privileges to do so.

It worked! I didn’t need to use asktgs to request new tickets. Everything worked nice and easily. I also tried psremoting against the domain controller to see if I had access through that.

Perfect! Just for fun, I then launched a grunt on the domain controller.

The grunt connected back. With that, my mission for this post was complete. The domain had been compromised through exploiting unconstrained delegation.

Without Unconstrained Delegation

To get the murphDA TGT on srv02, I simply ran “dir \\srv02.murph.coop\c$” as murphDA on a remote system. I also did ” dir \\srv01.murph.coop\c$” as murphDA. srv01 is not configured for unconstrained delegation. When I ran klist on srv01, this is what the kerberos tickets for the murphDA session looked like:

Instead of the TGT, it is only the TGS for the cifs/srv01.murph.coop service. I could ptt this ticket and access the file system of srv01 as the murphDA user, but that’s all I would be able to do. I would not be able to use it to request access to additional services as murphDA. Not quite as exciting!