windowsServer Windows Server Master Checklist
AD DS, DNS, DHCP, GPO, IIS, and server-role hardening for Domain Controllers and member servers.
A comprehensive, competition-ready checklist for hardening a Windows Server image (2008/2008 R2/2012/2012 R2/2016/2019) in CyberPatriot โ covering Active Directory Domain Services, DNS, DHCP, File/Print, IIS, and general member-server hardening. This document assumes the image may be a Domain Controller, a domain-joined member server, or a standalone server, and calls out where the correct approach differs.
Golden rule: Read the README on the Desktop FIRST. It tells you the scenario, the server's required roles (AD DS/DNS/DHCP/File/Print/Web), which users/groups must exist, and any instructions that override the generic advice below. If the README says a role or account is required, do not remove/disable it just because a checklist says "unnecessary." Scoring engines reward fixing vulnerabilities AND penalize breaking required functionality โ breaking AD, DNS, DHCP, or a required share can cost far more than any single hardening item is worth.
About the CIS references in this document: Where relevant, "Things to try / extra points" items below are cross-referenced against the CIS Microsoft Windows Server Benchmark. CIS publishes separate benchmark profiles for a Domain Controller and a Member Server โ a tip is flagged as DC-specific or Member-Server-specific where that distinction matters. Controls are also tagged Level 1 (baseline hardening, low risk of breaking functionality โ safe to apply broadly) or Level 2 (stricter/defense-in-depth, higher chance of affecting required functionality โ apply carefully and test). Controls are referenced by name/category rather than exact control-ID numbers, since numbering shifts between benchmark versions and OS releases โ treat these as directional guidance, not a substitute for the actual benchmark document if your team has access to it.
Table of Contents#
- Initial Recon & README
- Forensics Questions Strategy
- Server Roles & Features Audit (Do This Before Touching Services)
- Active Directory Users & Computers Auditing
- Group Policy Management (Domain-Level Policy)
- Password & Account Lockout Policy
- Active Directory Health, Replication & Kerberos Hardening
- DNS Server Hardening
- DHCP Server Checks
- File & Print Server / Share Permissions
- Windows Firewall
- Services Hardening
- Windows Updates
- Remote Desktop / RDS / Remote Access
- IIS Web Server Hardening
- Local Accounts & Built-In Accounts (On the Server Itself)
- Event Log & Audit Policy
- Registry, System Hardening & Exploit Mitigations
- Malware, Persistence & Backdoor Hunting
- Backup & Restore Considerations
- Server 2008 vs. 2012+ UI Differences
- Final Sweep / Closing Checklist
1. Initial Recon & README#
- Read the Desktop README completely before making any changes. Screenshot or copy it somewhere safe (it sometimes disappears or the scoring engine resets desktop contents).
- Identify the server's role(s) from the README AND from the OS itself โ a server can hold multiple roles simultaneously (e.g., DC + DNS + DHCP, or File + Print):
- Active Directory Domain Services (Domain Controller)
- DNS Server
- DHCP Server
- File and Storage Services / File Server
- Print and Document Services
- Web Server (IIS)
- Remote Desktop Services (RDS)
- Note the required users, groups, and OUs mentioned in the README โ these must survive your cleanup.
- Note any specific software required to remain installed (this is common on server images tied to a "business scenario").
- Identify the OS version (Server 2008/2008 R2/2012/2012 R2/2016/2019) since UI paths differ โ see Section 21.
- Check the hostname, domain name, and IP configuration match what the README expects โ don't break networking by "fixing" a static IP the DHCP/DNS role depends on.
Things to try / extra points#
- Run
Get-WindowsFeature | Where-Object Installedimmediately โ this is the fastest way to see every installed role/feature before you form an opinion about what's "supposed" to be there. - Run
systeminfoandhostnameto confirm OS build/edition and name match the README. - Check
ipconfig /alland compare DNS server, gateway, and DHCP-assigned vs. static addressing against what a DC/DNS/DHCP box should have (DCs and DNS/DHCP servers almost always need a static IP pointing at themselves for DNS). - If unsure whether the box is a DC, run
whoami /groups(look forEnterprise Domain Controllers) orGet-ADDomainControllerโ if that cmdlet works,ActiveDirectorymodule is present and it's likely a DC or has RSAT tools installed. - One-stop alternative:
Get-ComputerInfobundles OS build, edition, domain role, and boot time into a single object โ faster than runningsysteminfo+hostname+ipconfigseparately when you just need a quick sanity check. - Fallback if
Get-WindowsFeatureerrors out (mainly a 2008 R2 issue where the module isn't auto-loaded):Import-Module ServerManagerfirst, or fall back entirely to the Server Manager GUI's Roles/Features summary if PowerShell tooling isn't cooperating. - Verification habit: whatever you learn in this recon pass (roles, hostname, IP config), save it to a text file outside the Desktop (e.g.,
C:\Users\Public\recon_baseline.txt) โ you'll want to diff against this baseline in the Final Sweep to confirm nothing required was accidentally broken.
Tip: Do the recon pass with a notepad file open (not saved on the Desktop where a reset might wipe it) tracking: server roles found, accounts found, current firewall/policy state. You will reference this constantly.
๐ธ SNAPSHOT CHECKPOINT โ take one right now, before you touch anything#
Once you've read the README and identified the server's role(s), take a VM snapshot in the hypervisor (VirtualBox: Machine โ Take Snapshot; VMware: VM โ Snapshot โ Take Snapshot; Hyper-V Manager: right-click the VM โ Checkpoint) before making a single configuration change. This is a host-level safety net, separate from Windows' own System Restore โ it's the fastest way to recover if a later change breaks AD, DNS, or networking beyond repair. Do this now; don't wait.
2. Forensics Questions Strategy#
Server images frequently include forensics questions (text files asking you to identify a misconfiguration, an unauthorized account, a malicious file, etc.) that award points independently of system hardening.
- Find and read all forensics question files before changing anything they might be asking about โ changing state can make the answer harder to find or invalidate it.
- Answer directly in the file/format requested (usually a
.txton the Desktop) โ save it before moving on. - Common forensics themes on server images: unauthorized AD accounts, suspicious scheduled tasks, a stale/rogue DHCP or DNS entry, a suspicious share, a misconfigured GPO, or a specific log entry (Event ID) showing an intrusion.
Things to try / extra points#
- Before making destructive changes, take note of ("snapshot") current state so you can answer forensics questions accurately:Expected result: Three CSV files appear inpowershell
Get-ADUser -Filter * -Properties * | Export-Csv C:\Users\Public\ad_users_snapshot.csv -NoTypeInformation Get-LocalGroupMember Administrators | Export-Csv C:\Users\Public\local_admins_snapshot.csv -NoTypeInformation Get-WindowsFeature | Where-Object Installed | Export-Csv C:\Users\Public\roles_snapshot.csv -NoTypeInformationWhat this pipeline does
Get-ADUser -Filter * -Properties * means "get every AD user account, with every available property field" (by default PowerShell only shows a handful of common fields). The | (pipe) sends that full list into Export-Csv, which writes it out to a spreadsheet-readable file โ this is essentially "dump the entire user database to a file I can review or open in Excel."C:\Users\Public\. No console output if run without-Verbose; each CSV, opened in Notepad or Excel, shows one row per user/local admin/installed feature with the properties named. **If it fails:**Get-ADUsererrors with "The term 'Get-ADUser' is not recognized" โ theActiveDirectorymodule isn't loaded; runImport-Module ActiveDirectoryfirst, and if that also fails, install RSAT withAdd-WindowsFeature RSAT-AD-PowerShell(this cmdlet only exists/works on a Domain Controller or a member server with RSAT tools added โ it will never work on a workgroup box with no AD).Get-LocalGroupMembercan throw on very old builds where the cmdlet doesn't exist yet โ fall back tonet localgroup Administrators. "Access denied" on the CSV path means you're not running PowerShell as Administrator. - If a forensics question asks "who is the unauthorized user," cross-reference
Get-ADUser -Filter * -Properties WhenCreated,LastLogonDatesorted byWhenCreatedโ recently created accounts are prime suspects. - Check
Get-EventLog Security -InstanceId 4720 -Newest 20(orGet-WinEvent -FilterHashtable @{LogName='Security';Id=4720}) for recent "user account created" events โ gives you exact timestamps and the account that created the suspicious user. - Fallback if
Get-EventLogisn't available or feels slow:Get-WinEventis the modern replacement and works identically across all supported Windows Server versions โ prefer it going forward;Get-EventLogis deprecated in newer PowerShell versions and may not exist at all on non-Windows PowerShell hosts. - Edge case โ anchor suspicious activity to the boot time:
(Get-CimInstance Win32_OperatingSystem).LastBootUpTimetells you when the machine last rebooted. Anything timestamped before that (in Run keys, scheduled tasks, or recently modified files) survived a reboot and is more likely to be deliberate persistence rather than something you or a teammate did during this session. - Cross-check recently modified files, not just recently created ones โ an attacker editing an existing legitimate script is easy to miss if you only look for new files:Expected result: A table ofpowershell
Get-ChildItem C:\ -Recurse -File -ErrorAction SilentlyContinue | Where-Object { $_.LastWriteTime -gt (Get-Date).AddDays(-3) } | Select-Object FullName, LastWriteTime | Sort-Object LastWriteTime -DescendingFullName/LastWriteTimepairs, newest first โ expect it to be long and noisy (Windows Update, temp files, log rotation all touch files constantly), so scan for anything in an unusual location (C:\Users\Public,C:\ProgramData,C:\Windows\Temp, a user'sAppData\Roaming) rather than expecting a short clean list. If it fails: Runs very slowly or appears to hang โ a fullC:\recursive scan on a server with large data volumes or many files can take minutes; narrow the path (e.g.,C:\Users,C:\ProgramData) if you have a hunch where to look. Individual "Access denied" errors on system folders are expected and suppressed by-ErrorAction SilentlyContinueโ don't mistake a slow-but-running scan for a frozen one. - Remember: server changes are more disruptive than client changes. Document your baseline before you start disabling/removing things, in case you need to prove what was "before" vs "after" for a forensics answer.
Encoded / Obfuscated Data#
Forensics questions sometimes hide their real content behind an encoding rather than plain text โ a real CyberPatriot practice round included a base64-encoded message on the desktop. Recognize the pattern, then decode:
| Looks like | Likely encoding | Tell |
|---|---|---|
SGVsbG8gV29ybGQh |
Base64 | Only letters/digits/+//, often padded with =/==, length a multiple of 4 |
48656c6c6f20576f726c6421 |
Hex | Only 0-9 and a-f, always an even number of characters |
Uryyb Jbeyq |
ROT13 / Caesar shift | Garbled but word lengths/spacing/punctuation match real text |
Hello%20World%21 |
URL encoding | % followed by two hex digits |
# Base64 decode โ offline, no internet needed
[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("SGVsbG8gV29ybGQh"))
# Base64 decode a file with certutil (no PowerShell needed)
certutil -decode input.b64 output.txt
# Hex decode
-join (("48656c6c6f20576f726c6421") -split '(?<=\G..)(?!$)' | ForEach-Object { [char][Convert]::ToInt32($_,16) })
# ROT13 decode (self-inverse)
function Decode-Rot13($s) {
-join ($s.ToCharArray() | ForEach-Object {
if ($_ -cmatch '[a-z]') { [char]((([int][char]$_ - 97 + 13) % 26) + 97) }
elseif ($_ -cmatch '[A-Z]') { [char]((([int][char]$_ - 65 + 13) % 26) + 65) }
else { $_ }
})
}
Decode-Rot13 "Uryyb Jbeyq"
- The Base64 example prints
Hello World!; the hex example printsHello World!; the ROT13 function call printsHello World. - All run with zero internet access, using only built-in .NET types (
System.Convert,System.Text.Encoding) and PowerShell string operators.
FromBase64Stringthrows "Invalid length for a Base64 char array" โ the string is missing padding=characters or has line breaks/whitespace copied in from the source file; strip whitespace with.Trim()or-replace '\s',''before decoding.- The hex one-liner throws if the string has an odd number of characters or non-hex characters โ verify with
$s.Length % 2 -eq 0first. certutil -decoderequires the input to be pure standard Base64 (no line breaks longer than certutil expects is actually fine, but a non-Base64 file will produce "CertUtil: -decode command completed successfully" and still write garbage output โ open the result and sanity-check it rather than trusting the exit message alone).
- Check likely hiding spots beyond the obvious desktop
.txtfile: SYSVOL scripts, a GPO description field, a Scheduled Task's description, an AD user'sDescription/Notesattribute โ all of these can hold an encoded string on a server-flavored forensics question. - Try base64 first if unsure โ it's by far the most common encoding used, and a failed decode is instant confirmation to try something else.
3. Server Roles & Features Audit (Do This Before Touching Services)#
Order of operations matters. Server roles depend on specific underlying services. If you go straight to "Services Hardening" and start disabling things that look unfamiliar, you may cripple AD DS, DNS, DHCP, or File Services and lose far more points than you gain. Always inventory roles/features FIRST via Server Manager or
Get-WindowsFeature, understand what's actually required by the README, and only then touch individual services.
- Open Server Manager โ Manage โ Add/Remove Roles and Features (or Roles/Features panes on 2008 R2).
- Compare the installed roles list against the README's stated required roles.
๐ธ Snapshot checkpoint: Removing a role can be disruptive and isn't always cleanly reversible without a restart or reinstall. If you haven't already taken a snapshot in this session, take one now โ right before you start removing roles/role services โ so a wrong removal is a one-click revert instead of a rebuild.
- Remove roles/role services that are NOT required and NOT mentioned in the README (this is almost always worth points โ "Rogue Roles").
- Under Features, remove anything non-mission-critical. A default roleless install typically has zero extra features โ anything installed beyond defaults is suspect.
- For roles you ARE keeping, expand Role Services and remove unnecessary sub-components (e.g., keep DNS but not other bundled extras; keep IIS but remove FTP/CGI/ASP if not used).
- Run the Best Practices Analyzer (BPA) for each installed role (Server Manager โ select role โ Summary pane โ Best Practices Analyzer โ Start BPA Scan). Review items marked Noncompliant; understand each setting before changing it โ don't blindly "fix" everything BPA flags, since some Noncompliant items are intentional to the scenario.
- Review Configure IE Enhanced Security Configuration (IE ESC) โ set both Administrators and Users to On. Do this AFTER any internet-based downloads/updates you need, since ESC aggressively blocks browsing.
- Review Server Manager Remote Management โ leave enabled unless the README says otherwise; disabling it can break scoring engine connectivity in some images. Test carefully.
Things to try / extra points#
-
Get-WindowsFeature | Where-Object Installedโ full inventory, works on 2012+ without opening the GUI. -
Get-WindowsFeature | Where-Object {$_.InstallState -eq "Available"}โ see what's NOT installed (sanity check nothing sneaky was added outside Server Manager, e.g., via DISM/PowerShell directly, that Server Manager's role list might not surface obviously). - Remove a feature safely once confirmed unneeded:Expected result: Output object withpowershell
Remove-WindowsFeature -Name <FeatureName>Success : True,RestartNeeded : No(orYes), andFeatureResultlisting the feature(s) removed. IfRestartNeededisYes, the feature isn't fully gone until you reboot. If it fails: "The term 'Remove-WindowsFeature' is not recognized" happens on non-server SKUs or very old builds without the ServerManager module โ tryImport-Module ServerManagerfirst, or use the DOS/DISM fallback below. "Invalid feature name" means the<FeatureName>string doesn't match โ get exact names withGet-WindowsFeature | Where-Object Installed | Select-Object Name. Removing a feature that another installed role depends on can prompt a dependency warning or silently break that role โ read the confirmation text before proceeding, and add-Restartonly if you're sure you're ready for the box to reboot. Or in DOS:Expected result:shelldism /online /disable-feature /featurename:<FeatureName>Deployment Image Servicing and Management toolbanner followed by a progress percentage andThe operation completed successfully.(a restart may be flagged as required). If it fails: "Error: 87" or "Error: 0x800f080c" usually means the/featurename:value is wrong โ DISM feature names often differ from theGet-WindowsFeaturedisplay name (e.g., useGet-WindowsOptionalFeature -Onlineto list DISM-style names). "Error: 5" is access denied โ the Command Prompt must be run as Administrator. - IE ESC toggle from command line (registry method, works when GUI toggle is finicky):Expected result: Both commands printshell
REG ADD "HKLM\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A7-37EF-4b3f-8CFC-4F3A74704073}" /v IsInstalled /t REG_DWORD /d 00000001 /f REG ADD "HKLM\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A8-37EF-4b3f-8CFC-4F3A74704073}" /v IsInstalled /t REG_DWORD /d 00000001 /fThe operation completed successfully.; the first GUID (...A7-...) controls Administrators ESC, the second (...A8-...) controls Users ESC โIsInstalled = 1means ESC is ON for that group. If it fails: "ERROR: Access is denied" โ Command Prompt must be elevated (Run as Administrator); writing underHKLMalways requires admin rights. If the change doesn't visibly reflect in Server Manager's dashboard until you reopen it, that's normal โ Server Manager caches the ESC state and only re-reads it on refresh/reopen. Double-check you didn't transpose the two GUIDs (Administrators vs. Users) โ it's an easy copy-paste mistake that toggles the wrong group. - If a GUI setting can't be found or a command line change "silently fails" with no visible effect, verify with a read-back command rather than assuming success โ this is a classic way teams think they scored a point but didn't.
- Verify a removal actually completed:
Remove-WindowsFeaturecan returnSuccess = TruewithRestartNeeded = Yesโ the role isn't fully gone until that restart happens. Re-runGet-WindowsFeature -Name <FeatureName>afterward; ifInstallStatestill showsRemovedpending a restart, note that a reboot is required before the change is considered complete (tie this to a snapshot/reboot checkpoint). - Edge case โ feature installed outside Server Manager: a role or optional component enabled purely via DISM/PowerShell can sometimes be invisible or oddly labeled in the Server Manager GUI. If
Get-WindowsFeatureshows something installed that the GUI doesn't clearly surface, trust the PowerShell output and investigate withGet-WindowsOptionalFeature -Onlineas a second opinion (covers optional features that sit outside the Roles/Features model, like SMB1Protocol). - 2008 R2 fallback: if
Get-WindowsFeature/Remove-WindowsFeaturearen't available at all, the legacy tool isServerManagerCmd.exe -query/ServerManagerCmd.exe -remove <FeatureID>โ slower and less commonly needed, but useful if the newer cmdlets throw errors on an older image.
Tip: BPA has no reliable command-line equivalent worth relying on in competition โ do it through the GUI, one role at a time, and actually read what each Noncompliant item means before "fixing" it.
A note on Microsoft Baseline Security Analyzer (MBSA): older CyberPatriot checklists (including some of the source material this document was built from) list MBSA as a scan-and-patch tool worth running on a server. Don't bother โ Microsoft discontinued MBSA in 2018, it has no official support for Windows Server 2012 R2 and later, and it will generally fail to install or run correctly on any modern (2016+) server image. BPA above is Microsoft's actual current-generation replacement for that same "is this role configured per best practice" role. If you see MBSA mentioned in an older resource, treat it as outdated advice.
Third-party AV/cleaner GUI tools (Malwarebytes, CCleaner, Spybot โ Search & Destroy) are documented with full install-and-use steps in the Windows Client Master Checklist, Section 8 โ they work identically on Server if a desktop experience is installed and internet access is available/permitted. The install steps, exact menu options, and post-scan scoring-report check-in are the same; the only server-specific caveat is to never let a cleaner tool touch anything under C:\Windows\NTDS, SYSVOL, or an active role's data directories โ if a scan flags something there, investigate manually rather than letting the tool auto-remove it, since role-critical files can superficially resemble what these tools are designed to catch.
4. Active Directory Users & Computers Auditing#
Applies only if the box is a Domain Controller (has AD DS installed). Open Active Directory Users and Computers: Win + R โ dsa.msc.
This section bundles several distinct audit passes โ work through them as separate numbered steps rather than trying to do everything in one sweep of the console:
- [ ] Open the console and locate the accounts.
Win + Rโdsa.mscโ expand the domain tree โ click into Users, Computers, and any custom Organizational Units (OUs). Accomplishes: gets every account in front of you before you start judging any of them. - [ ] Pass 1 โ remove unauthorized users. Go through every account in the right-hand pane and compare against the README's roster. Right-click anything unauthorized โ Disable Account (prefer Disable over Delete unless you're confident). Accomplishes: closes unauthorized entry points first, before you get distracted by finer-grained settings.
- [ ] Pass 2 โ audit privileged group membership. Double-click each of:
Administrators,Domain Admins,Enterprise Admins,Schema Admins,Account Operators,Backup Operators,Server Operatorsโ Members tab โ remove anyone not authorized by the README. Accomplishes: limits who can do domain-wide damage, independent of whether their account itself looks "authorized." - [ ] Pass 3 โ per-account security flags. For each remaining authorized user, open Properties โ Account tab and confirm:
-
Guestand any other default/unused built-in accounts are disabled. -
krbtgtaccount exists, is NOT disabled, and is NOT deleted (critical AD account โ deleting it breaks the domain). - Password never expires is unchecked for regular users (unless the README explicitly requires it for a service account).
- Store password using reversible encryption is unchecked.
- Consider checking Account is sensitive and cannot be delegated for high-privilege accounts. Accomplishes: catches weaker per-account settings that survive even after the roster and group-membership passes are clean.
-
- [ ] Pass 4 โ structural checks. Check for stale/duplicate computer accounts in the Computers container that don't correspond to real machines in the scenario; verify the OU structure hasn't been tampered with (unexpected OUs, users moved out of expected OUs to dodge GPO application); check delegation on OUs to ensure delegated control doesn't grant excessive rights to non-admin groups. Accomplishes: catches structural manipulation that individual account checks won't surface.
Things to try / extra points#
- Full account + attribute dump for review:Expected result: A wide console table, one row per AD user, withpowershell
Get-ADUser -Filter * -Properties * | Select-Object Name, SamAccountName, Enabled, LastLogonDate, PasswordNeverExpires, PasswordLastSet | Format-Table -AutoSizeEnabledasTrue/False,LastLogonDateas a datetime (or blank if never logged on), andPasswordNeverExpires/PasswordLastSetpopulated. Table wraps or truncates in a narrow console โ widen the window or pipe toOut-GridView(if available) orFormat-Table -Wrapfor readability. If it fails: "Get-ADUser is not recognized" means theActiveDirectorymodule isn't loaded โ this cmdlet only works on a DC or a member server with RSAT'sRSAT-AD-PowerShellfeature installed (Add-WindowsFeature RSAT-AD-PowerShell, orImport-Module ActiveDirectoryif it's installed but not auto-loaded). Running this on a non-domain-joined standalone server will always fail since there's no AD to query. - Find disabled-but-still-privileged or otherwise inconsistent accounts:Expected result: A short list ofpowershell
Get-ADUser -Filter {Enabled -eq $false} | Select-Object Name, SamAccountNameName/SamAccountNamepairs for every disabled account โ should includeGuestand any account you've already disabled; cross-check it doesn't unexpectedly include an account that's supposed to be active. If it fails: Empty output (no table at all, cmdlet just returns) genuinely means zero disabled accounts exist โ that's valid, not a bug; disableGuestif it's still shown as enabled elsewhere. Filter syntax errors ("Variable is not defined" pointing atEnabled) usually mean a typo in the property name or missing{}around the filter โ property names in-Filterare case-insensitive but must be spelled exactly right. - Find accounts with dangerous flags set:Expected result: Each query returns only the accounts matching that risky flag โ ideally the second (reversible encryption) is empty, and the first (password never expires) is empty or limited to a documented service account. The third query is almost always non-empty (most environments don't require smartcard logon) โ it's informational, not necessarily a finding to fix. If it fails: All three queries return nothing at all (not even forpowershell
Get-ADUser -Filter {PasswordNeverExpires -eq $true} | Select-Object Name, SamAccountName, Enabled Get-ADUser -Filter {AllowReversiblePasswordEncryption -eq $true} | Select-Object Name, SamAccountName Get-ADUser -Filter {SmartcardLogonRequired -eq $false -and Enabled -eq $true} | Select-Object Name, SamAccountNameGuest/krbtgt) โ double check theActiveDirectorymodule actually loaded (Get-Module ActiveDirectory) rather than assuming a clean result; a silently failed module import can make every-Filterquery return empty instead of erroring. If a flag you fixed in the GUI still shows up here, you may be looking at a cached AD replication delay on a multi-DC setup โ re-run after a minute, or run directly against the PDC emulator. - Audit high-privilege group membership quickly:Expected result: A shortpowershell
Get-ADGroupMember -Identity "Domain Admins" | Select-Object Name, SamAccountName Get-ADGroupMember -Identity "Enterprise Admins" | Select-Object Name, SamAccountName Get-ADGroupMember -Identity "Schema Admins" | Select-Object Name, SamAccountName Get-ADGroupMember -Identity "Account Operators" | Select-Object Name, SamAccountNameName/SamAccountNamelist per group โEnterprise AdminsandSchema Adminsshould typically be empty or contain only the built-inAdministrator, since those groups only matter at forest-root-domain level and granting them broadly is a red flag. If it fails: "Cannot find an object with identity: 'Enterprise Admins'" โ this group only exists in the forest root domain; if you're on a child-domain DC, that query legitimately has nothing to find there (query it from the forest root DC instead, or accept it's out of scope for this box). This cmdlet requires theActiveDirectorymodule/RSAT and only makes sense on a DC or an RSAT-equipped member server โ it will error on a plain member server with no AD tools installed. - Check OU delegation for over-broad permissions:Expected result: A list of access control entries (powershell
Get-Acl -Path "AD:\OU=Employees,DC=domain,DC=local" | Select-Object -ExpandProperty AccessIdentityReference,ActiveDirectoryRights,AccessControlType) showing who has what delegated rights on that OU. If it fails: "Cannot find path 'AD:...'" means either theAD:PSDrive isn't mounted (it auto-mounts when theActiveDirectorymodule loads โ re-import it:Import-Module ActiveDirectory) or the DN (OU=Employees,DC=domain,DC=local) doesn't match this domain's actual structure โ get the real OU path withGet-ADOrganizationalUnit -Filter *first rather than guessing the DN. - Check who created new accounts recently (helps forensics + spotting rogue admin-added users):Expected result: The 15 most recently created AD accounts, newest first. If it fails: Samepowershell
Get-ADUser -Filter * -Properties WhenCreated | Sort-Object WhenCreated -Descending | Select-Object -First 15 Name, WhenCreatedActiveDirectorymodule/RSAT dependency as every AD cmdlet in this section โ see the recurring note above if it's not recognized. A very large/slow domain can make-Filter *take a while; that's expected on real environments, not a hang. - Critical edge case โ nested group membership:
Get-ADGroupMember -Identity "Domain Admins"only shows direct members. If an unauthorized user was added to a smaller group that is itself nested inside Domain Admins, a plain query misses them entirely. Always add-Recursive:Expected result: Every effective member of Domain Admins, including those added via nested group membership, not just direct additions. If it fails: "Cannot find an object with identity 'Domain Admins'" on a non-DC or a domain with a renamed built-in group is unusual but possible on a heavily customized image โ confirm the exact group name withpowershellGet-ADGroupMember -Identity "Domain Admins" -Recursive | Select-Object Name, SamAccountNameGet-ADGroup -Filter *first. Comparing this recursive result against the earlier non-recursive check from Pass 2 is exactly how you catch the nested-membership trick โ if the counts differ, investigate the gap. - Verification โ confirm a disable actually took effect: after disabling an account (GUI or PowerShell), read it back rather than trusting the click:Expected result:powershell
Get-ADUser -Identity <SamAccountName> -Properties Enabled | Select-Object Name, EnabledEnabled : Falsefor an account you just disabled. If it fails: Still showsEnabled : Trueafter disabling via the GUI โ you likely edited the wrong OU/found a same-named account in a different container, or the GUI change didn't actually save; re-open ADUC and confirm directly. "Cannot find an object with identity" means a typo in<SamAccountName>โ get the exact value withGet-ADUser -Filter * | Select SamAccountName. - Fallback if the
ActiveDirectoryPowerShell module isn't loaded/available (more common on 2008 R2 without RSAT tools installed): the legacydsquery/dsgetcommand-line tools ship with every DC and don't require the module:Expected result:shelldsquery user -disabled dsquery group -name "Domain Admins" | dsget group -members -expanddsquery user -disabledprints the distinguished names of disabled accounts, one per line. The pipeddsquery/dsgetcombo prints the expanded (including nested) member list of Domain Admins. If it fails: These are older, DN-based tools โ output is distinguished names (CN=...,OU=...,DC=...), not friendlySamAccountNames, which can be harder to read at a glance; that's normal for this tool, not an error. "The directory service was unable to allocate a relative identifier" or similar odd errors here usually indicate a deeper AD health problem outside the scope of this specific command โ see Section 7 for AD health checks.
Tip: Never delete
krbtgt,Domain Adminsitself, or the account you're currently logged in as. Disabling the wrong account can lock you out of the box entirely โ if unsure, disable rather than delete, and test login again immediately after.
5. Group Policy Management (Domain-Level Policy)#
Critical DC-specific concept: On a Domain Controller, Local Security Policy (
secpol.msc) is mostly grayed out or ineffective โ domain-linked Group Policy Objects (especially the Default Domain Policy and Default Domain Controllers Policy) override local policy. A very common beginner mistake is spending time editingsecpol.mscon a DC and being confused why nothing changes or why it reverts on the nextgpupdate. Usegpmc.msc(Group Policy Management Console) instead.
๐ธ Snapshot checkpoint: A bad edit to the Default Domain Policy or Default Domain Controllers Policy applies domain-wide and can lock out every account or machine in the domain (e.g., an overly strict lockout policy or a broken logon-rights change) โ that's a much bigger blast radius than a single-machine mistake. Take a snapshot before you start editing GPOs in this section.
This walkthrough bundles a lot of distinct steps โ follow it as a numbered sequence rather than trying to remember it as one block:
- [ ] Open GPMC.
Win + Rโgpmc.msc. Accomplishes: gets you into the console where domain-wide policy actually lives (remember, this replacessecpol.mscon a DC โ see the callout above). - [ ] Navigate to the policy objects. Expand Forest โ Domains โ [Your Domain] โ Group Policy Objects. Accomplishes: shows you every GPO that exists in the domain, linked or not โ this is your inventory.
- [ ] Open the Default Domain Policy for editing. Right-click Default Domain Policy โ Edit to open the Group Policy Management Editor. Accomplishes: this GPO is linked at the domain root, so anything you set here applies to every computer and user in the domain by default.
- [ ] Configure Account Policies. Navigate to Computer Configuration โ Policies โ Windows Settings โ Security Settings โ Account Policies, and set Password Policy and Account Lockout Policy to the values in Section 6. Accomplishes: this is the domain-wide password/lockout baseline โ see Section 6 for exact values.
- [ ] Configure Security Options. In the same Security Settings node, under Local Policies โ Security Options, set:
-
Accounts: Rename administrator accountโ set a custom name. -
Accounts: Rename guest accountโ set a custom name. -
Network access: Do not allow anonymous enumeration of SAM accounts and sharesโ Enabled. -
Interactive logon: Do not display last signed-inโ Enabled. Accomplishes: closes off several classic low-effort recon/enumeration paths that don't depend on password strength at all.
-
- [ ] Review the Default Domain Controllers Policy separately. This is a different GPO from the one you just edited โ it's where DC-specific user rights assignments (e.g., who can log on locally to a DC) live. Accomplishes: DC-specific settings won't be in the Default Domain Policy at all; skipping this step means you only did half the job.
- [ ] Hunt for malicious or orphaned GPOs. Review the Group Policy Objects list for anything unfamiliar (a GPO pushing a backdoor script, disabling the firewall, or adding a user to local admins), checking its Scope (linked OUs) and Settings (report view); separately check for unlinked/orphaned GPOs that aren't currently applied but could be relinked later. Accomplishes: catches policy-based attacks that don't show up if you only look at the two Default policies.
- [ ] Apply and verify. Force policy refresh, then confirm the changes actually landed:Expected result:cmd
gpupdate /force gpresult /rgpupdate /forceprints "Updating policy..." then "Computer Policy update has completed successfully." (and the same for User Policy).gpresult /rprints a summary showing which GPOs applied to this computer/user and which were filtered out, plus the resultant set of policy. If it fails:gpupdate /forcereporting success doesn't guarantee your specific setting took effect if a HIGHER-PRECEDENCE GPO (linked closer to the OU, or with "Enforced" set) overrides it โ that's exactly whatgpresult /ris for: check the "Denied" section of its output for any of your settings, which shows precedence conflicts. Ifgpresultshows your GPO wasn't applied at all, confirm it's actually linked to the right OU (an unlinked GPO, however well-configured, does nothing) and that neither the OU nor the GPO has Block Inheritance or a security-filtering setting excluding this computer/user. Accomplishes:gpupdate /forcepushes your edits out immediately instead of waiting for the normal refresh interval;gpresult /rgives you a quick sanity check that policy is applying at all.
Things to try / extra points#
- Enumerate all GPOs and their link status:Expected result: A table of every GPO in the domain with its display name, GUID, and last-modified timestamp. If it fails: "The term 'Get-GPO' is not recognized" โ thepowershell
Import-Module GroupPolicy Get-GPO -All | Select-Object DisplayName, Id, ModificationTimeGroupPolicymodule needs RSAT Group Policy Management tools installed (Install-WindowsFeature GPMCon a server missing it); on most DCs it's present by default, missing more often on a plain member server. - Find unlinked GPOs (a common place to hide unauthorized settings โ they don't show up as "active" but could be relinked):Expected result: A (hopefully short) list of GPOs that exist but aren't currently linked anywhere. If it fails: This one-liner callspowershell
Get-GPO -All | Where-Object { (Get-GPOReport -Guid $_.Id -ReportType Xml) -notlike "*<LinksTo>*" } | Select-Object DisplayName, IdGet-GPOReportonce per GPO, so it can be genuinely slow on a domain with many GPOs โ that's expected, not a hang. A GPO showing up here isn't automatically malicious (unlinked "template" or "staging" GPOs are a normal admin practice) โ the point is just to know it exists and could be relinked, not to delete every hit automatically. - Search SYSVOL for legacy Group Policy Preferences credentials (
cpassword) โ a classic, easily-reversible credential leak left in old GPP XML files:If found, remove the offending preference/GPO and rotate the exposed credential. Expected result: Matching lines with filename/path if any GPP XML file contains apowershellGet-ChildItem -Path "\\localhost\SYSVOL" -Recurse -Include *.xml,*.bat,*.ps1,*.vbs | Select-String -Pattern "cpassword","password"cpasswordattribute (these are trivially reversible โ Microsoft published the decryption key years ago) or the plaintext string "password". If it fails: No error mode; a hit here is a real, serious finding โ cpassword values are not a theoretical risk, they're a one-line decrypt away from a plaintext credential. If found, don't just delete the XML file โ identify and remove the actual GPO/GPP setting that created it (deleting the file alone may not stop the GPO from recreating it), and treat the exposed credential as compromised (rotate it). - Generate a full HTML report of a GPO for review instead of clicking through every node:Expected result: An HTML file written to the given path; open it in a browser for a full readable breakdown of every setting the GPO configures. If it fails: "Cannot find a GPO with the display name 'X'" โ GPO display names are case-sensitive-looking but usually match-insensitive in practice; more likely a typo โ get exact names frompowershell
Get-GPOReport -Name "Default Domain Policy" -ReportType Html -Path C:\Users\Public\DDP_Report.htmlGet-GPO -All | Select DisplayName. - Confirm policy is actually landing on the DC/member servers:Expected result: Same console summary as before; thecmd
gpresult /r gpresult /h C:\Users\Public\gpresult.html/hvariant writes a browsable HTML report instead (often easier to read than the console dump for a complex resultant policy). If it fails: No new failure modes beyond the earliergpresult /rnote โ if the HTML file doesn't open cleanly, confirm the path is writable and try opening it directly rather than double-clicking through Explorer if that behaves oddly on a locked-down image. - For the full effective-policy picture, use
gpresult /zinstead of/r. On a server, the Default Domain Policy, any local policy, and every OU-linked GPO all combine into one resultant set of policy (RSoP) โ/rgives you a summary, but/zdumps the complete, verbose resultant policy including which specific GPO won each setting and why. This is the only fully reliable way to see what's actually in effect, as opposed to what you think you set:Expected result: A much longer text dump thancmdgpresult /z > C:\Users\Public\gpresult_full.txt/r, including every individual setting and exactly which GPO (by name) set it โ this is what you grep/search through when you need to know "why is this specific setting what it is." If it fails: No special failure mode beyond what's already noted for/rโ if the output is too large to search comfortably in Notepad, open it in PowerShell ISE orless-equivalent, orSelect-Stringfor the specific setting name you're chasing (Select-String -Path C:\Users\Public\gpresult_full.txt -Pattern "PasswordComplexity"). GUI equivalent:Win + Rโrsop.mscopens a live Resultant Set of Policy snap-in you can browse interactively, which is often easier to skim than the text dump. - Verification fallback if
gpupdate /forceseems to silently do nothing: check the Group Policy operational log (Event Viewer โ Applications and Services Logs โ Microsoft โ Windows โ GroupPolicy โ Operational) for processing errors โ a GPO with a broken permission or a corrupted setting can fail to apply without any obvious error on screen. - The Security Options items already listed above (rename Administrator/Guest, disallow anonymous SAM/share enumeration, hide last signed-in user) align directly with CIS Benchmark Level 1 "Security Options" guidance for both the Domain Controller and Member Server profiles โ these are considered baseline, low-risk wins. A few more Level 1 Security Options worth adding while you're in this same GPO node:
-
Interactive logon: Do not require CTRL+ALT+DELโ Disabled. -
Network security: Do not store LAN Manager hash value on next password changeโ Enabled. -
Network security: LAN Manager authentication levelโ Send NTLMv2 response only. Refuse LM & NTLM. -
Microsoft network server: Digitally sign communications (always)โ Enabled. -
Domain member: Digitally encrypt or sign secure channel data (always)โ Enabled.
-
- DC-specific (Default Domain Controllers Policy โ User Rights Assignment): CIS's Domain Controller profile includes several User Rights Assignment checks worth reviewing here โ confirm
Add workstations to domain,Allow log on locally, andAllow log on through Remote Desktop Servicesare restricted to Administrators/authorized groups only, and thatDeny access to this computer from the networkincludes appropriate accounts (e.g., built-in local accounts) per the benchmark's guidance. This is a good companion check to the account audits in Section 4. - CIS Level 2 (worth trying if time allows and it won't break required roles): CIS's stricter profile includes tighter interactive-logon and network-access restrictions (e.g., further restricting anonymous access and remote registry paths) beyond the Level 1 items above โ these carry more risk of breaking legitimate tooling, so validate required functionality immediately after applying.
Tip: If you edit something in
secpol.mscon a DC and it doesn't stick, that's not a bug โ it's domain GPO overriding you. Go find the equivalent setting ingpmc.mscunder Default Domain Policy or Default Domain Controllers Policy instead.
6. Password & Account Lockout Policy#
On a Domain Controller, password/lockout policy is set at the domain level (Default Domain Policy via GPMC or PowerShell AD cmdlets) โ NOT via local secpol. On a standalone/member server, local policy via secpol.msc or secedit still applies normally.
| Setting | Recommended Value |
|---|---|
| Minimum password length | 14 characters |
| Password must meet complexity requirements | Enabled |
| Enforce password history | 24 passwords remembered |
| Maximum password age | ~30โ90 days (not 0/never) |
| Minimum password age | 1 day |
| Account lockout threshold | 5 invalid attempts |
| Account lockout duration | 30 minutes |
| Reset account lockout counter after | 15 minutes |
- On a DC: set these via GPMC โ Default Domain Policy (see Section 5) or via
Set-ADDefaultDomainPasswordPolicy. - On a standalone/member server: set via
secpol.mscโ Account Policies, orsecedit/net accounts. - Check for Fine-Grained Password Policies (FGPP) that might override the domain default for specific users/groups โ verify they aren't weaker than the domain policy for privileged accounts.
Things to try / extra points#
- Set domain password/lockout policy directly from PowerShell (fast, scriptable, avoids GUI round-trips):Expected result: Silent on success. Verify withpowershell
Set-ADDefaultDomainPasswordPolicy -MinPasswordLength 14 -ComplexityEnabled $true -PasswordHistoryCount 24 -LockoutThreshold 5 -LockoutDuration (New-TimeSpan -Minutes 30) -LockoutObservationWindow (New-TimeSpan -Minutes 15)Get-ADDefaultDomainPasswordPolicy(next command) rather than assuming it worked. If it fails: "Access is denied" means your account isn't a Domain Admin (or equivalent delegated right) โ this cmdlet modifies domain-wide policy and needs real privilege. This only works run against a DC (or targeting one via-Server) โ running it on a plain member server with no domain context will error. - Check current domain policy quickly:Expected result: An object listing every current password/lockout policy value for the domain โ confirm your new values landed. If it fails: Same module/RSAT/DC-context caveats as every other AD cmdlet in this document.powershell
Get-ADDefaultDomainPasswordPolicy - Check for Fine-Grained Password Policies that could be silently weaker than the domain default:Expected result: A list of any FGPP objects defined, or nothing if none exist (most environments don't use FGPPs at all โ empty is a normal, common outcome). If it fails: No error mode beyond the usual module/permission checks. If an FGPP DOES exist, check which users/groups it applies to (powershell
Get-ADFineGrainedPasswordPolicy -Filter *AppliesToproperty) and whether its values are actually weaker than the domain default you just set โ an FGPP always wins over the domain default for the accounts it targets, regardless of how strong you make the domain-wide policy. - On a standalone server (no domain), use
seceditfor a bulk policy push. This is an export โ edit โ re-import pattern โ three distinct steps, don't try to do it in one motion:- Export the current policy to a text file:Accomplishes: gives you a plain-text starting point instead of writing a policy file from scratch. Expected result:shell
secedit /export /cfg C:\secpol.cfgThe task has completed successfully.;C:\secpol.cfgnow exists. If it fails: "Access is denied" writing toC:\root on a locked-down image โ export toC:\Users\Public\secpol.cfginstead and use that path in every later step. - Edit the exported file (Notepad or PowerShell) and change the relevant lines under
[System Access]โ e.g.MinimumPasswordLength = 14,PasswordComplexity = 1,LockoutBadCount = 5. Accomplishes: this is where your actual policy values get set โ the export/import steps are just plumbing around this edit. Expected result: File saves normally; still valid.ini-style text. If it fails: A key you expect (e.g.PasswordHistorySize) missing from the exported file entirely โ just add it as a new line under[System Access],seceditaccepts appended keys as long as they're valid ones for that section. - Re-import the edited file to apply it:Accomplishes: pushes the edited values from the text file back into the actual local security database โ nothing changes on the system until this step runs. Expected result:shell
secedit /configure /db C:\Windows\security\local.sdb /cfg C:\secpol.cfg /areas SECURITYPOLICYThe task has completed successfully.If it fails: "The task has completed with one or more errors" here (unlike the export step) usually means a malformed line in your edited file โ check%windir%\security\logs\scesrv.logfor the specific line it choked on, fix it insecpol.cfg, and re-run; this command is safe to re-run. - Verify it actually applied โ re-export and check the values landed, or spot-check with
net accounts:Expected result:shellsecedit /export /cfg C:\secpol_verify.cfg net accountssecpol_verify.cfg's values match what you set;net accountsprints the new numbers. If it fails: Values didn't change despite a "successful" import โ on a DC, remember the callout at the top of this section: local secedit-based policy is overridden by domain GPO for a Domain Controller. If this is a DC, use the GPMC/Set-ADDefaultDomainPasswordPolicymethod instead โsecedithere only genuinely applies on a standalone/member server. Accomplishes:secedit /configurefails silently on some malformed edits โ always confirm rather than assuming step 3 worked.
- Export the current policy to a text file:
- GUI alternative to the whole secedit dance (same effect, no risk of a malformed
.cfgfile):secpol.mscโ Account Policies โ set the values directly in the UI. Slower for bulk changes, faster and safer for editing 2-3 values. - This whole section aligns with CIS Benchmark Level 1 "Account Policies" guidance (Password Policy + Account Lockout Policy), and the same values apply to both the Domain Controller and Member Server CIS profiles โ this is one of the most universally-agreed-upon baseline items in the benchmark, so treat the table above as safe to apply everywhere without much risk.
Tip: Don't set Maximum Password Age to 0 (never expires) thinking it's "safer" because it avoids lockouts โ a non-expiring password policy is itself commonly flagged as a vulnerability.
7. Active Directory Health, Replication & Kerberos Hardening#
AD Health & Replication#
- Run diagnostics to confirm the DC is healthy before and after making changes โ a broken DC can silently fail scoring checks for AD-dependent services.
- Confirm SYSVOL/NETLOGON shares are present and shared (
net shareshould listSYSVOLandNETLOGON). - Confirm time sync is correct โ Kerberos fails hard with clock drift beyond ~5 minutes.
Kerberos & Authentication Hardening#
- Audit accounts with Service Principal Names (SPNs) โ these are targets for Kerberoasting (offline cracking of service account password hashes).
- Audit accounts with Kerberos pre-authentication disabled โ targets for AS-REP Roasting.
- Check for accounts/computers configured with unconstrained delegation โ a high-value target since compromising them can yield cached admin TGTs.
- Consider enrolling high-privilege accounts into the Protected Users group to enforce strict Kerberos-only auth (blocks NTLM/digest auth and LSASS credential caching for those accounts) โ only do this if it won't break required functionality for a service account.
Things to try / extra points#
Full DC diagnostics (run early to baseline, run again after changes to confirm nothing broke):
shelldcdiag /vWhat dcdiag actually tests
dcdiag runs a whole battery of built-in health checks against a Domain Controller โ DNS, replication, time sync, trust relationships, and more โ all in one command. /v (verbose) makes it print full detail on every test instead of just pass/fail, which is why it's slower but far more useful when something's actually wrong.Expected result:A long verbose report ending each test withpassed test <TestName>โ scroll for anyfailed testline.If it fails:- A slow/long run (several minutes) is normal on
/v, not a hang. - Any
failed testneeds individual investigation โdcdiagoutput usually names the specific problem clearly enough to search for that exact test name plus "dcdiag" if you're unsure what it means; common early-competition failures are DNS-related (a DC that can't resolve its own SRV records) or time-sync related, both covered by other bullets in this section.
- A slow/long run (several minutes) is normal on
Replication health (only meaningful with multiple DCs, but harmless to run and check for errors otherwise):
shellrepadmin /replsummary repadmin /showreplWhat these repadmin commands check
Active Directory Domain Controllers constantly copy directory changes to each other ("replication") so every DC has the same data. /replsummary gives a quick one-screen health overview of that replication across all DCs; /showrepl drills into the specific errors for one DC if the summary shows a problem โ think of it as the summary dashboard vs. the detailed error log.Expected result:- On a single-DC domain, both commands report no partners / nothing to replicate โ that's a normal, expected outcome, not an error.
- With multiple DCs,
/replsummaryshows a per-DC summary with failure counts;0failures across the board is the goal.
If it fails:Non-zero replication failures need investigating withrepadmin /showreplfor the specific error code โ common causes are DNS resolution problems between DCs or a firewall blocking AD replication ports; don't just re-run the command expecting it to self-heal.Confirm domain controller discoverability:
shellnltest /dsgetdc:domain.local nltest /dclist:domain.localExpected result:/dsgetdcprints the discovered DC's name, IP, and site info;/dclistlists every DC in the domain.If it fails:"The RPC server is unavailable" or a DC-not-found error suggests DNS misconfiguration (the box can't find SRV records pointing at a DC) or the domain name is wrong โ double check the actual domain name withGet-ADDomainorsysteminfofirst rather than guessingdomain.local.Force time resync against a reliable source to prevent Kerberos failures from clock drift:
shellw32tm /config /manualpeerlist:"0.pool.ntp.org 1.pool.ntp.org" /syncfromflags:manual /reliable:YES /update w32tm /resyncExpected result:The command completed successfully.for the config line;/resyncprintsSending resync command to local computerthen a success confirmation.If it fails:- "no response from server" on
/resyncmost often means no internet access (pool.ntp.org unreachable) โ if this image is offline, external NTP won't work at all; instead sync against the DC itself if this is a member server (w32tm /config /syncfromflags:domhier /update) or accept the clock as-is if this IS the DC and there's no external reference available. - A DC being the domain's own time source is normal โ don't point the PDC emulator DC at itself in a loop; check
netdom query fsmofirst to confirm whether this box holds the PDC emulator role, which changes what "correct" time sync looks like for it specifically.
- "no response from server" on
List SPNs to find Kerberoasting targets:
powershellGet-ADUser -Filter {ServicePrincipalName -ne "$null"} | Select-Object Name, ServicePrincipalName, UserPrincipalNameRemove unnecessary SPNs:
shellsetspn -D <SPN> <AccountName>Expected result:- The
Get-ADUserquery lists user accounts (not computer accounts โ those normally have SPNs legitimately) that have an SPN registered; each is a theoretical Kerberoasting target since anyone can request a service ticket for it and attempt offline cracking. setspn -Dprints confirmation it removed the SPN.
If it fails:- An SPN legitimately tied to a running service (e.g., a SQL Server service account) will break that service if removed โ don't strip SPNs reflexively; cross-check against the README/required services first.
setspn -Dfailing with "cannot find SPN" means the exact<SPN>string didn't match โ copy it verbatim from the query output above rather than retyping it.
- The
Find AS-REP roastable accounts (pre-auth disabled):
powershellGet-ADUser -Filter {DoesNotRequirePreAuth -eq $true} | Select-Object Name, SamAccountNameFix:
powershellSet-ADUser -Identity <User> -DoesNotRequirePreAuth $falseExpected result:- The query lists accounts with Kerberos pre-authentication disabled (ideally empty โ this is rarely legitimately needed).
- The fix silently re-enables pre-auth requirement.
If it fails:Some legacy applications genuinely need pre-auth disabled for compatibility โ check before blanket-fixing every hit; if the README doesn't mention such a requirement, treat every hit as a real finding.Find unconstrained-delegation objects:
powershellGet-ADObject -Filter {UserAccountControl -band 0x800000} -Properties SamAccountName, UserAccountControl | Select-Object SamAccountName, NameRemediate via
dsa.mscโ object Properties โ Delegation tab โ Do not trust this user/computer for delegation. Expected result: A list of user/computer objects with the unconstrained-delegation UAC flag set โ DCs themselves normally show up here legitimately (that's expected/required for DCs specifically), so filter those out and focus on non-DC computer/user objects. If it fails: No error mode; the query itself is read-only. Removing unconstrained delegation from something that actually needs it (a legitimate multi-hop authentication scenario) breaks that functionality โ verify against the README/scenario description, especially for any non-DC hits, before remediating.Add sensitive admin accounts to Protected Users (test login afterward โ this changes auth behavior):
powershellAdd-ADGroupMember -Identity "Protected Users" -Members "DomainAdminUser"Expected result:Silent on success.If it fails:- Protected Users membership can lock an account out of legitimate access if that account relies on NTLM, DES/RC4 Kerberos encryption, delegation, or cached credentials for something required โ test login immediately after adding an account, and be ready to remove them from the group (
Remove-ADGroupMember) if something breaks. - This is explicitly called out as risky in the checklist bullet above for good reason.
- Protected Users membership can lock an account out of legitimate access if that account relies on NTLM, DES/RC4 Kerberos encryption, delegation, or cached credentials for something required โ test login immediately after adding an account, and be ready to remove them from the group (
Enforce AES-only Kerberos encryption (disable legacy RC4-HMAC) if the environment allows it โ this aligns with CIS Benchmark Level 1 guidance ("Network security: Configure encryption types allowed for Kerberos") which recommends restricting to AES128/AES256 and disabling DES/RC4:
powershellSet-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\Kerberos\Parameters" -Name "SupportedEncryptionTypes" -Value 24 -Type DWordExpected result:- Silent on success.
- Value
24= AES128 + AES256 only (bitmask: 8 + 16).
If it fails:- "Cannot find path" means the
Kerberos\Parameterskey doesn't exist yet on this box โ add-ForcetoSet-ItemPropertyto create it. - Legacy clients/services that only support RC4 will fail to authenticate after this change โ this is exactly why the verification step right below (checking
klist ticketsfor AES vs RC4) matters; if something breaks, this registry value is the first thing to suspect and can be reverted by deleting the key or setting it back to its prior value (commonly absent/default, which allows all types).
Require LDAP server signing โ CIS Level 1 ("Domain controller: LDAP server signing requirements" under Security Options). An unsigned LDAP bind can be relayed/tampered with in transit, the LDAP equivalent of the SMB-signing issue covered earlier โ this is a DC-only setting and won't appear on a member server. Fastest path โ GUI:
gpmc.msc/secpol.mscโ Local Policies โ Security Options โDomain controller: LDAP server signing requirementsโ Require signing. Command-line path (same export โ edit โ import pattern as Section 6):- Export current policy:
secedit /export /cfg C:\ldapsign.cfg /areas SECURITYPOLICY - Edit
C:\ldapsign.cfgand setLDAPServerIntegrity = 2. - Re-import:
secedit /configure /db C:\Windows\security\local.sdb /cfg C:\ldapsign.cfg /areas SECURITYPOLICY - Verify: re-export and confirm
LDAPServerIntegrity = 2is present, since a malformed edit fails silently just like in Section 6.
Tip: Requiring LDAP signing can break older/misconfigured LDAP clients that only bind unsigned โ if something in the topology stops authenticating after this change, that's the first setting to suspect.
- Export current policy:
Verify the AES-only Kerberos change actually took effect rather than trusting the registry write โ after setting
SupportedEncryptionTypes, force a fresh ticket and inspect it:shellklist purge klist ticketsLook for
AES256-CTS-HMAC-SHA1-96(orAES128) as the encryption type on freshly-issued tickets instead ofRC4-HMAC. Expected result:klist purgeclears cached tickets;klist tickets(after doing something that triggers a fresh ticket request, like accessing a network resource) shows the new tickets' encryption type field. If it fails: Ifklist ticketsstill showsRC4-HMACafter the registry change, the setting may not have propagated yet โ this registry value typically needs agpupdate /forceor logoff/logon (sometimes a reboot) to take effect for new ticket requests, it's not always instant. If tickets show empty right afterpurge, that's expected โ request one by accessing any Kerberos-authenticated resource, then re-runklist tickets.dcdiagfallback for targeted checks: running the full/vsweep is thorough but slow and noisy. If you only care about one thing (e.g., after a time-sync fix, or after touching services), run a targeted test instead:dcdiag /test:Advertising,dcdiag /test:Services,dcdiag /test:FrsEvent(orDFSREventon newer domains), ordcdiag /test:NetLogons.GUI alternative to
repadmin: Active Directory Sites and Services (dssite.msc) โ expand Sites โ [Site Name] โ Servers โ [DC] โ NTDS Settings โ right-click each connection โ Replicate Now. Slower for a full health check thanrepadmin /replsummary, but useful if you want to trigger replication interactively without memorizing switches.Disable Print Spooler on Domain Controllers (mitigates PrintNightmare-class RPC privilege escalation) โ this reflects a Domain-Controller-profile CIS recommendation added after PrintNightmare (recent CIS DC benchmark guidance calls for the Print Spooler service to be disabled on DCs); treat it as a Level 2 / higher-impact item since it removes a service outright โ only if the README doesn't require print services on this box:
powershellStop-Service -Name "Spooler" -Force Set-Service -Name "Spooler" -StartupType DisabledExpected result:Both silent on success; confirm withGet-Service SpoolershowingStopped/Disabled.If it fails:- "Cannot stop service Spooler on computer '.' " with an access-denied-style error means not running elevated.
- If a required print role/scenario needs the spooler, this will break printing entirely โ check the README first, exactly as the checklist bullet warns.
Enable Credential Guard to isolate LSA secrets from memory-dumping tools โ this maps to CIS Level 2 "Virtualization Based Security" guidance (requires appropriate hardware/VBS support โ verify it doesn't break the box before relying on it for points):
powershellSet-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa" -Name "LsaCfgFlags" -Value 1 -Type DWordExpected result:Silent on success; requires a reboot to actually take effect, and Credential Guard additionally requires UEFI, Secure Boot, and virtualization extensions to actually activate (not just the registry flag).If it fails:- On a virtual machine (which most CyberPatriot images are), Credential Guard often cannot actually activate even with this registry key set, because it needs nested virtualization/VBS support the hypervisor or VM configuration may not expose โ check
System Information(msinfo32.exe) โ "Virtualization-based security" status after reboot to confirm whether it's actually running, rather than assuming the registry write alone means it's active. - Don't rely on this for guaranteed points without confirming it actually took effect on this specific image.
- On a virtual machine (which most CyberPatriot images are), Credential Guard often cannot actually activate even with this registry key set, because it needs nested virtualization/VBS support the hypervisor or VM configuration may not expose โ check
If AD CS (Certificate Services) is installed, check certificate templates for
ENROLLEE_SUPPLIES_SUBJECT(allows a requester to supply their own SAN, enabling impersonation) viacerttmpl.mscโ verify Subject Name โ Supply in the request is Disabled on authentication templates.
Tip: Disabling Print Spooler or enabling Credential Guard can break required functionality on some scenario boxes โ verify against the README first, and re-test the specific required service after the change.
8. DNS Server Hardening#
Applies if the DNS Server role is installed. Open via Win + R โ dnsmgmt.msc, or Start โ Administrative Tools โ DNS.
Patch reminder โ configuration alone is not enough. The Windows DNS Server role has had serious remote-code-execution vulnerabilities in the past (e.g., CVE-2020-1350 / "SIGRed," a wormable RCE in the DNS Server role itself). A perfectly-configured zone with locked-down transfers and secure dynamic updates is still vulnerable if the underlying DNS Server binary is unpatched. Don't treat DNS hardening as "configure it correctly and move on" โ make sure Section 13 (Windows Updates) has actually been applied to this box, especially if it's offered any updates flagged critical/security for the DNS Server role specifically.
๐ธ Snapshot checkpoint: DNS and DHCP misconfigurations don't just affect this box โ they can break name resolution or address assignment for every other graded machine on the scoring network that depends on this server. Take a snapshot before changing zone transfer settings, dynamic update mode, forwarders, or (in the next section) DHCP scope options, so a mistake here doesn't cascade into other teams' โ or your own team's โ machines going dark.
- Open the DNS server's Properties โ Advanced tab and review server-level settings. If in doubt about a setting, leave "Fail on load if bad zone data" alone unless you know why โ misconfiguring it can take DNS offline.
- Open Root Hints tab โ server FQDNs should only be
a.root-servers.netthroughm.root-servers.net; verify against root-servers.org if IPs look suspicious. - Open Forwarders โ should typically be empty with "Use root hints if no forwarders are available" checked, unless the README specifies an internal/upstream forwarder to use.
- Open Debug Logging โ enable logging categories for visibility (this doesn't affect security posture directly but helps troubleshooting/forensics).
- Open Event Logging โ set to log All events.
- Open Trust Anchors โ should typically be empty unless DNSSEC is explicitly part of the scenario.
- For each Forward Lookup Zone:
- General tab โ set Dynamic updates to Secure only (never "Nonsecure and secure").
- Zone Transfers tab โ uncheck "Allow zone transfers", or if required for a secondary DNS server, restrict to "Only to servers listed on the Name Servers tab" โ never leave it open to "Any server."
- Check Interfaces tab on the server properties โ ensure the DNS server only listens on authorized/internal IP addresses, not all interfaces if some are untrusted.
- Audit zone records for anything unauthorized or suspicious (unexpected A/CNAME/TXT records, wildcard records used for tunneling or exfiltration).
- If this server is also the DHCP server, check the DNS integration tab on DHCP to ensure dynamic A/PTR record updates only occur for requesting DHCP clients (not unrestricted).
Things to try / extra points#
- Lock zone transfers down to named secondaries only:Expected result: Silent on success. Verify withpowershell
Set-DnsServerPrimaryZone -Name "domain.local" -SecureSecondaries "TransferToZoneNameServer"Get-DnsServerZone -Name domain.local | Select SecureSecondaries(or check the Zone Transfers tab in the GUI). If it fails: "Zone domain.local does not exist" โ use the real zone name fromGet-DnsServerZonerather than the placeholder. If a legitimate secondary DNS server stops replicating after this, confirm it's actually listed on the zone's Name Servers tab โTransferToZoneNameServeronly allows transfers to servers explicitly listed there. - Harden the cache against poisoning/spoofing:Expected result: Both silent on success. If it fails: No common failure mode beyond permission errors (run elevated). These are defense-in-depth tuning values, not commonly scenario-breaking โ low risk to apply.powershell
Set-DnsServerCache -CacheLockingPercent 96 Set-DnsServer -SocketPoolSize 2500 - Audit for suspicious record types often abused for data exfil/tunneling:Expected result: A list of TXT/CNAME records in the zone โ some are completely legitimate (SPF records, service aliases), so this needs judgment, not automatic removal. If it fails: No error mode; a long, unfamiliar-looking TXT record value (especially one that looks like encoded/random data rather than a normal SPF/verification string) is the actual thing worth flagging, not TXT records in general.powershell
Get-DnsServerResourceRecord -ZoneName "domain.local" | Where-Object { $_.RecordType -in @("TXT","CNAME") } - List all zones and their transfer/update settings at a glance:Expected result:powershell
Get-DnsServerZone Get-DnsServerZoneTransferPolicy -ZoneName "domain.local" -ErrorAction SilentlyContinueGet-DnsServerZonelists every zone with type (Primary/Secondary/Stub) and dynamic-update setting; the transfer-policy cmdlet often returns nothing (-ErrorAction SilentlyContinuesuppresses the error) since transfer policies are an advanced/rarely-used feature โ that's normal. If it fails: No real failure mode for the zone listing itself; use it to spot any zone withDynamicUpdateset toNonsecureAndSecure(the setting this section's checklist explicitly says should beSecureonly). - Check configured forwarders:Expected result: Lists configured forwarder IPs, or an empty/error-like response if none are configured (root hints only) โ which is the recommended default per this section's checklist unless the README specifies otherwise. If it fails: No real error mode; cross-check any listed forwarder IP against what the README expects โ an unfamiliar forwarder IP is a real finding (could redirect DNS resolution to attacker-controlled infrastructure).powershell
Get-DnsServerForwarder - Verify root hints programmatically:Expected result: Lists the 13 standard root server names/IPs (powershell
Get-DnsServerRootHinta.root-servers.netthroughm.root-servers.net). If it fails: Fewer than 13 entries, or an entry with a name/IP that doesn't match the real root server list, is a real finding โ verify suspicious-looking entries against the authoritative list at root-servers.org (the checklist bullet above already flags this). - Note on CIS coverage: the CIS Microsoft Windows Server Benchmark is primarily an OS-level baseline and does not have a dedicated DNS-role section โ the zone transfer/dynamic update hardening above comes from Microsoft's own DNS security guidance rather than a specific CIS control. Don't expect to find "DNS zone transfer" called out by name in the benchmark; it's still correct and worth doing.
- Verify zone transfers are actually blocked (don't just trust the checkbox) โ from another machine on the network, attempt an AXFR zone transfer against the server:A properly-locked-down server should refuse this ("Query refused" or similar) rather than dumping the full zone. Expected result (locked down):shell
nslookup server <DNS-server-IP> ls -d domain.localls -dreturns an error like "DNS server refused to transfer" โ no zone data dumped. Expected result (vulnerable): the full zone contents printed, meaning your transfer restriction isn't actually effective โ recheck the Zone Transfers tab /SecureSecondariessetting. If it fails to even connect: "DNS request timed out" or "can't find server name" means either the wrong IP was used or the DNS service isn't listening on that interface โ confirm the correct IP withipconfig /allon the DNS server itself first. - Edge case โ secondary zones: if this server hosts a secondary zone (a read-only copy transferred from another DNS server), zone-transfer restrictions are configured on the primary zone's server, not here โ don't waste time looking for a transfer-restriction setting on a secondary zone that doesn't have one.
- Check for stale/unpatched DNS Server updates specifically:
Get-HotFix | Where-Object {$_.Description -like "*Security*"} | Sort-Object InstalledOn -Descendingโ cross-reference against Section 13 rather than assuming general Windows Update coverage caught DNS-specific patches.
Tip: If DNS breaks after a change, the very first thing to check is the "fail on load if bad zone data" setting and whether the zone file itself got corrupted by an edit โ a restart is sometimes needed to fully take effect but is rarely the actual fix.
9. DHCP Server Checks#
DHCP is a comparatively simple, unauthenticated protocol, so role-specific vulnerabilities are limited โ the main risks are rogue/unauthorized DHCP servers and misconfigured scope options pointing clients to malicious infrastructure.
- Open DHCP Management Console:
Win + Rโdhcpmgmt.msc. - Right-click the server โ Manage authorized serversโฆ โ verify only legitimate domain DHCP servers are listed; Unauthorize any rogue entries.
- Expand IPv4 โ Scope โ Scope Options and verify:
- 003 Router points to the legitimate internal gateway.
- 006 DNS Servers points strictly to authorized internal DNS server(s).
- 015 DNS Domain Name matches the expected internal domain.
- Check scope lease duration and address range are sane for the scenario (not obviously reconfigured to something suspicious).
- If this server is also DNS-integrated, confirm dynamic DNS updates are restricted to only occur when explicitly requested by DHCP clients (Properties โ DNS tab on the DHCP server).
Things to try / extra points#
- Enumerate authorized DHCP servers in AD and spot rogue ones:Remove an unauthorized entry:powershell
Get-DhcpServerInDCExpected result:powershellRemove-DhcpServerInDC -DnsName "unauthorized.domain.local"Get-DhcpServerInDClists every DHCP server authorized in this AD domain (name + IP). Removal is silent on success. If it fails: "This operation can only be performed if the machine is joined to a domain" โ this only works on a domain-joined box with AD-integrated DHCP; a standalone DHCP server isn't tracked this way at all, so this check doesn't apply there. Don't unauthorize a server you're not certain is rogue โ cross-check against the README's described network topology first, since a legitimate secondary/backup DHCP server would also show up here. - Review scopes and options from PowerShell instead of clicking through the GUI:Expected result: Scope list with ID/range/state; option-value output shows each configured option (Router, DNS Servers, Domain Name, etc.) with its current value for that scope. If it fails: "ScopeId parameter is required" or similar if you leave the placeholder unfilled โ get the real scope ID from the first command's output first.powershell
Get-DhcpServerv4Scope Get-DhcpServerv4OptionValue -ScopeId <ScopeID> - Audit current leases for unexpected/unauthorized devices:Expected result: A table of active leases โ IP, MAC (powershell
Get-DhcpServerv4Lease -ScopeId <ScopeID>ClientId), hostname, lease expiry. If it fails: No error mode beyond a bad<ScopeID>; unfamiliar hostnames/MACs are worth a closer look but aren't automatically malicious on a real network โ cross-check against what devices the README/scenario says should be present, if it says anything at all. - Check for scope-level or server-level DNS dynamic update misconfiguration:Expected result: Shows whether dynamic DNS updates are enabled and under what conditions (e.g.,powershell
Get-DhcpServerv4DnsSettingDynamicUpdates: OnClientRequest,DeleteDnsRRonLeaseExpiry: True). If it fails: No real error mode; the finding to look for isDynamicUpdates: Always(updates DNS unconditionally) โ the checklist bullet above recommends restricting updates to only when explicitly requested by DHCP clients instead. - Note on CIS coverage: like DNS, the DHCP role isn't covered as its own section in the CIS Windows Server Benchmark โ the checks above come from general Microsoft/DHCP security guidance rather than a named CIS control. The benchmark's value here is indirect: keeping the underlying OS hardened (firewall, services, audit policy) still protects the DHCP role itself.
- Verify scope option changes actually reach a client: the fastest real-world confirmation is a lease renewal test from any client on the same scope โ
ipconfig /releasethenipconfig /renewโ and then check the client picked up the corrected Router/DNS options, not just that the server-side setting shows the new value. - Edge case โ superscopes and multiple scopes: if the server has more than one scope (or a superscope grouping several), scope options can be set at the scope level, the server level, or both, and server-level options only apply where a scope doesn't explicitly override them. Don't assume fixing one scope's options fixed all of them โ check
Get-DhcpServerv4Scopefor every scope ID present, not just the first one you find. - GUI cross-check:
dhcpmgmt.mscโ expand IPv4 โ Server Options (not just Scope Options) to see settings that apply server-wide unless a scope overrides them โ easy to miss if you only ever look inside a specific scope. is out of your control (it's on a different machine), but make sure YOUR box isn't accidentally running/authorizing an unintended DHCP scope, and that its own DHCP settings aren't the "rogue" element being tested.
10. File & Print Server / Share Permissions#
- Enumerate all shares โ remove or lock down anything not required by the README.
- For each required share, verify Share permissions AND NTFS permissions are least-privilege (avoid "Everyone: Full Control").
- Check for hidden/administrative shares beyond expected defaults (
C$,ADMIN$,IPC$are normal defaults โ extra custom hidden shares (name$) are suspicious). - If quotas are part of the scenario, verify File Server Resource Manager (FSRM) quotas match the README's requirements.
- Consider FSRM File Screening to block unauthorized executable uploads to shared drives if this fits the scenario (be careful โ don't block file types users legitimately need to share).
- For Print and Document Services: Server Manager โ Roles โ right-click Print and Document Services โ Remove Role Services for anything unneeded (e.g., unneeded print drivers, LPD service if not required).
Things to try / extra points#
- List all shares and permissions quickly:Expected result:powershell
Get-SmbShare Get-SmbShareAccess -Name "<ShareName>"Get-SmbSharelists every share (name, path, description);Get-SmbShareAccesslists the share-level permission entries (account + access right) for one named share. If it fails: "The term 'Get-SmbShare' is not recognized" on very old builds (2008 R2) โ usenet share(next bullet) instead, which is universally available. "Cannot find the share" means a typo in<ShareName>โ get exact names from the first command's output. - Legacy but fast equivalent:Expected result: Lists share names, paths, and remarks in a simple table. If it fails: No real error mode โ this command essentially always works, which is exactly why it's listed as the fallback for older builds.shell
net share - Find shares that aren't standard administrative shares (
$-suffixed) for review:Expected result: Only non-administrative, "real" shares (the ones users/apps actually connect to), filtering outpowershellGet-SmbShare | Where-Object { $_.Name -notlike "*$" }C$/ADMIN$/IPC$noise. If it fails: No error mode; note this filter is imperfect โ a custom hidden share intentionally named to end in$(the classic "hide a share" trick this section's checklist explicitly warns about) will be filtered OUT by this exact query, so also review the FULLGet-SmbSharelist from the previous bullet to catch customname$shares this filtered view would hide from you. - Check NTFS ACLs on a share's underlying folder:Expected result: A list of access control entries โ account + rights (powershell
Get-Acl "C:\Shares\Public" | Format-List icacls "C:\Shares\Public"FullControl,Modify,ReadAndExecute, etc.). If it fails: "Cannot find path" โ the actual share's local folder path may differ from this placeholder; get the realPathproperty fromGet-SmbShare's output first. SeeingEveryone: FullControlon a share's folder is the classic finding this section is checking for โ tighten it to the specific authorized group/users instead usingicaclsor the Security tab in Explorer. - Set up executable file screening via FSRM (GUI):
fsrm.mscโ File Screening Management โ right-click File Screens โ Create File Screenโฆ โ select the share path โ Block Executable Files โ Create. Expected result: New file screen appears in the list; attempting to copy an.exeinto that path from a client afterward fails with an access-denied-style error. If it fails:fsrm.mscnot found/won't open โ the File Server Resource Manager role service isn't installed; add it via Server Manager (Add-WindowsFeature FS-Resource-Manager) first. If a required application legitimately needs to write executables to that share, this will break it โ verify against the README before applying. - Enforce SMB signing to prevent tampering/relay attacks on file traffic โ this aligns with CIS Level 1 "Microsoft network server: Digitally sign communications (always)" and "Microsoft network client: Digitally sign communications (always)" Security Options, applicable to both DC and Member Server profiles:Expected result: Silent on success (thepowershell
Set-SmbServerConfiguration -RequireSecuritySignature $true -Force-Forcesuppresses the confirmation prompt). Verify withGet-SmbServerConfiguration | Select RequireSecuritySignature. If it fails: SMB signing adds CPU overhead and can slow file transfers on older/underpowered clients โ not usually enough to matter for a competition VM, but worth knowing. If an older SMB client (e.g. a legacy embedded device the scenario doesn't actually care about) can no longer connect afterward, that's expected โ signing requires compatible clients. - Disable SMBv1 (legacy, exploitable, rarely needed):Expected result: Output showing the feature state changing topowershell
Disable-WindowsOptionalFeature -Online -FeatureName SMB1Protocol -NoRestartDisabled; a restart is typically required to fully complete the removal despite-NoRestartdeferring it. If it fails: "Feature name SMB1Protocol is unknown" on very old builds where SMBv1 isn't managed as an optional feature the same way โ checkGet-WindowsOptionalFeature -Online -FeatureName SMB1Protocolfirst to confirm the exact feature name exists on this build. If any device on the network genuinely still requires SMBv1 (old printers/NAS devices sometimes do), this will break connectivity to it โ confirm nothing required depends on it first, though SMBv1 dependence is rare enough in a CyberPatriot scenario that this is usually safe to disable. - CIS Level 1 "Network access" tip not yet listed:
Network access: Restrict clients allowed to make remote calls to SAMโ restricting this to Administrators only reduces remote enumeration of the local account database via file-sharing protocols, and is a fairly low-risk win on a file server.
Tip: Removing a share the README calls "required" (even if its permissions look sloppy) will cost more than it's worth โ fix the permissions, don't delete the share, unless explicitly told to.
11. Windows Firewall#
- Ensure Windows Defender Firewall is enabled on all profiles (Domain, Private, Public) โ servers are frequently found with it disabled "for convenience."
- Set default inbound policy to block, default outbound to allow (unless the scenario needs specific outbound restrictions).
- Review inbound rules for anything unusual/overly permissive that isn't required by the server's roles (e.g., an inbound rule opening a random high port).
- Confirm rules exist (and are enabled) for the roles this server legitimately provides (AD DS, DNS, DHCP, File/Print, RDP if required) โ don't block your own required services.
- Log dropped/blocked packets if you want the extra audit trail (optional, low risk).
Things to try / extra points#
- Enable all profiles and set default policy in one shot:Expected result: Both printcmd
netsh advfirewall set allprofiles state on netsh advfirewall set allprofiles firewallpolicy blockinbound,allowoutboundWhat "allprofiles" means
Windows Firewall has three separate profiles โ Domain, Private, Public โ each with its own on/off state and rules, because you might want different rules depending on what network you're connected to. allprofiles applies the command to all three at once instead of having to repeat it three times with different profile names.Ok.If it fails: "The following command was not found" on very old syntax variants โ modernnetsh advfirewallsyntax as shown works on Server 2008 R2 and later; if genuinely on something older, use the GUI (wf.mscโ Properties) instead. This can immediately break RDP/remote management if you're connected remotely and the required inbound rules aren't already in place โ verify your own access method has an allow rule before/immediately after running this, per the section's own required-service warning below. - Review current rules for anything unexpected:Expected result: A table of every enabled inbound rule with its action (Allow/Block) and which profile(s) it applies to. If it fails: No error mode; a long list is normal (many built-in rules exist for Windows components) โ focus on custom-looking rule names or rules opening unusual high ports rather than trying to review every single built-in entry.powershell
Get-NetFirewallRule | Where-Object { $_.Enabled -eq "True" -and $_.Direction -eq "Inbound" } | Select-Object DisplayName, Action, Profile - Cross-check listening ports against firewall rules and running processes to spot something that shouldn't be open:Expected result: First command lists every listening TCP port with the owning process ID; second resolves a specific PID to its process name/path. If it fails: No error mode for either; an unfamiliar port with a process name you don't recognize is the actual thing to chase down โpowershell
Get-NetTCPConnection -State Listen | Select-Object LocalAddress, LocalPort, OwningProcess | Sort-Object LocalPort Get-Process -Id <PID>Get-Process -Id <PID> | Select Pathgives the full binary location, which is often the fastest way to judge whether something is legitimate. - This section aligns with CIS Level 1 "Windows Firewall with Advanced Security" guidance (same for DC and Member Server profiles) โ firewall state On, default inbound block/outbound allow for all three profiles (Domain, Private, Public) are core baseline items. A few more Level 1 firewall items worth checking while you're here:
- Confirm each profile is set to not display a notification when a program is blocked (avoids inconsistent behavior/interruptions during scoring) โ optional but consistent with CIS's "settings" guidance for each profile.
- Enable logging of dropped packets for each profile and increase the log file size limit well above the tiny Windows default, so evidence isn't immediately overwritten โ CIS recommends a substantially larger log size than the out-of-box default, though the exact recommended figure has varied across benchmark versions, so don't worry about hitting an exact number.
powershellSet-NetFirewallProfile -Profile Domain,Private,Public -LogBlocked True -LogFileName "%SystemRoot%\System32\LogFiles\Firewall\pfirewall.log"Expected result:- Silent on success.
- Confirm with
Get-NetFirewallProfile | Select Name, LogBlocked, LogFileName; the log file itself appears at that path once traffic is actually blocked.
If it fails:- "Access is denied" writing to the default log path is unusual for an elevated session but can happen on a heavily locked-down image โ redirect
-LogFileNameto a path you've confirmed is writable (e.g.%SystemRoot%\Temp\pfirewall.log) if so. - The log file won't appear until something is actually blocked and logged โ an empty/missing file immediately after running this isn't a failure, just means nothing's triggered it yet.
Tip: Before you block inbound broadly, make sure you know which ports AD/DNS/DHCP/RDP actually need (e.g., 53 DNS, 67/68 DHCP, 88 Kerberos, 389/636 LDAP, 445 SMB, 3389 RDP) โ a heavy-handed lockdown that breaks the domain controller's own required ports is a net loss.
12. Services Hardening#
Do this AFTER completing the Roles & Features audit in Section 3 โ many services underpin roles you've already decided to keep, so you should know what's "supposed" to be running before disabling anything.
- Review all running services; identify anything non-standard or unexplained.
- Disable/stop services clearly not required (Telnet, TFTP, Remote Registry, and similar legacy/high-risk services) โ unless required by the README.
- Do NOT disable services underpinning your confirmed server roles (e.g.,
NTDS,DNS,DHCPServer,Netlogon,W32Time,LanmanServerfor file shares). - Check service accounts โ services running as
LocalSystemunnecessarily, or with weak/known passwords for custom service accounts, are worth reviewing. - Check for unquoted service paths with spaces (privilege escalation vector).
Things to try / extra points#
List running services for review:
powershellGet-Service | Where-Object { $_.Status -eq "Running" } | Select-Object Name, DisplayNameExpected result:A table of every currently-running service's short name and display name.If it fails:No error mode; a long list is normal on a server with multiple roles โ cross-reference against the "don't disable these" list in the checklist bullets above before touching anything unfamiliar.Disable common legacy/high-risk services if present and unneeded:
powershellSet-Service -Name "RemoteRegistry" -StartupType Disabled -Status Stopped Set-Service -Name "TlntSvr" -StartupType Disabled -Status StoppedExpected result:Silent on success.If it fails:- "Cannot find any service with service name" for
TlntSvrjust means Telnet Server isn't installed on this image at all โ nothing to do, not an error worth chasing. - Remote Registry being genuinely needed for a remote-management tool the README requires is rare but possible โ verify first.
- "Cannot find any service with service name" for
Find services vulnerable to unquoted-path privilege escalation:
powershellGet-CimInstance Win32_Service | Where-Object { $_.PathName -notlike '"*' -and $_.PathName -like '* *' } | Select-Object Name, PathNameExpected result:Lists services whose executable path contains a space and isn't wrapped in quotes โ a real vulnerability (Windows tries each space-delimited segment as a possible executable, which an attacker can exploit by planting a malicious file at an earlier segment) if any hits come back.If it fails:No error mode; a hit needs manual remediation โ wrap the actual path in quotes via the service's registryImagePathvalue (HKLM:\SYSTEM\CurrentControlSet\Services\<ServiceName>) or the vendor's installer/config tool if one exists, then restart the service to confirm it still starts correctly with the quoted path.Cross-reference: for a DC, confirm these core services are running before/after any service cleanup:
NTDS,Netlogon,DNS,KDC,W32Time,ADWS,DFSR(orNtFrson older domains).powershellGet-Service NTDS, Netlogon, DNS, KDC, W32Time, ADWSExpected result:All listed asRunningon a healthy DC.If it fails:- "Cannot find any service with service name 'DNS'" means the DNS role genuinely isn't installed on this DC โ not every DC also runs DNS, so a missing service here isn't automatically wrong, just confirm against what roles Section 3's audit found.
- Any of these showing
Stoppedon a box that SHOULD have that role is a serious, high-priority finding โ restart it immediately (Start-Service <name>) and investigate why it stopped.
This aligns with CIS Benchmark "System Services" guidance (Level 1, both profiles), which recommends a "Disabled" or "Manual" startup type for a list of legacy/unnecessary built-in services not required by most scenarios (Telnet, TFTP, Simple TCP/IP Services, Fax, Print Spooler on non-print DCs, Remote Registry, etc.). Cross-check your running-services list above against that theme rather than trying to memorize an exact service list.
OpenSSH Server (
sshd) and Windows Subsystem for Linux (LxssManager) โ two modern optional features that CIS Level 1 says should be disabled/uninstalled unless explicitly required, and that a server-focused checklist can easily miss since they're client-image habits. Either one, left enabled unnecessarily on a server, is a full remote-access or Linux-userland attack-surface expansion:powershellGet-WindowsCapability -Online | Where-Object Name -like "OpenSSH.Server*" Get-Service sshd, LxssManager -ErrorAction SilentlyContinue # If present and not required by the README: Stop-Service sshd -ErrorAction SilentlyContinue; Set-Service sshd -StartupType Disabled -ErrorAction SilentlyContinue Set-Service LxssManager -StartupType Disabled -ErrorAction SilentlyContinueExpected result:Get-WindowsCapabilityshows install state (Installed/NotPresent) for OpenSSH Server;Get-Serviceshows current status for both if present (silently returns nothing for either that isn't installed, thanks to-ErrorAction SilentlyContinue).If it fails:- No real error mode given the suppression flags โ absence of both services is the common, expected case on most server images (neither is installed by default) and means this whole bullet is a no-op, not a failure.
- If the README specifically requires SSH access to this server (some Linux-interop scenarios do), don't disable
sshdโ harden itssshd_configinstead using the same guidance as the Linux checklist's SSH section.
Tip: When in doubt about a service tied to a required role, set it to the correct startup type and leave it running rather than disabling it outright โ a stopped critical service is one of the fastest ways to silently fail a scored check.
13. Windows Updates#
- Check Windows Update configuration/history โ determine if updates are being managed locally, via WSUS, or are disabled.
- Install available critical/security updates if the environment allows internet or WSUS access (note: many competition images are offline โ don't assume internet access; check the README for guidance).
- Ensure automatic updates aren't configured to something that silently blocks all patching if the scenario expects you to patch.
- If offline, at minimum ensure the Windows Update service itself isn't maliciously disabled, and update settings aren't configured to block a legitimate internal WSUS server.
Things to try / extra points#
- Check update history and pending updates:Expected result: A table of installed updates (KB numbers) with install dates, most recent first. If it fails:powershell
Get-HotFix | Sort-Object InstalledOn -DescendingInstalledOnshowing blank for some entries is a known cosmetic quirk ofGet-HotFixon certain builds โ not a real error; cross-check withGet-WmiObject Win32_QuickFixEngineeringfor an alternate view if the dates matter for a forensics question. A very oldInstalledOndate on everything is itself the finding (this box hasn't been patched in a long time), not a broken command. - Check Windows Update service state:Expected result: Shows current status โ typicallypowershell
Get-Service wuauservRunningorStopped(Windows Update service is often stopped between update checks even on a healthy system, which is normal, not a vulnerability by itself). If it fails: No error mode; ifStartTypeshowsDisabled(not just currently stopped), that's the actual finding โ a maliciously disabled Windows Update service prevents ALL future patching regardless of connectivity; fix withSet-Service wuauserv -StartupType Manual(its normal default). - If WSUS is configured, confirm the pointed-to server is the legitimate internal one, not a rogue redirect:Expected result: If WSUS is configured via policy, shows thepowershell
Get-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -ErrorAction SilentlyContinueWUServer/WUStatusServervalues โ verify these point at the actual internal WSUS server the README/network topology describes. If it fails: No output (with-ErrorAction SilentlyContinuesuppressing the "not found" error) just means WSUS isn't configured via this policy path at all โ the server is using default Windows Update/Microsoft Update instead, which isn't inherently wrong. AWUServervalue pointing at an unfamiliar IP/hostname is a real, serious finding โ that's a mechanism by which an attacker could push malicious "updates."
Tip: In most CyberPatriot images, do NOT run a full Windows Update pass blindly early on โ some images are intentionally offline/air-gapped and a stuck update check can waste significant round time. Check connectivity and README guidance first.
Why patching a Domain Controller specifically matters right now: 2026 has been a heavy year for critical, DC-targeting Windows vulnerabilities โ multiple Active Directory Domain Services remote-code-execution flaws (including one with a CVSS of 8.8 exploitable by any domain-authenticated attacker with no user interaction) and a critical Kerberos Key Distribution Center RCE affecting Server 2012 through Server 2025 domain controllers, all patched via routine cumulative updates across the year. There's also a critical, unauthenticated, network-reachable Windows DNS Server RCE (CVSS 9.8) โ directly relevant if this box is running the DNS role from Section 8. None of this means you should chase individual CVE numbers during a round โ it means the generic "make sure cumulative updates are actually installed" check above is disproportionately high-value on a DC compared to a regular workstation, since a DC is both the most attractive target and the one most likely to be running AD DS, Kerberos, and DNS all at once.
14. Remote Desktop / RDS / Remote Access#
- Determine if Remote Desktop is required by the README. If not required, disable it. If required, harden it rather than disabling it.
- Enforce Network Level Authentication (NLA) for RDP connections.
- Restrict Remote Desktop Users group membership to only authorized accounts.
- If Remote Desktop Services (RDS) role (Session Host/full RDS deployment) is installed, verify it's actually required by the scenario โ this is a heavier role than basic RDP and is a common "rogue role" candidate.
- Disable Remote Assistance and RDP Shadowing unless explicitly required.
- Review WinRM/PowerShell Remoting configuration โ ensure it's not accepting unencrypted traffic or trusting wildcard hosts.
Things to try / extra points#
- Disable RDP entirely if not required:Expected result: Silent on success; RDP connections to this box are refused immediately (no reboot required for this particular setting). If it fails: If you're managing this box over RDP right now, running this command disconnects you immediately โ only run it from local/console access, or via a different remote-management channel (WinRM/PowerShell remoting) you're certain will remain available. If disabled by mistake and you're now locked out remotely, you need local/hypervisor console access to re-enable it (set the value back topowershell
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server" -Name "fDenyTSConnections" -Value 1 -Type DWord0). - If RDP IS required, enforce NLA instead of disabling โ this aligns with CIS Level 1 "Require user authentication for remote connections by using Network Level Authentication," applicable to both DC and Member Server profiles:Expected result: Silent on success; new RDP connection attempts now require NLA (pre-authentication before the full session negotiates) โ visible as a policy difference in the RDP client's connection behavior. If it fails: Very old RDP clients that don't support NLA will be unable to connect at all after this change โ if the scenario requires access from an old/embedded RDP client, this could break required functionality; verify first. If you don't see a behavior change, confirm you're editing the right sub-key (powershell
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp" -Name "UserAuthentication" -Value 1 -Type DWordWinStations\RDP-Tcp, not the parentTerminal Serverkey used by the disable-RDP setting above โ easy to conflate the two paths). - CIS Level 1 item not yet listed:
Set client connection encryption levelโ High Level (under Remote Desktop Session Host settings) โ ensures RDP sessions use strong encryption rather than the client-negotiated default. - Device redirection hardening โ CIS Level 2 items covering what a connecting RDP client is allowed to redirect into the session: disallow LPT port redirection, disallow WebAuthn redirection (prevents a remote client's security key/biometric device from being usable inside the session, closing an unusual credential-relay path), and restrict UI Automation redirection. Lower priority than NLA/encryption but a legitimate "extra credit" pass if this server is RDP-reachable:Expected result: Silent on success (or creates the key path if missing โ addpowershell
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services" -Name "fDisableLPT" -Value 1 -Type DWord Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services" -Name "DisableWebAuthnRedirection" -Value 1 -Type DWord-Forceif you get a path-not-found error, since this policy path may not exist yet on a box that's never had these settings configured). If it fails: "Cannot find path" โ add-Forceto create the key. Low functional risk โ LPT redirection and WebAuthn redirection are rarely relied upon in a competition scenario, so this is a fairly safe Level 2 item to apply broadly. - Audit who's allowed to RDP in:Expected result: Lists accounts/groups currently in the Remote Desktop Users local group. If it fails: Empty result is fine if RDP access is granted only to Administrators (who can always RDP in regardless of this group's membership) โ don't assume empty means "nobody can RDP." Remove anyone unauthorized withpowershell
Get-LocalGroupMember -Group "Remote Desktop Users"Remove-LocalGroupMember -Group "Remote Desktop Users" -Member "<name>". - Disable Remote Assistance and RDP shadowing:Expected result: Silent on success; Remote Assistance invitations can no longer be created, and RDP session shadowing (an admin silently viewing/controlling another user's active RDP session) is disabled. If it fails: "Property Shadow does not exist" โ addpowershell
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Remote Assistance" -Name "fAllowToGetHelp" -Value 0 Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services" -Name "Shadow" -Value 0-Force, or create the key first withNew-Itemif the wholeTerminal Servicespolicy path is missing. Low functional risk โ neither feature is commonly relied upon in competition scenarios. - Lock down WinRM trusted hosts and require encryption:Expected result:powershell
Get-Item WSMan:\localhost\Client\TrustedHosts Set-Item WSMan:\localhost\Client\TrustedHosts -Value "" -Force Set-Item WSMan:\localhost\Service\AllowUnencrypted -Value $false -ForceGet-Itemshows the current trusted-hosts list (often*, meaning "trust anyone," on an unhardened image โ that's the finding). TheSet-Itemcalls clear it to empty and disable unencrypted WinRM traffic. If it fails: "Access is denied" โ the WinRM service/client config requires an elevated session. If your own remote-management tooling (scoring engine, a teammate's remote PowerShell session) relies on WinRM with the current TrustedHosts value, clearing it to empty can break that connectivity โ same caution as the RDP-disable warning above; know how you're connecting before locking this down. If WinRM itself isn't configured yet (WinRM: command not found-style errors), runEnable-PSRemotingfirst, or skip this bullet if WinRM isn't in use on this box at all.
Tip: If the scoring engine or your team connects to the box via RDP for management, disabling RDP outright can lock everyone out โ confirm how the box is being accessed before flipping this off.
15. IIS Web Server Hardening#
Applies if the Web Server (IIS) role is installed.
- First, browse the site from your host machine's browser before changing anything โ observe how it handles bad/missing-page requests, and get a feel for expected normal behavior.
- Inspect the default content folder
C:\inetpub\wwwrootโ look for anything beyond the expectediisstart.html/Default.htm/welcome.png. Open unfamiliar HTML files in Notepad and images in an image viewer to check for tampering. Delete non-critical/unexpected content. - Server Manager โ Roles โ right-click Web Server (IIS) โ Remove Role Services โ uncheck anything not needed. The most commonly exploited extras are ASP.NET, CGI, and FTP Server โ remove if not explicitly required.
- In IIS Manager (
inetmgr):- Authentication: disable Anonymous Authentication if the scenario allows (test that the site still functions as required afterward โ some scenarios intentionally need anonymous access and disabling it costs functionality points).
- Default Document: should be a short, standard ordered list (
Default.htm,Default.asp,index.htm,index.html,iisstart.htm) โ remove anything unexpected. - Error Pages: verify custom error paths look legitimate (standard
<LANGUAGE-TAG>defaults toen-US). - Handler Mappings: by default only
OPTIONSVerbHandler,TRACEVerbHandler, andStaticFileshould be present โ investigate anything extra. - HTTP Request Headers: should typically be empty โ remove anything present.
- Logging: ensure it's enabled (not greyed out).
- Modules: all module types should show as Native; Request Filtering, Output Caching, and Server Certificates (unless HTTPS is in use) should be empty by default.
- Shared Configuration: disable (uncheck) unless explicitly required.
- Disable dangerous/unnecessary HTTP verbs (TRACE, TRACK, and typically OPTIONS/DELETE) via Request Filtering.
- Enforce TLS 1.2/1.3 and disable SSLv3/TLS 1.0/TLS 1.1 at the SChannel registry level.
- Remove executable rights from upload/content directories (e.g.,
/uploads/,/images/) โ restrict handler access policy to Read/Script only, not Execute.
Things to try / extra points#
- Deny dangerous verbs via PowerShell:Expected result: Silent on success. Confirm via IIS Manager โ Request Filtering โ HTTP Verbs tab, or by sending an actualpowershell
Import-Module WebAdministration Add-WebConfigurationProperty -Filter /system.webServer/security/requestFiltering/verbs -PSPath "IIS:\" -Name "." -Value @{verb='TRACE'; allowed='false'} Add-WebConfigurationProperty -Filter /system.webServer/security/requestFiltering/verbs -PSPath "IIS:\" -Name "." -Value @{verb='TRACK'; allowed='false'}TRACErequest to the site and confirming it's rejected (403). If it fails: "The term 'Import-Module WebAdministration' ... module could not be loaded" means the IIS Management Scripts and Tools feature isn't installed โ add it via Server Manager (Add-WindowsFeature Web-Scripting-Tools) first. Duplicate entries (running this twice) can error "already exists" โ harmless, means it's already configured. - Disable legacy TLS/SSL protocols:Expected result: Silent on success; requires a reboot to fully take effect (SChannel protocol settings are read at boot/service-start, not live). If it fails: No common error beyond permission issues. Older clients that only support TLS 1.0/1.1 will be unable to connect via HTTPS afterward โ if the scenario has legacy client requirements, verify before applying broadly; for a normal competition scenario this is almost always safe and expected.powershell
$Path = "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols" New-Item -Path "$Path\TLS 1.0\Server" -Force | Out-Null Set-ItemProperty -Path "$Path\TLS 1.0\Server" -Name "Enabled" -Value 0 -Type DWord New-Item -Path "$Path\TLS 1.1\Server" -Force | Out-Null Set-ItemProperty -Path "$Path\TLS 1.1\Server" -Name "Enabled" -Value 0 -Type DWord - Restrict handler execute rights on a specific directory (e.g., uploads):Expected result: Silent on success; uploading and then attempting to EXECUTE a script/binary from that directory should now fail even if the upload itself succeeds. If it fails: "Cannot find path 'IIS:\Sites\Default Web Site\uploads'" โ the site name or subfolder doesn't match this server's actual structure; list real site names withpowershell
Set-WebConfigurationProperty -Filter /system.webServer/handlers -Name accessPolicy -Value "Read, Script" -PSPath "IIS:\Sites\Default Web Site\uploads"Get-Websiteand real virtual directories withGet-WebVirtualDirectoryfirst. - Disable anonymous auth via PowerShell:Expected result: Silent on success; browsing the site afterward should now prompt for credentials instead of loading directly. If it fails: If the scenario's website is supposed to be publicly viewable without login, this breaks that requirement โ exactly the warning in the checklist tip below; test the site's actual expected behavior immediately after this change and revert (powershell
Set-WebConfigurationProperty -Filter /system.webServer/security/authentication/anonymousAuthentication -Name enabled -Value False -PSPath IIS:\-Value True) if it broke something required. - Audit installed native/managed modules for anything resembling a webshell or unauthorized ISAPI filter:Expected result: Lists installed IIS modules by name and image path โ cross-reference against the standard default module list mentioned in the checklist bullets above (mostlypowershell
Get-WebGlobalModule Get-WebManagedModuleNativetype on a default install). If it fails: No error mode; an unfamiliar module name or one pointing at a DLL in an unusual path (not the standard%windir%\System32\inetsrv\location) is the actual finding worth investigating directly. - If the server hosts a backend application (PHP/ASP.NET with a database, etc.), also work through a web-application-specific checklist in addition to this one โ general IIS hardening alone won't cover app-level injection/auth flaws.
- Note on CIS coverage: IIS hardening isn't part of the Windows Server Benchmark at all โ CIS publishes a separate CIS Microsoft IIS Benchmark. The items in this section come from Microsoft/general web-hardening guidance rather than the Windows Server benchmark; if your team wants deeper IIS-specific coverage, that's the document to pull from, not this one.
Tip: Disabling Anonymous Authentication can break a site that's supposed to be publicly viewable in the scenario. Test the site's expected use case immediately after the change; if it breaks required functionality, re-enable and look for a different fix (e.g., restricting by NTFS ACL instead).
16. Local Accounts & Built-In Accounts (On the Server Itself)#
Distinct from AD/domain accounts โ this is the local SAM database on the server itself (relevant even on a DC for the local Administrator, and very relevant on member/standalone servers).
- Enumerate local accounts (including any hidden from Control Panel) and local group memberships โ local admin group membership is separate from Domain Admins and is frequently overlooked.
- Disable unused built-in accounts:
Guest,DefaultAccount,WDAGUtilityAccount(if present and unused). - Rename the built-in
Administratoraccount (also enforced via GPO in Section 5 for domain-joined boxes, but verify locally too). - Review local
Administratorsgroup membership on the server โ remove any account (local or domain) that doesn't need local admin rights on this specific box. - Review local
Remote Desktop Usersgroup membership.
Things to try / extra points#
- Dump all local accounts, including ones not shown in Control Panel:Expected result: A table of every local (non-domain) account on this specific machine's SAM database. If it fails: "not recognized" on very old builds โ fall back topowershell
Get-LocalUser | Select-Object Name, Enabled, AccountExpires, LastLogon, PasswordRequirednet user(universally available). Remember: on a DC, most user management happens in AD, not here โ this cmdlet only shows the small set of genuinely local accounts (built-in Administrator/Guest and any locally-created ones). - Audit local privileged group membership (separate from
Get-ADGroupMemberโ this is the box's own local SAM):Expected result: Lists local AND domain accounts/groups that have been granted local admin / local RDP rights on this specific machine. If it fails: No error mode; the important distinction to remember (per the checklist bullet above) is that this is separate from Domain Admins โ a domain account can be a completely unprivileged domain user and STILL be a local admin on this one box if someone added it here specifically, which is exactly what this check is meant to catch.powershellGet-LocalGroupMember Administrators Get-LocalGroupMember -Group "Remote Desktop Users" - Disable unused built-ins and rename Administrator:Expected result: Eachshell
net user Guest /active:no net user DefaultAccount /active:no wmic useraccount where name='Administrator' call rename name='RenamedAdmin'net usercommand prints "The command completed successfully."; thewmicrename showsReturnValue = 0;in its output block. If it fails: "Access is denied" means not elevated. If you're currently logged in AS the Administrator account, renaming it can be disorienting but won't log you out mid-session โ your existing session stays valid, but be aware the username changes for any NEW login.wmicis removed by default on Windows 11 24H2+ โ if "not recognized," rename viaRename-LocalUser -Name "Administrator" -NewName "RenamedAdmin"(PowerShell) or the Local Users and Groups GUI instead. - This section aligns with CIS Level 1 "Accounts" Security Options, consistent across DC and Member Server profiles:
Accounts: Guest account statusโ Disabled,Accounts: Rename administrator account,Accounts: Rename guest account. One more Level 1 item worth adding here:Accounts: Limit local account use of blank passwords to console logon onlyโ Enabled โ prevents a local account with a blank password from being used for network logon.
Tip: On a Domain Controller, "local" accounts are limited (DCs share a single SAM/AD database for most purposes) โ but the local
Administratorsgroup concept still matters on member servers and standalone file/print/web boxes. Don't skip this section just because the box is domain-joined.
17. Event Log & Audit Policy#
- Enable auditing across key categories: Account Management, Logon/Logoff, Policy Change, Privilege Use, System, Object Access.
- Check the Security log for failed logon attempts (Event ID 4625) and account lockouts (Event ID 4740) โ these can reveal brute-force attempts or a misconfigured service account, and are common forensics-question fodder.
- Check for account creation events (Event ID 4720) to spot when an unauthorized account was added.
- Ensure log sizes are reasonably large and set to not overwrite too quickly (so evidence isn't lost) โ but don't fight the scenario if a specific log size is mandated.
- Enable PowerShell Script Block Logging for better visibility into what's been run on the box.
Things to try / extra points#
- Enable broad category auditing (fast baseline):Or enable everything (maximum logging, use if the scenario emphasizes forensics/audit points):shell
auditpol /set /category:"Account Management" /success:enable /failure:enable auditpol /set /category:"Logon/Logoff" /success:enable /failure:enable auditpol /set /category:"Policy Change" /success:enable /failure:enable auditpol /set /category:"Privilege Use" /success:enable /failure:enable auditpol /set /category:"System" /success:enable /failure:enableWhat auditpol is configuring
Windows can log security events, but which categories get logged is off by default for most of them. auditpol /set turns on logging for a specific category โ here, "Account Management" (things like account creation/deletion/changes). /success:enable /failure:enable means log both successful AND failed attempts in that category, not just one or the other.Expected result: Each line printsshellauditpol /set /category:* /success:enable /failure:enableThe command was successfully executed.If it fails: "The category name is unrecognized" โ category names are exact and version-sensitive; runauditpol /list /category:*to get this build's exact recognized names rather than retyping from memory. Access denied means not elevated. - Query failed logons directly for forensics/lockout investigation:powershell
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4625} -MaxEvents 25 Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4740} -MaxEvents 25 - Query recent account-creation events:Expected result (all threepowershell
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4720} -MaxEvents 25Get-WinEventqueries): Up to 25 matching event records, newest first, with timestamp, account, and details. If it fails: "No events were found that match the specified selection criteria" is a normal result meaning nothing of that type has happened yet (or the relevant audit category isn't enabled โ see the bullet just above; enable auditing FIRST, then these queries will start finding events going forward, but won't retroactively find events from before auditing was on). - Enable PowerShell logging:Expected result: Silent on success. Verify by running any PowerShell command afterward and checking Event Viewer โ Applications and Services Logs โ Microsoft โ Windows โ PowerShell โ Operational for a new Script Block logging event (Event ID 4104). If it fails: No real failure mode for the registry write itself. If events still aren't appearing, confirm the PowerShell Operational log itself is enabled (right-click the log in Event Viewer โ check "Enable Logging" if greyed out/disabled) โ the registry policy controls WHAT gets logged, but the log channel itself also needs to be turned on to actually record it.powershell
New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" -Force | Out-Null Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" -Name "EnableScriptBlockLogging" -Value 1 - This section aligns closely with CIS Benchmark Level 1 "Advanced Audit Policy Configuration" (both DC and Member Server profiles) โ the broad-category
auditpolcommands above cover most of what CIS asks for. A few more granular CIS Level 1 audit subcategories worth explicitly enabling if you want closer alignment:shellauditpol /set /subcategory:"Credential Validation" /success:enable /failure:enable auditpol /set /subcategory:"Security Group Management" /success:enable /failure:enable auditpol /set /subcategory:"Other Account Management Events" /success:enable /failure:enable - DC-specific CIS Level 1 subcategories (these only make sense on a Domain Controller โ the CIS Domain Controller profile calls these out specifically, they don't apply to the Member Server profile):Expected result (both subcategory blocks): Each line printsshell
auditpol /set /subcategory:"Directory Service Access" /success:enable /failure:enable auditpol /set /subcategory:"Directory Service Changes" /success:enable /failure:enable auditpol /set /subcategory:"Kerberos Authentication Service" /success:enable /failure:enable auditpol /set /subcategory:"Kerberos Service Ticket Operations" /success:enable /failure:enableThe command was successfully executed.If it fails: "The subcategory name is unrecognized" โ same fix as the category-level version above:auditpol /list /subcategory:*shows exact recognized names on this build. Running the DC-specific block on a non-DC member server will still often succeed syntactically (the subcategory names exist generically) but won't generate meaningful events since there's no AD DS/Kerberos KDC role generating that activity โ harmless to run, just not useful there. - CIS also recommends significantly increasing the default Application/Security/System event log maximum size well beyond the small out-of-box default, so a burst of activity doesn't overwrite evidence before you can review it โ exact size thresholds have varied across benchmark versions, so treat "much bigger than default" as the actionable guidance rather than chasing one specific number.
Tip: Turning on full auditing can generate a LOT of Security log noise fast โ if you need to find a specific event later, filter by Event ID and a tight time window rather than scrolling manually.
18. Registry, System Hardening & Exploit Mitigations#
General Windows hardening still applies to servers โ apply judiciously and re-test required functionality after each change.
- Enforce UAC to always prompt on the secure desktop.
- Disable AutoRun/AutoPlay globally.
- Restrict null sessions / anonymous SAM enumeration.
- Disable LLMNR/NBT-NS/mDNS to reduce local name-resolution poisoning risk (verify this doesn't break internal name resolution the scenario depends on).
- Enforce SMB signing; disable SMBv1.
- Enable system-wide exploit mitigations (DEP, SEHOP, ForceRelocateImages).
- Protect LSASS as a protected process to hinder credential-dumping tools.
- Restrict removable media (USB mass storage) if appropriate to the scenario.
- Set PowerShell execution policy to Restricted unless scripts are required to run.
- Set Boot-Start Driver Initialization Policy (Early Launch Antimalware) to only load Good/Unknown drivers at boot.
- Disable "Shutdown: Allow system to be shut down without having to log on."
Things to try / extra points#
- Most of the items in this section map onto CIS Level 1 "Security Options" / "Administrative Templates" guidance (both DC and Member Server profiles) โ UAC behavior, AutoPlay, null-session/anonymous restrictions, and SMB signing are all standard baseline items. LLMNR/mDNS disabling and PowerShell execution policy restriction are good practice but are not core CIS Windows Server Benchmark controls โ they come from broader Microsoft security guidance, so don't expect to find them called out by that name in the benchmark itself.
- Early Launch Antimalware boot policy โ CIS Level 1, both profiles. Stops a malicious or unsigned boot-start driver from loading before AV gets a chance to scan it:Expected result: Silent on success; takes effect on next boot. If it fails: No common error beyond permissions. Valuepowershell
New-Item -Path "HKLM:\SYSTEM\CurrentControlSet\Control\EarlyLaunch" -Force | Out-Null Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\EarlyLaunch" -Name "DriverLoadPolicy" -Value 3 -Type DWord3= "Good and unknown" (blocks known-bad only) โ the more aggressive1("Good only") can prevent legitimate unsigned/unknown drivers required by the scenario's hardware from loading;3is the safer default unless you specifically know every driver on this box is signed. - "Shutdown: Allow system to be shut down without having to log on" โ CIS Level 1 Security Options item, should be Disabled. On by default this lets anyone at the physical console (or console-equivalent) power off the server from the logon screen without ever authenticating โ a low-tech but real availability/DoS-adjacent finding that's easy to miss because it's not framed as an "access" setting:Expected result: Bothpowershell
# secpol.msc / GPMC โ Security Options โ "Shutdown: Allow system to be shut down without having to log on" โ Disabled secedit /export /cfg C:\shutdownpol.cfg /areas SECURITYPOLICY # Edit C:\shutdownpol.cfg, set: ShutdownWithoutLogon = 0 secedit /configure /db C:\Windows\security\local.sdb /cfg C:\shutdownpol.cfg /areas SECURITYPOLICYseceditcommands printThe task has completed successfully.If it fails: Samesecedittroubleshooting as Section 6's password-policy walkthrough โ a malformed edited line shows as "completed with one or more errors," check%windir%\security\logs\scesrv.logfor specifics. On a DC, remember local secedit-based policy can be overridden by domain GPO โ set this via GPMC's Default Domain Policy instead if it doesn't stick. - UAC hardening:powershell
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" -Name "EnableLUA" -Value 1 -Type DWord Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" -Name "ConsentPromptBehaviorAdmin" -Value 2 -Type DWord Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" -Name "PromptOnSecureDesktop" -Value 1 -Type DWord - Disable AutoRun/AutoPlay:powershell
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer" -Name "NoDriveTypeAutoRun" -Value 255 -Type DWord Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Explorer" -Name "NoAutoplayfornonVolume" -Value 1 -Type DWord - Restrict null sessions and anonymous enumeration:powershell
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa" -Name "RestrictAnonymous" -Value 1 -Type DWord Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa" -Name "RestrictAnonymousSAM" -Value 1 -Type DWord Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa" -Name "EveryoneIncludesAnonymous" -Value 0 -Type DWord Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters" -Name "RestrictNullSessAccess" -Value 1 -Type DWord - Disable LLMNR/mDNS multicast name resolution:powershell
New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\DNSClient" -Force | Out-Null Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\DNSClient" -Name "EnableMulticast" -Value 0 -Type DWord - System-wide exploit mitigations:powershell
Set-ProcessMitigation -System -Enable DEP, EmulateAtlThunks, SEHOP, ForceRelocateImages - Protect LSASS memory from dumping tools (e.g., Mimikatz-style attacks) โ flag this as a CIS-adjacent Level 2 item: it's a strong defense-in-depth control but can interfere with some legitimate security/monitoring tools that hook LSASS, so test after applying:powershell
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa" -Name "RunAsPPL" -Value 1 -Type DWord - Restrict USB mass storage:powershell
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\USBSTOR" -Name "Start" -Value 4 -Type DWord - Restrict PowerShell execution policy:powershell
Set-ExecutionPolicy -ExecutionPolicy Restricted -Scope LocalMachine -Force - Reset system folder ACLs if ownership/permissions look tampered with:cmd
icacls "C:\Windows\System32" /reset /T /C /Q - System file integrity check if core binaries seem replaced/corrupted:Expected result (all registry/mitigation/policy blocks above): everyshell
sfc /scannow DISM /Online /Cleanup-Image /RestoreHealthSet-ItemProperty/New-Itemis silent on success โ verify with a matchingGet-ItemPropertyread-back rather than trusting the write alone.Set-ProcessMitigationis also silent; confirm withGet-ProcessMitigation -System.Set-ExecutionPolicyis silent; confirm withGet-ExecutionPolicy -List.icacls /resetprints one line per file/folder processed, ending in a summary of successes/failures.sfc /scannowtakes several minutes and ends with one of: no violations found, violations found and repaired, or violations found but NOT all repaired (requiringDISM /RestoreHealthfirst to fix the underlying component store, then re-runningsfc). If it fails: "Cannot find path" on anySet-ItemPropertyโ add-Force(creates the key) or run the pairedNew-Itemline first if one is shown.RunAsPPL(LSASS protection) can break legitimate security tools or some VPN/smartcard software that hooks LSASS โ test required functionality immediately after. Restricting the execution policy toRestrictedblocks ALL.ps1scripts from running, including your own remediation scripts โ if you need to keep running PowerShell scripts for the rest of the round, useRemoteSignedinstead, or plan to temporarily bypass with-ExecutionPolicy Bypasson a per-invocation basis.icacls /resetonSystem32is a heavy hammer โ it resets to INHERITED defaults, which is usually correct for cleaning up tampered permissions, but double-check nothing scenario-specific was intentionally customized there first.sfc /scannowreporting unrepairable violations after aDISM /RestoreHealthpass usually means the DISM online repair source itself couldn't fetch clean files (no internet/WSUS) โ if this image is offline, this specific repair may not be achievable; note it rather than looping on it.
Tip: Disabling LLMNR/NetBIOS can break name resolution on networks that rely on it for non-DNS hosts โ if other scenario machines suddenly can't be reached by name, this is one of the first things to check.
19. Malware, Persistence & Backdoor Hunting#
Windows Defender Antivirus (not to be confused with Windows Defender Firewall in Section 11)#
Server 2016+ ships Windows Defender Antivirus by default (unless the feature was explicitly removed via Server Manager). It's easy to assume "servers don't need AV configuration" and skip this entirely โ don't:
Confirm real-time protection is actually on, not just installed:
powershellGet-MpComputerStatus | Select AntivirusEnabled, RealTimeProtectionEnabled, AntispywareEnabled Get-MpPreference | Select DisableRealtimeMonitoring, PUAProtectionCIS Level 1: PUA (Potentially Unwanted Application) protection should be set to Block, and Network Protection (blocks connections to known-malicious IPs/domains at the network level) should be enabled โ both are off by default and neither shows up if you only check "is Defender on":
powershellSet-MpPreference -PUAProtection Enabled Set-MpPreference -EnableNetworkProtection EnabledCheck for malicious scan exclusions โ a whole-drive or user-folder exclusion means malware sitting there is never scanned:
powershellGet-MpPreference | Select ExclusionPath, ExclusionExtension, ExclusionProcessCheck for Image File Execution Options (IFEO) hijacks โ a classic accessibility-tool backdoor (e.g.,
sethc.exe/utilman.exeremapped tocmd.exe).Check Run/RunOnce registry keys (both
HKLMandHKCU, plus WOW6432Node) for unauthorized startup entries.Verify Winlogon
ShellandUserinitvalues are unmodified defaults.Check for WMI event subscriptions used for fileless persistence.
Review Scheduled Tasks for anything running elevated or from a non-standard path outside
\Microsoft\....Check for AppInit_DLLs / AppCertDlls injection hooks.
Scan common drop locations (
C:\Users\Public,C:\ProgramData,C:\Windows\Temp) for unauthorized executables/scripts/media.Check Alternate Data Streams (ADS) on user/system folders for hidden payloads.
Review the hosts file for unauthorized DNS-spoofing entries.
Check for port proxies / port forwarding rules and unexpected static routes.
Things to try / extra points#
- IFEO hijack check:powershell
Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\*" -ErrorAction SilentlyContinue | Select-Object PSChildName, Debugger - Startup key audit:powershell
Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run*","HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Run*","HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run*" - Winlogon check (expected:
Shell = explorer.exe,Userinit = C:\Windows\system32\userinit.exe,):powershellGet-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" | Select-Object Shell, Userinit - WMI persistence sweep:powershell
Get-CimInstance -Namespace root\subscription -ClassName __EventConsumer Get-CimInstance -Namespace root\subscription -ClassName __EventFilter - Scheduled Task sweep:powershell
Get-ScheduledTask | Where-Object { $_.State -ne "Disabled" -and $_.TaskPath -notlike "\Microsoft*" } | Select-Object TaskName, TaskPath - DLL injection hook check:powershell
Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows" -Name "AppInit_DLLs" -ErrorAction SilentlyContinue Get-ItemProperty "HKLM:\System\CurrentControlSet\Control\Session Manager\AppCertDlls" -ErrorAction SilentlyContinue - Rogue file sweep:powershell
Get-ChildItem -Path C:\Users\Public, C:\ProgramData, C:\Windows\Temp -Recurse -Include *.mp3,*.mp4,*.avi,*.exe,*.bat,*.vbs,*.ps1 -ErrorAction SilentlyContinue - Alternate Data Stream sweep:powershell
Get-ChildItem -Path C:\Users\ -Recurse -Stream * | Where-Object { $_.Stream -ne ':$DATA' } - Hosts file review:powershell
Get-Content C:\Windows\System32\drivers\etc\hosts | Where-Object { $_ -notlike "#*" -and $_.Trim() -ne "" } - Port proxy / routing table review:Expected result (all persistence-hunting checks above): each is read-only and should come back mostly/entirely empty on a clean system โ Run keys will have a handful of legitimate vendor entries (that's normal), WMI subscriptions/AppInit_DLLs/AppCertDlls/port proxies should typically be completely empty, Winlogon Shell/Userinit should exactly match the documented expected defaults, and the hosts file should have no active (non-comment) lines beyond maybe acmd
netsh interface portproxy show all route print127.0.0.1 localhostentry. If it fails: No error mode for any of these โ they're all pure reads.Get-ChildItem -Stream *can be slow across a largeC:\Userstree; that's expected, not a hang. A hit on ANY of these (a Winlogon Shell that isn'texplorer.exe, a non-emptyAppInit_DLLs, a WMI event consumer/filter pair, a scheduled task outside\Microsoft\...you don't recognize, an ADS attached to an otherwise-normal-looking file) is a genuine, high-priority finding โ these are all real, documented persistence techniques, not false-positive-prone heuristics like some earlier bulk-search checks in this document. - Admin-tool sabotage check โ a recurring technique that disables Task Manager/
cmd.exe/Control Panel for the current user via raw registry policy keys, without any corresponding GPO (sogpresultalone won't show it as a policy source โ you have to check the registry directly). This matters on a server too: a locked-outcmd.execan block your own remediation commands.Remove any that are set:powershellGet-ItemProperty "HKCU:\Software\Policies\Microsoft\Windows\System" -Name "DisableCMD" -ErrorAction SilentlyContinue Get-ItemProperty "HKCU:\Software\Microsoft\Windows\CurrentVersion\Policies\System" -Name "DisableTaskMgr" -ErrorAction SilentlyContinue Get-ItemProperty "HKCU:\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer" -Name "NoControlPanel" -ErrorAction SilentlyContinueExpected result: The check queries silently return nothing if clean (withpowershellRemove-ItemProperty -Path "HKCU:\Software\Policies\Microsoft\Windows\System" -Name "DisableCMD" -ErrorAction SilentlyContinue Remove-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Policies\System" -Name "DisableTaskMgr" -ErrorAction SilentlyContinue Remove-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer" -Name "NoControlPanel" -ErrorAction SilentlyContinue-ErrorAction SilentlyContinuesuppressing the "not found" noise); a returned value of1means that lockdown IS active. The removal commands are silent whether or not the property existed. If it fails: No real error mode given the suppression flags. This entire check only coversHKCUโ the currently logged-in user's hive โ if the sabotage was applied to a DIFFERENT user's profile, you won't see it here; you'd need to load that user's hive (reg load) to check it, similar to the technique noted in other sections of this document. If Task Manager/cmd.exe are still blocked after removing these values, log off and back on (or open a fresh Explorer session) โ some of these policies are read once at logon/shell-start, not live. -
gpresult /zโ dump the full effective policy actually in force (local + Default Domain Policy + any OU-linked GPOs, all combined) as a final verification pass:Expected result: Same as Section 5'spowershellgpresult /z > C:\gpresult_after.txt/zusage โ a large, detailed dump of every effective setting and which GPO/policy source won it. If it fails: Same troubleshooting as Section 5's identical command โ no new failure modes here, this is just re-run as a final confirmation pass rather than a first check. On a server this is the only reliable way to see the real resulting policy, since local secpol.msc, the Default Domain Policy, and any OU-specific GPOs all stack โ checking secpol.msc alone (especially on a DC, where it's largely inert) will not tell you what's actually being enforced.
Tip: These checks apply to server images too, not just clients โ a compromised server is often a more valuable persistence target than a workstation, so don't skip this section just because the box is "just a DC."
20. Backup & Restore Considerations#
- Confirm whether Windows Server Backup or a similar backup role/feature is required by the README โ don't remove it if so.
- Be aware that AD changes (deleting OUs, GPOs, or accounts) are harder to reverse than client-side tweaks โ when uncertain, prefer disabling over deleting.
- Check Volume Shadow Copies for unexpected staged/hidden files (attackers sometimes stash tools in shadow copy-accessible paths) โ but don't indiscriminately delete shadow copies if backups depend on them.
- If you must make a broad, risky change (e.g., large GPO edit, bulk account changes), document the before-state first (see Section 2) so you can recover context if something breaks.
Things to try / extra points#
- List existing shadow copies:Expected result: A list of existing volume shadow copies with creation timestamps, or "No items found that satisfy the query" if none exist (a completely normal result on many images, not an error). If it fails: "Access is denied" โ needs an elevated prompt. This is read-only and safe to run any time; don't delete shadow copies unless you're certain backups don't depend on them (per the checklist bullet above).shell
vssadmin list shadows - Snapshot critical state before large changes (reuse the same approach as the Forensics section):Expected result: One HTML file containing every GPO's full settings report, written to the given path. If it fails: Samepowershell
Get-GPOReport -All -ReportType Html -Path C:\Users\Public\AllGPOs_Before.htmlGroupPolicy-module/RSAT dependency as every other GPO cmdlet in this document โ see Section 5's notes if "not recognized." Can take a while on a domain with many GPOs; that's expected for a full-Allreport, not a hang.
Tip: Competition rounds are time-boxed โ a mistake that requires restoring AD from backup will usually cost you more time than the round has left. Favor smaller, verifiable, reversible changes over big sweeping ones when working on AD-critical infrastructure.
21. Server 2008 vs. 2012+ UI Differences#
Source material for this checklist assumes a 2008 R2-style interface unless otherwise noted; be ready to adapt:
| Area | Server 2008 / 2008 R2 | Server 2012+ |
|---|---|---|
| Roles/Features management | "Roles" and "Features" panes in Server Manager | Unified "Manage โ Add Roles and Features" wizard |
| Server Manager Remoting | Simple toggle | "Configure Server Manager Remote Management" link, PowerShell Configure-SMRemoting |
| Start Menu | Classic Start Menu โ Administrative Tools | Start Screen / searchable Administrative Tools; many admins use Win + R + .msc shortcuts directly (works on both) |
| IE ESC | Present, same registry keys | Present; some inconsistencies reported |
| PowerShell cmdlet availability | Fewer built-in AD/DNS/DHCP cmdlets (may need RSAT modules imported explicitly) | Much richer built-in module support (ActiveDirectory, DnsServer, DhcpServer) |
- If a GUI setting described in this checklist can't be found on the installed OS version, try the equivalent PowerShell/
net/seceditcommand instead โ the underlying setting almost always exists even if the UI path moved. - If you run a command-line change and it appears to succeed with no error but nothing changes, always verify with a read-back command before assuming it worked โ silent failures are common with older OS versions and cmdlet/module mismatches.
- On 2008 R2, confirm the
ActiveDirectory,DnsServer,DhcpServer, andGroupPolicyPowerShell modules are imported/available before relying on cmdlets from this checklist:Expected result: Silent on success; afterward, cmdlets from all four modules (e.g.powershellImport-Module ActiveDirectory, DnsServer, DhcpServer, GroupPolicyGet-ADUser,Get-DnsServerZone,Get-DhcpServerv4Scope,Get-GPO) become available in this session. If it fails: "The specified module was not loaded because no valid module file was found" for any of the four means that role's RSAT tools genuinely aren't installed on this box โ install viaAdd-WindowsFeature RSAT-AD-PowerShell,RSAT-DNS-Server,RSAT-DHCP, orGPMCrespectively (only install the ones for roles actually relevant to this server โ a plain file server doesn't needDnsServer/DhcpServermodules, for instance). This import only lasts for the current PowerShell session โ re-run it (or open a fresh session where the relevant role is already installed, which auto-loads its module) each time you start a new session on 2008 R2-era boxes where auto-loading isn't reliable.
22. Final Sweep / Closing Checklist#
๐ธ Snapshot checkpoint: Take one more snapshot now, before your final verification pass and before any last reboot. If something in this closing sweep reveals a problem (a broken required service, a locked-out account), you want a very recent known-good point to fall back to instead of unwinding hours of hardening changes by hand.
- Re-read the README one final time โ confirm every required role, account, and service is still functioning as specified.
- Re-run
Get-WindowsFeature | Where-Object Installedand compare against your Section 3 baseline โ confirm nothing required was accidentally removed. - Re-run
dcdiag /v(if a DC) and confirm no new errors were introduced by your changes. - Confirm DNS resolution still works both internally and (if applicable) externally:
nslookup domain.local,nslookup google.com. - Confirm DHCP is still issuing leases if required: check
Get-DhcpServerv4Leaseor test a client renewal. - Confirm required shares are still reachable with correct permissions:
Get-SmbShare, test access as a non-admin authorized user if possible. - Confirm you can still log in as the accounts the README says should have access (don't lock yourself or required service accounts out).
- Verify Windows Firewall is enabled on all profiles and required ports are still open.
- Verify all forensics question answers are saved in the expected location/format.
- Do one last pass through Event Viewer Security log for anything alarming introduced during your own changes (e.g., repeated failed logons from your own testing that could be mistaken for an attack, account lockouts you caused).
- Save/document a final summary of every change made, in case a teammate needs to pick up where you left off or you need to justify a change in a written question.
Final tip: On server images, the biggest score swings come from (1) not breaking required AD/DNS/DHCP/File/Web functionality, and (2) catching the "big rock" items โ rogue roles, unauthorized privileged accounts, open zone transfers, and disabled firewalls โ rather than chasing every obscure registry tweak. Work top-down through this checklist, verify as you go, and prioritize breadth over exhaustive depth on any single obscure setting if time is short.