Skip to content

AD-CHAIN-002: Coercion plus unconstrained delegation

medium high   Attack paths   MITRE ATT&CK T1187, T1558   Reads: derived from other findings

A non-DC host has unconstrained delegation and DC coercion is confirmed through the Print Spooler or assumed, so whoever controls that host can capture a DC's TGT and DCSync.

Also known as: printerbug-unconstrained

Why it matters

A host with unconstrained delegation keeps a copy of the TGT of every account that authenticates to it with Kerberos. The Print Spooler on a DC lets any domain user make the DC authenticate to a host of their choice (RpcRemoteFindFirstPrinterChangeNotification, the PrinterBug). The spooler is only one such route: PetitPotam (EFSRPC) and DFSCoerce (MS-DFSNM) do the same without it. Together:

  1. The attacker controls a server with unconstrained delegation (AD-DEL-001), or any account that can change such a host's DNS record or SPN.
  2. They coerce the DC into connecting to that server; the DC authenticates with Kerberos and its TGT lands in the server's memory.
  3. With the DC's TGT they run DCSync and get every hash in the domain, krbtgt included.

Across a forest trust that allows TGT delegation, the same trick hands over the other forest's DC as well.

This path is Kerberos, not NTLM relay. The policy Restrict NTLM: Outgoing NTLM traffic to remote servers (RestrictSendingNTLMTraffic) does nothing against it, so ad-doctor does not count it as a mitigation here. See AD-CHAIN-003 for where it does help.

How ad-doctor detects it

A compound finding. It fires when at least one enabled account other than a DC has TRUSTED_FOR_DELEGATION (0x80000) in userAccountControl (AD-DEL-001). Nothing ad-doctor can read proves coercion blocked, so a missing spooler does not silence it:

  • at least one DC opened the spoolss named pipe in the SMB probe, so the Print Spooler is running there (AD-INF-009): high, "confirmed coercion route";
  • otherwise, including when the probe did not run: medium, "coercion is assumed possible".

The objects are the unconstrained-delegation accounts, for example APP01$. The spooler probe is skipped with --no-probes, and probes every DC unless --single-dc is given.

ad-doctor does not trigger any coercion; it only opens the spooler pipe and closes it. It does not test EFSRPC or DFSNM.

Attributes and probes read
  • userAccountControl (TRUSTED_FOR_DELEGATION)
  • probe: spoolss pipe on each DC

Severity

High when a DC answered the spooler probe: the combination is a known one-step path from one compromised server to the whole domain. Medium when no spooler was confirmed: coercion is then assumed possible, not shown.

False positives and limits

  • The host is itself tier 0 and administered like a DC. The chain still turns any compromise of that host into a domain compromise, so fix it rather than accept it.
  • Turning the spooler off lowers this finding to medium and does not clear it: other coercion methods work without the spooler.

Fix

  1. Remove unconstrained delegation from every non-DC account: this is what closes the path. Use constrained or resource-based delegation for the services that need it:

    Get-ADComputer -Filter 'TrustedForDelegation -eq $true -and PrimaryGroupID -ne 516'
    Set-ADComputer APP01 -TrustedForDelegation $false -WhatIf
    
  2. Stop and disable the Print Spooler on every DC, which removes one route. DCs do not need it unless they are print servers, which they should not be:

    Get-Service -ComputerName dc01 -Name Spooler
    Invoke-Command -ComputerName dc01 { Stop-Service Spooler -WhatIf; Set-Service Spooler -StartupType Disabled -WhatIf }
    

Better, set System Services > Print Spooler = Disabled in the Default Domain Controllers Policy.

  1. Protect privileged accounts from delegation: add them to Protected Users or set Account is sensitive and cannot be delegated.

  2. On forest trusts, keep TGT delegation disabled (netdom trust ... /EnableTGTDelegation:No).

Verify

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

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

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

References

Microsoft and vendors

Research

Tools

MITRE ATT&CK

Available since 0.1.0.