Mission Objective
SCADA systems are the backbone of industrial ops—power grids, water plants, you name it. But they’re also prime targets for anyone with a grudge and a keyboard. The goal? Build a DMZ that’s tighter than a vault, using Windows Server 2022 to keep remote access secure, admins in check, and attackers out. We’re talking least privilege, MFA, and enough GPOs to make a sysadmin weep. No company names, no breadcrumbs—just raw, reusable tech.
Gear Check
Before you swing the hammer, here’s what you need in the toolbox:
Hardware: Two rigs—One for the Domain Controller (DC), lean on Server 2022 Core; one for RDS, beefy enough for Server 2022 Datacenter (GUI). Static IPs in <DMZ_subnet>, wired to a gateway at <gateway>.
Software: Windows Server 2022 ISO (Core and Datacenter editions), latest ADMX templates from Microsoft (download here), Duo MFA setup for RDS.
Prep: A clean network segment for the DMZ, admin creds ready (adm1_<yourname>, adm2_<yourname>), and a test box to prove it works. Logs go to C:\ProgramData\*.txt—keep an eye on ’em.
No exotic kit—just standard iron and a sharp mind. Double-check your IPs and Duo config, or you’re dead in the water.
The Build
This ain’t a sandbox—it’s a locked vault. Two boxes: DC on Server 2022 Core, RDS on Server 2022 Datacenter (GUI). Here’s the grind:
Gear
Domain Controller: Windows Server 2022 Core, <DMZ_subnet> static IP, AD DS, DNS, Central Store stuffed with fresh ADMX files.
RDS Server: Windows Server 2022 Datacenter (GUI), <DMZ_subnet> IP, RDS roles (Session Host, Broker, Web Access, Gateway) serving Edge via RemoteApp.
Network: Static routes through <gateway>—no loose ends.
The Setup
DC Core:
Promote to DC for <domain>.dmz, <NETBIOS> as shorthand.
Extend AD schema for Modern LAPS—random admin passwords, AD-stored.
# Log file setup $LogFile = "C:\ProgramData\ADSetupLog.txt" function Write-Log { param ([string]$msg); Add-Content -Path $LogFile -Value "$(Get-Date): $msg" } # DC promotion Install-WindowsFeature -Name AD-Domain-Services, DNS -IncludeManagementTools Install-ADDSForest -DomainName "<domain>.dmz" -DomainNetbiosName "<NETBIOS>" ` -SafeModeAdministratorPassword (ConvertTo-SecureString "<SafeModePass>" -AsPlainText -Force) ` -ForestMode WinThreshold -DomainMode WinThreshold -InstallDNS -Force Set-DnsClientServerAddress -InterfaceAlias "Ethernet" -ServerAddresses "<DC_IP>" Write-Log "DC promoted for <domain>.dmz and DNS set" # LAPS schema update if (-not (Get-Module -ListAvailable -Name LAPS)) { Install-WindowsFeature -Name LAPS } Import-Module LAPS, ActiveDirectory -ErrorAction Stop Add-ADGroupMember -Identity "Schema Admins" -Members "<NETBIOS>\adm1_<yourname>" Add-ADGroupMember -Identity "Enterprise Admins" -Members "<NETBIOS>\adm1_<yourname>" Update-LapsADSchema Remove-ADGroupMember -Identity "Schema Admins" -Members "<NETBIOS>\adm1_<yourname>" -Confirm:$false Remove-ADGroupMember -Identity "Enterprise Admins" -Members "<NETBIOS>\adm1_<yourname>" -Confirm:$false Write-Log "LAPS schema extended, admin rights cleaned up"
OUs: Admin (Ring 1, Ring 2, Ring 3, Groups), DMZ (Member Servers, Users, Disabled, Computers).
# Log file $LogFile = "C:\ProgramData\ADSetupLog.txt" function Write-Log { param ([string]$msg); Add-Content -Path $LogFile -Value "$(Get-Date): $msg" } Import-Module ActiveDirectory -ErrorAction Stop # Root OUs $rootOUs = @("Admin", "DMZ") foreach ($ou in $rootOUs) { if (-not (Get-ADOrganizationalUnit -Filter "Name -eq '$ou'" -SearchBase "DC=<domain>,DC=dmz")) { New-ADOrganizationalUnit -Name $ou -Path "DC=<domain>,DC=dmz" -ProtectedFromAccidentalDeletion $true Write-Log "Created root OU: $ou" } } # Admin sub-OUs and Groups $adminSubOUs = @("Ring 1", "Ring 2", "Ring 3", "Groups") foreach ($subOU in $adminSubOUs) { if (-not (Get-ADOrganizationalUnit -Filter "Name -eq '$subOU'" -SearchBase "OU=Admin,DC=<domain>,DC=dmz")) { New-ADOrganizationalUnit -Name $subOU -Path "OU=Admin,DC=<domain>,DC=dmz" -ProtectedFromAccidentalDeletion $true Write-Log "Created sub-OU: $subOU under Admin" } } foreach ($ring in @("Ring 1", "Ring 2", "Ring 3")) { $groupName = "Admin_$ring" -replace " ", "_" if (-not (Get-ADGroup -Filter "SamAccountName -eq '$groupName'" -SearchBase "OU=$ring,OU=Admin,DC=<domain>,DC=dmz")) { New-ADGroup -Name $groupName -SamAccountName $groupName -GroupScope Global -Path "OU=$ring,OU=Admin,DC=<domain>,DC=dmz" Write-Log "Created group: $groupName" } } New-ADGroup -Name "Deny_Interactive_Logon" -SamAccountName "Deny_Interactive_Logon" -GroupScope Global ` -Path "OU=Groups,OU=Admin,DC=<domain>,DC=dmz" -Description "Deny interactive logon rights" Write-Log "Created Deny_Interactive_Logon group" # DMZ sub-OUs $dmzSubOUs = @("Member Servers", "Computers", "Users", "Disabled") foreach ($subOU in $dmzSubOUs) { if (-not (Get-ADOrganizationalUnit -Filter "Name -eq '$subOU'" -SearchBase "OU=DMZ,DC=<domain>,DC=dmz")) { New-ADOrganizationalUnit -Name $subOU -Path "OU=DMZ,DC=<domain>,DC=dmz" -ProtectedFromAccidentalDeletion $true Write-Log "Created sub-OU: $subOU under DMZ" } } # Admin users $admins = @( @{SamAccountName = "adm1_<yourname>"; Ring = "Ring 1"; Password = "<Adm1Pass>"}, @{SamAccountName = "adm2_<yourname>"; Ring = "Ring 2"; Password = "<Adm2Pass>"} ) foreach ($admin in $admins) { $sam = $admin.SamAccountName $ring = $admin.Ring $pwd = ConvertTo-SecureString $admin.Password -AsPlainText -Force $groupName = "Admin_$ring" -replace " ", "_" if (-not (Get-ADUser -Filter "SamAccountName -eq '$sam'")) { New-ADUser -SamAccountName $sam -UserPrincipalName "$sam@<domain>.dmz" -Name $sam ` -Path "OU=$ring,OU=Admin,DC=<domain>,DC=dmz" -AccountPassword $pwd -Enabled $true -PasswordNeverExpires $true Add-ADGroupMember -Identity $groupName -Members $sam Write-Log "Created user $sam in $ring and added to $groupName" } } Add-ADGroupMember -Identity "Domain Admins" -Members "Admin_Ring_1" Write-Log "OUs, groups, and admins set up"
RDS Fortress:
Join <domain>.dmz, slot into OU=Member Servers,OU=DMZ.
# Log file $LogFile = "C:\ProgramData\RDSSetupLog.txt" function Write-Log { param ([string]$msg); Add-Content -Path $LogFile -Value "$(Get-Date): $msg" } # Install RDS roles (GUI via Server Manager, then script post-reboot) Set-DnsClientServerAddress -InterfaceAlias "Ethernet" -ServerAddresses "<DC_IP>" Add-Computer -DomainName "<domain>.dmz" -Credential (Get-Credential -UserName "<NETBIOS>\adm1_<yourname>") ` -OUPath "OU=Member Servers,OU=DMZ,DC=<domain>,DC=dmz" -Restart Write-Log "RDS joined <domain>.dmz and rebooted into Member Servers OU" # Post-reboot as adm2_<yourname> Add-LocalGroupMember -Group "Administrators" -Member "<NETBIOS>\Domain Admins" -ErrorAction SilentlyContinue Write-Log "Added Domain Admins to local Administrators (temp until GPO kicks in)"
RDS roles lock users to Edge RemoteApp—no desktop joyrides.
Duo MFA on the Gateway—two keys or no entry.
Groups & Admins:
adm1_<yourname> (Ring 1, Domain Admin), adm2_<yourname> (Ring 2), test user (MFA alias needed).
Deny_Interactive_Logon in Admin/Groups—no local or RDP logins.
Default Administrator axed via GPO and script.
GPO Arsenal (domain root unless noted):
Server - Local Admin Group: Administrators to Admin_Ring_2 (Member Servers only).
Server - Modern LAPS - AD: LAPS, AD storage, <custom_admin>, max complexity (Member Servers only).
RDS Auto Logoff: 5-minute disconnect (Member Servers only).
Computer - Deny Interactive Logon: Deny_Interactive_Logon blocks local/RDP.
Computer - Disable Administrator User: Kills default admin.
Computer - SMB Signing Required / Disable Anon Enum: SMB signing on, anon shares off.
Computer - Disable LLMNR: Multicast name resolution—gone.
Computer - Disable Solicited Remote Assistance: No helpdesk backdoors.
Computer - Disable Autoplay: Autorun’s toast—USB threats neutralized.
Computer - Disable Network Bridge: No bridge-building here.
Computer - Enable Network Protection Block: Defender slams risky sites.
Computer - Enable NLA for RDP: NLA or bust.
Computer - NTLMv2 Response Only: NTLMv2 only—LM/NTLM banned.
Computer - Enable WinRM and PowerShell: WinRM live, PowerShell "RemoteSigned," TrustedHosts to <domain>.dmz.
# Log file $LogFile = "C:\ProgramData\RDSSetupLog.txt" function Write-Log { param ([string]$msg); Add-Content -Path $LogFile -Value "$(Get-Date): $msg" } # Ensure modules if (-not (Get-Module -ListAvailable -Name GroupPolicy)) { Install-WindowsFeature -Name GPMC } if (-not (Get-Module -ListAvailable -Name ActiveDirectory)) { Install-WindowsFeature -Name RSAT-AD-PowerShell } if (-not (Get-Module -ListAvailable -Name LAPS)) { Install-WindowsFeature -Name LAPS } Import-Module ActiveDirectory, GroupPolicy, LAPS -ErrorAction Stop Write-Log "Imported AD, GroupPolicy, and LAPS modules" # Domain root and Member Servers OU $domainRoot = "DC=<domain>,DC=dmz" $memberServersOU = "OU=Member Servers,OU=DMZ,DC=<domain>,DC=dmz" # GPO: Server - Local Admin Group (Member Servers) $gpoName1 = "Server - Local Admin Group" if (-not (Get-GPO -Name $gpoName1)) { New-GPO -Name $gpoName1 -Comment "Add Admin_Ring_2 to Administrators" New-GPLink -Name $gpoName1 -Target $memberServersOU -LinkEnabled Yes Write-Log "Created and linked $gpoName1 to $memberServersOU" } # GPO: Server - Modern LAPS - AD (Member Servers) $gpoName2 = "Server - Modern LAPS - AD" if (-not (Get-GPO -Name $gpoName2)) { New-GPO -Name $gpoName2 -Comment "Configure LAPS for member servers" New-GPLink -Name $gpoName2 -Target $memberServersOU -LinkEnabled Yes Write-Log "Created and linked $gpoName2 to $memberServersOU" } # GPO: RDS Auto Logoff (Member Servers) $gpoName3 = "RDS Auto Logoff" if (-not (Get-GPO -Name $gpoName3)) { New-GPO -Name $gpoName3 -Comment "Auto-logoff RDS users after 5 min" New-GPLink -Name $gpoName3 -Target $memberServersOU -LinkEnabled Yes Set-GPRegistryValue -Name $gpoName3 -Key "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services" ` -ValueName "MaxDisconnectionTime" -Type DWord -Value 300000 Write-Log "Created and configured $gpoName3 to $memberServersOU" } # GPO: Computer - Deny Interactive Logon $gpoName4 = "Computer - Deny Interactive Logon" if (-not (Get-GPO -Name $gpoName4)) { New-GPO -Name $gpoName4 -Comment "Deny interactive logon for group" New-GPLink -Name $gpoName4 -Target $domainRoot -LinkEnabled Yes Write-Log "Created and linked $gpoName4 to $domainRoot" } # GPO: Computer - Disable Administrator User $gpoName5 = "Computer - Disable Administrator User" if (-not (Get-GPO -Name $gpoName5)) { New-GPO -Name $gpoName5 -Comment "Disable local Administrator" New-GPLink -Name $gpoName5 -Target $domainRoot -LinkEnabled Yes Write-Log "Created and linked $gpoName5 to $domainRoot" } # GPO: Computer - SMB Signing Required / Disable Anon Enum $gpoName6 = "Computer - SMB Signing Required / Disable Anonymous Enumeration of Shares" if (-not (Get-GPO -Name $gpoName6)) { New-GPO -Name $gpoName6 -Comment "Require SMB signing, disable anon shares" New-GPLink -Name $gpoName6 -Target $domainRoot -LinkEnabled Yes Write-Log "Created and linked $gpoName6 to $domainRoot" } # GPO: Computer - Disable LLMNR $gpoName7 = "Computer - Disable LLMNR" if (-not (Get-GPO -Name $gpoName7)) { New-GPO -Name $gpoName7 -Comment "Disable LLMNR" New-GPLink -Name $gpoName7 -Target $domainRoot -LinkEnabled Yes Set-GPRegistryValue -Name $gpoName7 -Key "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\DNSClient" ` -ValueName "EnableMulticast" -Type DWord -Value 0 Write-Log "Created and configured $gpoName7 to $domainRoot" } # GPO: Computer - Disable Solicited Remote Assistance $gpoName8 = "Computer - Disable Solicited Remote Assistance" if (-not (Get-GPO -Name $gpoName8)) { New-GPO -Name $gpoName8 -Comment "Disable remote assistance" New-GPLink -Name $gpoName8 -Target $domainRoot -LinkEnabled Yes Set-GPRegistryValue -Name $gpoName8 -Key "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services" ` -ValueName "fAllowToGetHelp" -Type DWord -Value 0 Write-Log "Created and configured $gpoName8 to $domainRoot" } # GPO: Computer - Disable Autoplay $gpoName9 = "Computer - Disable Autoplay" if (-not (Get-GPO -Name $gpoName9)) { New-GPO -Name $gpoName9 -Comment "Disable Autoplay and Autorun" New-GPLink -Name $gpoName9 -Target $domainRoot -LinkEnabled Yes Set-GPRegistryValue -Name $gpoName9 -Key "HKLM\SOFTWARE\Policies\Microsoft\Windows\Explorer" ` -ValueName "NoAutorun" -Type DWord -Value 1 Set-GPRegistryValue -Name $gpoName9 -Key "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer" ` -ValueName "NoDriveTypeAutoRun" -Type DWord -Value 255 Set-GPRegistryValue -Name $gpoName9 -Key "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer" ` -ValueName "NoAutoplayfornonVolume" -Type DWord -Value 1 Write-Log "Created and configured $gpoName9 to $domainRoot" } # GPO: Computer - Disable Network Bridge $gpoName10 = "Computer - Disable 'Installation and configuration of Network Bridge on your DNS domain network'" if (-not (Get-GPO -Name $gpoName10)) { New-GPO -Name $gpoName10 -Comment "Prohibit Network Bridge" New-GPLink -Name $gpoName10 -Target $domainRoot -LinkEnabled Yes Set-GPRegistryValue -Name $gpoName10 -Key "HKLM\SOFTWARE\Policies\Microsoft\Windows\Network Connections" ` -ValueName "NC_AllowNetBridge_NLA" -Type DWord -Value 0 Write-Log "Created and configured $gpoName10 to $domainRoot" } # GPO: Computer - Enable Network Protection Block $gpoName11 = "Computer - Enable Network Protection Block" if (-not (Get-GPO -Name $gpoName11)) { New-GPO -Name $gpoName11 -Comment "Enable Defender network protection" New-GPLink -Name $gpoName11 -Target $domainRoot -LinkEnabled Yes Set-GPRegistryValue -Name $gpoName11 -Key "HKLM\SOFTWARE\Policies\Microsoft\Windows Defender\Windows Defender Exploit Guard\Network Protection" ` -ValueName "EnableNetworkProtection" -Type DWord -Value 1 Write-Log "Created and configured $gpoName11 to $domainRoot" } # GPO: Computer - Enable NLA for RDP $gpoName12 = "Computer - Enable NLA for RDP" if (-not (Get-GPO -Name $gpoName12)) { New-GPO -Name $gpoName12 -Comment "Require NLA for RDP" New-GPLink -Name $gpoName12 -Target $domainRoot -LinkEnabled Yes Set-GPRegistryValue -Name $gpoName12 -Key "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services" ` -ValueName "UserAuthentication" -Type DWord -Value 1 Write-Log "Created and configured $gpoName12 to $domainRoot" } # GPO: Computer - NTLMv2 Response Only $gpoName13 = "Computer - NTLMv2 Response Only" if (-not (Get-GPO -Name $gpoName13)) { New-GPO -Name $gpoName13 -Comment "Enforce NTLMv2 only" New-GPLink -Name $gpoName13 -Target $domainRoot -LinkEnabled Yes Set-GPRegistryValue -Name $gpoName13 -Key "HKLM\SYSTEM\CurrentControlSet\Control\Lsa" ` -ValueName "LmCompatibilityLevel" -Type DWord -Value 5 Write-Log "Created and configured $gpoName13 to $domainRoot" } # GPO: Computer - Enable WinRM and PowerShell settings $gpoName14 = "Computer - Enable WinRM and PowerShell settings" if (-not (Get-GPO -Name $gpoName14)) { New-GPO -Name $gpoName14 -Comment "Enable WinRM and PowerShell" New-GPLink -Name $gpoName14 -Target $domainRoot -LinkEnabled Yes Set-GPRegistryValue -Name $gpoName14 -Key "HKLM\SOFTWARE\Policies\Microsoft\Windows\PowerShell" ` -ValueName "EnableScripts" -Type DWord -Value 1 Set-GPRegistryValue -Name $gpoName14 -Key "HKLM\SOFTWARE\Policies\Microsoft\Windows\PowerShell" ` -ValueName "ExecutionPolicy" -Type String -Value "RemoteSigned" Set-GPRegistryValue -Name $gpoName14 -Key "HKLM\SOFTWARE\Policies\Microsoft\Windows\WinRM\Client" ` -ValueName "AllowBasic" -Type DWord -Value 0 Set-GPRegistryValue -Name $gpoName14 -Key "HKLM\SOFTWARE\Policies\Microsoft\Windows\WinRM\Client" ` -ValueName "TrustedHosts" -Type String -Value "*.<domain>.dmz" Set-GPRegistryValue -Name $gpoName14 -Key "HKLM\SOFTWARE\Policies\Microsoft\Windows\WinRM\Service" ` -ValueName "AllowBasic" -Type DWord -Value 0 Set-GPRegistryValue -Name $gpoName14 -Key "HKLM\SOFTWARE\Policies\Microsoft\Windows\WinRM\Service" ` -ValueName "AllowAutoConfig" -Type DWord -Value 1 Set-GPRegistryValue -Name $gpoName14 -Key "HKLM\SOFTWARE\Policies\Microsoft\Windows\WinRM\Service" ` -ValueName "IPv4Filter" -Type String -Value "*" Write-Log "Created and configured $gpoName14 to $domainRoot" } # LAPS Delegation $dmzOU = "OU=DMZ,DC=<domain>,DC=dmz" Set-LapsADComputerSelfPermission -Identity $dmzOU Set-LapsADReadPasswordPermission -Identity $dmzOU -AllowedPrincipals "<NETBIOS>\Admin_Ring_2" Set-LapsADReadPasswordPermission -Identity "OU=Disabled,$dmzOU" -AllowedPrincipals "<NETBIOS>\Admin_Ring_3" Set-LapsADReadPasswordPermission -Identity "OU=Computers,$dmzOU" -AllowedPrincipals "<NETBIOS>\Admin_Ring_3" Write-Log "LAPS delegated: Ring 2 on DMZ, Ring 3 on Disabled/Computers" Write-Host -ForegroundColor Green "Manual GPMC Steps Required:" Write-Host -ForegroundColor Green "1. Server - Local Admin Group > Restricted Groups: Administrators > Members: <NETBIOS>\Admin_Ring_2" Write-Host -ForegroundColor Green "2. Server - Modern LAPS - AD > System > LAPS: AD backup, <custom_admin>, max complexity" Write-Host -ForegroundColor Green "3. Computer - Deny Interactive Logon > User Rights: Add '<NETBIOS>\Deny_Interactive_Logon' to Deny logon local/RDP" Write-Host -ForegroundColor Green "4. Computer - Disable Administrator User > Security Options: Accounts: Administrator status = Disabled" Write-Host -ForegroundColor Green "5. Computer - SMB Signing Required > Security Options: SMB signing on, anon enum off" Write-Host -ForegroundColor Green "6. Computer - Enable WinRM > System Services: WinRM = Automatic/Start" Write-Host -ForegroundColor Green "7. Computer - Enable WinRM > Firewall: TCP 5985 Allow, Domain/Private, 'Windows Remote Management (HTTP-In)'"
LAPS Lockdown:
Admin_Ring_2 reads OU=DMZ.
Admin_Ring_3 gets OU=Disabled and OU=Computers under DMZ.
The Payoff
This rig’s a steel trap:
Access: Tiered admins (Ring 1-3), MFA via Duo, default admin DOA. Admin_Ring_2 owns local boxes.
RDS: Edge-only, 5-minute timeout, no local logins—users get the bare minimum.
Network: RDP (3389), WinRM (5985) firewalled, SMB signed, routes pinned to <gateway>.
Hardening: LAPS randomizes creds, NTLMv2 locks crypto, LLMNR/Autoplay/Bridge are history.
Monitoring: Logs at C:\ProgramData\ADSetupLog.txt and RDSSetupLog.txt—track every twitch.
Auditors eat this up: least privilege, MFA, no weak spots. Attackers? They’re smashing their heads on concrete.
Exit Strategy
Test It: Fire up a VM, join <domain>.dmz, slam gpupdate /force. Verify TrustedHosts (winrm get winrm/config/client), WinRM running (Get-Service WinRM), GPOs live (gpresult /r).
Tweak It: Plug in your <DMZ_subnet> and <gateway>. Swap <custom_admin> for your ops handle.
Post It: Hashnode’s primed—drop this generic beast and let the community feast.
This DMZ’s industrial-grade—no fluff, all muscle. Got questions, Got Improvements? Mainframe is listening. Stay sharp, crew.