Files
linux-scripts/windows-security-baseline-audit.ps1
chiefgeek a1a17e81a1 Sync all scripts from website downloads — 352 scripts total
Includes updated JS challenge scripts with Claude-User whitelist,
same-site referer bypass, Blackbox-Exporter allowed bot, and all
new exporters, cheat sheets, and automation scripts.
2026-05-25 03:31:08 +02:00

521 lines
23 KiB
PowerShell

<#
.SYNOPSIS
Windows Security Baseline Audit Script
.DESCRIPTION
Audits a Windows Server or Workstation against Microsoft Security Baselines
and CIS Benchmarks. Checks account policies, audit policies, user rights,
security options, firewall configuration, Windows Update, SMB hardening,
TLS settings, and service configuration. Produces a summary report with
pass/fail/warning counts and an HTML report.
.PARAMETER OutputPath
Path for the HTML report (default: C:\SecurityAudit\baseline-audit.html)
.PARAMETER Format
Output format: 'html' (default), 'csv', or 'json'
.PARAMETER ChecksFile
Path to a custom checks JSON file (optional)
.NOTES
Author: Phil Connor
Contact: contact@mylinux.work
Website: https://mylinux.work
License: MIT
Version: 1.0
#>
param(
[string]$OutputPath = "C:\SecurityAudit\baseline-audit-$(Get-Date -Format 'yyyyMMdd-HHmmss').html",
[ValidateSet('html', 'csv', 'json')]
[string]$Format = 'html',
[string]$ChecksFile
)
$ErrorActionPreference = 'SilentlyContinue'
# ============================================================================
# RESULTS COLLECTION
# ============================================================================
$script:Results = [System.Collections.ArrayList]::new()
$script:PassCount = 0
$script:FailCount = 0
$script:WarnCount = 0
$script:InfoCount = 0
function Add-AuditResult {
param(
[string]$Category,
[string]$Check,
[ValidateSet('Pass', 'Fail', 'Warn', 'Info')]
[string]$Status,
[string]$Expected,
[string]$Actual,
[string]$Description
)
switch ($Status) {
'Pass' { $script:PassCount++ }
'Fail' { $script:FailCount++ }
'Warn' { $script:WarnCount++ }
'Info' { $script:InfoCount++ }
}
[void]$script:Results.Add([PSCustomObject]@{
Category = $Category
Check = $Check
Status = $Status
Expected = $Expected
Actual = $Actual
Description = $Description
})
}
# ============================================================================
# ACCOUNT POLICIES
# ============================================================================
function Test-AccountPolicies {
Write-Host "Checking account policies..." -ForegroundColor Cyan
# Export security policy
$tempFile = "$env:TEMP\secpol_export.cfg"
secedit /export /cfg $tempFile /quiet 2>$null
if (-not (Test-Path $tempFile)) {
Add-AuditResult "Account Policies" "Security Policy Export" "Fail" "Exported" "Failed" "Cannot export security policy"
return
}
$secpol = Get-Content $tempFile
# Password length
$minLen = ($secpol | Select-String 'MinimumPasswordLength\s*=\s*(\d+)' | ForEach-Object { $_.Matches.Groups[1].Value }) -as [int]
$expected = 14
if ($minLen -ge $expected) {
Add-AuditResult "Account Policies" "Minimum Password Length" "Pass" ">= $expected" "$minLen" "CIS: Minimum password length"
} else {
Add-AuditResult "Account Policies" "Minimum Password Length" "Fail" ">= $expected" "$minLen" "CIS: Minimum password length should be $expected+"
}
# Password history
$history = ($secpol | Select-String 'PasswordHistorySize\s*=\s*(\d+)' | ForEach-Object { $_.Matches.Groups[1].Value }) -as [int]
if ($history -ge 24) {
Add-AuditResult "Account Policies" "Password History" "Pass" ">= 24" "$history" "CIS: Enforce password history"
} else {
Add-AuditResult "Account Policies" "Password History" "Fail" ">= 24" "$history" "CIS: Should remember 24+ passwords"
}
# Maximum password age
$maxAge = ($secpol | Select-String 'MaximumPasswordAge\s*=\s*(\d+)' | ForEach-Object { $_.Matches.Groups[1].Value }) -as [int]
if ($maxAge -le 365 -and $maxAge -gt 0) {
Add-AuditResult "Account Policies" "Maximum Password Age" "Pass" "<= 365 days" "$maxAge days" "CIS: Maximum password age"
} else {
Add-AuditResult "Account Policies" "Maximum Password Age" "Fail" "<= 365 days" "$maxAge days" "Password age too high or unlimited"
}
# Account lockout threshold
$lockout = ($secpol | Select-String 'LockoutBadCount\s*=\s*(\d+)' | ForEach-Object { $_.Matches.Groups[1].Value }) -as [int]
if ($lockout -ge 1 -and $lockout -le 5) {
Add-AuditResult "Account Policies" "Account Lockout Threshold" "Pass" "1-5 attempts" "$lockout" "CIS: Account lockout threshold"
} else {
Add-AuditResult "Account Policies" "Account Lockout Threshold" "Fail" "1-5 attempts" "$lockout" "Should lock after 1-5 failed attempts"
}
# Lockout duration
$lockDuration = ($secpol | Select-String 'LockoutDuration\s*=\s*(\d+)' | ForEach-Object { $_.Matches.Groups[1].Value }) -as [int]
if ($lockDuration -ge 15) {
Add-AuditResult "Account Policies" "Account Lockout Duration" "Pass" ">= 15 min" "$lockDuration min" "CIS: Lockout duration"
} else {
Add-AuditResult "Account Policies" "Account Lockout Duration" "Fail" ">= 15 min" "$lockDuration min" "Should be 15+ minutes"
}
# Password complexity
$complexity = $secpol | Select-String 'PasswordComplexity\s*=\s*1'
if ($complexity) {
Add-AuditResult "Account Policies" "Password Complexity" "Pass" "Enabled" "Enabled" "CIS: Password must meet complexity requirements"
} else {
Add-AuditResult "Account Policies" "Password Complexity" "Fail" "Enabled" "Disabled" "Enable password complexity"
}
Remove-Item $tempFile -Force -ErrorAction SilentlyContinue
}
# ============================================================================
# AUDIT POLICIES
# ============================================================================
function Test-AuditPolicies {
Write-Host "Checking audit policies..." -ForegroundColor Cyan
$auditPolicies = @{
"Credential Validation" = "Success and Failure"
"Logon" = "Success and Failure"
"Logoff" = "Success"
"Account Lockout" = "Failure"
"User Account Management" = "Success and Failure"
"Security Group Management" = "Success"
"Process Creation" = "Success"
"Audit Policy Change" = "Success and Failure"
"Authentication Policy Change" = "Success"
"Sensitive Privilege Use" = "Success and Failure"
"System Integrity" = "Success and Failure"
"Security State Change" = "Success"
}
$auditpolOutput = auditpol /get /category:* 2>$null
foreach ($policy in $auditPolicies.GetEnumerator()) {
$line = $auditpolOutput | Select-String $policy.Key | Select-Object -First 1
if ($line) {
$currentSetting = $line.ToString().Trim()
if ($currentSetting -match $policy.Value -or $currentSetting -match "Success and Failure") {
Add-AuditResult "Audit Policies" $policy.Key "Pass" $policy.Value "Configured" "Advanced audit policy"
} else {
Add-AuditResult "Audit Policies" $policy.Key "Fail" $policy.Value "Not configured" "Enable in Advanced Audit Policy"
}
} else {
Add-AuditResult "Audit Policies" $policy.Key "Warn" $policy.Value "Not found" "Cannot determine audit policy status"
}
}
}
# ============================================================================
# FIREWALL
# ============================================================================
function Test-FirewallConfiguration {
Write-Host "Checking firewall configuration..." -ForegroundColor Cyan
$profiles = @('Domain', 'Private', 'Public')
foreach ($profile in $profiles) {
$fw = Get-NetFirewallProfile -Name $profile -ErrorAction SilentlyContinue
if ($fw) {
if ($fw.Enabled) {
Add-AuditResult "Firewall" "$profile Profile Enabled" "Pass" "Enabled" "Enabled" "Windows Firewall $profile profile"
} else {
Add-AuditResult "Firewall" "$profile Profile Enabled" "Fail" "Enabled" "Disabled" "Enable Windows Firewall for $profile"
}
if ($fw.DefaultInboundAction -eq 'Block') {
Add-AuditResult "Firewall" "$profile Inbound Default" "Pass" "Block" "Block" "Default inbound action"
} else {
Add-AuditResult "Firewall" "$profile Inbound Default" "Fail" "Block" "$($fw.DefaultInboundAction)" "Set default inbound to Block"
}
if ($fw.LogFileName) {
Add-AuditResult "Firewall" "$profile Logging" "Pass" "Enabled" $fw.LogFileName "Firewall logging path"
} else {
Add-AuditResult "Firewall" "$profile Logging" "Warn" "Enabled" "Not configured" "Enable firewall logging"
}
}
}
}
# ============================================================================
# SMB HARDENING
# ============================================================================
function Test-SMBHardening {
Write-Host "Checking SMB hardening..." -ForegroundColor Cyan
# SMBv1
$smb1 = Get-SmbServerConfiguration -ErrorAction SilentlyContinue
if ($smb1) {
if (-not $smb1.EnableSMB1Protocol) {
Add-AuditResult "SMB" "SMBv1 Disabled" "Pass" "Disabled" "Disabled" "SMBv1 should be disabled (security risk)"
} else {
Add-AuditResult "SMB" "SMBv1 Disabled" "Fail" "Disabled" "Enabled" "Disable SMBv1: Set-SmbServerConfiguration -EnableSMB1Protocol $false"
}
if ($smb1.RequireSecuritySignature) {
Add-AuditResult "SMB" "SMB Signing Required" "Pass" "Required" "Required" "SMB signing prevents MITM attacks"
} else {
Add-AuditResult "SMB" "SMB Signing Required" "Fail" "Required" "Not required" "Enable SMB signing"
}
if ($smb1.EncryptData) {
Add-AuditResult "SMB" "SMB Encryption" "Pass" "Enabled" "Enabled" "SMB 3.0+ encryption"
} else {
Add-AuditResult "SMB" "SMB Encryption" "Warn" "Enabled" "Disabled" "Consider enabling SMB encryption"
}
}
}
# ============================================================================
# TLS CONFIGURATION
# ============================================================================
function Test-TLSConfiguration {
Write-Host "Checking TLS configuration..." -ForegroundColor Cyan
$protocols = @{
'SSL 2.0' = @{ Path = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 2.0\Server'; Expected = 'Disabled' }
'SSL 3.0' = @{ Path = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 3.0\Server'; Expected = 'Disabled' }
'TLS 1.0' = @{ Path = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Server'; Expected = 'Disabled' }
'TLS 1.1' = @{ Path = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1\Server'; Expected = 'Disabled' }
'TLS 1.2' = @{ Path = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Server'; Expected = 'Enabled' }
}
foreach ($proto in $protocols.GetEnumerator()) {
$regPath = $proto.Value.Path
$expected = $proto.Value.Expected
$enabled = $true
if (Test-Path $regPath) {
$val = Get-ItemProperty -Path $regPath -Name 'Enabled' -ErrorAction SilentlyContinue
if ($val -and $val.Enabled -eq 0) { $enabled = $false }
}
if ($expected -eq 'Disabled') {
if (-not $enabled -or (Test-Path $regPath)) {
$disabledCheck = Get-ItemProperty -Path $regPath -Name 'Enabled' -ErrorAction SilentlyContinue
if ($disabledCheck -and $disabledCheck.Enabled -eq 0) {
Add-AuditResult "TLS" "$($proto.Key) Disabled" "Pass" "Disabled" "Disabled" "Legacy protocol disabled"
} else {
Add-AuditResult "TLS" "$($proto.Key) Disabled" "Warn" "Disabled" "Not explicitly disabled" "Disable $($proto.Key) via registry"
}
} else {
Add-AuditResult "TLS" "$($proto.Key) Disabled" "Warn" "Disabled" "Registry key not set" "Explicitly disable $($proto.Key)"
}
} else {
Add-AuditResult "TLS" "$($proto.Key) Enabled" "Info" "Enabled" "Default" "TLS 1.2 enabled by default on modern Windows"
}
}
}
# ============================================================================
# WINDOWS UPDATE
# ============================================================================
function Test-WindowsUpdate {
Write-Host "Checking Windows Update status..." -ForegroundColor Cyan
try {
$lastUpdate = Get-HotFix | Sort-Object InstalledOn -Descending | Select-Object -First 1
if ($lastUpdate) {
$daysSince = ((Get-Date) - $lastUpdate.InstalledOn).Days
if ($daysSince -le 30) {
Add-AuditResult "Windows Update" "Last Update" "Pass" "<= 30 days" "$daysSince days ago" "HotFix: $($lastUpdate.HotFixID)"
} elseif ($daysSince -le 90) {
Add-AuditResult "Windows Update" "Last Update" "Warn" "<= 30 days" "$daysSince days ago" "Updates may be overdue"
} else {
Add-AuditResult "Windows Update" "Last Update" "Fail" "<= 30 days" "$daysSince days ago" "System needs patching"
}
}
} catch {
Add-AuditResult "Windows Update" "Last Update" "Warn" "<= 30 days" "Unknown" "Cannot determine update status"
}
}
# ============================================================================
# SERVICES
# ============================================================================
function Test-ServiceConfiguration {
Write-Host "Checking service configuration..." -ForegroundColor Cyan
$riskyServices = @{
'RemoteRegistry' = 'Remote Registry - should be disabled'
'Spooler' = 'Print Spooler - disable on servers not used for printing'
'SNMP' = 'SNMP - legacy protocol, disable if unused'
'TelnetClient' = 'Telnet - insecure, use SSH instead'
'W3SVC' = 'IIS - disable if not serving web content'
}
foreach ($svc in $riskyServices.GetEnumerator()) {
$service = Get-Service -Name $svc.Key -ErrorAction SilentlyContinue
if ($service) {
if ($service.Status -eq 'Running') {
Add-AuditResult "Services" "$($svc.Key)" "Warn" "Stopped/Disabled" "Running" $svc.Value
} elseif ($service.StartType -eq 'Automatic') {
Add-AuditResult "Services" "$($svc.Key)" "Warn" "Disabled" "Auto-start" $svc.Value
} else {
Add-AuditResult "Services" "$($svc.Key)" "Pass" "Disabled" "$($service.StartType)" $svc.Value
}
}
}
}
# ============================================================================
# ADDITIONAL CHECKS
# ============================================================================
function Test-AdditionalSecurity {
Write-Host "Checking additional security settings..." -ForegroundColor Cyan
# RDP NLA
$nla = Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' -Name 'UserAuthentication' -ErrorAction SilentlyContinue
if ($nla -and $nla.UserAuthentication -eq 1) {
Add-AuditResult "RDP" "Network Level Authentication" "Pass" "Enabled" "Enabled" "NLA required for RDP"
} else {
Add-AuditResult "RDP" "Network Level Authentication" "Fail" "Enabled" "Disabled" "Enable NLA for RDP connections"
}
# BitLocker
$bitlocker = Get-BitLockerVolume -MountPoint 'C:' -ErrorAction SilentlyContinue
if ($bitlocker -and $bitlocker.ProtectionStatus -eq 'On') {
Add-AuditResult "Encryption" "BitLocker C: Drive" "Pass" "On" "On" "System drive encrypted"
} else {
Add-AuditResult "Encryption" "BitLocker C: Drive" "Warn" "On" "Off or N/A" "Consider enabling BitLocker"
}
# Windows Defender
$defender = Get-MpComputerStatus -ErrorAction SilentlyContinue
if ($defender) {
if ($defender.RealTimeProtectionEnabled) {
Add-AuditResult "Defender" "Real-Time Protection" "Pass" "Enabled" "Enabled" "Windows Defender real-time protection"
} else {
Add-AuditResult "Defender" "Real-Time Protection" "Fail" "Enabled" "Disabled" "Enable real-time protection"
}
$sigAge = $defender.AntivirusSignatureAge
if ($sigAge -le 3) {
Add-AuditResult "Defender" "Signature Age" "Pass" "<= 3 days" "$sigAge days" "AV signatures up to date"
} else {
Add-AuditResult "Defender" "Signature Age" "Fail" "<= 3 days" "$sigAge days" "Update AV signatures"
}
}
# Guest account
$guest = Get-LocalUser -Name 'Guest' -ErrorAction SilentlyContinue
if ($guest -and -not $guest.Enabled) {
Add-AuditResult "Accounts" "Guest Account Disabled" "Pass" "Disabled" "Disabled" "Guest account should be disabled"
} elseif ($guest) {
Add-AuditResult "Accounts" "Guest Account Disabled" "Fail" "Disabled" "Enabled" "Disable the Guest account"
}
# Administrator account renamed
$admin = Get-LocalUser | Where-Object { $_.SID -match 'S-1-5-21-.*-500$' }
if ($admin -and $admin.Name -ne 'Administrator') {
Add-AuditResult "Accounts" "Administrator Renamed" "Pass" "Renamed" $admin.Name "Built-in admin account renamed"
} else {
Add-AuditResult "Accounts" "Administrator Renamed" "Warn" "Renamed" "Administrator" "Consider renaming the built-in admin account"
}
}
# ============================================================================
# REPORT GENERATION
# ============================================================================
function New-HTMLReport {
$total = $script:PassCount + $script:FailCount + $script:WarnCount + $script:InfoCount
$scorePercent = if (($script:PassCount + $script:FailCount) -gt 0) {
[math]::Round($script:PassCount / ($script:PassCount + $script:FailCount) * 100, 1)
} else { 0 }
$html = @"
<!DOCTYPE html>
<html>
<head>
<title>Windows Security Baseline Audit</title>
<style>
body { font-family: 'Segoe UI', Tahoma, sans-serif; margin: 20px; background: #1a1a2e; color: #e0e0e0; }
h1 { color: #ff6a00; }
h2 { color: #ffffff; border-bottom: 1px solid #333; padding-bottom: 5px; }
.summary { display: flex; gap: 20px; margin: 20px 0; }
.stat { background: #2a2a3e; padding: 15px 25px; border-radius: 8px; text-align: center; }
.stat .number { font-size: 2em; font-weight: bold; }
.pass { color: #4caf50; }
.fail { color: #f44336; }
.warn { color: #ff9800; }
.info { color: #2196f3; }
table { border-collapse: collapse; width: 100%; margin: 15px 0; }
th, td { padding: 8px 12px; text-align: left; border: 1px solid #333; }
th { background: #2a2a3e; color: #ff6a00; }
tr:nth-child(even) { background: #222233; }
.status-pass { color: #4caf50; font-weight: bold; }
.status-fail { color: #f44336; font-weight: bold; }
.status-warn { color: #ff9800; font-weight: bold; }
.status-info { color: #2196f3; }
.score { font-size: 1.5em; color: #ff6a00; margin: 10px 0; }
</style>
</head>
<body>
<h1>Windows Security Baseline Audit</h1>
<p>Generated: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') | Host: $env:COMPUTERNAME | OS: $((Get-CimInstance Win32_OperatingSystem).Caption)</p>
<div class="score">Compliance Score: ${scorePercent}%</div>
<div class="summary">
<div class="stat"><div class="number pass">$($script:PassCount)</div>Pass</div>
<div class="stat"><div class="number fail">$($script:FailCount)</div>Fail</div>
<div class="stat"><div class="number warn">$($script:WarnCount)</div>Warn</div>
<div class="stat"><div class="number info">$($script:InfoCount)</div>Info</div>
<div class="stat"><div class="number">$total</div>Total</div>
</div>
"@
$categories = $script:Results | Group-Object Category
foreach ($cat in $categories) {
$html += "<h2>$($cat.Name)</h2>`n<table><tr><th>Check</th><th>Status</th><th>Expected</th><th>Actual</th><th>Description</th></tr>`n"
foreach ($result in $cat.Group) {
$statusClass = "status-$($result.Status.ToLower())"
$html += "<tr><td>$($result.Check)</td><td class='$statusClass'>$($result.Status)</td><td>$($result.Expected)</td><td>$($result.Actual)</td><td>$($result.Description)</td></tr>`n"
}
$html += "</table>`n"
}
$html += "</body></html>"
$outputDir = Split-Path $OutputPath -Parent
if (-not (Test-Path $outputDir)) {
New-Item -Path $outputDir -ItemType Directory -Force | Out-Null
}
$html | Out-File -FilePath $OutputPath -Encoding utf8
}
# ============================================================================
# MAIN
# ============================================================================
Write-Host ""
Write-Host "===== Windows Security Baseline Audit =====" -ForegroundColor Green
Write-Host "Host: $env:COMPUTERNAME" -ForegroundColor Cyan
Write-Host ""
Test-AccountPolicies
Test-AuditPolicies
Test-FirewallConfiguration
Test-SMBHardening
Test-TLSConfiguration
Test-WindowsUpdate
Test-ServiceConfiguration
Test-AdditionalSecurity
# Generate report
switch ($Format) {
'html' { New-HTMLReport }
'csv' {
$outputDir = Split-Path $OutputPath -Parent
if (-not (Test-Path $outputDir)) { New-Item -Path $outputDir -ItemType Directory -Force | Out-Null }
$csvPath = $OutputPath -replace '\.html$', '.csv'
$script:Results | Export-Csv -Path $csvPath -NoTypeInformation
$OutputPath = $csvPath
}
'json' {
$outputDir = Split-Path $OutputPath -Parent
if (-not (Test-Path $outputDir)) { New-Item -Path $outputDir -ItemType Directory -Force | Out-Null }
$jsonPath = $OutputPath -replace '\.html$', '.json'
$script:Results | ConvertTo-Json -Depth 3 | Out-File -FilePath $jsonPath -Encoding utf8
$OutputPath = $jsonPath
}
}
# Summary
$total = $script:PassCount + $script:FailCount + $script:WarnCount + $script:InfoCount
$scorePercent = if (($script:PassCount + $script:FailCount) -gt 0) {
[math]::Round($script:PassCount / ($script:PassCount + $script:FailCount) * 100, 1)
} else { 0 }
Write-Host ""
Write-Host "===== Audit Summary =====" -ForegroundColor Green
Write-Host " Pass: $($script:PassCount)" -ForegroundColor Green
Write-Host " Fail: $($script:FailCount)" -ForegroundColor Red
Write-Host " Warn: $($script:WarnCount)" -ForegroundColor Yellow
Write-Host " Info: $($script:InfoCount)" -ForegroundColor Cyan
Write-Host " Total: $total"
Write-Host ""
Write-Host " Compliance Score: ${scorePercent}%" -ForegroundColor $(if ($scorePercent -ge 80) { 'Green' } elseif ($scorePercent -ge 60) { 'Yellow' } else { 'Red' })
Write-Host ""
Write-Host " Report: $OutputPath" -ForegroundColor Cyan
Write-Host ""