Guides · MAEDS 2026 handout

Active Directory Security

K-12 hardening playbook

Jim Tyler, K-12 Director of Technology · MAEDS 2026 · Updated 2026-10-17

A plain-language guide to locking down a K-12 Active Directory domain: admin accounts that cannot be stolen, servers that cannot be reached from a classroom PC, Group Policy that cannot be abused, and backups that actually run, with separate rules for domain controllers, servers and workstations.

Every item answers four questions: What to set, Why it matters, Where it lives (Group Policy path, PowerShell, or console), and Who catches it if you skip it. Rows are meant to be scanned during a presentation or used as a working checklist. The "Who catches it" tag is the point of this handout: a large share of the rows that matter most are tagged MANUAL, because no scanner reads them.

Use this page as a working checklist: tick the box on each item as you finish it. Progress is saved in your browser only. 134 items.

Scanner legend

TAGMEANING
PINGCASTLEPingCastle (free, single EXE, five-minute run) reports it. Rule names are given where stable; they drift between versions. Purple Knight covers most of the same ground.
GUERRILLAGuerrilla (free, CC BY 4.0, written by the author of this handout, PowerShell 7 module, agentless and read-only) reports it. Check IDs are given (ADPRIV-028, ADSCRIPT-004 and so on); 211 AD checks across privileged accounts, ACLs, Kerberos, AD CS, GPO contents, logon scripts, NTLM-relay preconditions, Tier 0 hygiene and logging, plus Entra ID and Google Workspace modules. Every verdict is PASS, FAIL, WARN or an explicit Not Assessed.
BLOODHOUNDBloodHound / SharpHound (or an equivalent Get-Acl sweep) shows it as an edge in the attack graph: ACLs, group nesting, sessions, delegation, and in the CE edition AD CS.
ADELEGADeleg (free, single EXE, GUI or CSV) lists every delegation in the directory that is not a Windows default, per container, and names the trustee and the right. Built for the "who was ever given what, and is it still needed" question; diff two exports to see what changed.
LOCKSMITHLocksmith (free PowerShell module) audits Active Directory Certificate Services for the ESC1 to ESC8 (and newer) template and CA misconfigurations and in Mode 4 offers to fix each issue after warning about its impact. Only relevant if a CA exists.
SCRIPTSENTRYScriptSentry (free PowerShell script, Invoke-ScriptSentry.ps1) reads every logon script, GPO script and NETLOGON file and reports hard-coded credentials, unsafe permissions on scripts, NETLOGON/SYSVOL and referenced shares, non-existent shares, and admins with logon scripts. GPP passwords are a PingCastle finding.
MANUALNo scanner reads it. It lives inside a GPO's user-rights section, on a member server's file system, in Task Scheduler, in a service's logon account, in a sync tool's config, in patch state, or outside AD entirely. You have to go look.
BUILT-INA Windows default or a Defender/Security Baseline item; your job is to confirm nobody turned it off.
PAIDNeeds a licensed product (Defender for Identity, Entra ID P1/P2, a SIEM). Listed so nobody budgets for it by accident. Everything else in this handout, every scanner above included, is free.

Quick rule: the scanners see AD objects, ACLs, GPO existence and permissions, SYSVOL scripts, and (Guerrilla) a good share of GPO contents and relay preconditions. None of them sees user-rights assignments as they land on a DC, which servers inherit which GPOs because of where they sit, stored task passwords, service logon accounts, NTFS ACLs on script folders on member servers, missing updates, or anything in Google. Phases 4, 6 and 8 are mostly MANUAL; run every scanner anyway, because the rows they do cover are the ones that are easiest to miss by hand.

What each scanner reads

TOOLRUN IT WITHREADSDOES NOT READ
PingCastlePingCastle.exe --healthcheck from any domain-joined PCAD objects, group membership, password ages, trusts, DC settings reachable over LDAP/SMB, GPO passwords and permissions, SYSVOL script ACLsGPO contents beyond a fixed list, member-server configuration, tasks, services, patch state
GuerrillaInstall-Module Guerrilla; AD assessment from a domain-joined PowerShell 7 session (catalog at guerrilla.army)Everything PingCastle reads plus GPO contents (Restricted Groups, scripts, scheduled tasks, audit, firewall, PowerShell logging, LAPS, AppLocker), logon-script contents, NTLM-relay preconditions, Tier 0 service accounts in privileged groups, AD CS ESC1 to ESC16, Entra sync account hygiene, shadow credentials, attack paths to Tier 0Effective (RSOP) user rights, member-server local groups and tasks, patch state, folders outside SYSVOL
BloodHoundSharpHound collector, BloodHound CE for the graphACL edges, nesting, sessions, local admin (with the right collection), delegation, AD CS (CE)Anything that is not an edge: settings, policies, patch state
ADelegadeleg.exe (GUI); check adeleg.exe --help for CSV exportEvery non-default ACE on every container and object, grouped by trusteeGroup membership, settings, GPO contents
LocksmithInvoke-Locksmith -Mode 1 (report), -Mode 4 (fix)Certificate templates, CA settings and ACLs, enrollment endpointsAnything outside AD CS
ScriptSentrydot-source the script, then Invoke-ScriptSentryLogon scripts (user attribute, GPO, NETLOGON), the shares they reference, their contentsScripts and folders on member servers that no logon references

Scoring trap: PingCastle's global score is the highest of its four category scores, not a sum. Fixing ten small items in one category does nothing to the score if another category still has a 50-point finding. Use the score as a lagging indicator; fix attack paths, not points.

Before you start

Confirm every change can land on every DC

Set torepadmin /replsummary and repadmin /showrepl clean on every DC; DFSR SYSVOL healthy (dfsrdiag replicationstate)
WhyEvery GPO, SYSVOL and ACL change below has to reach all DCs. Half-applied security is worse than none because you will believe it is done.
WherePowerShell on any DC
Who catches itPINGCASTLE GUERRILLA (ADDOM-007)

Take a full backup first

Set towbadmin start systemstatebackup on one DC to a share only DCs can write, plus Backup-GPO -All, plus an export of every admin group's membership and every GPO's links
WhySeveral items below edit the Default Domain Controllers Policy and SYSVOL permissions. The undo is a restore.
WhereRecipe R8
Who catches itPINGCASTLE (A-BackupMetadata flags a DC with no backup in 7 days)

Create a break-glass Domain Admin

Set toOne account, Domain Admins only, not in Protected Users and excluded from any Authentication Policy Silo, 30+ random characters stored offline (sealed envelope or vault), LogonWorkstations limited to the DCs and one admin host, an alert on any 4624/4768 for it
WhyPhase 1 removes NTLM and cached credentials from every real admin. If Kerberos, DNS or time breaks, this is the only account that can still sign in.
WhereADUC; alert in Phase 9
Who catches itMANUAL (a scanner will flag it as an admin not in Protected Users; that is intentional)

Turn on the AD Recycle Bin

Set toEnable-ADOptionalFeature 'Recycle Bin Feature' -Scope ForestOrConfigurationSet -Target <forest>; check tombstoneLifetime and msDS-DeletedObjectLifetime (blank means it uses tombstoneLifetime); if either is 60 (forests created before 2003 SP1), set it to 180
WhyPhase 7 deletes a lot of objects. With the Recycle Bin on, Restore-ADObject brings any of them back with attributes and group memberships for 180 days.
WherePowerShell on a DC (irreversible, but there is no reason to want it off)
Who catches itPINGCASTLE GUERRILLA (ADDOM-009)

Pick a pilot OU

Set toOne building or grade band for workstation changes; one non-critical member server for server changes
WhyEvery phase goes to pilot first. Several items in this handout caused district-wide prompts or lockouts when applied domain-wide in one step.
WhereADUC
Who catches itN/A

Run every free scanner once and keep the output

Set toPingCastle (PingCastle.exe --healthcheck --server <domain>, archive the .xml next to the HTML); Guerrilla (AD assessment from PowerShell 7, keep the JSON and HTML); SharpHound collection into BloodHound CE; adeleg.exe --csv baseline.csv; Invoke-ScriptSentry; Invoke-Locksmith -Mode 1 if a CA exists. All read-only; all from the admin host as a Domain Admin
WhySix baselines on day one. PingCastle's XML and Guerrilla's JSON hold the scores and verdicts for diffing between runs; ADeleg's CSV is the delegation inventory you diff every quarter; the BloodHound graph is where you confirm the paths the rest of this handout closes.
WhereAdmin host. Guerrilla's AD attack-detection files may be blocked by Defender heuristics on import; add a path exclusion for the module folder from an elevated prompt (the files are inert PowerShell)
Who catches itN/A

Phase 0: Inventory (what to read before changing anything)

One day of reading finds the paths that matter. Do all of these before Phase 1; Recipe R1 has the commands.

Effective user rights on every DC

Set toDump RSOP_UserPrivilegeRight on each DC and on one hypervisor, one server and one workstation. Any principal other than the built-in defaults on SeTakeOwnershipPrivilege, SeBackupPrivilege, SeRestorePrivilege, SeDebugPrivilege, SeTcbPrivilege, SeLoadDriverPrivilege, SeImpersonatePrivilege, SeRemoteShutdownPrivilege, SeEnableDelegationPrivilege or SeMachineAccountPrivilege is a finding
WhyA user right granted by an Enforced GPO at the domain root beats the Default Domain Controllers Policy and lands on DCs. A staff group holding "Take ownership" on a DC is a one-step path to Domain Admin, and no scanner computes RSOP.
WhereGet-CimInstance -Namespace root\rsop\computer RSOP_UserPrivilegeRight
Who catches itMANUAL (no scanner computes RSOP; Guerrilla ADGPO-017 reads Restricted Groups, not user rights)

Local Administrators on every server

Set toGet-LocalGroupMember Administrators on every enabled server; record which members arrive by Restricted Groups and which are direct
WhyHelpdesk or workstation-admin groups on a server that holds a sync credential, a backup, or ntds.dit copies is a path from any classroom PC to Tier 0.
WhereInvoke-Command to all servers (Recipe R1)
Who catches itBLOODHOUND (if SharpHound collected local groups) MANUAL

Scheduled tasks and services with a stored domain credential, on every server

Set toTasks with LogonType = Password and a domain UserId; services whose StartName is a domain user
WhyA stored password is recoverable by any local admin on that server. A task that is dead but still scheduled keeps trying the old password daily and looks like an attack in the DC logs.
WhereRecipe R1
Who catches itMANUAL (GUERRILLA ADTIER-002 to 005 flag backup, hypervisor, config-management and SQL service accounts that sit in privileged groups, not tasks)

Contents of CN=Computers and CN=Users

Set toList every object in both default containers with OS and last logon
WhyObjects there receive every domain-root GPO with no OU to block it, and no OU-linked policy of their own. Servers that were never moved out are the ones that quietly get the workstation GPOs.
WhereGet-ADComputer -SearchBase "CN=Computers,<domain DN>"
Who catches itGUERRILLA (ADTIER-006 for admin accounts outside a Tier 0 OU) MANUAL for the policy consequence

SYSVOL and NETLOGON ACLs and ownership, on each DC separately

Set toicacls \\<dc>\SYSVOL\<domain>\scripts and Get-Acl on Policies\* grouped by owner, run against each DC's UNC path
WhyA write ACE for Authenticated Users on NETLOGON, or ownership of Policies by a disabled account, is a path to code execution on every machine. Folder ACLs do not always replicate between DCs, so reading one DC proves nothing about the other.
WherePowerShell
Who catches itPINGCASTLE (P-DelegationLoginScript, P-DelegationGPOData) SCRIPTSENTRY GUERRILLA (ADSCRIPT-001/002/007, ADACL-011) MANUAL for the per-DC comparison

Patch level of DCs, hypervisors and Tier 0 servers

Set toBuild number plus UBR from HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion, compared to the current cumulative update; and the effective Windows Update policy on the server OUs
WhyAUOptions=3 (download and notify) on a server OU means updates wait for a click that never comes. Scanners do not check patch state at all.
WhereRecipe R1
Who catches itMANUAL (GUERRILLA ADDOM-005 and ADSTALE-006 catch obsolete OS versions only, not missing updates)

Who can DCSync

Set toACEs on the domain root for DS-Replication-Get-Changes and -All (GUIDs 1131f6aa-… and 1131f6ad-…). Expect Administrators, Domain Controllers, Enterprise Domain Controllers (plus Enterprise Read-only Domain Controllers on Get-Changes only), and exactly one Entra Connect sync account
WhyAny other principal can pull every password hash. Stale MSOL_* accounts left by a retired or reinstalled Entra Connect server are the usual extra.
WhereRecipe R13
Who catches itBLOODHOUND (DCSync edge) PINGCASTLE ADELEG GUERRILLA (ADPRIV-028, ADACL-012)

Where ntds.dit copies live

Set toFind every IFM, system-state, VM checkpoint and backup repository that contains a DC's disk; list who is local admin on each host. If an IFM job exists and nobody can say what it is for, that is the first copy to delete
WhyWhoever can read the file has every hash. Backup servers and hypervisors are Tier 0 whether or not they are treated that way. Fewer copies beats better ACLs.
WhereBackup console, Hyper-V/vSphere, file search for ntds.dit
Who catches itMANUAL

Plaintext credentials in script and export folders on automation servers

Set toSearch every scripts, tools and automation folder for CSV/TXT/LOG files whose header contains password; check the folder's NTFS ACL for inherited BUILTIN\Users read
WhyLegacy SIS-to-AD-to-Google sync scripts commonly dump their full input, passwords included, into a log folder on every run, for years, readable by every local user.
WhereRecipe R11
Who catches itMANUAL (SCRIPTSENTRY and GUERRILLA ADSCRIPT-004/006 cover logon scripts and SYSVOL/NETLOGON, not arbitrary folders on member servers)

Gate: inventory written down, backup taken, break-glass account tested from the admin host, pilot OU chosen.

Phase 1: Privileged accounts and tiering

Domain Admins = named human admin accounts only

Set toRemove every vendor, service, LDAP-bind, test, seasonal and "temporary" account; Enterprise Admins empty except when a forest-level change is in progress; Schema Admins empty
WhyEvery member can DCSync the krbtgt hash and forge Golden Tickets. Firewall LDAP-bind accounts, secretaries added "to fix a permission once", and accounts named after a season are the classic residents.
WhereADUC, or Get-ADGroupMember -Recursive for the nine groups in Recipe R1
Who catches itPINGCASTLE (P-AdminNum, P-SchemaAdmin, P-AdminPwdTooOld) BLOODHOUND GUERRILLA (ADPRIV-001 to 003, 024, 025)

