Skip to content

AD-INF-006: Trusts that let SIDs or TGTs through

high   Infrastructure   MITRE ATT&CK T1134.005, T1482   Reads: LDAP

A trust to another domain or forest lets SID history through or allows TGT delegation, so a compromise of the other side can be carried into this domain.

Also known as: sid-filtering

Why it matters

A trust is only as safe as the domain on the other side. Two settings decide whether an attacker who controls that domain can step into this one:

  • SID filtering. When this domain trusts another one, the domain controllers filter the SIDs in tickets coming from it. With filtering off on an external trust, or with SID history enabled on a forest trust, an attacker who owns the trusted domain forges a ticket whose SID history contains the SID of a privileged group in this domain and is treated as a member of it. Forest trusts with SID history still drop RIDs below 1000, so the targets there are custom admin groups and accounts with rights over tier 0.
  • TGT delegation across a forest trust. With delegation enabled, users of one forest who authenticate to a host with unconstrained delegation in the other forest leave their TGT there. An attacker who owns such a host in the trusted forest, and coerces a domain controller of this forest to authenticate to it (PrinterBug, PetitPotam), gets the DC's TGT and can DCSync this domain. This is why Microsoft disabled TGT delegation across forest trusts by default in 2019.

How ad-doctor detects it

ad-doctor reads the trustedDomain objects under CN=System and evaluates trustAttributes and trustDirection for every trust that leaves the forest (trusts with TRUST_ATTRIBUTE_WITHIN_FOREST, 0x20, are skipped):

Trust Reported when
External, outbound or two-way TRUST_ATTRIBUTE_QUARANTINED_DOMAIN (0x4) not set: SID filtering disabled
Forest, outbound or two-way TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL (0x40) set: SID history enabled
Forest, inbound or two-way TRUST_ATTRIBUTE_CROSS_ORGANIZATION_ENABLE_TGT_DELEGATION (0x800) set

Each object is the partner name with the reason, for example partner.example (external, SID filtering disabled).

Attributes and probes read
  • trustPartner
  • trustDirection
  • trustType
  • trustAttributes

Severity

Always high: each reported trust turns a compromise of the other domain or forest into a compromise of this one.

False positives and limits

  • Migrations in progress. SID history is sometimes enabled on purpose while users are moved from one forest to another. Accept the finding with the planned end of the migration as the expiry (accepted risks), and disable it when the migration is over.
  • Both sides under the same administrators. If the other domain is run by the same tier-0 team with the same controls, the trust adds less risk; it is still a path an auditor will want documented.

Fix

  1. Review every trust that leaves the forest.

    Get-ADTrust -Filter * | Select-Object Name, Direction, ForestTransitive,
        SIDFilteringQuarantined, SIDFilteringForestAware, TGTDelegation
    
  2. Remove trusts nothing uses any more. That is the strongest fix.

  3. Re-enable SID filtering. netdom trust has no dry-run mode; run each option without a value first, which only displays the current state:

    netdom trust corp.example /domain:partner.example /quarantine
    netdom trust corp.example /domain:partner.example /quarantine:yes
    netdom trust corp.example /domain:partner.example /enablesidhistory:no
    

Use /quarantine on external trusts and /enablesidhistory:no on forest trusts. 4. Disable TGT delegation across forest trusts:

```
netdom trust corp.example /domain:partner.example /enabletgtdelegation:no
```
  1. Remove unconstrained delegation on both sides of the trust (AD-DEL-001) and stop the spooler on DCs (AD-INF-009), which removes the coercion half of the TGT attack.

Verify

ad-doctor scan dc01.corp.example --ldaps -u 'CORP\auditor' --only AD-INF-006

To accept the risk instead, add an entry to the file passed with --accept (see accepted risks):

[[accept]]
check = "AD-INF-006"
objects = ["..."]        # optional; without it the whole check is accepted
reason = "..."
expires = 2027-03-31

References

Microsoft and vendors

Specifications

Research

MITRE ATT&CK

Available since 0.1.0.