Active Directory Spotlight: Trusts — Part 2. Operational Guidance
In the first part of this Active Directory (AD) spotlight I introduced the mechanics of Active Directory Trusts and highlighted what a Trust consist of. If you just stumbled across this post let me quickly summarize the main parts of part:
There are two objects that are created when a trust is established: A trust account and a Trust Domain Object (TDO). Both of these objects are created on each side of the trust.
There are 7 Trust Characteristics that should be known:
- Trust Partner
- Trust Types (aka. Trust Flavors)
- Trust Direction
- Trust Authentication Level
- Trust Transitivity
- TGT Delegation
- SID Filtering
In this second part I will get down to the operational bits to put all the theoretical knowledge of part 1 into practice. To do this we'll walk through the following sections:
- Evaluate Trust Characteristics
- Enumerate AD Trusts
- Red-Team Operations
- Blue-Team Operations
- Review
Additionally, I will release some tooling so that you can put this all into practice yourself. If you just came here for the tools, you'll find them here:
Evaluate Trust Characteristics
Okay so let's run down where all of those mentioned Trusts Characteristics can be found.
Side Note: If you ever feel like you missed a bit of background knowledge skip back to Part 1 of this spotlight to re-iterate what these characteristics mean.
Side Note #2: If you came to see AD Trust in action and rather want to read up on the details later feel free to skip to section "Enumerate AD Trusts".
TrustPartner & TrustDirection
The TrustPartner and TrustDirection characteristics are the easiest to get a hold off as they're directly within the attributes trustPartner and trustDirection of the Trust Domain Object (TDO):
Keep in mind that there is a TDO for each side of the Trust relationship so always analyze both TDOs for each trust.
The trustDirection value is actually an Integer that can have the following values:
- 0x00000001: TRUST_DIRECTION_INBOUND
- 0x00000002: TRUST_DIRECTION_OUTBOUND
- 0x00000003: TRUST_DIRECTION_BIDIRECTIONAL
The documentation for this attribute can be found here.
TrustFlavor
The TrustFlavor needs a little more logic to extract. To recall there are the following 6 different flavors:
- ParentChild Trusts
- TreeRoot Trusts
- Shortcut (internally named "CrossLink") Trusts
- Forest Trusts
- External Trusts
- Realm Trusts (internally named "Kerberos") Trusts
To get started we can query the trustType attribute of the TDO, which holds one of the following values:
- 0x00000001: The trusted domain is a Windows domain not running Active Directory
- 0x00000002: The trusted domain is a Windows domain running Active Directory
- 0x00000003: The trusted domain is running a non-Windows, RFC4120-compliant Kerberos distribution
- 0x00000004: Historical reference; this value is not used in Windows
If the trustType attribute happens to be 0x00000003, we can directly conclude that the inspected TDO describes a Realm Trust (aka. Kerberos Trust).
If the value of this attribute is not 0x00000003, we need some more information. In this case we can check if the Domain that we're investigating and its Trust Partner (that is specified in the TDO) are in the same Forest by checking the presence of the TDO trustAttributes flag TRUST_ATTRIBUTE_WITHIN_FOREST (0x00000020). If that's the case we need to check if one of the two trust entities is a child of the other — in which case we have a ParentChild relationship. If they're not parent and child we can check if the two entities have the same rootDomainNamingContext and if that's the case we have a Shortcut relationship (aka. CrossLink) and if not we have a TreeRoot relationship.
If the two trust entities are not in the same forest the trust flavor is determined by the presence of the TDO trustAttributes flag TRUST_ATTRIBUTE_FOREST_TRANSITIVE (0x00000008). If this flag is present we have a Forest Trust and if it's not present we have an External Trust.
A lot of IFs and THENs, huh? If you want more readable/programmatical statements, you can find my Powershell implementation of this here.
Authentication Level
To recap there are three different types of Authentication Levels:
- Forest-Wide Authentication
- Domain-Wide Authentication
- Selective Authentication
The Authentication Level of a trust is derived from the trustAttributes flags of a TDO, with the following logic:
If the trust relationship is made within a forest boundary (aka if the TRUST_ATTRIBUTE_WITHIN_FOREST (0x00000020) flag is set), then Forest-Wide Authentication will always be used. If the trust relationship crosses a forest boundary and the TRUST_ATTRIBUTE_CROSS_ORGANIZATION (0x00000010) flag is set then Selective Authentication is used. If the trust relationship crosses a forest boundary, but the trust is marked as transitive (aka if the TRUST_ATTRIBUTE_FOREST_TRANSITIVE flag is set), then Forest-Wide Authentication will be used.
In any other case Domain-Wide Authentication is used.
Interesting to note:
Trusts within a Forest always use Forest-Wide Authentication (and this can not be disabled).
My Powershell code for this can be found here.
Transitivity
To recap a trust can either be transitive (enabled) or non-transitive (disabled).
The transitivity status of a trust depends on the trustAttributes flags of a TDO and the Trust Flavor of the inspected trust, with the following logic:
If the TRUST_ATTRIBUTE_NON_TRANSITIVE (0x00000001) flag is set then the transitivity is disabled. If the TRUST_ATTRIBUTE_WITHIN_FOREST (0x00000020) flag is set then the transitivity is enabled. If the TRUST_ATTRIBUTE_FOREST_TRANSITIVE (0x00000008) flag is set then the transitivity is enabled.
In any other case the transitivity is disabled.
Interestingly to note:
Trusts within a Forest are (per default) always transitive if not explicitly disabled.
My Powershell code for this can be found here.
TGT Delegation
To recap a trust can either have TGT Delegation enabled or disabled.
The status of the TGT Delegation of a trust is defined and documented in section 3.3.5.7.5 in [MS-KILE] and is derived from the trustAttributes flags of a TDO with the following logic:
If the TRUST_ATTRIBUTE_CROSS_ORGANIZATION_NO_TGT_DELEGATION (0x00000200) flag is set, then TGT Delegation is disabled. If the TRUST_ATTRIBUTE_QUARANTINED_DOMAIN (0x00000004) flag is set, then TGT Delegation is disabled. If the TRUST_ATTRIBUTE_CROSS_ORGANIZATION_ENABLE_TGT_DELEGATION (0x00000800) flag is set, then TGT Delegation is enabled. If the TRUST_ATTRIBUTE_WITHIN_FOREST (0x00000020) flag is set, then TGT Delegation is enabled.
Interestingly to note:
You can't TGT delegate in quarantined trusts (trusts that have the TRUST_ATTRIBUTE_QUARANTINED_DOMAIN flag set).
My Powershell code for this can be found here.
SID Filtering
To recap SID Filtering was introduced to counter privilege escalation attacks exploiting the SID History attribute. You can term SID Filtering to be enabled or disabled, but always be aware of what that means aka. what SIDs are filtered.
The one clear thing that can (and should) easily evaluate is the following:
If the TRUST_ATTRIBUTE_QUARANTINED_DOMAIN (0x00000004) TDO flag is set, then only SIDs from the trusted domain are allowed (all others are filtered).
So the immediate take away here is:
If you want the most restrictive environments all trusts should set the TRUST_ATTRIBUTE_QUARANTINED_DOMAIN flag on each side of the trust. Including Trusts within forests.
If the TRUST_ATTRIBUTE_QUARANTINED_DOMAIN flag is not set, the SIDs that are filtered depend on the Trust Flavor and various trustAttributes flags. The logic that sits behind this might be too complex to put it in text, so I'll created lookup tables to check what is filtered:
In the above table you'll find the SID Filtering rules for various TrustFlavors in its default configuration, meaning with the TDO trustAttributes flags that are applied by default when these TrustFlavors are created.
Of course the TDO trustAttributes flags can be changed (on each side), in which case you want to have a look at the table below:
When evaluating SID Filtering always keep in mind that apart from the trustAttributes flags there are some rules defined within section 4.1.2.2 of [MS-PAC] that target specific SIDs and determine if these are filtered or not.
My Powershell code for this can be found here.
Enumerate AD Trusts
Okay so this is where the fun part begins...
In order to ease the process of enumerating trusts I wrote Enum-ADTrusts.ps1, which does pretty much what it says, it enumerates all the trust relationship that the current user can find.
The output of this tool will print out all trust relationship in a table. This output should once more express that it's important to check both ends of the trust (because the characteristics could differ).
If you're interested about the reason behind detected values of certain trust characteristics you can add the -IncludeReason flag and figure out why:
As this output might be a bit tedious to work through, especially for bigger environments that you're not familiar with I've added the parameter -ShowGraphNotation switch to get a textual graph representation:
Once you got this textual graph representation, paste it in text file and use a graphing tool, such as graph-easy as shown below:
Side Note: When you copy the textual graph representation, ensure that there are no line-breaks for inline statements in the file. For example in the screenshot above you can see that the label field is broken into multiple lines (because the Powershell terminal is not wide enough), ensure that this field is a single line in your text file.
graph-easy <FileContainingTheGraphNotation>
Once you run Enum-ADTrusts.ps1 be aware that all the trust relationship information are fetched via LDAP and preferably (if that server is operational) from the Global Catalog server. As the Global catalog contains information about every object in the forest it might also contain information about trust entities that you can't reach (e.g. due to network segmentation or because they are offline). You might therefore experience that the script takes a while to complete (because it waits on network connections), if you want more insights about what is fetched and what the script is doing add the -Verbose flag and let it tell you:
Red-Team Operations
When conducting a Red-Team operation there are the following actions that you want to consider:
Enumerate (all) AD Trust relationships
You can run Enum-ADTrust.ps1 to collect information about all trust relationship or use the information from above to build your own tooling. You can certainly optimize the scripting and caching to minimize the network traffic but be aware of the footprint (LDAP queries) you leave in the network.
Enum-ADTrust.ps1 (as of now) is not optimized for reduced traffic or to query only specific relationships (not sure I will add this in the future), but based on the code it should be pretty straight forward to slim down the information that is requested over the network.
Find interesting relationships
An example of interesting trust relationships are those for which SID filtering is disabled. Details on how to exploit these scenarios are provided in the next section.
Also of interest could be trusts for which TGT Delegation is enabled. For delegation abuse scenarios à la PrinterBug. For an overview of how various Delegation scenarios can be exploited you might want to read through my post Kerberos Delegation: A Reference Overview.
Furthermore Forest-Wide Authentication trusts could be interesting to abuse default permissions, e.g. in order to read the SYSVOL share and find CPasswords or other interesting details in the GroupPolicy share of your target.
There are many more interesting scenarios that can be abused, for examples when foreign users have been added to the targeted domain with access given to certain objects (Shares, Computers, etc.). This spotlight is not suited to fit those special case scenarios, but as a start I can recommend to dive into Will's blog post A Guide to Attacking Domain Trusts. In my opinion Will made a great point here with the following statement in his blog:
[...] trusts are normally implemented for a reason, meaning more often than not some type of cross-domain user/group/resource "nesting" probably exists, and in many organizations these relationships are misconfigured.
Exploit Weak SID-Filterings
As detailed in part 1 of this spotlight, SID Filtering was introduced to counter attacks exploiting the SID History attribute. So in order to exploit any weak SID Filtering rules you have two options:
- You can manipulate the actual SID History attribute for your (or another) user to include a SID of a domain user that trusts your domain; OR
- You can forge an Inter-Realm TGT ticket and include the a useful SID (or multiple useful SIDs) in the Privilege Attribute Certificate Data Structure [MS-PAC] of that Kerberos ticket.
As the SID History attribute is a protected attribute you need some extra effort to write to this attribute — you can't just change it in the GUI or submit changes to the ADSI object.
The other option on the other hand is pretty easy to setup thanks to @gentilkiwi and mimikatz (❤), here's a rundown of how such an attack could look like:
Step 0: The Scenario
We're on a host in the domain Shield.SafeAlliance.local.
We've compromised this domain and want to escalate our privileges to gain access to the parent domain SafeAlliance.local.
Using Enum-ADTrust.ps1 we figured that Shield.SafeAlliance.local and SafeAlliance.local have a default ParentChild trust relationship, meaning SID Filtering is disabled and only a few SIDs are always filtered as per section 4.1.2.2 of [MS-PAC].
Step 1: Getting the trust key
As we have compromised the child domain Shield.SafeAlliance.local we can use our administrative access in this domain to extract the trust key (aka. the password of the trust account), which in this case is named "SafeAlliance$". We'll use mimikatz to get that key:
mimikatz # lsadump::dcsync /domain:Shield.SafeAlliance.local /user:SafeAlliance$
Got the key: 4ed816553e29fa59495e3dc88887fbdf
Step 2: Forge an Inter-Realm TGT
Next we want to use that trust key to forge an Inter-realm TGT to authenticate in SafeAlliance.local. Apart from the trust key we'll need the SID of our current domain and our target domain:
- SID of Shield.SafeAlliance.local:
S-1-5-21-3838094677-1022008726-3036488648
We can get the SID of our current domain (for example) by running whoami /USER - SID of SafeAlliance.local:
S-1-5-21-977387657-1903550393-2077642190
We can get that SID (for example) from ADSI as follows:
(New-Object System.Security.Principal.SecurityIdentifier(([ADSI]\"LDAP://SafeAlliance.local\").objectSid.Value,0)).Value
After we grabbed these two SIDs we'll use mimikatz again to forge the Inter-Realm TGT:
mimikatz # kerberos::golden /domain:Shield.SafeAlliance.local /sid:S-1-5-21-3838094677-1022008726-3036488648 /user:Nick.Furry /target:SafeAlliance.local /service:krbtgt /rc4:4ed816553e29fa59495e3dc88887fbdf /sids:S-1-5-21-977387657-1903550393-2077642190-519
Notice that we add the extra SID of the Enterprise Admin Group (*-519) in this case, which I chose because this group is allowed to access the C$-Share, which will use as a PoC later on.
An overview of the mimikatz arguments used above is given here:
/domain: The name of the domain that you're currently in (that is trusted by your target domain)
/sid: The SID of the domain you're currently in
/user: Your current user (can be any user, doesn't matter)
/target: The target domain where you want to gain access
/service: The service for which the ticket should be created, use \"krbtgt\" as you want to create an TGT for the targeted (/target) domain/rc4: The NTLM (RC4) trust key aka. the password hash of the trust account (check out Part 1 for more information).
/sids: Comma separated list of SIDs that you want to include in the PAC of the TGT that mimikatz is going to create for you
As you might have noticed I have not used mimikatz's "/ptt" parameter to inject the ticket into memory right away, that is because I wanted to have that ticket on disk (to analyze it) and serve it from disk to Rubeus to fetch a service ticket for a specific service in the next step.
Step 3: Ask for a service ticket
As we now have an Inter-Realm TGT, we can use that TGT to ask SafeAlliance.local for a service ticket. For this PoC I'll ask for a cifs ticket to access the C$-Share of the Domain Controller of SafeAlliance.local (which proves the compromise).
As we have not used mimikatz's /ptt flag in the command before, mimikatz wrote the generated TGT to disk as ticket.kirbi. We'll use this ticket with Rubeus now to request a service ticket for the cifs service.
C:> Rubeus.exe asktgs /service:cifs/PDC-SA.SafeAlliance.local /ticket:ticket.kirbi /dc:PDC-SA.SafeAlliance.local /ptt
Side note: As you might have guessed here the name of the DC of SafeAlliance.local is PDC-SA.SafeAlliance.local.
This time we'll set the /ptt flag and have our service ticket attached to our current session, which we can confirm with the klist
command.
Step 4: Profit
Now that the cifs ticket is attached to our session we can use that to access the C$-Share of SafeAlliance's DC:
Step 5: Can I have a Video, please?
Sure ;)
Step 6: Clarifying misconceptions
Does this privileges escalation work per default?
>> Yes.
Why though?
>> Because in a default ParentChild trust SID Filtering is disabled and only a few SIDs (those that are marked as \"Always\" filtered in section [4.1.2.2](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-pac/55fc19f2-55ba-4251-8a6a-103dd7c66280). of [MS-PAC]) are filtered. This enables us to add an extra SID (in this case the SID of the Enterprise Admins group) to the PAC of the Inter-Realm Kerberos TGT.
When requesting the cifs ticket the authentication authority (the KDC) does not filter out the Enterprise Admins (EA) SID and returns us a cifs service ticket with that powerful SID.
We present that service ticket to our targeted service (the cifs service of the targeted DC), which sees the EA SID and grants us access, as this SID is allowed to access the C$-Share.
Would that also work outside a ParentChild trust?
>> Absolutely.
The catch: Section [4.1.2.2](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-pac/55fc19f2-55ba-4251-8a6a-103dd7c66280). of [MS-PAC] has a SID category called \"ForestSpecific\" SIDs, all SIDs marked as \"ForestSpecifc\" are filtered out in trust relationships that cross a forest boundary, e.g. Forest Trusts or External Trusts. Hence the *-519 (EA) SID is filtered out due to that rule. Other powerful Well-known SIDs are also filtered out, which leaves us empty handed in default setups.
**However**: If you manage to find a SID that is not filtered out due to section [4.1.2.2](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-pac/55fc19f2-55ba-4251-8a6a-103dd7c66280). of [MS-PAC] (e.g. a SID of a regular user) and that grants access to resources in the target domain than this attack works just as shown above.
Would SID Filtering prevent this attack:
>> Yes.
Would SID Filtering prevent the other case where a regular user SID is used?
>> Yes.
Blue-Team Operations
The first step to defend an Active Directory Environment and find weak spots within trust relationships is to enumerate all existing trust relationships and analyze their characteristics.
Feel free to use Enum-ADTrust.ps1 to enumerate the existing trust and analyze the displayed characteristics based on part 1 of this sport light and the section "Evaluate Trust Characteristics".
Compare the characteristics and ensure especially that both ends of each trust relationship are configured the same.
Enable SID Filtering
Consider to set up strict SID Filtering in any trust relationship (even within a forest), if your environment does not require SIDs of the SID History to travel across a trust boundary.
To enable SID Filtering the following netdom command can be used on a DC:
netdom trust <TrustingDomain> /domain:<TrustedDomain> /Quarantine:Yes
## An example call I used in my Lab looks like this
netdom trust SafeAlliance.local /domain:Shield.SafeAlliance.local /Quarantine:Yes
Restrict TGT Delegation
Consider to restrict delegated TGT tickets to be sent over a trust boundary. Double check Part 1 of this spotlight, as well as section "Evaluate Trust Characteristics" to get some background information about TGT delegation, if needed.
You can restrict TGT Delegation with the following netdom command:
netdom trust <TrustingDomain> /domain:<TrustedDomain> /EnableTgtDelegation:No
## An example call I used in my Lab looks like this
netdom trust Asgard.local /domain:Shield.SafeAlliance.local /EnableTgtDelegation:Yes
Note: If you've quarantined all your trust relationship there is no need to disable TGT delegation as this is already disabled for quarantined domains.
Restrict the Authentication Level
Consider to use "Selective Authentication" for every trust relationship, that feels "maintainable" for Selective Authentication. The reason I'm stating it this way is due to the extra effort that needs to be conducted to not "break routines" or "lock out" valid users/operations.
To recap from part 1 of this spotlight:
Selective Authentication does not enforce denied (or granted) access, it specifies that a user must explicitly be Allowed-To-Authenticate or otherwise the Domain Controller will reject the access request right away.
Therefore users that need to authenticate across a trust boundary must explicitly be allowed to do that.
Selective authentication can be set with the following netdom command:
netdom trust <TrustingDomain> /domain:<TrustedDomain> /SelectiveAUTH:Yes
## An example call I used in my Lab looks like this
netdom trust SafeAlliance.local /domain:Shield.SafeAlliance.local /SelectiveAUTH:Yes
Detecting attacks
As attacks abusing weak trust relationships will likely use valid authentication and authorization operations there is no silver bullet to detect these operations. However, here's what could be done to enhance detection:
Event: 4769
Event ID 4769 contains information about requested Kerberos service tickets, therefore also the cifs service ticket created in the Red-Team operations section above is logged with this event, as shown below:
As said within the Red-Team operation section it should be noted that the client name of the service ticket can be arbitrarily chosen (if chosen "NotExisting" in the example above), so be aware that this client name might be faked by attackers.
Moreover, it should not be underestimated that there will be a massive amount of 4769 events in everyday environments, therefore this detection technique is more like searching a needle in a hay stack. However, if you have suitable log aggregation and filter mechanism in place you might want to try to find potentially suspicious needles in the Event ID 4796 hay stack. When approaching this, we recommend to start off with only a selected portion of servers to not flood your Log aggregation tooling.
If you're wondering about the Inter-Realm TGT that was forged to request the cifs service ticket that we spotted above and if that Inter-Realm TGT wouldn't be the logical choice to hunt down: It would, but this Inter-Realm TGT was forged manually (not requested from the KDC) and therefore there are no reliable logs of this activity. But speaking of the Inter-Realm TGT there is another options to hunt for...
Event: 4662
Event ID 4662 contains information about operations performed on AD objects. We can use this event ID with the control access right GUID for DS-Replication-Get-Changes-All {1131f6ad-9c07-11d1-f79f-00c04fc2dcd2}
to spot the DCSync attack that was uses in the Red-Team operations section to get the trust key, which was then used to forge the Inter-Realm TGT.
Note that this event must be captured on the Domain Controller of the domain where the attacker came from, as the attacker abused privileged access in this domain to take over the targeted domain. In the sample scenario from above this originating domain was the child domain Shield.SafeAlliance.local. The captured 4662 Event is shown below:
As much as filtering for the access GUID {1131f6ad-9c07-11d1-f79f-00c04fc2dcd2}
makes sense to enhance detection capabilities, it should be noted that we're not spotting the attack itself here, but only a certain attack mechanic. If the attacker had used local access to the child DC and extracted the trust key from the local machine then Event ID 4662 would show us any indication of the attack.
Review
By far the best recommendation I can give to learn more about this stuff is getting your own hands on the matter and make experience with it. To encourage people to do this I want to give a very brief wrap up of the steps to play around with trusts.
Start off by downloading a Windows Client and Server Version from Microsoft's Evaluation Center. Spin up two Server VMs, Install Active Directory Domain Services (AD DS) and promote the two servers to two individual Domain Controllers (DCs). Use the "Active Directory Domains And Trusts" GUI on the DC or the "trust" module of the netdom command line tool to create a trust between the two newly created forests.
netdom trust /?
The general syntax of the netdom trust command line as follows:
netdom trust <TrustingDomain> /domain:<TrustedDomain> /flag
The following table shows the which TDO trustAttributes flags are toggled by different netdom parameters:
Enumerate the created trust using Enum-ADTrust.ps1 with the "-Verbose" script or manually based on the "Enumerate Trust Characteristic" section.
Use the netdom trust flags as detailed above to change the trust characteristics.
Use mimikatz and Rubeus, as detailed in the "Red-Team Operations" section, to forge Inter-Realm TGTs and request service tickets from the other domain.
Save the generated Kerberos tickets (TGT & service tickets) to disk (or use mimikatz to write them to disk) and check out what's inside these tickets. To learn about the insights of a Kerberos ticket I've build a tool to read .kirbi (ccache) Tickets, based on impacket and Dirk-Jan's getftST.py tool.
krbTicketView.py will print out the contents of a given .kirbi or .ccache file, even if you don't know the key of the tickets inside the file:
If you do know the key it will print the encrypted contents:
The "Extra SIDs" field will be of special interest as this will be where you added SIDs will be contained (if they haven't been filtered).
Feel free to use that tool to understand what's inside the tickets that you forged using mimikatz or requested using Rubeus.
References & Further Reading
If you’re not done with AD Trusts yet and want to double check what you’ve just read, I can recommend a read through the following guides:
- https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc773178(v=ws.10)?redirectedfrom=MSDN
- http://www.harmj0y.net/blog/redteaming/a-guide-to-attacking-domain-trusts/
- http://www.harmj0y.net/blog/redteaming/not-a-security-boundary-breaking-forest-trusts/
- https://dirkjanm.io/active-directory-forest-trusts-part-one-how-does-sid-filtering-work/