Clear adminCount and re-enable inheritance after every removal

Set toSet-ADUser <user> -Clear adminCount, then $acl.SetAccessRuleProtection($false,$true) on the object
WhyRemoving an account from a protected group does not undo what AdminSDHolder did to it. The account keeps a protected ACL (no inheritance, no delegated helpdesk rights), and PingCastle keeps flagging it.
WhereRecipe R1 (last block)
Who catches itPINGCASTLE (A-AdminSDHolder) GUERRILLA (ADPRIV-020/021)

Operators and other legacy groups empty

Set toAccount Operators, Server Operators, Backup Operators, Print Operators, DnsAdmins, Group Policy Creator Owners, Cert Publishers, Key Admins: all empty, or Domain Admins only where something truly needs it
WhyEach one is a documented path to Domain Admin (Backup Operators can read ntds.dit; DnsAdmins can load a DLL on the DC; Group Policy Creator Owners can author a GPO that any later link turns into code execution).
WhereADUC
Who catches itPINGCASTLE BLOODHOUND GUERRILLA (ADPRIV-004 to 008, ADTRADE-008/009)

Separate admin accounts per person

Set to<user>-adm holds every admin group membership; the everyday <user> account is a plain user with mail and M365 only. The -adm account never signs in interactively to a workstation
WhyThe everyday account is the one typed on staff PCs, phished, and used in the browser. Whatever it can reach is what an attacker gets with one stolen session or one keylogger. This applies to the helpdesk technician first: their daily account is usually local admin on half the servers.
WhereADUC; Phase 1 deny-logon GPO enforces the "never on a workstation" half
Who catches itBLOODHOUND (shows the everyday account's sessions on workstations) MANUAL

Built-in Administrator (RID 500) as sealed break-glass only

Set toRemove it from every group it was added to over the years (teacher, office, department groups) but leave its default built-in memberships alone; Account is sensitive and cannot be delegated; 30+ character password rotated and stored offline; never used interactively; in a GPO on the DC OU and another on the server and workstation OUs (never the domain root), add <DOMAIN>\Administrator to Deny access to this computer from the network, Deny log on as a batch job, Deny log on as a service and Deny log on through Remote Desktop Services
WhyThe built-in Administrator cannot be locked out and is the account every attacker tries first. A domain-wide group membership on it means any share or GPO scoped to that group includes a never-expiring Domain Admin.
WhereADUC; DDCP user rights
Who catches itPINGCASTLE (S-PwdNeverExpires, P-AdminLogin) GUERRILLA (ADPRIV-010, 017) MANUAL for the group memberships

No "password never expires" on any human admin

Set toUnchecked on every Domain/Enterprise Admin; password age under one year
WhyA years-old password on a Domain Admin is the one in every old backup, old export and old breach dump.
WhereADUC
Who catches itPINGCASTLE (S-PwdNeverExpires, P-AdminPwdTooOld) GUERRILLA (ADPRIV-010, ADPRIV-017)

Every human admin in Protected Users

Set toDomain Admins, Enterprise Admins, Schema Admins and every -adm account: member of Protected Users. Not the break-glass account, not service accounts. Roll out one account at a time, a day apart (Recipe R3)
WhyMembers cannot use NTLM, DES or RC4, cannot be delegated, get no cached credentials, and hold a 4-hour non-renewable TGT. Pass-the-hash and NTLM relay against those accounts stop working. This is the single highest-value control in the handout and also the one with the longest gotcha list; read Recipe R3 and the Gotchas section first.
WhereADUC › Users › Protected Users
Who catches itPINGCASTLE (P-ProtectedUsers) GUERRILLA (ADPRIV-029/030)

Watch out: RDP, SMB and WMI by IP address stop working for these accounts (no SPN, so NTLM fallback, which is now refused). Connect by FQDN. See the Gotchas section.

Deny admin logon to workstations

Set toA GPO linked only at the workstation OU: Deny log on locally, Deny log on through Remote Desktop Services, Deny log on as a batch job, Deny log on as a service = Domain Admins, Enterprise Admins (and the -adm accounts' group). Network logon is not denied (Recipe R4)
WhyGuarantees a Tier 0 credential is never typed on a classroom PC, which is where keyloggers and LSASS dumps happen. Leaving network logon alone keeps remote management (WMI, WinRM, SMB) working from the admin host.
WhereComputer Configuration › Policies › Windows Settings › Security Settings › Local Policies › User Rights Assignment
Who catches itPINGCASTLE (P-LogonDenied) GUERRILLA (ADPRIV-026/027 cover the DC side)

Watch out: Never link it to a server or DC OU. Tell the admins first; the first one to RDP to a staff PC afterwards will think the domain is broken.

Local Administrators on Tier 0 = Domain Admins only

Set toDCs (implicit), hypervisors, the backup server, the Entra Connect server, any server holding ntds.dit copies, the print server if drivers come from it: local Administrators = Domain Admins plus the one service account that must be there. No helpdesk group, no "WorkstationAdmins", no legacy automation account
WhyA local admin on any of these can read every hash, decrypt the sync credential, or push a driver to every PC. This is where the "helpdesk → Entra Connect → DCSync" path is cut.
WhereRestricted Groups in a Tier 0 GPO, or by hand; verify with Get-LocalGroupMember
Who catches itBLOODHOUND (AdminTo edges, if collected) GUERRILLA (ADTIER-002/003 for backup and hypervisor accounts in privileged groups) MANUAL

Helpdesk rights delegated, not granted

Set toFour scoped groups with explicit AD ACEs: password reset (ResetPassword + write pwdLastSet/lockoutTime on user objects under the staff OU), user creation (CreateChild user + GenericAll on descendant users in the staff OU), computer management (CreateChild/DeleteChild computer + GenericAll on descendant computers in the workstation OU), group membership (write member on the operational groups' containers). Explicit Deny of all four on the IT/service-account OU and on infrastructure groups (Recipe R12)
WhyHelpdesk does its job without Account Operators, Domain Admins, or local admin on servers. The deny on the IT OU stops a helpdesk account resetting an admin's password.
WhereADUC › Delegate Control, or Set-Acl on an AD: drive
Who catches itBLOODHOUND ADELEG (shows every non-default delegation per container; confirm each one is intended) GUERRILLA (ADACL-008)

Watch out: A technician whose RDP session predates the group will report "still can't reset passwords". The token is stale. Full sign-out, not disconnect/reconnect.

Admin work only from an admin host

Set toDomain Admin credentials are used from DCs, a dedicated admin host or PAW, and nowhere else. Never from a personal laptop, never from a non-domain device, never over RDP from a workstation
WhyEvery host an admin password touches is a place it can be dumped. DC 4768 events show exactly which hosts those are (Recipe R13).
WherePolicy, enforced by the deny-logon GPO and (later) an Authentication Policy Silo
Who catches itBLOODHOUND (HasSession) GUERRILLA (ADPRIV-026/027) MANUAL (4768 source review)

Authentication Policy Silo for Domain Admins (advanced, do last)

Set toOne Authentication Policy: UserAllowedToAuthenticateFrom = DCs plus the admin host, UserTGTLifetimeMins = 240; one Silo containing the admin accounts and those computers. Create with -Enforce:$false (audit mode) and read Microsoft-Windows-Authentication/AuthenticationPolicyFailures-DomainController for 2 to 4 weeks before enforcing
WhyProtected Users stops NTLM; a Silo stops the Kerberos ticket itself from being issued anywhere but the admin host. Requires 2012 R2 domain functional level, Kerberos armoring on the DCs (Phase 2), and Kerberos client support for claims, compound authentication and Kerberos armoring = Enabled on the admin host.
WhereActive Directory Administrative Center › Authentication
Who catches itMANUAL

Watch out: The break-glass account must be outside the Silo and tested before enforcement. An enforced Silo with a wrong computer list locks every admin out at once.

Rotate krbtgt twice

Set toReset the krbtgt password, wait longer than the maximum TGT lifetime (10 hours default) plus replication, reset again; repeat annually
WhyAny Golden Ticket forged from an old krbtgt hash stops working after the second reset. One reset is not enough: the DC accepts tickets signed with the previous password.
WhereMicrosoft's New-KrbtgtKeys.ps1, or ADUC on the PDC emulator
Who catches itPINGCASTLE (A-Krbtgt) GUERRILLA (ADPRIV-022/023)

No shadow credentials or stray key material on privileged principals

Set tomsDS-KeyCredentialLink empty on every admin account, DC and Tier 0 computer object unless Windows Hello for Business or a certificate-based logon is deliberately in use; Key Admins and Enterprise Key Admins empty
WhyA write to that attribute lets an attacker authenticate as the object with a certificate they generated, without knowing or changing its password. It survives password resets.
WhereGet-ADObject -Filter {msDS-KeyCredentialLink -like '*'} -Properties msDS-KeyCredentialLink; ADUC for the two groups
Who catches itGUERRILLA (ADTRADE-006, ADTRADE-008) BLOODHOUND (AddKeyCredentialLink edge)

Gate: admin group membership matches the written list; every removed account has adminCount cleared; break-glass signs in from the admin host; deny-logon GPO verified present on a workstation and absent on a DC.

Phase 2: Authentication protocols

NTLMv2 only, LM hash never stored

Set toNetwork security: LAN Manager authentication level = Send NTLMv2 response only. Refuse LM & NTLM (LmCompatibilityLevel=5) in the Default Domain Controllers Policy first, then the domain; Do not store LAN Manager hash value on next password change = Enabled
WhyNTLMv1 and LM responses are crackable offline in minutes from a single captured handshake.
WhereComputer Configuration › Policies › Windows Settings › Security Settings › Local Policies › Security Options
Who catches itPINGCASTLE (S-OldNtlm, A-LMHashAuthorized) GUERRILLA (ADDOM-016/017)

Watch out: Audit 4624 events whose LmPackageName is NTLM V1 for two weeks first. Multifunction copiers doing scan-to-folder and old NAS units are the usual NTLMv1 holdouts. Do not test with a Protected Users account; it fails regardless.

NTLM minimum session security

Set toMinimum session security for NTLM SSP based clients and … servers = Require NTLMv2 session security + Require 128-bit encryption (0x20080000)
WhyWithout it, an NTLMv2 session can still negotiate down to weak session keys that relay tools exploit.
WhereSame Security Options node
Who catches itMANUAL

NTLM auditing, then restriction

Set toRestrict NTLM: Audit NTLM authentication in this domain = Enable all, and Audit Incoming NTLM Traffic = Enable auditing for all accounts, for 30 days; read Microsoft-Windows-NTLM/Operational (events 8001-8004) on the DCs; then Restrict NTLM: NTLM authentication in this domain = Deny for domain accounts to domain servers, with exceptions listed by server name
WhyProtected Users only covers the admins. Restricting NTLM domain-wide closes relay for everyone, but only after you know which copier, NAS, legacy app and by-IP management tool still depends on it.
WhereSame Security Options node; exceptions in Add server exceptions in this domain
Who catches itMANUAL

Watch out: Every tool that connects by IP address uses NTLM. See the Gotchas section.

LDAP signing required, channel binding enforced

Set toDomain controller: LDAP server signing requirements = Require signing; LDAP server channel binding token requirements = When supported first (LdapEnforceChannelBinding=1), then Always (=2) after the audit is clean
WhyStops LDAP relay (an attacker relaying a captured machine authentication to LDAP to add a computer account or change an ACL).
WhereDDCP › Security Options; audit events 2889 (unsigned binds) and 3039/3040/3041 (CBT) in Directory Service log after setting 16 LDAP Interface Events diagnostics to 2
Who catches itPINGCASTLE GUERRILLA (ADDOM-013/014, ADNET-001/002)

Watch out: Firewalls, VPN appliances, wireless controllers and copiers doing LDAP user lookup must be moved to LDAPS (636) or StartTLS first. Each 2889 event names one of them. LDAPS satisfies signing, but under channel binding = Always an LDAPS client that does not send a channel-binding token fails; watch event 3039 before moving to Always.

SMB signing required everywhere, SMB1 removed

Set toMicrosoft network server: Digitally sign communications (always) = Enabled and Microsoft network client: … (always) = Enabled on DCs, servers and workstations; Disable-WindowsOptionalFeature -Online -FeatureName SMB1Protocol / Remove-WindowsFeature FS-SMB1
WhyDCs usually already require signing; member servers and hypervisors usually do not, and an unsigned-SMB hypervisor is the relay target that yields a DC's disk. Windows 11 24H2 Pro/Education/Enterprise require signing both ways by default; Server 2025 requires it outbound only, so member servers still need the server-side GPO. Required signing also disables guest share access, which breaks some copier scan-to-folder setups.
WhereSecurity Options; Server Manager / DISM
Who catches itPINGCASTLE (S-SMB-v1; signing on DCs) GUERRILLA (ADDOM-015, ADNET-003/004, domain policy) MANUAL for individual member servers

Watch out: Old copiers and scanners that write to SMB shares may only speak SMB1. Replace the destination share with an SFTP or cloud drop, or isolate them.

Netlogon secure channel signed and sealed

Set toDomain member: Digitally encrypt or sign secure channel data (always) = Enabled; Domain controller: Allow vulnerable Netlogon secure channel connections not configured (enforcement is the default since 2021)
WhyZerologon-class attacks.
WhereSecurity Options
Who catches itBUILT-IN (confirm)

Kerberos AES only

Set toNetwork security: Configure encryption types allowed for Kerberos = AES128, AES256, Future; set msDS-SupportedEncryptionTypes to AES on every service account with an SPN; reset the krbtgt and service account passwords once after the change so AES keys exist
WhyRC4 tickets are what Kerberoasting cracks. A 4769 with encryption type 0x17 is a roastable ticket. Since the April and July 2026 updates (CVE-2026-20833), DCs default to AES-only for accounts without an explicit msDS-SupportedEncryptionTypes, so RC4 is now opt-in per account; check System log Kdcsvc events 201-209 (203/204/208/209 = blocked) as well as 4769 0x17. A legacy device that still needs RC4 needs an explicit per-account exception.
WhereSecurity Options; Set-ADUser -KerberosEncryptionType AES128,AES256
Who catches itPINGCASTLE (S-DesEnabled; RC4 rules) GUERRILLA (ADKERB-009, ADPRIV-014) MANUAL (watch 4769 0x17 for a month before removing RC4; old Macs, Linux boxes and appliances may need it)

No Kerberos delegation

Set toZero objects with unconstrained delegation (TrustedForDelegation) other than DCs; constrained and resource-based delegation only where a documented app needs it; every admin account flagged Account is sensitive and cannot be delegated
WhyA server with unconstrained delegation that an attacker controls collects the TGT of every user and computer that connects to it, DCs included.
WhereGet-ADComputer -Filter {TrustedForDelegation -eq $true}; Get-ADObject -LDAPFilter '(msDS-AllowedToActOnBehalfOfOtherIdentity=*)'; ADUC account options
Who catches itPINGCASTLE (P-UnconstrainedDelegation, P-Delegated) BLOODHOUND GUERRILLA (ADKERB-004 to 008)

No SPNs on user accounts; gMSA where a service needs one

Set toGet-ADUser -Filter {ServicePrincipalName -like '*'} returns only accounts with 30+ character random passwords or nothing; prefer a group Managed Service Account for anything that needs an SPN
WhyAny domain user can request a service ticket for any SPN and crack it offline (Kerberoasting). A gMSA's password is 240 bytes, random, and rotates itself (every 30 days by default).
WhereNew-ADServiceAccount; Set-ADUser -ServicePrincipalNames
Who catches itPINGCASTLE (P-Kerberoasting) BLOODHOUND GUERRILLA (ADKERB-001/002, ADTRADE-010)

No accounts with pre-authentication disabled

Set toGet-ADUser -Filter {DoesNotRequirePreAuth -eq $true} returns nothing
WhyAS-REP roasting needs no credentials at all.
WhereADUC account options
Who catches itPINGCASTLE BLOODHOUND GUERRILLA (ADKERB-003, ADPRIV-012)

LLMNR, NetBIOS name service and mDNS off; WPAD blocked

Set toTurn off multicast name resolution = Enabled (Computer Configuration › Administrative Templates › Network › DNS Client); NetBIOS over TCP/IP disabled via DHCP option 001 Microsoft Disable NetBIOS = 2 or per-NIC; DNS Global Query Block List keeps wpad and isatap (default)
WhyResponder-style poisoning of these broadcasts is how an attacker on the student Wi-Fi gets the first NTLM hash.
WhereGPO; DHCP console; dnscmd /info /globalqueryblocklist
Who catches itGUERRILLA (ADNET-005 to 008) PINGCASTLE (A-NoGPOLLMNR)

Nobody can add computers to the domain by default

Set toSet-ADDomain -Identity (Get-ADDomain) -Replace @{'ms-DS-MachineAccountQuota'=0}; DDCP Add workstations to domain = Administrators (and the helpdesk computer-management group via delegation, Recipe R12)
WhyThe default lets every user create 10 computer objects, which is the starting point for resource-based delegation and relay attacks.
WherePowerShell; DDCP User Rights Assignment
Who catches itPINGCASTLE BLOODHOUND GUERRILLA (ADACL-009)

Watch out: Delegated CreateChild on the computer OU is not subject to the quota, so the helpdesk group keeps working.

Anonymous and Pre-Windows 2000 access closed

Set toNetwork access: Do not allow anonymous enumeration of SAM accounts and shares = Enabled; remove Authenticated Users (and Anonymous Logon, Everyone) from Pre-Windows 2000 Compatible Access after confirming no legacy app enumerates the directory anonymously; SeNetworkLogonRight on DCs = Administrators, Authenticated Users, Enterprise Domain Controllers (not Everyone)
WhyLets unauthenticated devices on the network list users and groups for password spraying.
WhereSecurity Options; ADUC Builtin container; DDCP User Rights
Who catches itPINGCASTLE (A-PreWin2000AuthenticatedUsers, A-PreWin2000Anonymous) GUERRILLA (ADDOM-018)

Watch out: Some older NAS, RADIUS and LDAP-browsing appliances rely on Pre-Win2000 membership. Pilot by removing it for a week and watching for failures.

Kerberos armoring (FAST) and claims

Set toDDCP: KDC support for claims, compound authentication and Kerberos armoring = Supported (not "Always provide claims" or "Fail unarmored authentication requests"); Default Domain Policy: Kerberos client support for claims, compound authentication and Kerberos armoring = Enabled. Leave "fail unarmored requests" off until non-Windows clients (Macs, copiers, LDAP-bind appliances) are confirmed working
WhyPrerequisite for Authentication Policy Silos; also protects the AS exchange from offline password guessing against the TGT.
WhereComputer Configuration › Administrative Templates › System › KDC and › Kerberos
Who catches itMANUAL

Certificate Services: every ESC path closed (only if AD CS exists)

Set toNo template that combines client-authentication EKU with Enrollee supplies subject and enrollment open to broad groups (ESC1); no Any Purpose or empty EKU templates (ESC2); enrollment-agent templates restricted (ESC3); template and CA object ACLs writable by admins only (ESC4, ESC5, ESC7); EDITF_ATTRIBUTESUBJECTALTNAME2 off on the CA (ESC6); web enrollment and CES/CEP endpoints removed, or HTTPS with Extended Protection only (ESC8); IF_ENFORCEENCRYPTICERTREQUEST set on the CA (certutil -setreg CA\InterfaceFlags +IF_ENFORCEENCRYPTICERTREQUEST) (ESC11); CA auditing on; Manager approval on any template that must stay loose
WhyA misconfigured template lets any domain user request a certificate as any user, Domain Admins included, and the certificate keeps working after password resets. It is the most common Domain Admin path in domains that have a CA, and the one least likely to be noticed because the CA "just works."
WhereInvoke-Locksmith -Mode 1 (report) and -Mode 4 (interactive fix, with backups); Certificate Templates console; certutil -getreg policy\EditFlags on the CA
Who catches itLOCKSMITH (ESC1 to ESC8 plus ESC11/13/15 in current builds) GUERRILLA (ADCS-001 to 019, ESC1 to ESC16 and EKUwu) BLOODHOUND (CE edition ADCS edges) PINGCASTLE (partial)

Watch out: If there is no CA, every tool reports nothing and this row is done. Do not stand up a CA "to modernize" without reading the ESC list first; a default-template CA is born vulnerable.

Gate: 30 days of NTLM and LDAP audit reviewed and every legacy source named; copiers and appliances moved to LDAPS/NTLMv2 or isolated; no 4769 with 0x17 from a Windows client.

Phase 3: Group Policy and SYSVOL

No user right in any GPO beyond Windows defaults, especially in Enforced GPOs

Set toEvery [Privilege Rights] line in every GptTmpl.inf reviewed; SeTakeOwnershipPrivilege, SeBackupPrivilege, SeRestorePrivilege, SeDebugPrivilege, SeLoadDriverPrivilege, SeRemoteShutdownPrivilege = *S-1-5-32-544 (Administrators) only, or not set (the default DC grants to Backup/Server/Print Operators are acceptable only if those groups are empty, per Phase 1); SeImpersonatePrivilege = Administrators, LOCAL SERVICE, NETWORK SERVICE, SERVICE (the default; do not trim it); SeTcbPrivilege empty; SeEnableDelegationPrivilege empty on non-DCs
WhyOne line granting a staff group "Take ownership" in an Enforced root GPO reaches every DC and gives every member a trivial path to Domain Admin. Scanners do not parse this file.
Where\\<domain>\SYSVOL\<domain>\Policies\{GUID}\Machine\Microsoft\Windows NT\SecEdit\GptTmpl.inf, or the GPO editor › User Rights Assignment; verify with RSOP (Phase 0)
Who catches itMANUAL

Watch out: GptTmpl.inf is UTF-16LE. If you edit it directly instead of through the editor, bump the version (Recipe R2) or clients ignore the change.

NETLOGON and SYSVOL: Authenticated Users read-only

Set toicacls \\<dc>\SYSVOL\<domain>\scripts /grant:r "Authenticated Users:(OI)(CI) (RX)" after /save -ing a backup; same for Policies. Apply and verify on each DC
WhyAnything run from NETLOGON at logon (BGInfo, mapping scripts, inventory agents) runs as every user, admins included. A write ACE for Authenticated Users there is code execution on every machine.
WhereRecipe R2
Who catches itPINGCASTLE (P-DelegationLoginScript, P-DelegationGPOData) SCRIPTSENTRY GUERRILLA (ADSCRIPT-001/002/007)

Watch out: ACL changes replicate through DFSR, but a stalled or legacy-FRS SYSVOL will not carry them, and the replicated folder's root ACL is not replicated. Read the ACL back on every DC; apply per-DC if needed.

SYSVOL owned by Administrators, not by a former admin

Set toOwner of Policies, scripts and every object beneath = BUILTIN\Administrators on each DC; remove explicit ACEs for disabled or departed accounts
WhyAn owner can rewrite the ACL regardless of what the ACL says. A disabled account that owns every GPO folder becomes Domain Admin the day it is re-enabled.
WhereGet-ChildItem … \| Get-Acl \| Group-Object Owner; icacls /setowner
Who catches itGUERRILLA (ADACL-011) MANUAL

GPO edit rights = Domain Admins and the GPO's creator only

Set toReview each GPO's delegation tab; vendor accounts, helpdesk and "Authenticated Users: Edit" removed; Group Policy Creator Owners empty (Phase 1)
WhyEdit on a GPO linked to any OU is code execution on every machine in that OU at next refresh.
WhereGPMC › GPO › Delegation; Get-GPPermission -All
Who catches itPINGCASTLE (P-DelegationGPOData) BLOODHOUND (GenericWrite on GPO) ADELEG GUERRILLA (ADACL-013/014, ADGPO-007)

No passwords in Group Policy Preferences

Set tofindstr /S /I cpassword \\<domain>\SYSVOL\<domain>\Policies\*.xml returns nothing; any hit means the item is deleted and the account's password reset
WhyThe GPP encryption key has been public since 2012. Every domain user can read SYSVOL.
WhereSYSVOL
Who catches itPINGCASTLE (A-PwdGPO) SCRIPTSENTRY GUERRILLA (ADGPO-012, ADTRADE-001)

Logon scripts: no credentials, no writable or non-DC shares

Set toEvery script referenced by a user's scriptPath, by a GPO, or sitting in NETLOGON reviewed: no hard-coded usernames or passwords, no net use with a password, no calls to shares on member servers that are writable by Authenticated Users, no references to shares or hosts that no longer exist; unused scripts deleted
WhyA logon script runs as every user who gets it, including admins. A script that maps a drive with an embedded service password leaks it to every user; one that runs an executable from a writable share on a file server runs whatever a student put there; one that points at a hostname nobody owns can be claimed.
WhereInvoke-ScriptSentry (reports each class of problem with the file and the offending line); Get-ADUser -Filter {scriptPath -like '*'} -Properties scriptPath
Who catches itSCRIPTSENTRY GUERRILLA (ADSCRIPT-003 to 010) PINGCASTLE (writable-script cases only)

Disabled and "old" GPOs deleted, not kept

Set toAny GPO with both halves disabled, or named "old", "test", "copy of", reviewed and deleted (back up first with Backup-GPO)
WhyThey still exist in SYSVOL with their settings (cleartext-password-allowed, minimum length 3, legacy SIDs with batch logon rights) and are one re-enable away from applying.
WhereGPMC
Who catches itGUERRILLA (ADGPO-002 to 004) MANUAL

Hardened UNC paths for SYSVOL and NETLOGON

Set toComputer Configuration › Administrative Templates › Network › Network Provider › Hardened UNC Paths: \\*\SYSVOL and \\*\NETLOGON = RequireMutualAuthentication=1, RequireIntegrity=1
WhyPrevents a spoofed DC from serving policy to a workstation.
WhereGPO linked at the domain root
Who catches itBUILT-IN (default since 2015; confirm it was not undone)

Default Domain Controllers Policy left close to default

Set toNon-default user rights removed: SeTcbPrivilege for Backup Operators, batch/service logon for the built-in Administrator; SeNetworkLogonRight hardened from its default (which includes Everyone) to Administrators, Authenticated Users, Enterprise Domain Controllers; SeMachineAccountPrivilege empty or Administrators
WhyThe DDCP is where ten years of "fix the backup job" edits accumulate. Each is a small step an attacker chains.
WhereGPMC › Default Domain Controllers Policy › User Rights Assignment
Who catches itMANUAL

GPO backup weekly

Set toBackup-GPO -All -Path <backup share> plus Get-GPOReport -All -ReportType Html in the AD backup job (Recipe R8)
WhyRestoring one GPO is a two-minute operation if you have the backup and a rebuild if you do not.
WhereRecipe R8
Who catches itN/A

Gate: RSOP on both DCs shows default user rights; SYSVOL ACL and ownership identical on every DC; no cpassword hits; every Enforced link documented.

Phase 4: Tier 0 servers, hypervisors and backups

Tier 0 is anything that can yield a DC: the DCs, the hypervisors that run them, the backup server and its repository, the Entra Connect server, any server holding ntds.dit copies, and (for driver reasons, Phase 5) the print server. Treat all of them like DCs.

Servers install updates on a schedule, not on a click

Set toConfigure Automatic Updates = 4 - Auto download and schedule the install, scheduled day/time and Always automatically restart at the scheduled time on the server and DC OUs; or servers managed by the endpoint tool with a monthly ring
WhyOption 3 (download and notify) on a server OU means nobody is ever notified. DCs and hypervisors fall a year or two behind while workstations stay current, and public exploit code for Netlogon, Kerberos, LDAP and Hyper-V bugs is live against Tier 0.
WhereComputer Configuration › Administrative Templates › Windows Components › Windows Update › Manage end user experience
Who catches itMANUAL (compare build and UBR monthly; Recipe R1)

Watch out: Patch one hypervisor at a time after confirming the other DC is healthy; verify repadmin /showrepl after each DC reboot. Guest clocks after a cold boot can be hours off until w32time corrects them, so events right after a reboot are mistimed.

Every server in a server OU; nothing in CN=Computers

Set toredircmp "OU=New Joins - Staging,<domain DN>" (run as Domain Admin from any domain-joined machine; it writes to the PDC emulator, which must be online; the OU must already exist); every existing server moved into OU=Servers (block inheritance there) or OU=Tier0,OU=Servers; staging OU emptied weekly
WhyObjects in CN=Computers receive every domain-root GPO, including workstation Restricted Groups, shutdown tasks, and Point-and-Print settings. Servers that have sat there for years are the ones the helpdesk is local admin on.
Whereredircmp; ADUC
Who catches itPINGCASTLE (counts CN=Computers objects) GUERRILLA (ADTIER-006) MANUAL for the consequence

Watch out: "Unwilling To Perform" from redircmp means the domain functional level is below 2003 or the target DN is wrong (OU= vs CN=); "No Such Object" means the OU doesn't exist. Blocking inheritance on OU=Servers also cuts off domain-linked baselines and update policy unless those links are Enforced. Moving a server changes which GPOs it gets (update policy, baseline, loss of workstation GPOs); review the destination OU's links first. The helpdesk may lose RDP to a server they used for ADUC; add them to local Remote Desktop Users there, not Administrators.

Hypervisors as Tier 0

Set toLocal Administrators = Domain Admins (+ the backup service account); SMB signing required; Print Spooler disabled; no RDP from workstations; patched on the same cadence as DCs; VM configuration and VHDX folders not shared; management NIC on an admin VLAN
WhyHypervisor admin = mount the DC's VHDX offline = ntds.dit + SYSTEM hive = every hash.
WhereHyper-V / vSphere host configuration
Who catches itGUERRILLA (ADTIER-003) MANUAL

Backup server and repository as Tier 0

Set toBackup software runs as a dedicated svc-backup account (never a human Domain Admin); that account is local admin on the hypervisors and the backup server only; repository is immutable, offline-copied, or at minimum on a share only the backup account can write; backup console admins = Domain Admins
WhyThe backup server can restore a DC anywhere and holds credentials to every host. A human admin's password stored in the backup console is recoverable by any local admin there and breaks every job the day that admin goes into Protected Users.
WhereBackup console › Credentials; local groups on hosts
Who catches itGUERRILLA (ADTIER-002) MANUAL

Watch out: Give the backup server a DNS A record and connect to it by name. Backup servers are often reachable only by IP, which forces NTLM.

Entra Connect server as Tier 0

Set toLocal Administrators = Domain Admins only; ADSyncAdmins = current Domain Admins only; exactly one MSOL_* (or the newer connector account) holds DCSync; stale MSOL_* accounts from retired or reinstalled Entra Connect servers deleted along with their explicit ACEs on the domain root and AdminSDHolder; patched; off the user VLAN
WhyThe sync account can replicate every hash by design. Anyone who is local admin on this server, or in ADSyncAdmins, can extract its credential from the ADSync database.
WhereADUC; Get-Acl AD:\<domain DN> and AD:\CN=AdminSDHolder,CN=System,…; Synchronization Service Manager
Who catches itBLOODHOUND (DCSync edge) PINGCASTLE GUERRILLA (ADTIER-001, ADPRIV-028) MANUAL for ADSyncAdmins and local admins

DC backups: VM-level first, system state second, IFM only with eyes open

Set to(1) Application-consistent VM-level backups of every DC, taken by a Tier 0 backup server with VSS / application-aware processing on, to an immutable or offline-copied repository; this is the restore you will actually use. (2) A wbadmin system-state backup on one DC to a share only DCs can write, on its own schedule; this is the authoritative-restore path. (3) Backup-GPO -All plus an LDIF/CSV export weekly for the "restore one GPO, one OU, one group" cases. (4) An IFM copy (ntdsutil ifm) is optional and not generally advised: it is a complete ntds.dit with every password hash as a plain file on whatever server the script wrote to. Take one only when you need an offline-mountable copy for forensics, keep it on a DC or an encrypted Tier 0 volume, and delete it on a schedule (Recipe R8)
WhyEvery backup that holds a DC's disk holds every hash; the question is how many copies exist and who can reach them. A VM backup is one copy, inside the backup repository, behind the backup product's access control. An IFM is a second copy sitting as files, and in practice it ends up on an automation server with a helpdesk group in local Administrators. PingCastle's and Guerrilla's "backup too old" rules are satisfied by any method that updates the DC's backup timestamp (system state, and VM backups that invoke the in-guest NTDS VSS writer as a full, not copy, backup), so there is no scanner reason to prefer IFM.
WhereBackup console (application-aware processing on for DC VMs; repository immutability on); wbadmin start systemstatebackup; Recipe R8
Who catches itPINGCASTLE (A-BackupMetadata) GUERRILLA (ADTIER-002 for the backup account's group membership) MANUAL for where the copies land and who can read them

Watch out: Never revert a DC VM to a checkpoint or restore a VM backup older than the tombstone lifetime; VM-GenerationID (Server 2012+ guest on a hypervisor that exposes it) protects against USN rollback when a checkpoint is applied or an image restored, but it is not a substitute for a VSS backup and does nothing on hosts that don't expose it, so still avoid checkpoint reverts on DCs. Verify repadmin /showrepl and Get-ADReplicationAttributeMetadata after any restore. If you do schedule an IFM or export task as SYSTEM on a DC, read the log after the first run: a task that "succeeds" in one second with exit code 1 is application control blocking PowerShell from reading the script; allow the hash before scheduling. The existing system-state job that "has always run" may have stopped months ago; check its last-success date.

If ntds.dit copies exist as files: Domain Admins only, and not local admins

Set toFirst ask whether the copy needs to exist (previous row). If it does: destination folder ACL = SYSTEM, the DC's computer account (Modify), and Domain Admins; inheritance off; no helpdesk or automation group in local Administrators on the host; encrypted volume. Optionally a conditional ACE requiring Domain Admins and Protected Users ((XA;OICI;FA;;;WD;((Member_of {SID(DA)}) && (Member_of {SID(AP)}))))
WhyThe folder ACL does not stop a local admin (take ownership, backup privilege). Only tiering the host does, and only not making the copy removes the risk.
Whereicacls; conditional ACE via ConvertStringSecurityDescriptorToSecurityDescriptorW (the .NET SDDL parser rejects callback ACEs)
Who catches itMANUAL

Watch out: Never point a backup at a drive letter that is a cloud-sync client. A non-elevated admin session that tightens the ACL can lock itself out of its own output; keep an explicit ACE for your own account while working.

Security log large enough to matter

Set toSpecify the maximum log file size for Security = 4,194,240 KB (4 GB) on DCs, 1 GB on servers
WhyA busy DC at the default size (20 MB on clients, larger on recent servers) keeps only days. Investigations start a week after the event.
WhereComputer Configuration › Administrative Templates › Windows Components › Event Log Service › Security
Who catches itMANUAL

DCs take time from a real source, not the hypervisor

Set toPDC emulator: w32tm /config /manualpeerlist:"<ntp1>,0x9 <ntp2>,0x9" /syncfromflags:manual /reliable:yes; on every DC VM, VMICTimeProvider Enabled=0 under HKLM\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders; hypervisors sync to the same external source, never to a DC they host
WhyA DC that takes time from the host which takes time from the DC is circular. Kerberos tolerates 5 minutes; a cold boot can be hours off.
Wherew32tm; registry
Who catches itMANUAL

DNS hygiene

Set toSecure dynamic updates only on every AD-integrated zone; scavenging on with sane refresh/no-refresh; A/PTR/SRV records for retired DCs deleted; every server has an A record (so management can use FQDN); forwarders reachable and a second resolver path tested
WhyA stale DC name that resolves to a reusable IP can be impersonated for anything still configured against it. A server without a DNS record forces NTLM.
WhereDNS Manager
Who catches itPINGCASTLE (A-DnsZoneUpdate rules) GUERRILLA (ADDOM-012, ADSTALE-011) MANUAL

Gate: every Tier 0 host patched to the current cumulative update; local Administrators on each = Domain Admins plus documented service accounts; weekly AD backup produced output twice; no object left in CN=Computers.

Phase 5: Workstations

Windows LAPS

Set toUpdate-LapsADSchema once (schema master, Schema Admins membership added for the duration and removed after); Set-LapsADComputerSelfPermission on the workstation and server OUs; GPO: Configure password backup directory = Active Directory, Password Settings = 24 characters / 30 days, Post-authentication actions = Reset password and log off, Name of administrator account to manage only if the built-in account was renamed (Recipe R6)
WhyEvery imaged PC shares one local Administrator password. One compromised PC becomes every PC by pass-the-hash. LAPS makes each password unique and rotating.
WhereComputer Configuration › Administrative Templates › System › LAPS
Who catches itPINGCASTLE (A-LAPS-Not-Installed) GUERRILLA (ADPWD-016 to 018, ADGPO-023)

Watch out: Extending the schema needs a fresh Kerberos ticket after being added to Schema Admins. If the admin is in Protected Users, that means lock/unlock or sign-out, not klist purge. Remove yourself from Schema Admins afterwards; PingCastle charges for a non-empty Schema Admins.

No extra local administrators; local accounts cannot log on over the network

Set toLocal Administrators on workstations = the LAPS-managed account, Domain Admins (harmless with the deny-logon GPO) and the workstation-admin group; every vendor, imaging, "admin" and "tech" local account removed; Deny access to this computer from the network = S-1-5-114 (Local account and member of Administrators group)
WhyLeftover local accounts share passwords across the fleet and are exempt from LAPS. The S-1-5-114 deny makes even a leaked local admin password useless remotely.
WhereRestricted Groups; User Rights Assignment
Who catches itBLOODHOUND (if local groups collected) MANUAL

Credential Guard and LSA protection on

Set toTurn On Virtualization Based Security = Enabled, Credential Guard = Enabled with UEFI lock; LSASS as protected process (RunAsPPL=2 under HKLM\SYSTEM\CurrentControlSet\Control\Lsa, or the Configures LSASS to run as a protected process template on 22H2+)
WhyStops Mimikatz-style credential dumping from memory on the PC. Does not stop ticket theft from a process the user runs, which is why the deny-logon GPO still matters.
WhereComputer Configuration › Administrative Templates › System › Device Guard; › System › Local Security Authority
Who catches itBUILT-IN only on Windows 11 22H2+ Enterprise/Education, domain-joined, on capable hardware (LSA protection only on clean installs, and without UEFI lock; RunAsPPL=2 is honored only on Windows 11 22H2+, older builds need 1) (confirm with Get-CimInstance -ClassName Win32_DeviceGuard -Namespace root\Microsoft\Windows\DeviceGuard)

RDP on workstations: NLA, restricted, or off

Set toEither RDP disabled on workstations, or enabled with NLA (Phase 2), Allow log on through Remote Desktop Services = Remote Desktop Users only, and shadowing with consent
WhyWorkstation RDP enabled domain-wide "for support" with NLA off is a pre-auth attack surface on every PC.
WhereRemote Desktop Session Host policies; User Rights Assignment
Who catches itMANUAL

Point and Print restrictions at Microsoft defaults, after drivers are pre-staged

Set toRestrictDriverInstallationToAdministrators=1, NoWarningNoElevationOnInstall=0, UpdatePromptSettings=0 (Computer Configuration › Administrative Templates › Printers › Point and Print Restrictions and Limits print driver installation to Administrators); Package Point and print - Approved servers = the print server's FQDN. Only after every driver model name the print server uses has been installed on every PC in machine context (Recipe R7)
WhyWith the restrictions off, anyone who controls the print server (or any server a PC can be pointed at) installs a driver as SYSTEM on every PC without a prompt. With the restrictions on but drivers not pre-staged, every staff member gets a UAC prompt for every printer, district-wide, the next morning.
WhereRecipe R7
Who catches itMANUAL

Watch out: Group Policy Preferences printer items re-evaluate connections at every refresh, so the number of PCs that need a driver pull is far larger than a small sample suggests. Pilot one building for a week.

Computer names 15 characters or fewer

Set toNaming standard enforced at imaging; for existing longer names, add HOST/<fullname> and RestrictedKrbHost/<fullname> SPNs to the computer object (Recipe R10), or rename
WhyOnly the short-name SPN is truncated to 15 characters, so a request for HOST/<full short name> (no domain suffix) finds no SPN. DCOM/WMI's second authentication step asks for RPCSS/<fullname>, finds no SPN, and falls back to NTLM, which Protected Users refuses: remote management of those PCs fails with "Access is denied" for every protected admin. Long names also break the secure channel on some builds (symptom: "the clock is always wrong").
WhereRecipe R10
Who catches itMANUAL

Windows 10 and other end-of-life machines retired

Set toNo enabled computer object with an OS past end of support; Windows 10 (October 2025), Server 2012 R2 (October 2023), Server 2016 (January 2027)
WhyUnpatchable OS = permanent foothold.
WhereGet-ADComputer -Filter * -Properties OperatingSystem,OperatingSystemVersion
Who catches itPINGCASTLE (S-OS-W10, S-OS-2012 and similar) GUERRILLA (ADSTALE-005/006)

Host firewall on, management ports only from the admin host

Set toWindows Firewall on in all profiles; inbound 135, 445, 5985/5986 and the dynamic RPC range allowed only from the admin host and IT subnet; everything else default-deny
WhyLateral movement between workstations needs these ports. Students on the same VLAN should not be able to reach a teacher PC's SMB.
WhereComputer Configuration › Windows Settings › Security Settings › Windows Defender Firewall
Who catches itGUERRILLA (ADGPO-019, policy present) MANUAL for the rule contents

Application control with a documented allow process

Set toThreatLocker, AppLocker or WDAC in enforce mode on servers and DCs first, workstations after a learning period; a written procedure for allowing a new hash, and for lifting a time-based lockdown policy out of hours
WhyStops the payload even when a credential is stolen.
WhereVendor console or Computer Configuration › Windows Settings › Security Settings › Application Control Policies
Who catches itGUERRILLA (ADGPO-022, AppLocker/WDAC policy present)

Watch out: See the Gotchas section. Application control will block your own scheduled backup script, your own logon-time utility from NETLOGON, and your diagnosis of a network problem (some products rewrite DNS answers on the host). Every one of those looks like something else.

Defender tamper protection, ASR rules, and cached logons

Set toTamper protection on (including DCs); attack surface reduction rules in block mode for Office child processes, credential stealing from LSASS, and executable content from email; Interactive logon: Number of previous logons to cache = 0 on servers (leave laptops at the default)
WhyDefaults that get turned off during troubleshooting and never turned back on.
WhereDefender policy; Security Options
Who catches itBUILT-IN (confirm) GUERRILLA (ADLOG-005)

Gate: LAPS password readable for every pilot PC; Point-and-Print pilot building one week with no prompts; no long host names without SPNs; firewall rules verified from a student VLAN.

Phase 6: Service accounts, scheduled tasks and applications

No scheduled task or service runs as a human admin

Set toEvery task with a stored domain password and every service with a domain StartName moved to a gMSA, a svc- account, LocalSystem, or deleted (Recipe R5)
WhyA stored password is recoverable by any local admin on that server. A task running as the admin "interactively" (no stored password) depends on that admin's live Kerberos ticket and silently fails overnight once the admin is in Protected Users. A service running as an admin fails with error 1069 the first time that admin's password changes, usually at a weekend reboot.
WhereRecipe R1 (find), Recipe R5 (fix)
Who catches itMANUAL

Service account pattern

Set tosvc-<purpose> in a Service Accounts OU; not a member of any admin group; password never expires, cannot change password, 30+ random characters typed into the task and stored nowhere else; LogonWorkstations = the one host; NTFS Modify on the script folder only; Log on as a batch job granted on that host; task logon type Password (runs whether or not anyone is signed in); fine-grained password policy applied to a global security group that holds every svc- account (a PSO cannot be applied to an OU; Phase 7)
WhyLeast privilege and no human dependency. The error for a missing batch-logon right is 0x80070569.
WhereRecipe R5
Who catches itPINGCASTLE (A-NoServicePolicy if no FGPP exists for them) GUERRILLA (ADPWD-002/003, ADTIER-007) MANUAL

Watch out: Granting the batch-logon right needs an elevated session on the host; a non-elevated admin session cannot. A DPAPI-protected credential file is bound to the user who created it; the service account needs its own copy, created in its own context. Anything that reads a mapped cloud drive (Google Drive for desktop, OneDrive) only works in the interactive session of the user who mounted it and cannot move to a service account without rework.

Applications that validate admin logins over NTLM get their own login

Set toPrint management, monitoring, helpdesk and backup consoles that authenticate "Windows users" via NTLM: create a dedicated non-protected login per admin (<user>-pc style) in the application, or switch the app to Kerberos/SAML
WhyOnce the real admin is in Protected Users the app says "invalid username or password" and the DC logs 0xC000006E. Weakening the admin to fix the app is the wrong direction.
WhereApplication's own user management
Who catches itMANUAL

LDAP bind accounts for firewalls, VPNs, wireless, copiers

Set toOne non-privileged account per appliance, in the Service Accounts OU, LDAPS only, read-only, no group memberships beyond Domain Users, password rotated and the appliance updated at the same time
WhyThe bind password sits in the appliance config, often exportable. It must not be a Domain Admin, and it must not travel over cleartext LDAP.
WhereADUC; appliance LDAP settings
Who catches itPINGCASTLE GUERRILLA (ADPRIV-024, flags it only if it is privileged or has an SPN)

Script and credential folders on automation servers

Set toC:\Scripts (or wherever tasks run from) = SYSTEM, Administrators and the specific svc- accounts; inherited BUILTIN\Users CreateFiles/CreateDirectories removed; credential files (API keys, service-account JSON, DPAPI blobs) in a sub-folder with an explicit ACL for the one account that uses them
WhyWith the default inherited ACL any local user can plant a file where a Domain Admin task executes, or read the Google service-account key.
Whereicacls <folder> /inheritance:r /grant:r "SYSTEM:(OI)(CI)F" "Administrators: (OI)(CI)F" "<svc>:(OI)(CI)M"
Who catches itMANUAL

Watch out: icacls /reset /T re-inherits the parent; do /inheritance:r /grant on the parent after resetting children. A non-elevated admin session cannot set an owner.

Legacy sync scripts write no PII and no passwords to logs

Set toEvery scheduled script's output reviewed; logs contain usernames and IDs only; any credential hand-off file (new-student passwords for an office) written to an ACL'd folder and deleted on a schedule; old log archives containing password columns deleted (Recipe R11)
WhyA SIS export with a password column, copied to a log folder hourly for a year, is every student password in the district in plaintext, readable by every local user.
WhereRecipe R11
Who catches itMANUAL (SCRIPTSENTRY and GUERRILLA ADSCRIPT-004/006 cover logon scripts only)

Assistants and automation tooling denied the credential folders

Set toDeny rules in any AI assistant, RMM or scripting tool configuration for the credential folder, the SIS export file, and backup locations
WhyA tool that can read the folder will eventually read it into a transcript or a ticket.
WhereTool's settings (for Claude Code: permissions.deny entries in settings.json)
Who catches itMANUAL

Gate: Recipe R1's task and service query returns only svc-, gMSA and built-in accounts; every svc- account documented with owner, host and purpose.

Phase 7: Directory hygiene

Stale users disabled, then deleted

Set toEnabled users with no logon in 180 days disabled and moved to a Disabled OU with a description stamp; never-logged-on accounts older than 90 days disabled; disabled accounts older than one year deleted (Recycle Bin on)
WhyHundreds of dormant accounts are the password-spraying surface. A seasonal or default password on an account nobody watches is the first foothold.
WhereSearch-ADAccount -AccountInactive -TimeSpan 180 -UsersOnly
Who catches itPINGCASTLE (S-Inactive, P-Inactive) GUERRILLA (ADSTALE-001/003)

pwdLastSet = 0 forced to change; no "password not required"

Set toEvery enabled account with pwdLastSet=0 ("must change at next logon" left set from creation) either used or disabled; PasswordNotRequired false on every account
WhyAn account that was never logged on since creation still has the creation password; those are predictable. PasswordNotRequired allows a blank password regardless of policy.
WhereGet-ADUser -Filter {pwdLastSet -eq 0 -and Enabled -eq $true}; -Filter {PasswordNotRequired -eq $true}
Who catches itPINGCASTLE (S-PwdNotRequired) GUERRILLA (ADPWD-010, ADPRIV-011)

Watch out: An account with "must change at next logon" cannot authenticate to applications that do username/password against AD (print release, web portals) until someone signs in interactively once.

Password policy with fine-grained policies

Set toDefault domain policy: 12+ characters, complexity on, history 24, lockout 10 / 15 min; FGPP for svc- accounts: 30+ characters, never expires; FGPP for admins: 15+ characters, 365-day maximum age; consider Entra password protection or a banned-password list
WhyOne policy cannot fit staff, service accounts and admins. A missing FGPP for service accounts is a PingCastle finding on its own.
WhereActive Directory Administrative Center › System › Password Settings Container
Who catches itPINGCASTLE (A-MinPwdLen, A-NoServicePolicy) GUERRILLA (ADPWD-001 to 008, 021/022)

Stale computers disabled, then deleted, after BitLocker keys are exported

Set toComputers with no logon and no machine-password change in 90+ days disabled, stamped, and moved to a Disabled OU; deleted after a further 90 days; before any delete, export every msFVE-RecoveryInformation object (recovery password, GUID, parent computer) to a file kept with the backups
WhyStale computer objects are relay and resource-based-delegation fodder. BitLocker recovery keys are child objects of the computer and die with it.
WhereSearch-ADAccount -AccountInactive -ComputersOnly; Get-ADObject -Filter {objectClass -eq 'msFVE-RecoveryInformation'} -Properties msFVE-RecoveryPassword
Who catches itPINGCASTLE (S-C-Inactive) GUERRILLA (ADSTALE-002, ADPWD-020, ADTRADE-003)

Watch out: A decommission list from a human will contain machines that authenticated this morning. Check LastLogonDate, PasswordLastSet and recent 4768 events before deleting anything from a list. Exclude AZUREADSSOACC (it never logs on; its password changes only when you roll the key) and the Entra Connect server from stale sweeps. If Entra Connect syncs computer objects, a bulk delete shows up as a bulk Entra device delete on the next cycle, and Entra Connect stops export at its accidental-delete threshold (default 500) until you raise or disable it temporarily.

Student accounts out of AD if students live in Google

Set toIf student identity is Google (Chromebooks, SIS-provisioned Google accounts), delete the student user tree from AD after the checks in Recipe R9; keep student OUs and groups only if something references them
WhyThousands of accounts nobody uses are thousands of spraying targets and a password-divergence problem every time the SIS and AD disagree. Removing them also shrinks every other query in this handout.
WhereRecipe R9
Who catches itPINGCASTLE (counts them as inactive) GUERRILLA (ADSTALE-001)

Watch out: Find and neutralise every directory-sync tool first (Recipe R9). A dormant Google Cloud Directory Sync config pointed at the student OU with "suspend users" will suspend every student Google account the next time someone runs it.

OU structure that policies can be scoped to

Set toSeparate OUs for workstations (by building if useful), servers, Tier 0 servers, staging, service accounts, admin accounts, disabled users, disabled computers; nothing live in CN=Users or CN=Computers; empty OUs deleted
WhyEvery GPO and every delegation in this handout is applied per OU.
WhereADUC
Who catches itGUERRILLA (ADSTALE-009, abandoned OUs) MANUAL

Orphaned ACEs and legacy groups removed

Set toExplicit ACEs for deleted SIDs, for Exchange groups when Exchange is gone, for RAS and IAS Servers, MDT, imaging groups and individual humans on OUs: removed; empty legacy groups that still hold Write on something: deleted
WhyAny future member of an empty group with GenericAll on an OU inherits it. An individual's User-Force-Change-Password on a staff OU is a takeover of every account in it.
WhereBloodHound, or a Get-Acl sweep of every OU and container filtered to GenericAll / WriteDacl / WriteOwner / ExtendedRight / WriteProperty
Who catches itBLOODHOUND ADELEG (built for exactly this: every ACE that is not a Windows default, per container, diffable between runs) PINGCASTLE (P-Delegated* rules) GUERRILLA (ADACL-001 to 008, 010, 012, 015)

Retired DC remnants gone

Set toNo computer object, DNS A/SRV record, NTDS Settings object, or SPN left for a DC that no longer exists; nltest /dclist and Get-ADDomainController -Filter * agree with reality
WhyA retired DC's name pointing at a reusable IP can be impersonated for any client still configured against it.
Wherentdsutil metadata cleanup; DNS Manager; Sites and Services
Who catches itPINGCASTLE (S-DC rules) GUERRILLA (ADDOM-004, ADTRADE-002)

Vendor and contractor accounts scoped and expiring

Set toEvery vendor account has an owner, an expiration date (Set-ADAccountExpiration), no admin group membership, and membership in Remote Desktop Users only on the host it needs
WhyVendor accounts with 10-year-old passwords and domain-wide RDP are a reliable finding.
WhereADUC
Who catches itPINGCASTLE (password age, never expires) GUERRILLA (ADPWD-009, ADPWD-015) MANUAL for scope

Gate: zero enabled accounts inactive over 180 days; zero pwdLastSet=0 on enabled accounts; FGPPs in place; CN=Users holds only built-ins; BitLocker export file verified readable.

Phase 8: Cloud sync boundaries (Entra Connect, Google sync)

Entra Connect OU filtering matches intent

Set toSynchronization Service Manager › AD connector › Configure Directory Partitions › Containers: only the OUs that should become Entra users; the student OU excluded if students are Google-only; the Disabled OUs excluded; computer objects excluded unless hybrid join is wanted; verified in the wizard, not assumed
WhySync scope decides what a bulk AD change does in the cloud. Including computers means a stale-computer purge becomes an Entra device purge.
WhereEntra Connect wizard › Customize synchronization options › Domain and OU filtering; Device options
Who catches itMANUAL

Hybrid join and device registration only if wanted

Set toIf devices should not be in Entra: set Register domain joined computers as devices to Disabled (deleting the GPO leaves it unconfigured), delete the forest Service Connection Point (CN=62a0ff2e-97b9-4513-943f-0d221bd30080,CN=Device Registration Configuration,CN=Services,CN=Configuration,<forest DN>) after exporting it, and run dsregcmd /leave on already-joined PCs
WhyEvery hybrid-joined device is another identity in the cloud with its own attack surface, and the SCP makes every new domain join register automatically.
WhereGPO; ADSI Edit (Configuration partition); dsregcmd
Who catches itMANUAL

Seamless SSO computer account handled deliberately

Set toIf Seamless SSO is used: AZUREADSSOACC excluded from stale sweeps and its Kerberos key rolled every 30 days (Update-AzureADSSOForest). If not used: disable Seamless SSO in Entra Connect, then delete the account (export with Export-Clixml first; Recycle Bin keeps it 180 days)
WhyThe account's key decrypts every user's SSO ticket. A key that has never been rotated is a silver-ticket generator; a stale-sweep that disables it breaks SSO for everyone.
WhereEntra Connect wizard; PowerShell
Who catches itPINGCASTLE (T-AzureADSSO) GUERRILLA (ADTRADE-005)

Google Cloud Directory Sync removed or scoped

Set toIf Google accounts are provisioned from the SIS by scripts, GCDS is uninstalled, its config files deleted, and its OAuth grant revoked in the Admin console. If GCDS is used: scope limited to the staff OU, suspension limit set, config backed up, and a documented owner
WhyGCDS reads an OU and makes Google match it. Left configured against an OU that is about to be emptied, it suspends or deletes the Google accounts to match.
WhereGCDS Configuration Manager; Admin console › Security › API controls › Domain-wide delegation and the admin user's connected apps
Who catches itMANUAL

SIS-to-Google provisioning: no plaintext password trail

Set toThe provisioning script keeps a state file of minted credentials (so it never re-randomises a password Google already has), logs usernames and IDs only, hands credentials to offices through an ACL'd folder with scheduled deletion, and never copies the SIS export anywhere else
WhySee Phase 6 and Recipe R11.
WhereScript review
Who catches itMANUAL

Watch out: Do not use Google's lastLoginTime to decide a student is locked out. Chromebook users sign in with cached credentials and the field rarely updates; a bulk password reset based on it changes passwords for students who knew theirs, and their Chromebooks report "password changed" days later.

Gate: Entra Connect scope documented; no sync tool exists without an owner; a bulk test delete of one disabled test user and one disabled test computer produces the expected (and only the expected) cloud effect.

Phase 9: Detection

Advanced audit policy on DCs

Set toAudit: Force audit policy subcategory settings to override = Enabled; Success+Failure for Logon, Kerberos Authentication Service, Kerberos Service Ticket Operations, Credential Validation, Directory Service Access, Directory Service Changes, Security Group Management, User Account Management, Process Creation (with command line), Sensitive Privilege Use, Audit Policy Change; Success for Computer Account Management
WhyWithout subcategory auditing the DC does not log 5136 (GPO/ACL change), and the legacy category policies cannot be tuned.
WhereComputer Configuration › Policies › Windows Settings › Security Settings › Advanced Audit Policy Configuration (DC GPO)
Who catches itPINGCASTLE (A-AuditDC) GUERRILLA (ADLOG-001, ADLOG-004, ADGPO-018)

SACL for DCSync on the domain root

Set toAuditing entry on the domain root: Everyone, Success+Failure, All extended rights, this object only
WhyProduces a 4662 with the replication GUIDs every time anything replicates. Alert when the subject is not a DC or the one Entra Connect account (Recipe R13).
WhereADSI Edit › domain root › Properties › Security › Advanced › Auditing
Who catches itMANUAL

PowerShell logging

Set toTurn on Module Logging (*), Turn on PowerShell Script Block Logging, and transcription to a write-only share, on DCs and servers at minimum
WhyMost post-exploitation is PowerShell.
WhereComputer Configuration › Administrative Templates › Windows Components › Windows PowerShell
Who catches itPINGCASTLE (A-AuditPowershell) GUERRILLA (ADLOG-002/003, ADGPO-021)

Events forwarded off the box

Set toWindows Event Forwarding (source-initiated, GPO Configure target Subscription Manager) from DCs and Tier 0 servers to a collector, or a SIEM agent; at minimum 4624, 4625, 4648, 4662, 4672, 4688, 4697, 4698, 4719, 4720, 4724, 4728, 4732, 4756, 4768, 4769, 4771, 4776, 5136, 5137, 5141, 7045
WhyA DC's log is the first thing an attacker clears, and a 4 GB log still wraps in a week on a busy DC.
WhereComputer Configuration › Administrative Templates › Windows Components › Event Forwarding; wecutil on the collector
Who catches itGUERRILLA (ADLOG-006, subscription manager configured) MANUAL (PINGCASTLE checks only that the audit policy exists)

Alerts that matter

Set toAny 4662 with GUID 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2 or 1131f6aa-… from a subject that is not a DC or the sync account; 4728/4732/4756 on Domain Admins, Enterprise Admins, Schema Admins, Administrators, Protected Users, Group Policy Creator Owners; 4769 with ticket encryption 0x17; 4768 for an admin from a new source IP; 5136/5137 on any GPO or on AdminSDHolder; 4624 type 10 (RDP) to a DC from anything but the admin host; 7045 (new service) on a DC; any logon for the break-glass account
WhyEach is one line that marks a DCSync, a privilege escalation, a Kerberoast, or admin credentials in a new place.
WhereCollector / SIEM rules; or scheduled Get-WinEvent scripts that mail a digest
Who catches itMANUAL

Know what the noise means

Set toDocument for the helpdesk: 4776/4625 with 0xC000006E = Protected Users refusing NTLM, not a bad password; 4771 0x18 every ~100 minutes from a server = a stale disconnected session holding an old password (log it off; set a disconnected-session limit); 4771 at the same second daily = a scheduled task with a stored password; 4625 every 10 minutes from the backup server = a stored credential that changed
WhyReal alerts drown in these within a week of Phase 1 unless everyone knows what they look like.
WhereRunbook
Who catches itN/A

Defender for Identity on DCs (optional)

Set toSensor on each DC; alerts for DCSync, Golden Ticket, lateral movement, reconnaissance
WhyBuys most of the alerting above with no collector to build.
WhereMicrosoft Defender portal
Who catches itPAID (EMS E5/A5, Microsoft 365 E5/A5 or E5/A5 Security, or a standalone Defender for Identity license; Entra ID P2 does not include it)

Gate: a test DCSync (DSInternals Get-ADReplAccount -SamAccountName <test> -Server <dc> run from the admin host by an account holding replication rights) produces an alert within the hour; a test addition to Domain Admins produces an alert; the helpdesk runbook is written.

Phase 9a: Seams the review surfaced

These close the gaps between the two directories, between "detect" and "respond", and in the threat model itself. The attacker's agent never touches your endpoints; it reaches you over LDAP, SMB, Kerberos and HTTPS with credentials it harvested, and its command-and-control is a legitimate AI API. Everything here assumes that: the sync server and the backup server as Tier 0, servers that cannot reach the internet, the devices that are not Windows, decoys in the places an agent looks first, automated response on the rules that are never legitimate, and the procedures a vishing call and a real compromise actually test.

The sync bridge and the backup server

Sync server (Entra Connect, GCDS) treated as Tier 0

Set toThe server running Entra Connect, Google Cloud Directory Sync or Password Sync lives in OU=Tier0,OU=Servers, gets the DC GPO baseline (deny-logon rights for non-Tier-0 admins, LAPS, no other roles, no browser use), is patched on the DC schedule and backed up like a DC; its admin logons come only from the PAW or jump host
WhyIt holds a credential to both directories. If passwords sync or either directory is the IdP for the other, one compromise is both, and this box is almost never treated as the Tier 0 asset it is. An agent that maps the domain values it above any single DC.
WhereActive Directory Users and Computers › move the computer object; the Tier 0 GPO; Phase 8 for the sync account itself
Who catches itGUERRILLA (Entra sync account hygiene) MANUAL (OU placement and GPO scope)

Watch out: Password hash sync to Google is a choice, not a default. If it is on, write down why; if nobody can, turn it off and let Google be the IdP for Chromebooks with its own second factor.

Backup server off-domain, or Tier 0 with its own credentials

Set toPreferred: the backup server and repository are not domain members; local admin only, unique password in the password manager, MFA on the console. If it must be domain-joined: Tier 0 OU, deny-logon GPO for all non-Tier-0 admins, backup console admins are not Domain Admins, repository immutable (object lock) or offline-copied, alert on any job deletion or retention change
WhyAn attacker with DCSync has the backup service account. If the backup server is a domain member on the server VLAN with a writable repository, backups are deleted before encryption. Minneapolis recovered because theirs survived; most districts' would not.
WhereBackup console › users and roles; repository › immutability / retention lock; Phase 4 for the backup account
Who catches itMANUAL

Watch out: Immutability with a retention the admin can shorten is not immutability. Set the lock so that even the backup administrator cannot reduce it inside the window.

Egress and DNS from the server side

Server and management VLANs: outbound deny-by-default

Set toFirewall policy for DC, server, hypervisor, backup and management subnets: outbound denied except patching, licensing and named vendor FQDNs; no general web browsing from any server; the jump host and admin workstations follow the same rule
WhyA domain controller has no business reaching the internet. An agent on a compromised server phones home to a legitimate AI API over HTTPS, which every filter allows and TLS hides. The only control that stops it is that the server cannot reach anything you did not name.
WhereFirewall › outbound policy per VLAN; allowlist by FQDN where the firewall supports it
Who catches itMANUAL

Watch out: WSUS/PDQ, AV signature updates, KMS, NTP and the certificate authority's CRL/OCSP endpoints all need explicit allows; log the denies for two weeks in monitor mode before enforcing so you find them.

Alert on LLM-provider hostnames from server or management subnets

Set toDNS query logging on the internal resolvers (Windows DNS analytic log or the resolver's query log) shipped to the SIEM; a rule that fires on any query from a server, hypervisor, backup or management IP for api.anthropic.com, api.openai.com, generativelanguage.googleapis.com, bedrock*.amazonaws.com, api.x.ai, api.mistral.ai, api.together.xyz and similar
WhyThe command-and-control of an agent-driven intrusion is a model API. Staff workstations may legitimately reach these; a server never does. It is the single most specific detection for the agent thesis.
WhereDNS Server › Event Viewer › Applications and Services › Microsoft › Windows › DNS-Server › Analytical (enable); Wazuh rule on the DNS log
Who catches itMANUAL

Watch out: Keep the hostname list in the SIEM, not the firewall, and review it quarterly; new providers appear monthly.

DNS-over-HTTPS blocked; internal DNS is the only resolver

Set toFirewall blocks known DoH resolvers (Cloudflare, Google, Quad9, NextDNS and the browser-built-in lists) and outbound 853 (DoT); Chrome policy DnsOverHttpsMode = off; Windows policy Configure DNS over HTTPS (DoH) name resolution = Prohibit DoH; only the internal resolvers are reachable on 53
WhyDoH bypasses the filter and hides the query from the DNS log you just started alerting on. Blocking it on the network and in policy keeps every lookup visible.
WhereFirewall › application control / URL category; Computer Configuration › Administrative Templates › Network › DNS Client; Chrome policy
Who catches itMANUAL

Wired access and the devices that are not Windows

802.1X on wired classroom ports

Set toWired ports authenticate with 802.1X (certificate or device auth via NPS/RADIUS); unknown devices land on a quarantine VLAN with internet only; MAC authentication bypass only for printers and IoT that cannot do 802.1X, each pinned to its own VLAN; unused ports disabled; alert on a new MAC on a port that already has one
WhyGuest wireless is segmented, but a device on a classroom jack gets the staff VLAN, which is the premise of the LLMNR item in Phase 2. A parent's laptop at a basketball game or a $30 implant left behind maps what the port lets it see.
WhereSwitch port config; NPS network policy; DHCP scopes for the quarantine VLAN
Who catches itMANUAL

Watch out: Start with the ports in public-facing rooms (gym, library, cafeteria, front office); classroom ports in locked rooms can follow.

Copier and MFP credentials: dedicated, low-privilege, LDAPS only

Set toScan-to-folder and LDAP address lookup use one dedicated account per fleet (svc-mfp-scan), member of Domain Users only, denied interactive and remote logon, password in the password manager; LDAP lookup over LDAPS (636) to a DC; SMB scan target is a share only that account can write; every MFP admin password changed from default and the web console on the printer VLAN only
WhyAn MFP stores an AD credential and will hand it to any host that answers as the LDAP or SMB server it was pointed at (the pass-back attack). A real domain-user credential in a copier is the first thing an automated sweep finds, and ThreatLocker never sees a copier.
WhereEach MFP's admin console › Network › LDAP / Scan settings; the svc-mfp-scan account's logon rights via GPO deny-logon lists
Who catches itMANUAL (GUERRILLA for the account's group membership and logon rights)

Watch out: Some older MFPs can only do plain LDAP on 389. If LDAP signing is enforced on the DCs (Phase 2) they will stop looking up addresses; that is the correct outcome. Replace or isolate the device rather than relax signing.

Vendor remote-access tools on OT/IoT inventoried and gated

Set toA written list of every vendor remote tool reaching HVAC, cameras, door controllers, bell systems and the SIS (ScreenConnect, AnyDesk, TeamViewer, vendor VPN appliances); each reachable only from the vendor's known IPs through a firewall rule that is time-boxed and logged; the devices themselves on their own VLANs with no route to server subnets
WhyThese are standing remote-control paths into your building that no Windows control sees. The vendor's security is yours.
WhereFirewall › inbound rules per vendor; VLAN assignments; the inventory in the password manager or CMDB
Who catches itMANUAL

Finding the agent after it is in

Weekly baseline diff of users, groups and ACLs

Set toA scheduled script exports users (created, enabled, lastLogon, adminCount), membership of every privileged group, domain and OU ACEs (ADeleg), GPO links and trusts to a dated folder on the admin host; diffs against last week; mails the delta; any new account, new privileged member or new ACE on a Tier 0 object is reviewed the same day
WhyA documented post-access step for an automated intrusion is creating its own account for persistence. You already diff mailbox filters weekly in Workspace; the directory deserves the same. The monthly PingCastle diff in Phase 10 is too slow for this.
WhereScheduled task on the admin host; Get-ADUser, Get-ADGroupMember, ADeleg export, Get-GPO; Recipe R13 for the DCSync-rights ACE check
Who catches itMANUAL (BLOODHOUND for the quarterly graph diff)

Watch out: Diff the shortest paths to Domain Admins in BloodHound CE quarterly as well. PingCastle and Purple Knight score configuration; BloodHound walks the exact privilege graph an agent walks.

Honeytokens in the places an agent looks first

Set toA decoy service account with an SPN and a 25-character password that is never used (alert on any 4769 for it); a creds.csv or passwords.txt with fake contents in the scripts share, with a SACL that logs 4663 on read; a canary document in a Drive folder and a canary AWS/API key in a config file, both from canarytokens.org; one decoy computer object with a plausible name and no real host
WhyAn agent enumerates everything, in order, at machine speed. A Kerberoast request for an account nobody uses, a read of a file nobody should open, or a canary key being tried are each a single event that is never legitimate. It is the cheapest high-signal control that exists against the agent thesis.
WherePhase 9 audit policy (4769, 4663 with SACLs); Wazuh rules; canarytokens.org for the document and key tokens
Who catches itMANUAL

Watch out: Name the decoys plausibly and leave them alone; a decoy that is in a scanner's exclusion list or documented in a wiki is not a decoy. Tell only the people who would otherwise investigate the alert.

Respond in minutes: automated action on the never-legitimate rules

Set toWazuh active response on a short list: disable the account on a DCSync 4662 from a non-DC subject, on a Kerberoast of the decoy SPN, on a privileged-group add outside change windows; ThreatLocker host isolation on a ringfence violation from an agent process; a documented un-do procedure and a second admin who can run it at 2 AM
WhyDetection in minutes is only half the sentence. At 2 AM on a holiday, an alert with no action is an audit log. The honest counter to machine speed is machine-speed response on the handful of rules that are never legitimate.
WhereWazuh › active-response configuration; ThreatLocker › Detect policies › Isolate; the IR runbook
Who catches itMANUAL

Watch out: Test each automated action against a lab account first and write down what it breaks. An action that disables the sync account at 7 AM on a false positive is an outage you caused.

The procedure the vishing simulation tests

Help-desk verification procedure for resets and MFA changes

Set toWritten and rehearsed: no password reset, unlock, MFA reset or security-key enrollment for admin, finance, HR or central-office accounts by phone or chat, ever; for everyone else, callback to the number on file or a shared verification word; every reset logged with the method used and reviewed weekly against 4723/4724 and the Entra/Google reset logs
Why80% of a K-12 help desk's day is resets and unlocks; that is the call an attacker, or an agent with a cloned voice, makes. Trained listeners cannot tell modern clones from real voices; a procedure that does not depend on recognizing a voice is the only one that survives that.
WhereWritten procedure; help-desk ticket template with a required verification method field; the vishing simulation grades the procedure, not the person
Who catches itMANUAL

Recovery you have actually rehearsed

Post-compromise credential rotation runbook

Set toA written, ordered runbook: krbtgt twice (Recipe), every gMSA and service account, the sync account and its keys, every stored task and service password, LAPS passwords, AD CS CA key if the CA was reachable, every OAuth token and app password in Workspace and Entra, re-enrol security keys for admins, rotate the break-glass accounts; owners and estimated hours per step
WhyAfter a confirmed compromise, every credential the attacker could have read is burned. That is days of work; with no runbook it is weeks of exposure while someone works out the order.
WhereThe runbook lives printed in the break-glass envelope and in the password manager
Who catches itN/A

Tested AD forest recovery with a measured RTO

Set toAnnually: restore one DC from the Tier 0 backup into an isolated lab network, seize FSMO roles, reset krbtgt and the DSRM password, confirm authentication works, time it; document the steps that failed; keep the DSRM passwords and the isolated-network procedure in the break-glass envelope
WhyA restore you have never run is a hope. Forest recovery has a dozen steps that are only obvious after you have done them once, and the first time should not be the real one.
WhereBackup console › restore to isolated network; Microsoft's AD Forest Recovery Guide for the sequence
Who catches itN/A

Watch out: Hypervisor snapshots are not backups and reverting a DC from one without VM-GenerationID support causes USN rollback; test from the backup product, not from a snapshot.

Gate: sync and backup servers in the Tier 0 OU or off-domain; server VLAN egress in enforce mode with the deny log reviewed; one honeytoken of each kind placed and its alert tested; the baseline diff has run for four weeks; the verification procedure is written and the help desk has been tested against it; the rotation runbook is printed; one forest-recovery rehearsal timed.

Phase 10: Keep it that way

Monthly: PingCastle and Guerrilla, diffed against last month

WhyCatches drift. The PingCastle score going up by 15 means someone re-added a vendor to Domain Admins; a Guerrilla check flipping from PASS to FAIL names the control. Keep both outputs; the diff is the report.
WhereScheduled task on the admin host; keep PingCastle's XML and Guerrilla's JSON

Monthly, after Patch Tuesday: Tier 0 build numbers

WhyConfirms the Phase 4 update policy is still working.
WhereRecipe R1 patch block

Weekly: DC VM backup and system-state job both succeeded, and the export job's log has no errors

WhyA backup task that fails in one second is indistinguishable from one that ran, unless someone reads the log. The system-state job is the one that stops silently.
WhereBackup console job history; wbadmin get versions; export log; Recipe R8

Weekly: staging OU emptied, new long host names given SPNs

WhyEvery new join lands in staging with no policy; every new 16-character name is unmanageable by protected admins.
WhereADUC; Recipe R10

Quarterly: admin group membership, local Administrators on Tier 0, DCSync ACEs, Protected Users membership

WhyThe four lists that turn a stolen password into a domain.
WhereRecipe R1 and R13

Quarterly: ADeleg export diffed; ScriptSentry run; Locksmith if a CA exists

WhyDelegations accumulate one ticket at a time and nobody removes them; logon scripts get edited by hand; certificate templates get duplicated for a project and never locked down. Each tool answers one of those in minutes.
Whereadeleg.exe --csv <file> (diff against last quarter); Invoke-ScriptSentry; Invoke-Locksmith -Mode 1

Quarterly: stored-credential tasks and services sweep

WhySomeone will schedule a report as themselves again.
WhereRecipe R1

Quarterly: stale users, stale computers, pwdLastSet=0, expiring vendor accounts

WhyHygiene decays at the rate people are hired and devices are replaced.
WherePhase 7 queries

Quarterly: RSOP user rights on a DC, a server and a workstation; Enforced link list

WhyGPO edits for one problem add rights for everyone.
WherePhase 0

Semi-annually: krbtgt double reset; Seamless SSO key roll if applicable (monthly for the SSO key)

WherePhase 1, Phase 8

Annually: restore test (one GPO from backup, one deleted user from the Recycle Bin, one DC from system state into an isolated network)

WhyA backup that has never been restored is a hypothesis.

After every incident or outage: write down which of the above would have caught it earlier, and add that check

Recipes

R1: Inventory pass

# Effective user rights (run on each DC, one hypervisor, one server, one workstation)
Get-CimInstance -Namespace root\rsop\computer RSOP_UserPrivilegeRight |
  Select-Object UserRight, AccountList, GPOID | Sort-Object UserRight
# Domain-root and Enforced links
Get-GPInheritance -Target (Get-ADDomain).DistinguishedName | Select-Object -Expand GpoLinks
Get-GPO -All | % { $r=[xml](Get-GPOReport $_.Id -ReportType Xml); $r.GPO.LinksTo | ? NoOverride -eq 'true' | Select @{n='GPO';e={$r.GPO.Name}},SOMName }
# Privileged group membership with password age
foreach ($g in 'Domain Admins','Enterprise Admins','Schema Admins','Administrators',
         'Group Policy Creator Owners','Account Operators','Server Operators',
         'Backup Operators','Print Operators','DnsAdmins','Protected Users') {
  Get-ADGroupMember $g -Recursive -ErrorAction SilentlyContinue | Get-ADUser -Properties PasswordLastSet,LastLogonDate,Enabled,Description,adminCount |
    Select-Object @{n='Group';e={$g}},SamAccountName,Enabled,PasswordLastSet,LastLogonDate,adminCount,Description
}
# Local Administrators, stored-credential tasks, domain-account services, patch level on every server
$servers = (Get-ADComputer -Filter {OperatingSystem -like '*Server*' -and Enabled -eq $true}).DNSHostName
Invoke-Command -ComputerName $servers -ErrorAction Continue {
  [pscustomobject]@{
    Host     = $env:COMPUTERNAME
    Admins   = (Get-LocalGroupMember Administrators).Name -join ';'
    Tasks    = (Get-ScheduledTask | ? { $_.Principal.LogonType -eq 'Password' -and $_.Principal.UserId -match '\\' } |
                % { "$($_.TaskName)=$($_.Principal.UserId)" }) -join ';'
    Services = (Get-CimInstance Win32_Service | ? StartName -match '^(?!LocalSystem|NT AUTHORITY|NT SERVICE|$)' |
                % { "$($_.Name)=$($_.StartName)" }) -join ';'
    Build    = (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion').CurrentBuild + '.' +
               (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion').UBR
    AU       = (Get-ItemProperty 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU' -ErrorAction SilentlyContinue).AUOptions
  }
} | Export-Csv servers-inventory.csv -NoTypeInformation

# Default containers
Get-ADComputer -SearchBase "CN=Computers,$((Get-ADDomain).DistinguishedName)" -Filter * -Properties OperatingSystem,LastLogonDate
Get-ADUser -SearchBase "CN=Users,$((Get-ADDomain).DistinguishedName)" -Filter {Enabled -eq $true} -Properties LastLogonDate

# SYSVOL / NETLOGON ACL and ownership, per DC
foreach ($dc in (Get-ADDomainController -Filter *).HostName) {
  icacls "\\$dc\SYSVOL\$env:USERDNSDOMAIN\scripts"
  Get-ChildItem "\\$dc\SYSVOL\$env:USERDNSDOMAIN\Policies" -Recurse | Get-Acl | Group-Object Owner | Select Count,Name
}

# After removing an account from a protected group
Set-ADUser <sam> -Clear adminCount
$acl = Get-Acl "AD:\$((Get-ADUser <sam>).DistinguishedName)"
$acl.SetAccessRuleProtection($false, $true); Set-Acl "AD:\$((Get-ADUser <sam>).DistinguishedName)" $acl

R2: Editing a GPO or SYSVOL directly (when the GroupPolicy module is unavailable or double-hops)

  1. Back up: copy the GPO's SYSVOL folder and Get-ADObject <gPC DN> -Properties * to a dated folder.
  2. Edit on the PDC emulator: GptTmpl.inf (UTF-16LE), Registry.pol (binary "PReg" format; use a proper writer), or GPP XML.
  3. Bump versionNumber on the gPC object and in GPT.INI: +1 for a machine-side change, +65536 for a user-side change. Clients skip a GPO whose version did not change.
  4. repadmin /syncall <pdc> <domain DN> /e /d /A /P, then confirm the version and file contents on every other DC.
  5. gpupdate /target:computer /force on a test machine; verify with RSOP (user rights) or gpresult /h.
  6. For permission changes under SYSVOL: apply on each DC's UNC path and read back on each. Do not assume DFSR carried the ACL.
  7. Deleting a GPO by hand: unlink from every gPLink, Remove-ADObject -Recursive on the gPC, then delete the SYSVOL folder on the PDC.

R3: Protected Users rollout

  1. Inventory every use of each admin account (Recipe R1: tasks, services; plus application credential stores, RDP sessions via query user /server:<host>, 4768 source IPs via R13).
  2. Move every task and service to a service account (R5). Give applications that authenticate over NTLM their own admin login. Give the backup product its own account.
  3. Create the break-glass account (Before you start) and test it from the admin host.
  4. Add one admin to Protected Users. Wait a day. Read the DC Security log for 4776 and 4625 with 0xC000006E and 4771 with 0x18 for that account; each source host is something still using NTLM or an old password. Fix it (FQDN, SPN, service account, log off the stale session).
  5. Repeat for each admin, including the built-in Administrator last.
  6. Tell every admin: FQDN only; expect to re-authenticate every 4 hours in long sessions; never klist purge; a stale disconnected RDP session will generate pre-auth failures until it is logged off.
  7. Set a disconnected-session time limit on servers (Set time limit for disconnected sessions, Remote Desktop Session Host › Session Time Limits) so stale sessions stop accumulating.

R4: Deny admin logon to workstations

  1. New GPO, user configuration disabled. Computer Configuration › Policies › Windows Settings › Security Settings › Local Policies › User Rights Assignment: - Deny log on locally, Deny log on through Remote Desktop Services, Deny log on as a batch job, Deny log on as a service = Domain Admins, Enterprise Admins (add the -adm accounts' group if it is separate). - Leave Deny access to this computer from the network unset for these groups.
  2. Link at the workstation OU only. Verify the link list does not include servers, DCs, or a parent of either.
  3. Verify on one workstation (RSOP_UserPrivilegeRight shows four SeDeny* entries), one server and one DC (none).
  4. Give each admin a non-privileged account in the workstation-admin group for hands-on work at a PC.

R5: Service account for a scheduled task

# AD side
New-ADUser svc-<purpose> -Path "OU=Service Accounts,<domain DN>" -AccountPassword (Read-Host -AsSecureString) `
  -Enabled $true -PasswordNeverExpires $true -CannotChangePassword $true -KerberosEncryptionType AES256
Set-ADUser svc-<purpose> -LogonWorkstations '<host>'
# Host side (elevated)
icacls C:\Scripts\<purpose> /grant "DOMAIN\svc-<purpose>:(OI)(CI)M"
# Grant "Log on as a batch job": secedit export, add the SID to SeBatchLogonRight, secedit import; or Local Security Policy GUI
# Task: Register-ScheduledTask ... -User DOMAIN\svc-<purpose> -Password <pw> (LogonType Password)

Prefer a gMSA where the task is PowerShell or a service that supports it. Once per forest, a KDS root key must exist (Add-KdsRootKey). Then New-ADServiceAccount gmsa-<purpose> -DNSHostName gmsa-<purpose>.<domain> -PrincipalsAllowedToRetrieveManagedPassword <host>$ (name 15 characters or fewer; -DNSHostName is the gMSA's own name, not the host's); grant it Log on as a batch job on the host; Install-ADServiceAccount on the host; task principal DOMAIN\gmsa-<purpose>$ with no password.

R6: Windows LAPS

  1. On the schema master, as a member of Schema Admins (add yourself, get a fresh ticket by signing out and in, remove yourself afterwards): Update-LapsADSchema.
  2. Set-LapsADComputerSelfPermission -Identity "OU=Workstations,<domain DN>" and the same for the server OU.
  3. GPO (Computer Configuration › Administrative Templates › System › LAPS): Configure password backup directory = Active Directory; Password Settings = complexity 4, length 24, age 30; Post-authentication actions = Reset the password and log off the managed account, 24 hours; Enable password encryption = Enabled (2016 DFL+); Configure authorized password decryptors = Domain Admins or the helpdesk group.
  4. Link to the pilot OU; gpupdate; Get-LapsADPassword <computer> -AsPlainText from the admin host.
  5. Delegate Get-LapsADPassword read to the helpdesk group with Set-LapsADReadPasswordPermission. With encryption on, the helpdesk must also be the authorized decryptor, which takes a single principal: use one group that holds both Domain Admins and the helpdesk.

R7: Point and Print, done in the right order

  1. On the print server: Get-PrinterDriver | Select Name, Manufacturer and the driver package paths (Get-PrinterDriver | Select InfPath).
  2. Push the packages to every PC in machine context: pnputil /add-driver <inf> /install then Add-PrinterDriver -Name "<exact model name>" for each name in step 1, via the endpoint tool or a SYSTEM startup task. Verify Get-PrinterDriver on a sample of PCs lists every name.
  3. GPO: Package Point and print - Approved servers = print server FQDN; Point and Print Restrictions = Enabled, users can only point and print to these servers, When installing drivers for a new connection = Do not show warning or elevation prompt is removed, i.e. NoWarningNoElevationOnInstall=0, UpdatePromptSettings=0; Limits print driver installation to Administrators = Enabled (RestrictDriverInstallationToAdministrators=1).
  4. Link to one building OU for one week. Watch for "Printer driver software installation" UAC prompts and Group Policy Printers errors in the Application log.
  5. Roll out. Keep a rollback copy of the GPO.
  6. Independently (no user impact): Allow Print Spooler to accept client connections = Disabled on workstations and servers with a Deny-Apply ACE for the print server; spooler disabled on DCs, hypervisors and non-printing servers.

R8: DC backups

Tier 1: VM-level (the one you will restore from).

  1. In the backup product, the DC VMs get application-aware (VSS) processing so the directory database is quiesced; verify the job log shows the VSS writer for NTDS succeeded.
  2. Repository: immutable (object lock) or an offline/air-gapped copy; the backup service account is svc-backup, never a human admin (Phase 4).
  3. Restore test annually into an isolated network. Never revert a DC VM to a hypervisor checkpoint.

Tier 2: system state (the authoritative-restore path).

  1. On one DC (the PDC is fine): scheduled task as SYSTEM, wbadmin start systemstatebackup -backupTarget:\\ <tier0 share> -quiet, weekly. Share and NTFS: SYSTEM and Domain Admins only; the DC's computer account Modify.
  2. Confirm its last-success date monthly (wbadmin get versions). This is the job that silently stops.

Tier 3: object-level exports (restore one thing without touching the DC).

  1. Weekly script on the PDC as SYSTEM: Backup-GPO -All -Path <stage>\GPO; Get-GPOReport -All -ReportType Html; ldifde -f <stage>\domain.ldf -d "<domain DN>" -p subtree; CSV exports of users, groups, memberships, computers, OUs; INVENTORY.txt with FSMO roles, sites, subnets, trusts, password policy, DNS zones, counts; SHA-256 manifest; README with restore steps; robocopy to a Tier 0 share; prune older than 60 days.
  2. Allow the script's hash in application control before the first run. Monday after: read the log, confirm the folder exists and is the expected size.

Optional and not generally advised: IFM.

  1. ntdsutil "activate instance ntds" ifm "create sysvol full <stage>" quit quit produces a full ntds.dit plus SYSTEM/SECURITY hives: every hash, as files. Only take it if you have a stated forensic need (offline mount with dsamain -dbpath <copy> -ldapport 51389, which exists only on a DC or AD LDS server), keep it on a DC or an encrypted Tier 0 volume, and delete it on a schedule. It satisfies the scanners' backup-age rule, but so do tiers 1 and 2, so that is not a reason.

R9: Retiring student accounts (or any bulk delete) safely

  1. List every system that reads the OU: Entra Connect (OU filter), GCDS (config), print management, library, cafeteria, LMS, RADIUS/NPS, any script. For each: does it delete, suspend or disable on sync? Neutralise or re-scope before deleting.
  2. Confirm Google student accounts are provisioned from the SIS, not from AD.
  3. Confirm no NPS/RADIUS server exists that authenticates Wi-Fi against student AD accounts.
  4. Confirm the only GPOs linked to the student OU affect nothing else.
  5. Export: every user (DN, GUID, memberOf, attributes) to CSV; every BitLocker key under every computer to be deleted; Backup-GPO -All; a system-state backup (R8 tier 2).
  6. Confirm the Recycle Bin is on and msDS-DeletedObjectLifetime (blank means it equals tombstoneLifetime) is 180 days.
  7. Delete in batches with Remove-ADObject -Recursive from the PDC; keep the result CSV (DN, GUID, status).
  8. Keep OUs and groups unless nothing references them; remove the GPO links that now apply to nothing.
  9. Expect: ntds.dit does not shrink until an offline defrag; Entra shows a large delete if the scope included the objects; sync tools re-scoped in step 1 do nothing.

R10: SPNs for host names over 15 characters

Get-ADComputer -Filter {Enabled -eq $true} -Properties DNSHostName,ServicePrincipalName |
  ? { ($_.DNSHostName -split '\.')[0].Length -gt 15 } | % {
    $c    = $_
    $full = ($c.DNSHostName -split '\.')[0]
    $add  = @("HOST/$full","RestrictedKrbHost/$full") | ? { $c.ServicePrincipalName -notcontains $_ }
    if ($add) { Set-ADComputer $c -ServicePrincipalNames @{Add=[string[]]$add} }
  }

Safe to re-run. Add to the weekly staging-OU check, or cap names at 15 characters at imaging.

R11: Finding plaintext credentials in script and export folders

# Files whose first line looks like a header with a password column
Get-ChildItem C:\Scripts,C:\Tools,C:\Automation -Recurse -Include *.csv,*.txt,*.text,*.log -ErrorAction SilentlyContinue |
  ? { (Get-Content $_.FullName -First 1 -ErrorAction SilentlyContinue) -match '(?i)pass(word|wd)' } |
  Select-Object FullName, Length, LastWriteTime
# Who can read the folder
icacls C:\Scripts
# Service-account keys, DPAPI blobs, private keys
Get-ChildItem C:\Scripts -Recurse -Include *.json,*.cred,*.ppk,*.pem,*.key,.env* | Select FullName

For each hit: is it still produced? (If the producer is dead, delete the archive.) Who reads it? (ACL to that account only.) Can the producer stop writing the column? (Usually yes.)

R12: Helpdesk delegation groups

GroupScopeAllowDeny
Helpdesk Password Resetuser objects under the staff OUExtended right Reset Password; Write pwdLastSet, lockoutTime (and pager or whatever carries the badge number)Same rights on the IT / admin / service-account OU
Helpdesk User Creationstaff OU and sub-OUsCreateChild (user); GenericAll on descendant user objectsBoth on the IT / admin / service-account OU
Helpdesk Computer Managementworkstation OUCreateChild + DeleteChild (computer); on descendant computers, Reset Password, validated writes to dNSHostName and SPN, and Write Account Restrictions (not GenericAll, which also exposes legacy LAPS passwords and RBCD)Nothing on the server OUs or CN=Computers
Helpdesk Group Membershipthe containers holding operational groups (printing, drive mapping, licensing)Read + Write on the member attribute, InheritedObjectType = groupExplicit Deny on admin, delegation and infrastructure groups (AdminSDHolder-protected groups need none; SDProp resets their ACL hourly)

Apply with Set-Acl on an AD: drive pinned to one DC (-Server), then repadmin /syncall. Export each container's SDDL before and after. The technician signs out fully before testing; a reconnected session keeps the old token.

R13: DCSync rights and admin credential locations

# Who can replicate
$dn = (Get-ADDomain).DistinguishedName
(Get-Acl "AD:\$dn").Access | ? { $_.ObjectType -in '1131f6aa-9c07-11d1-f79f-00c04fc2dcd2','1131f6ad-9c07-11d1-f79f-00c04fc2dcd2' } |
  Select IdentityReference, ObjectType, AccessControlType
# AdminSDHolder write access (should be built-ins only)
(Get-Acl "AD:\CN=AdminSDHolder,CN=System,$dn").Access | ? { $_.ActiveDirectoryRights -match 'Write|GenericAll' } | Select IdentityReference,ActiveDirectoryRights

# Where admin passwords are typed (TGT requests by source IP), last 7 days, all DCs
$admins = (Get-ADGroupMember 'Domain Admins' -Recursive).SamAccountName
foreach ($dc in (Get-ADDomainController -Filter *).HostName) {
  Get-WinEvent -ComputerName $dc -FilterHashtable @{LogName='Security';Id=4768;StartTime=(Get-Date).AddDays(-7)} |
    ? { $_.Properties[0].Value -in $admins } |
    Select @{n='User';e={$_.Properties[0].Value}}, @{n='IP';e={$_.Properties[9].Value}}, TimeCreated
} | Group User, IP | Select Count, Name

# NTLM refused by Protected Users (0xC000006E) by account and source
Get-WinEvent -FilterHashtable @{LogName='Security';Id=4776} -MaxEvents 5000 |
  ? { $_.Properties[-1].Value -eq '0xc000006e' } | Group { "$($_.Properties[1].Value) from $($_.Properties[2].Value)" } | Select Count,Name

Gotchas: what breaks when you turn things on

CHANGEWHAT BREAKS OR SURPRISES YOUDO THIS FIRST
Protected Users (or any NTLM restriction)RDP, SMB and WMI by IP fail (no SPN, NTLM fallback refused). DCOM to host names over 15 characters fails "Access is denied" (truncated SPN). 4-hour non-renewable TGT: long sessions lose every ticket. klist purge leaves the session with no tickets until lock/unlock or a fresh sign-in. Tasks running as the admin interactively fail overnight. Apps that validate logins over NTLM (print management web UI, some helpdesk tools) say "invalid password". Backup software with the admin's stored credential fails every job. Stale disconnected sessions log pre-auth failures every ~100 minutes.FQDN everywhere; DNS records for every server; SPNs for long names (R10); tasks and services to service accounts (R5); app-specific admin logins; break-glass account outside the group; disconnected-session limit; one admin at a time (R3).
LmCompatibilityLevel=5Copiers (scan-to-SMB), old NAS, some Linux Samba clientsTwo weeks of 4624 LmPackageName = NTLM V1 audit; do not test with a protected account
LDAP signing / channel bindingFirewall, VPN, wireless controller, copier and library LDAP binds in cleartextDirectory Service event 2889 audit; move each to LDAPS/636
SMB signing required on serversSMB1-only scanners and copiers; some very old NASReplace the destination with SFTP/cloud drop or isolate
Kerberos AES only / RC4 removedOld Macs, Linux boxes, appliances with RC4-only keytabs; service accounts without an AES key until their password is reset onceWatch 4769 0x17 for a month; reset SPN account passwords after setting AES
Removing Authenticated Users from Pre-Windows 2000Legacy NAS, RADIUS and LDAP browsersRemove for a week on a test basis; have the rollback ready
Removing an account from an admin groupAdminSDHolder rule stays; account keeps protected ACL; helpdesk cannot reset its passwordClear adminCount, re-enable inheritance
Moving servers out of CN=ComputersDifferent GPOs apply (update policy, baseline); workstation GPOs stop (shutdown tasks, drive maps); helpdesk loses local admin and RDPReview destination OU links; add helpdesk to Remote Desktop Users where needed
Deny admin logon to workstationsAdmins cannot RDP or sign in to any staff PCNon-privileged workstation-admin accounts; tell admins first; never link to server OUs
Authentication Policy Silo enforcedEvery admin locked out at once if the computer list is wrongAudit mode 2 to 4 weeks; break-glass outside the silo and tested
Kerberos armoring "fail unarmored"Non-Windows clients and appliances cannot authenticateRun in Supported mode first; leave fail-unarmored off
SYSVOL / NETLOGON ACL changeMay not reach the other DC (replicated-folder root ACL, a broken DFSR, or a lagging DC)Apply and read back on each DC
GPO edited directly in SYSVOLClients ignore itBump versionNumber in AD and GPT.INI (R2)
Redirecting new joins to a staging OUNewly joined servers get workstation policy (shutdown task, Restricted Groups) until movedWeekly staging-OU check; move servers at once
Point-and-Print restrictions onDistrict-wide UAC prompts for every printer the next morning; GPP printer items re-trigger it every refreshPre-stage every driver model as SYSTEM; pilot one building (R7)
Spooler disabled on a serverSomething on it printed (badge printer, label printer, report-to-PDF)Check Get-Printer on the server first
AUOptions=4 on serversServers now reboot on the schedule; both DCs or both hypervisors on the same schedule reboot togetherStagger schedules; one DC and one host per window
Password change on an admin that runs a serviceService fails with 1069 at next restart, usually a weekend rebootMove services off human accounts before the change
New export or backup task as SYSTEM on a DCApplication control blocks the script; "succeeded" in 1 second with exit 1Allow the hash; read the log after the first run (R8)
IFM backup "for safety"A second full copy of every password hash as plain files, usually on a server with helpdesk local adminsPrefer VM-level and system state; if IFM is truly needed, DC-only or encrypted, deleted on a schedule
Reverting a DC VM to a checkpointUSN rollback: the DC silently stops replicating and nobody notices for weeksNever checkpoint-revert a DC; restore from backup and verify repadmin /showrepl
Tightening the backup folder ACLYour own non-elevated session can no longer read it; icacls /reset /T re-inherits the parentKeep an explicit ACE for yourself; order the icacls steps (Phase 6)
Stale-computer sweepDisables AZUREADSSOACC (breaks Seamless SSO) or the Entra Connect server; deletes BitLocker keys; list includes live machinesExclude SSO and sync accounts; export msFVE-RecoveryInformation; check LastLogon and 4768
Deleting student (or any bulk) AD accountsA dormant directory-sync config suspends the matching Google accounts; print management or library deletes users; Entra deletes devicesR9 step 1: find and neutralise every sync first
Removing hybrid joinAlready-joined PCs stay joined; Entra Connect still syncs computer objectsdsregcmd /leave; change the connector scope
LAPS schema extensionNeeds Schema Admins plus a fresh ticket; protected admins cannot klist purgeSign out and in after adding yourself; remove yourself after
Bulk Google password reset keyed on lastLoginTimeChromebook users were not locked out; now they are, and the device reports it days laterUse Reports API login events
Moving suspended Google usersOU-based licensing counts suspended usersRoot-level Suspended Users OU
Application control on the admin hostDNS answers rewritten locally; "network is down" conclusions drawn from the wrong host; weekend lockdown policy blocks remediationTest network problems from a second host; document the out-of-hours policy lift
15-character NetBIOS limitBroken SPNs, broken secure channel, "clock is always wrong"Naming standard; R10
Helpdesk delegation ACL addedTechnician "still can't" until full sign-outSign out, not disconnect
Non-elevated admin session on a serverCannot set owner, cannot grant logon rights, cannot read folders it just tightenedStart-Process -Verb RunAs for the elevated step
Disconnected-session time limitAdmins' long-running interactive jobs die when the session is reapedMove long jobs to tasks under service accounts
Security log raised to 4 GBNothing; raising the size keeps the existing events (only shrinking needs a clear)Raise it, don't shrink it

Rollout order

Pilot (one server, one building OU) › Tier 0 servers › remaining servers › workstation OUs by building

Each step waits for the previous step's gate.

  1. Before you start and Phase 0 (read, back up, break-glass, baseline PingCastle). One day.
  2. Phase 3 user rights and SYSVOL ACLs, Phase 1 admin group cleanup, Phase 6 dead stored-password tasks: under an hour each, zero user impact, each closes a critical path. Do these the same week.
  3. Phase 4 tiering: redircmp, servers into OUs, local admins on Tier 0, Entra Connect cleanup, patch policy and a patch window.
  4. Phase 6 service accounts for everything that runs as a human.
  5. Phase 1 Protected Users, one admin at a time, after step 4.
  6. Phase 1 deny-logon GPO and separate -adm accounts.
  7. Phase 4 backups: VM-level with application-aware processing, system state on a DC, exports; confirm two runs of each; delete any IFM job nobody can justify.
  8. Phase 5: LAPS, spooler inbound block, firewall; Point-and-Print only after driver pre-staging.
  9. Phase 2 protocol hardening with its audit periods (NTLM, LDAP, SMB, RC4), copiers and appliances fixed as they surface.
  10. Phase 7 hygiene and Phase 8 sync boundaries; student retirement if applicable.
  11. Phase 9 detection, in parallel from step 3 so the retests themselves show up in the logs.
  12. Phase 1 Authentication Silos and Phase 2 armoring, audit mode first, last of all.
  13. Phase 10 on the calendar.

DCs vs. servers vs. workstations at a glance

DOMAIN CONTROLLERSTIER 0 / MEMBER SERVERSWORKSTATIONS
Local AdministratorsDomain Admins (implicit)Domain Admins + one svc- where requiredLAPS account + workstation-admin group
Who may log on interactivelyDomain Admins from the admin hostDomain Admins from the admin host; svc- as batch/service onlyUsers; Domain Admins denied
Patch policyScheduled install, staggeredScheduled install, staggeredEndpoint tool / scheduled install
Print SpoolerDisabledDisabled unless it is the print serverRunning, inbound connections blocked
SMB signingRequiredRequiredRequired
LDAP signing / CBTRequired / Alwaysn/an/a
NTLMAudit then restrict; admins never (Protected Users)SameSame
RDPNLA, admin host onlyNLA, admin host and documented vendorsNLA + Remote Desktop Users, or off
Credential Guard / LSA PPLLSA PPLLSA PPL (+ CG where supported)Both
Security log4 GB, forwarded1 GB, forwarded (Tier 0)Default, local
FirewallOn; management from admin hostOn; management from admin hostOn; 135/445/5985 from admin host only
Time sourceExternal NTP on the PDC; VM IC provider offDomain hierarchyDomain hierarchy
BackupVM-level (application-aware, immutable repo) + system state weekly; GPO/LDIF exports; IFM only with a stated needPer roleNone (data lives elsewhere)
OUDomain ControllersServers / Tier0Workstations by building; staging for new joins

Group Policy paths and PingCastle rule names move between releases. If one is wrong, open an issue or PR.

Jim Tyler · MAEDS 2026 · An independent project, written and published on my own time. Free to use and share. Admin console paths, Group Policy paths and scanner rule names move between releases; if one is wrong, tell me.