ADTRUST-010: Trust Key Age and Rotation

Platform
Active Directory
Category
AD Trust Relationships
Severity
High
Zero Trust pillar
Identity (weight 3)
Golden fixtures
3
Branch coverage
Observed: fixtures prove the verdicts they exercise
Provenance
baseline

What it checks

For every trustedDomain object in CN=System,<domain DN> the check reads: - pwdLastSet (FILETIME of the last successful inter-realm key change; converted to a UTC timestamp and compared against now()) - trustAuthOutgoing and trustAuthIncoming (encoded LSAPR_AUTH_INFORMATION_INTERNAL blobs containing the current key, the previous key, and the kvno for each direction) - trustAttributes (to determine whether the trust is intra-forest, where rotation is automatic between writable DCs, or external/forest, where rotation requires Netlogon on both sides to be healthy) - trustDirection (1 inbound, 2 outbound, 3 bidirectional; inbound trusts are rotated by the trusting domain, outbound by the trusted partner, so a stale outbound key implies the partner has stopped rotating) - whenChanged on the trustedDomain object (a secondary indicator: if pwdLastSet is fresh but whenChanged is stale, only the kvno was bumped without the rest of the trust metadata being touched) The check then queries every reachable domain controller for the Netlogon policy that gates rotation: - HKLM\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters\DisablePasswordChange (DWORD; if 1, the DC will not initiate any machine or trust password change) - HKLM\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters\MaximumPasswordAge (DWORD in days; if set higher than 30 the rotation window stretches accordingly, and a value above 60 is a hard flag) - HKLM\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters\RefusePasswordChange (DWORD; if 1, the DC will not accept inbound password change requests, which silently breaks rotation on the partner side) The corresponding Group Policy settings under Computer Configuration > Policies > Windows Settings > Security Settings > Local Policies > Security Options ("Domain controller: Refuse machine account password changes" and "Domain member: Disable machine account password changes" and "Domain member: Maximum machine account password age") are also resolved via gpresult /R against the Domain Controllers OU so that an inherited policy override is surfaced even when the local registry has not yet refreshed. A finding is raised when any of the following are true: pwdLastSet on a trustedDomain object is older than 30 days; DisablePasswordChange = 1 on any DC; MaximumPasswordAge > 30 on any DC; RefusePasswordChange = 1 on any DC that holds a trust head; or the trustAuthOutgoing kvno has not incremented in the last two rotation windows.

Why it matters

The inter-realm trust key is the cross-forest equivalent of krbtgt. Any principal who can extract that key can forge inter-realm referral tickets (TGTs that the trusting domains KDC will accept on the strength of the shared key) for arbitrary users in the trusted domain, with arbitrary group SIDs in the PAC, for the full lifetime of the key. Mimikatz lsadump::trust /patch extracts the key directly from LSASS on any DC; DCSync against the trust account (TRUSTED_DOMAIN$ or DOMAIN$) extracts it remotely with only Replicating Directory Changes rights. Once extracted, the only thing that invalidates the key is rotation: every 30 days the legitimate Netlogon process bumps the kvno and the prior key is retired (one previous version is kept to handle in-flight tickets, then discarded on the next rotation). When rotation silently fails, that 30-day exposure window becomes open-ended. Real environments routinely have trust keys that are several years old: the trust was created during a merger, the partner forest was deprovisioned without removing the trust object, or a Group Policy from 2009 set MaximumPasswordAge to 999 and was never revisited. Benjamin Delpys "Golden Trust" technique (analogous to a Golden Ticket but signed with the trust key rather than krbtgt) becomes a permanent forest-spanning backdoor: the attacker recovers the key once, exfiltrates it, and uses it to mint TGTs against the trusting forest for as long as the kvno on the trustedDomain object remains unchanged. Detection is hard because the forged tickets look like legitimate cross-forest referrals to the trusting DC, with no Event 4625 or 4768 anomaly to flag. The blast radius depends on which other ADTRUST controls are present. Combined with SID filtering disabled (ADTRUST-002), the attacker can inject Enterprise Admin SIDs of the trusting forest into the forged ticket. Combined with TGT delegation enabled (ADTRUST-003), the attacker can chain to unconstrained delegation primitives. Even with both mitigations in place, the attacker still gets authentication as any user in the trusted domain into resources the trust authorizes, which is enough for lateral movement, data theft, and onward escalation.

Attack path

