External Trusts Are Evil
Posted on Tue 14 March 2023 in Active Directory
This post is duel posted on the Semperis blog.
Another day, another “by design” but clearly unintended Windows AD feature. While playing with Kerberos tickets I discovered an issue that allowed me to authenticate to other domains within a forest across external non-transitive trusts. This means that there is in fact no such thing as a “non-transitive trust”, and the description as such when creating a trust is at best misleading, leaving systems administrators with a false sense of security. After reporting this issue to Microsoft, I received the following response:
This post details the issue discovered but as Microsoft do not feel that it affects security and as a result are not planning on fixing this issue, there is no way to avoid what is being described within this post apart from not using external trusts at all.
Trusts and Transitivity
Will Schroeder published a great post on trusts back in 2017. If you are not too familiar with the different trusts and how they work, I highly recommend reading that first as I will not be going into as much detail on the various types. For this post, I will not be discussing intra-forest trusts (trusts within a single forest) other than as they apply to the specific attack path that I am going to discuss. I also will not be discussing selective authentication trusts, but I have never actually seen these in real environments. The remaining trusts are forest trusts and external trusts. Forest trusts are trusts between two forests; or more accurately a transitive trust between the root domains of two forests, which means any user from any domain within the trusted forest can authenticate to any domain within the trusting forest. External trusts, on the other hand, are trusts between two domains and are described as being “non-transitive”, which should mean that only users within the trusted domain can authenticate against only the trusting domain.
Other technical differences are present between forest and external trusts, but these will be discussed if and when required for this post.
Microsoft describes trust transitivity as follows:
Transitivity determines whether a trust can be extended outside of the two domains with which it was formed.
- A transitive trust can be used to extend trust relationships with other domains.
- A non-transitive trust can be used to deny trust relationships with other domains.
This description is very clear. For non-transitive trusts, only the two domains involved in the trust can authenticate to each other and not beyond. As I will demonstrate in this post, this is not the case.
To properly demonstrate this issue, several multi-domain forests are required with external trusts between them. I have setup the lab as follows:
This setup involves three (3) forests, two (2) of which have three (3) domains and the other containing two (2) domains. The domains semperis.lab and treetest.lab have a bidirectional non-transitive external trust:
The domains grandchild1.child1.semperis.lab and semperisaz.lab also have a bidirectional non-transitive external trust:
This should make it possible to demonstrate the implications and limitations of this issue.
How Kerberos Cross-Trust Authentication Works
The main thing that is required to authenticate to a service across a trust using Kerberos is a referral (or referral TGT). This is a ticket requested from your local domain controller (DC) for the foreign domain. This section assumes a basic understanding of how the normal Kerberos authentication flow works, for a detailed explanation of that check out Sean Metcalf’s Detecting Kerberoasting Activity post. It would also be advantageous to be reasonably familiar with Rubeus (as I will be using it to request tickets in the remaining sections). This section will focus on the semperis.lab forest which is sufficient to demonstrate how ticket requests across trusts are performed, the information here is, however, applicable to any “allowed” trust path.
The simplest example of cross-trust authentication is to authenticate to a service on a domain which has a direct trust with the local domain. The trust that the domain grandchild1.child1.semperis.lab has with child1.semperis.lab, shown in Figure 5 above, is an example of this. In this situation, the first step, after obtaining the initial TGT, in obtaining a service ticket for an account in grandchild1.child1.semperis.lab to a service in child1.semperis.lab is to obtain a referral for child1.semperis.lab:
As shown in Figure 6 above, the request was made to a DC (SGC1DC1.grandchild1.child1.semperis.lab) within the domain (grandchild1.child1.semperis.lab) local to the authenticating user (lowpriv). The request was made for the service krbtgt/child1.semperis.lab and the fact that the ServiceRealm (srealm) is the local domain (grandchild1.child1.semperis.lab) within the resulting ticket, shows that this ticket is a referral. The diagram below shows this:
This referral can now be used to request service tickets (STs) from the foreign domain:
Here an ST was requested from the foreign DC (SC1DC1.child1.semperis.lab) for the service host/SC1DC1.child1.semperis.lab using the referral retrieved previously.
To take this one step further, if a service on the forest root domain (semperis.lab) is required, a referral for this domain cannot be directly requested from the local (grandchild1.child1.semperis.lab) domain:
As shown in Figure 10 above, a referral for krbtgt/semperis.lab was requested from the local DC sgc1dc1.grandchild1.child1.semperis.lab. However, the ticket returned by the DC was for the service krbtgt/child1.semperis.lab, meaning that this is a referral for the domain child1.semperis.lab, NOT semperis.lab.
This can be explicitly shown by trying to use this ticket to request a ST for the semperis.lab domain:
As shown in Figure 12 above, this results in an AP_ERR_BAD_INTEGRITY error. This is because the referral ticket is encrypted with the trust key for the grandchild1.child1.semperis.lab --> child1.semperis.lab trust. The DCs in the root domain semperis.lab do not have knowledge of this key and so are not able to decrypt the ticket.
To request a service ticket for the root domain semperis.lab, first a referral for semperis.lab must be requested from a DC in the domain child1.semperis.lab using this referral:
Here a request was made to the foreign DC (sc1dc1.child1.semperis.lab) using the referral for the domain child1.semperis.lab to request a further referral for the root domain semperis.lab. It should be noted here that in order to request this ticket, the /targetdomain argument is required. This is because by default Rubeus will use the domain within the ticket passed to it to fill in the domain in the TGS-REQ, in this case that would be grandchild1.child1.semperis.lab and would result in a ERR_WRONG_REALM error as the domain local to the DC is child1.semperis.lab. This will be very important in the next section.
Lastly, this resulting referral for semperis.lab can be used to request STs for the semperis.lab domain:
This ST request from the user [email protected]b made to the DC SDC1.semperis.lab for the SPN host/SDC1.semperis.lab is shown in the following diagram and was successful even though the two domains involved do not have a direct trust due to the trust path being allowed:
This method of requesting referrals for trusting domains can be followed to request STs for any service within any domain for which the trust path is “allowed”.
Making The Non-Transitive Transitive
Now that we understand how authentication across trusts happens, let’s look at how it is possible to traverse external trusts to authenticate to domains that should be prohibited.
As shown in the Figure 3, the domains semperisaz.lab and grandchild1.child1.semperis.lab have a bidirectional external trust, this means that after retrieving a TGT for any account within the domain semperisaz.lab it is possible to request a referral for grandchild1.child1.semperis.lab:
The diagram showing this request is below:
This referral can be used to request STs for services on the domain grandchild1.child1.semperis.lab, but if we try to obtain a referral to other domains within the same forest (for example child1.semperis.lab) we get an ERR_PATH_NOT_ACCEPTED error, which is expected:
This is because the semperisaz.lab --> granchild1.child1.semperis.lab trust is non-transitive, so the path from semperisaz.lab to child1.semperis.lab is not allowed.
However, we can request a “local” TGT for grandchild1.child1.semperis.lab:
I call this a “local” TGT because unlike the referral, which has a ServiceRealm (srealm) of semperisaz.lab, the ServiceRealm is grandchild1.child1.semperis.lab.
Using this “local” TGT, a referral for child1.semperis.lab can now be requested:
This process can be seen in the following diagram:
We now have a usable referral which can be used to request STs for child1.semperis.lab using any account from the semperisaz.lab domain and without making any changes to trusts or accounts within AD. This is demonstrated below:
In Figure 26, a ST was requested from the DC sc1dc1.child1.semperis.lab for the service host/sc1dc1.child1.semperis.lab as the user [email protected]:
Furthermore, this method can be used to hop around any domain within the same forest that grandchild1.child1.sermperis.lab exists in. We can demonstrate this by requesting a referral to the root domain semperis.lab:
A referral was requested for semperis.lab from the DC sc1dc1.child1.semperis.lab as the user [email protected]:
Fortunately for security, hopping across further trusts outside of the forest (external or forest trusts) is not possible using this method. As shown in Figure 3, the root domain semperis.lab has a bidirectional external trust with treetest.lab, this trust can be used to demonstrate that limitation:
This at least stops an attacker using this method of trust hopping from hopping into another forest.
This issue is clearly useful for attackers trying to elevate privileges within a forest from across a trust. Other than being able to query domain information from domains that systems administrators may think would be disallowed (and query more sensitive domains or potentially domains with weaker security), this also makes it possible to perform attacks such as Kerberoasting or NTLM authentication coercion on domains that may seem to be disallowed.
While it is not possible to hop further using this specific method, I did write a post a while ago about the ability to create machine accounts across trusts and that this may open up new avenues of attack for attackers. This situation is another example of that.
Using the referral retrieved for semperis.lab, it is possible to request a ticket for the LDAP service on a DC in semperis.lab:
Here a ST for ldap/sdc1.semperis.lab was requested using the referral for semperis.lab as the user [email protected]:
This ST can be injected and used to create a machine account directly in semperis.lab (given the configuration allows Authenticated Users, as is default):
This causes the DC sdc1.semperis.lab to create the machine account TestComp within the semperis.lab domain:
This newly created TestComp machine account is a local account within the domain semperis.lab. Now that we have a machine account that exists within semperis.lab, we can retrieve a TGT for that account:
Requesting the machine account TGT looks as follows:
This machine account TGT is allowed to request a referral to the trusting domain treetest.lab:
Here the machine account’s TGT was used to request a referral for the treetest.lab domain from the DC SDC1.semperis.lab:
Lastly, the issue described in this post can again be used to gain access to the inaccessible dsptest.lab domain. First by requesting a “local” TGT for treetest.lab:
The “local” TGT is requested using the referral for treetest.lab as the account [email protected] from the DC TDC1.treetest.lab:
Then using this “local” TGT to request a referral for dsptest.lab:
Here a referral was requested for the domain dsptest.lab from the DC TDC1.treetest.lab as the user [email protected]:
It should be clear that using the combination of these two methods would make it possible to hop very deep into AD enterprise infrastructures, using any low privileged account on a domain which has an external trust to any domain within a forest.
Unfortunately, the only way to prevent this attack path is to remove external trusts completely. If this is not possible, detection is possible. The relevant Windows events are 4769’s (A Kerberos service ticket was requested). The first indication is that a “local” TGT is requested from an account in a different forest:
Here the Account Domain field is a domain that belongs to a different forest and the Service Name is krbtgt. This event is followed by another 4769 requesting a referral:
Here the Account Domain is a domain within a different forest and the Service Name is another domain within the local forest.
It is worth noting that these two events can be on different DCs within the same domain, they do not have to reside on the same DC. At this point, any service tickets requested (4769’s) using this referral will have an Account Domain that contains a domain that should not be allowed to authenticate to the local domain.
Plans to consume these events and detect this attack are planned for an upcoming release of Directory Services Protector (DSP).
The biggest problem here is Microsoft’s description of non-transitive trusts being, at best, misleading to systems administrators. This problem is exaggerated by Microsoft’s refusal to accept that this affects security. As it stands, by creating an external “non-transitive” trust, companies must accept that any account within the trusted domain can authenticate against any domain within the whole forest where the trusting domain resides.
As demonstrated above, in the “Hopping Further” section of this post, this research again highlights the importance of disallowing Authenticated Users from being able to create machine accounts, as not doing so not only puts domains within the forest at a higher risk, but also puts any domains (and the whole forest within which they reside) which have an external trust with any domain within the forest at a higher risk. This is due to the ability to create machine account across trusts, even while using the technique used within this blog post to authenticate against domains which should be disallowed. More information on the machine account quota and how to prevent low privileged machine account creation can be found on Kevin Robertson’s excellent post “MachineAccountQuota is USEFUL Sometimes”.
If nothing else, I hope this blog post informs systems administrators of the real forest-wide risk involved in implementing external trusts. Afterall, how can systems administrators be expected to properly secure their environments while thinking authentication is not possible where it actually is.
- 2022/05/04 – MSRC case created.
- 2022/05/12 – Case status changed to “Review/Repro”.
- 2022/06/17 – Case status changed to “Develop” with email that stated "We confirmed the behavior you reported. We'll continue our investigation and determine how to address this issue."
- 2022/06/17 – Case status changed to “Complete – Resolved”.
- 2022/08/24 – Comment left on case to find out the status.
- 2022/09/02 – A follow-up comment left on case to find out the status.
- 2022/09/14 – A follow-up email sent to find out the status of the case.
- 2022/09/29 – Email received explaining the issue was not determined to affect security.
- 2023/03/14 – Public disclosure - Blog post released.
- Will Schroeder for the creation of Rubeus without which this research very well may not have been done.
- Everyone that reviewed this post and provided feedback, including:
- Elad Shamir
- Harjinder Nijjar
- Andrew Schwartz
- Jonny Johnson