Four misconfigurations, no CVE: the over-privileged service-account chain that takes a whole forest

  • active-directory
  • kerberos
  • privilege-escalation
  • forest-trust
  • detection

The domain compromises that teach the most are the ones with no CVE in them. No memory corruption, no unpatched service, nothing a patch cycle would have closed. Just four ordinary configuration choices that are each defensible on their own and catastrophic in sequence. Follow those four links and you go from one low-value foothold to Domain Admin of an entire forest, and the same chain crosses a forest trust into a second forest on the way. This is the capstone post for a small series: each link has its own deep-dive, and here you learn how they assemble, because the assembly is what you audit against.

What you will learn

  • How four independently reasonable settings combine into total forest compromise.
  • The order of the chain and the single prerequisite each link needs from the one before it.
  • Why attackers reach for configuration rather than exploits, and what that means for defense.
  • How to reproduce the whole chain in a lab by stitching together the component labs.
  • The one idea that makes defense tractable: you do not fix all four, you break any one on every DC.

Prerequisites and scope: you hold one credential in the first forest and can reach a domain controller in the target forest over a routed path. Every link is a misconfiguration or an over-broad permission, not a bug, so every link closes to a password policy, an ACL, or a group-membership review, not a patch.

The mental model: a chain, not a key

A lot of security teaching frames compromise as finding "the" vulnerability, a single key that opens the door. This chain is the opposite and it is more common in mature environments: no door is forced. Picture a series of rooms, each unlocked from inside the previous one. You are let into room one by a weak password. Room one holds the key to room two. Room two holds the key to room three. Room four is the vault. Nobody picked a lock; every door was opened with a key found in the last room, and every key was left there by a reasonable-sounding decision.

The defensive consequence of that shape is the most important thing in this post: you do not need to lock every door. A chain breaks at its weakest present link, so locking any one door strands the attacker in the previous room. That is why least privilege, not patching, defends against this class.

The chain in one picture

The four-link over-privilege chain A held identity cross-forest Kerberoasts a weak service account, that account reads a gMSA password, the gMSA is in Server Operators on the DC and carries SeBackupPrivilege, and that privilege extracts NTDS.dit. Each link hands a credential or privilege to the next. Held identity in CORP.LOCAL 1 cross-forest Kerberoast, crack offline svc_sql: admincount, SPN, weak password 2 ReadGMSAPassword reads the gMSA hash gmsa_svc$: managed NT hash, read from LDAP 3 gMSA sits in BUILTIN\Server Operators WinRM to DC, token holds SeBackupPrivilege 4 SeBackup: VSS + robocopy /B of NTDS.dit NTDS.dit: krbtgt + every hash = Domain Admin
Four links, no CVE. Each box is unlocked by the key found in the one above it. Break any single link (a managed password, a tight retrieval list, an empty Server Operators, or backup rights off the account) and the chain stops there.

Link 1: a reachable, kerberoastable, weak-password account

A bidirectional forest trust lets a principal in the first forest request service tickets for accounts in the second. That is the trust working as designed. The problem is what it reaches: a service account in the target forest with admincount=1, a Service Principal Name set, and a password a human chose years ago.

impacket-GetUserSPNs 'CORP.LOCAL/held_user:Password1' -target-domain VENDOR.LOCAL \
  -dc-ip 10.10.20.5 -request -outputfile roast.hashes
hashcat -m 13100 roast.hashes /usr/share/wordlists/rockyou.txt

Why it works, and the clock and resolution gotchas, are in cross-forest Kerberoasting over a trust (and the in-domain basics in Kerberoasting from Kali). The link exists because a privileged service account kept a crackable password. A managed password breaks this link by itself.

Link 2: ReadGMSAPassword, the delegation nobody audits

The account you just cracked holds ReadGMSAPassword over a Group Managed Service Account. A gMSA is supposed to remove the human-password problem: its password is 240 bytes of machine-managed randomness that rotates automatically. But the directory lets you nominate which principals may retrieve it, and here an ordinary (roastable) account was nominated. So you read the gMSA's current NT hash straight from LDAP, no cracking.

nxc ldap 10.10.20.5 -u svc_sql -p '<cracked>' --gmsa
#   -> gmsa_svc$  NT <nthash>

The mechanism and the msDS-ManagedPassword blob are in ReadGMSAPassword. The link exists because the retrieval list was scoped to a non-tier-0 account.

Link 3: the managed account is in a privileged group

A gMSA is only as dangerous as what it can do, and here it is a member of BUILTIN\Server Operators on the domain controller. That group can reconfigure services on a DC (which run as SYSTEM) and it carries SeBackupPrivilege. So the gMSA logs in over WinRM and its token already holds the privilege that ends the game.

nxc winrm 10.10.20.5 -u gmsa_svc$ -H <nthash>
#   -> (Pwn3d!)  token holds SeBackupPrivilege + SeRestorePrivilege

Why this mild-sounding group is tier-0, and both of its escalation routes, are in Server Operators to Domain Admin. The link exists because a service account was placed in a built-in privileged group instead of being granted one narrow right.