Step 1: Reconnaissance. The attacker, holding any authenticated foothold in the trusted forest, enumerates trusts with Get-ADTrust -Filter * or nltest /domain_trusts /all_trusts and identifies trusts with a stale pwdLastSet via Get-ADObject -Filter {ObjectClass -eq "trustedDomain"} -Properties pwdLastSet, trustPartner. Trusts older than 30 days are candidates: the longer the key has been live, the more time the attacker has to operate before forced rotation. Step 2: Trust key extraction. With Domain Admin (or SYSTEM on a DC), the attacker runs mimikatz lsadump::trust /patch on a domain controller. The command prints the current and previous inter-realm keys for every trust the DC participates in, in RC4-HMAC, AES128, and AES256 forms, along with the kvno. Alternatively, with Replicating Directory Changes (DCSync) rights, the attacker runs secretsdump.py -just-dc-user TRUSTED$ DOMAIN/user@DC and pulls the same material remotely without touching LSASS. Step 3: Key durability check. The attacker records the kvno and compares it to subsequent enumeration. If pwdLastSet has not changed in 30+ days and trustAuthOutgoing kvno has not incremented, the extracted key remains valid indefinitely. If MaximumPasswordAge is set high (visible via reg query \\DC\HKLM\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters or via Group Policy enumeration), the key is durable for the configured interval. Step 4: Golden Trust forgery. The attacker forges an inter-realm referral TGT signed with the recovered key. Mimikatz: kerberos::golden /domain:trusted.local /sid:<trusted-SID> /sids:<trusting-EA-SID> /rc4:<trustkey> /user:Administrator /service:krbtgt /target:trusting.local /ticket:trust.kirbi. Impacket: ticketer.py -nthash <trustkey> -domain-sid <trusted-SID> -domain trusted.local -extra-sid <trusting-EA-SID> -spn krbtgt/trusting.local Administrator. The resulting ticket is an inter-realm TGT (krbtgt/trusting.local@trusted.local) that the trusting forests KDC accepts as a legitimate referral. Step 5: Service ticket exchange. The attacker presents the forged inter-realm TGT to the trusting domains KDC (asktgs / Rubeus asktgs / Impacket getST) and requests a service ticket for any SPN in the trusting domain. The KDC, seeing a valid inter-realm signature, issues the service ticket with the requested user identity and (if SID filtering is disabled) the injected ExtraSids. Step 6: Persistence. Because the trust key is not rotated, the attacker can repeat steps 4 and 5 indefinitely. Even if the attackers original foothold in the trusted forest is detected and remediated, the recovered trust key continues to grant cross-forest authentication. The only effective remediation is to reset the trust on both sides (netdom trust ... /reset /passwordt:<new>), which bumps the kvno and invalidates the previously recovered material.

How Guerrilla assesses it

Guerrilla binds to each domain in (Get-ADForest).Domains and enumerates trustedDomain objects under CN=System with an LDAP filter of (objectClass=trustedDomain), requesting the attributes pwdLastSet, trustPartner, trustDirection, trustType, trustAttributes, trustAuthOutgoing, trustAuthIncoming, whenChanged, and msDS-TrustForestTrustInfo. pwdLastSet is converted from FILETIME to a UTC timestamp and the delta from now() is computed in days. Any value greater than 30 days raises a finding; values greater than 90 days are escalated to High severity. The trustAuthOutgoing blob is parsed for the kvno field and the count of historical keys: if only one key is present (no previous key), the trust has either never rotated or the previous key was discarded without a successful rotation in between. For the Netlogon registry side, the check iterates every domain controller returned by Get-ADDomainController -Filter * and reads HKLM\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters\DisablePasswordChange, MaximumPasswordAge, and RefusePasswordChange via remote registry (with a fallback to WinRM Invoke-Command Get-ItemProperty). Any DC reporting DisablePasswordChange = 1, MaximumPasswordAge > 30, or RefusePasswordChange = 1 is captured. The check also resolves the effective Group Policy on the Domain Controllers OU by inspecting GptTmpl.inf under SYSVOL for the corresponding RegistryValues entries and surfaces the originating GPO name so the operator knows what to edit. Finally, the check optionally calls netdom trust <trusting> /domain:<trusted> /verify against a sample of trusts to confirm the secure channel is healthy. A failing verify on a trust whose pwdLastSet is fresh indicates that rotation completed on the DS side but the secure channel between Netlogon services is broken, a state that will cause the next rotation to fail silently. Results are emitted as one row per trust with KeyAgeDays, KVNO, PreviousKeyPresent, DisablePasswordChangeAnywhere, MaxPasswordAgeMax, and SecureChannelHealthy columns.

Recommended value

Trust passwords rotated within the last 30 days. Automatic trust password rotation not disabled

Remediation

Check the trust password last set date by examining the trustAuthOutgoing attribute or running 'netdom trust /domain:trusted.domain /verify'. If the trust password is stale, reset it using 'netdom trust /domain:trusted.domain /Reset'. Verify that no GPO or registry setting has disabled automatic trust password rotation

Fixture-proven verdicts

Every verdict below is proven by a golden fixture in the module's gating test suite. This table derives from the last green run; it cannot be edited by hand.

Verdict scenarios for ADTRUST-010
ScenarioExpected verdict
cleanPASS
known-badWARN
throttledNot Assessed

Framework mappings

NIST SP 800-53
IA-5(1), SC-12
CIS AD Benchmark
3.2.4
MITRE ATT&CK
T1482, T1550.003