The purpose of this post is to attempt to explain some research I did not long ago on performing S4U across a domain trust. There doesn't seem to be much research in this area and very little information about the process of requesting the necessary tickets.
I highly recommend reading Elad Shamir's Wagging the Dog post before reading this, as here I'll primarily focus on the differences between performing S4U within a single domain and performing it across a domain trust but I won't be going into a huge amount of depth on the basics of S4U and it's potential for attack, as Elad has already done that so well.
I first thought of the ability to perform cross domain S4U when looking at the following Microsoft advisory. It states:
“To re-enable delegation across trusts and return to the original unsafe configuration until constrained or resource-based delegation can be enabled, set the EnableTGTDelegation flag to Yes.”
This makes it clear that it is possible to perform cross domain constrained delegation. The problem was I couldn't find anywhere that gave any real detail as to how it is performed, and the tools used to take advantage of constrained delegation did not support it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
This allowed me to figure out how it works and implement it into Rubeus.
To perform standard constrained delegation, 3 requests and responses are required: 1. AS-REQ and AS-REP, which is just the standard Kerberos authentication. 2. S4U2Self TGS-REQ and TGS-REP, which is the first step in the S4U process. 3. S4U2Proxy TGS-REQ and TGS-REP, which is the actual impersonation to the target service.
I created a visual representation as the ones I've seen previously weren't the easiest to understand:
In this it's the ticket contained within the final TGS_REP that is used to access the target service as the impersonated user.
After hours of using Will's Powershell to generate S4U traffic and staring at packet dumps, this is how I understood cross domain S4U to work:
Clearly there's a lot more going on here, so let me try to explain.
The first step is still the same, a standard Kerberos authentication with the local domain controller. (1 and 2)
A service ticket is requested for the foreign domains krbtgt service from the local domain controller. (3 and 4)
- The users real TGT is required for this request.
- This is known as the inter-realm TGT or cross domain TGT. This resulting service ticket is used to request service tickets for services on the foreign domain from the foreign domain controller.
Here's where things start to get a little complicated. And the S4U2Self starts.
A service ticket for yourself as the target user you want to impersonate is requested from the foreign domain controller. (5 and 6)
- This requires the cross domain TGT.
- This is the first step in the cross domain S4U2Self process.
A service ticket for yourself as the user you want to impersonate is now requested from the local domain controller. (7 and 8)
- This request includes the users normal TGT as well as having the S4U2Self ticket, received from the foreign domain in step 3, attached as an additional ticket.
- This is the final step in the cross domain S4U2Self process.
And finally the S4U2Proxy requests. As with S4U2Self, it involves 2 requests, 1 to the local DC and 1 to the foreign DC.
A service ticket for the target service (on the foreign domain) is requested from the local domain controller. (9 and 10)
- This requires the users real TGT as well as the S4U2Self ticket, received from the local domain controller in step 4, attached as an additional ticket.
- This is the first step in the cross domain S4U2Proxy process.
A service ticket for the target service is requested from the foreign domain controller. (11 and 12)
- This requires the cross domain TGT as well as the S4U2Proxy ticket, received from the local domain controller in step 5, as an additional ticket.
- This is the service ticket used to access the target service and the final step in the cross domain S4U2Proxy process.
I implemented this full process into Rubeus with this PR, which means that the whole process can be carried out with a single command.
The implementation primarily involves the
CrossDomainS4U2Proxy() functions, along with the addition of 2 new command line switches,
/targetdc, and some other little modifications.
/targetdc are passed on the commandline, Rubeus executes a cross domain S4U, otherwise a standard one is performed.
What's The Point?
Good question. This could be a useful attack path in some unusual situations. Let me try to explain one.
Consider the following infrastructure setup:
There are 2 domains, in a single forest. internal.zeroday.lab (the parent and root of the forest) and child1.internal.zeroday.lab (a child domain).
We've compromised a standard user, child.user, on child1.internal.zeroday.lab, this user can also authenticate against the SQL server ISQL1 in internal.zeroday.lab as a low privileged user:
As Elad mentions in the MSSQL section of his blog post, if the SQL server has the WebDAV client installed and running, xp_dirtree can be used to coerce an authentication to port 80.
What is important here is that the machine account quota for internal.zeroday.lab is 0:
This means that the standard method of creating a new machine account using the relayed credentials will not work:
The machine account quota for child1.internal.zeroday.lab is still the default 10 though:
So the user child.user can be used to create a machine account within the child1.internal.zeroday.lab domain:
As the machine account belongs to another domain, ntlmrelayx.py is not able to resolve the name to a SID:
For this reason I made a small modification which allows you to manually specify the SID, rather than a name. First we need the SID of the newly created machine account:
--sid switch can be used to specify the SID of the machine account to delegate access to:
The configuration can be verified using
So now everything is in place to perform the S4U and impersonate users to access ISQL1.
The NTLM hash of the newly created machine account is the ast thing that is required:
The following command can be used to perform the full attack and inject the service ticket for immediate use:
This command does a number of things but simply put, it authenticates as TestChildSPN$ from child1.internal.zeroday.lab against IC1DC1.child1.internal.zeroday.lab and impersonates internal.admin from internal.zeroday.lab to access http/ISQL1.internal.zeroday.lab.
Now let's look at this in a bit more detail.
As described previously, the first step is to perform a standard Kerberos authentication and recieve the account's TGT that has been delegated access (TestChildSPN in this case):
This TGT is then used to request the cross domain TGT from IC1DC1.child1.internal.zeroday.lab (the local domain controller):
This is simply a service ticket to krbtgt/internal.zeroday.lab. This cross domain TGT is then used on the foreign domain in exactly the same manner the users real TGT is used on the local domain.
It is this ticket that is then used to request the S4U2Self service ticket for TestChildSPN$ for the user internal.admin from IDC1.internal.zeroday.lab (the foreign domain controller):
To complete the S4U2Self process, the S4U2Self service ticket is requested from IC1DC1.child1.internal.zeroday.lab, again for TestChildSPN$ for the user internal.admin, but this time the users real TGT is used and the S4U2Self service ticket retrieved from the foreign domain in the previous step is attached as an additional ticket within the TGS-REQ:
To begin the impersonation, a S4U2Proxy service ticket is requested for the target service (http/ISQL1.internal.zeroday.lab in this case) from IC1DC1.child1.internal.zeroday.lab. As this request is to the local domain controller the users real TGT is used and the local S4U2Self, received in the previous step, is atached as an additional ticket in the TGS-REQ:
Lastly, a S4U2Proxy service ticket is also requested for http/ISQL1.internal.zeroday.lab from IDC1.internal.zeroday.lab. As this request is to the foreign domain controller, the cross domain TGT is used, and the local S4U2Proxy service ticket received in the previous step is attached as an additional ticket in the TGS-REQ. Once the final ticket is received, Rubeus automatically imports the ticket so it can be used immediately:
Now that the final service ticket has been imported it's possible to get code execution on the target server:
While it was possible to perform this across trusts within a single forest, I didn't manage to get this to work across external trusts. It would probably be possible but would require a non-standard trust configuration.
With most configurations this wouldn't be required as you could either create a machine account within the target domain or delegate to the same machine account, as I've discussed in a previous post, but it's important to understand the limits of what is possible with these types of attacks.
The mitigations are exactly the same as Elad discusses in his blog post as the attack is exactly the same, the only difference is here I'm performing it across a domain trust.
A big thaks to Will Schroeder for all of his work on delegation attacks and Rubeus. Also Elad Shamir for his detailed work on resource-based constrained delegation attacks and contributions to Rubeus which helped me greatly when trying to implement this. Benjamin Delpy for all of his work on Kerberos tickets in mimikatz and kekeo.
I'm sure there are many more too, without these guys work, research in this area would be much further behind where it currently is!