Link 4: the backup privilege reads the directory database

SeBackupPrivilege lets its holder read any file regardless of DACL. On a DC that file is NTDS.dit, which holds every hash including krbtgt. It is locked by the running directory service, so you snapshot the volume, then copy it in backup mode to bypass the lock and the ACL.

diskshadow -> shadow of C: -> expose as Z:
robocopy /B Z:\Windows\NTDS ntds.dit ; reg save HKLM\SYSTEM system.hive
impacket-secretsdump -ntds ntds.dit -system system.hive LOCAL
#   -> Administrator hash, krbtgt hash (+AES), every account in VENDOR.LOCAL

The exact sequence and why backup mode beats the lock are in SeBackupPrivilege on a domain controller. With krbtgt the forest is yours. The link exists because a backup-capable privilege reached a non-backup account.

The crossing that did not work (and why that is the lesson)

Faced with the trust, the instinct is to forge a golden ticket in the forest you own and inject the target forest's Enterprise Admins SID through SID history. It fails, and the failure is a control doing its job: forest-trust SID filtering strips injected SIDs with a RID below 1000, and every built-in privileged group sits below that line. A separate trap waits for anyone forging with the RC4 key on a patched DC. Both are in why your cross-forest golden ticket is not admin. The durable takeaway: the crossing that worked was a real credential the trust legitimately let us request, not a forged SID. Attackers reach for configuration because configuration is what is left unlocked.

Build it in a lab

The whole chain is the five component labs stitched together, so build it incrementally and watch each key appear in the next room:

  1. Two forests with a bidirectional trust, from the cross-forest Kerberoast lab.
  2. A roastable svc_sql in the target forest with a weak password; roast and crack it.
  3. A gMSA whose retrieval list contains svc_sql; read its hash per the ReadGMSAPassword lab.
  4. Add the gMSA to Server Operators; confirm whoami /priv shows SeBackupPrivilege per the Server Operators lab.
  5. Extract NTDS.dit per the SeBackup lab.

Then do the exercise that teaches the defense: remove any one link (rotate svc_sql to a managed password, or empty the gMSA retrieval list, or pull the gMSA out of Server Operators) and rerun. Watch the chain die at that step.

Detection, across the chain

No single alert catches this, which is the point. Watch the whole shape:

  • Link 1: event 4769 with RC4 (0x17) for many SPNs from one source, especially across a trust.
  • Link 2: LDAP reads of msDS-ManagedPassword (event 4662) by anything but the service's own hosts.
  • Link 3: membership of built-in privileged groups as a standing inventory, and changes to it (4728/4732/4756).
  • Link 4: shadow-copy creation and diskshadow/robocopy /B against NTDS.dit, and SeBackupPrivilege at logon (event 4672) for a non-backup account.

Feed your own directory to BloodHound and the entire chain renders as a path from svc_sql to the DC before an attacker walks it. The attacker's map and your audit are the same graph.

Prevention: break any one link

  • Managed passwords on service accounts, so Link 1 has nothing to crack.
  • Scope ReadGMSAPassword to tier-0 hosts only; audit the retrieval list like Domain Admins.
  • Empty the built-in privileged groups (Server Operators, Backup Operators, Account Operators).
  • Keep SeBackupPrivilege and SeRestorePrivilege off anything that is not your backup system, and treat that system as tier-0.
  • Keep forest-trust SID filtering on and consider selective authentication across the trust.

Each bullet closes one line of the diagram independently. You need any one of them on every DC, which is exactly why least privilege, not patching, defends against this class.

Check yourself

  1. Why is "break any one link" a stronger defensive frame than "fix every link"?
  2. Trace what each link hands to the next. What is the single thing Link 2 needs from Link 1?
  3. Why did the forged cross-forest golden ticket fail where the Kerberoast succeeded?
  4. A defender has budget to harden exactly one thing on every DC this quarter. Name a defensible choice and say which link it breaks.

Answers:

  1. Because a chain stops at its weakest present link, so one lock strands the attacker. Fixing all four is ideal but not required; one is sufficient per DC, which makes the problem tractable.
  2. Each link hands a credential or privilege the next consumes. Link 2 needs exactly the cracked password of svc_sql from Link 1, because ReadGMSAPassword is checked against that principal.
  3. SID filtering stripped the injected built-in SID (RID below 1000), so the ticket authenticated without privilege. The Kerberoast used a real, legitimately issued ticket for a weak account, which the trust was designed to hand over.
  4. For example, empty Server Operators and Backup Operators (breaks Link 3), or mandate managed passwords on service accounts (breaks Link 1). Either one alone stops this specific chain.

Use

Authorized and educational use only: your own lab, a directory you own, or an engagement with written permission. This is a defensive walkthrough of a documented chain, tools named rather than carried, written so the people who run Active Directory can find this shape in their own environment and break it. It is not a runbook for use against systems you do not own.

References

Back to all techniques