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):

TrustPartner and TrustDirection

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 two different types of Authentication Levels:

  • Forest-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_ATTRIBUTE_WITHIN_FOREST (0x00000020) flag is set then Forest-Wide Authentication is used. If the TRUST_ATTRIBUTE_CROSS_ORGANIZATION (0x00000010) flag is set then Selective Authentication is used.

In any other case Forest-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:

Default SID Filtering

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:

Custom SID Filtering

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.

Enum-ADTrusts.ps1

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:

Enum-ADTrusts.ps with -IncludeReason

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:

Enum-ADTrusts.ps with -ShowGraphNotation

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>

graph-easy output

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:

Enum-ADTrusts.ps with -Verbose

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$

Mimikatz output

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<br> 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<br> 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

Rubeus output

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.

klist.exe output

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:

Ticket usage

Step 5: Can I have a Video, please?

Sure ;)

Exploitation Video

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:

Event ID 4769

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:

Event ID 4662

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:

netdom.exe to TDO trustAttributes

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:

krbTicketView.py

If you do know the key it will print the encrypted contents:

krbTicketView.py with key

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:

Carsten Sandker
Carsten is part of our Offensive Security Team, helping our clients identify vulnerabilities and preventing attacks. He is specialized in Active Directory security and scenario-based attacks.