<# .SYNOPSIS Automated Firewall Profile Hardening .DESCRIPTION Standardizes Windows Firewall profiles (Domain, Private, Public) by: - Setting default inbound action to Block on all profiles - Setting default outbound action to Allow (change if you want outbound lockdown too) - Enabling firewall on all profiles - Enabling logging for dropped (blocked) packets, with log size/path config - Opening only explicitly specified management ports (inbound allow rules) - Optionally scoping those rules to specific remote IPs/subnets (recommended for RMM/remote access ports) .NOTES Run as Administrator. Intended for deployment via TacticalRMM or similar RMM as a Collector Task / script check. Designed to be idempotent - safe to re-run. Author: Husky Logic #> [CmdletBinding()] param( # Management ports to allow inbound. Add/remove as needed. # Format: Name = @{ Port = ; Protocol = 'TCP'/'UDP'; RemoteAddress = 'Any' or CIDR/IP list } [hashtable[]]$ManagementRules = @( @{ Name = "Allow-RDP-Management"; Port = 3389; Protocol = "TCP"; RemoteAddress = "Any" }, @{ Name = "Allow-WinRM-HTTP"; Port = 5985; Protocol = "TCP"; RemoteAddress = "Any" }, @{ Name = "Allow-WinRM-HTTPS"; Port = 5986; Protocol = "TCP"; RemoteAddress = "Any" } # Example scoped rule (uncomment/edit as needed): # @{ Name = "Allow-RMM-Agent"; Port = 4899; Protocol = "TCP"; RemoteAddress = "203.0.113.0/24" } ), # Where to store the firewall logs [string]$LogPath = "$env:SystemRoot\System32\LogFiles\Firewall\pfirewall.log", # Max log file size in KB (Windows firewall log cap, 1-32767 KB) [ValidateRange(1,32767)] [int]$LogMaxSizeKB = 16384, # Firewall profiles to harden [ValidateSet("Domain","Private","Public")] [string[]]$Profiles = @("Domain","Private","Public"), # Set to $true if you also want outbound to default-deny (high impact - test first!) [switch]$BlockOutboundByDefault ) $ErrorActionPreference = "Stop" function Write-Log { param([string]$Message, [string]$Level = "INFO") $ts = Get-Date -Format "yyyy-MM-dd HH:mm:ss" Write-Host "[$ts] [$Level] $Message" } # --- Pre-flight: must be admin --- $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) if (-not $isAdmin) { Write-Log "This script must be run as Administrator." "ERROR" exit 1 } # --- Ensure log directory exists --- $logDir = Split-Path -Path $LogPath -Parent if (-not (Test-Path $logDir)) { Write-Log "Creating firewall log directory: $logDir" New-Item -Path $logDir -ItemType Directory -Force | Out-Null } Write-Log "Starting firewall hardening for profiles: $($Profiles -join ', ')" # --- 1. Configure each profile: enable, default block inbound, logging --- foreach ($profileName in $Profiles) { Write-Log "Configuring profile: $profileName" $outboundAction = if ($BlockOutboundByDefault) { "Block" } else { "Allow" } Set-NetFirewallProfile -Profile $profileName ` -Enabled True ` -DefaultInboundAction Block ` -DefaultOutboundAction $outboundAction ` -NotifyOnListen True ` -LogFileName $LogPath ` -LogMaxSizeKilobytes $LogMaxSizeKB ` -LogBlocked True ` -LogAllowed False ` -LogIgnored True Write-Log "Profile '$profileName' set: Inbound=Block, Outbound=$outboundAction, Logging=Enabled (dropped packets)" } # --- 2. Remove any prior rules created by this script (idempotency) --- Write-Log "Cleaning up previously created management rules (if any)..." foreach ($rule in $ManagementRules) { $existing = Get-NetFirewallRule -DisplayName $rule.Name -ErrorAction SilentlyContinue if ($existing) { Write-Log "Removing existing rule '$($rule.Name)' before recreating." Remove-NetFirewallRule -DisplayName $rule.Name } } # --- 3. Create management port allow rules --- foreach ($rule in $ManagementRules) { Write-Log "Creating inbound allow rule: $($rule.Name) - $($rule.Protocol)/$($rule.Port) from $($rule.RemoteAddress)" $params = @{ DisplayName = $rule.Name Direction = "Inbound" Action = "Allow" Protocol = $rule.Protocol LocalPort = $rule.Port Profile = $Profiles Enabled = "True" } if ($rule.RemoteAddress -and $rule.RemoteAddress -ne "Any") { $params["RemoteAddress"] = $rule.RemoteAddress } New-NetFirewallRule @params | Out-Null } # --- 4. Summary / verification --- Write-Log "Hardening complete. Current profile state:" Get-NetFirewallProfile -Profile $Profiles | Select-Object Name, Enabled, DefaultInboundAction, DefaultOutboundAction, LogBlocked, LogFileName, LogMaxSizeKilobytes | Format-Table -AutoSize | Out-String | Write-Host Write-Log "Active management rules:" Get-NetFirewallRule -DisplayName ($ManagementRules.Name) | Get-NetFirewallPortFilter | Select-Object InstanceID, Protocol, LocalPort | Format-Table -AutoSize | Out-String | Write-Host Write-Log "Done. Review $LogPath after some traffic has passed to confirm blocked-packet logging is active."