Compliance/Test-NISTCompliance.ps1
|
function Test-NISTCompliance { <# .SYNOPSIS Tests system compliance against NIST 800-53 security controls .DESCRIPTION Evaluates Windows system against NIST (National Institute of Standards and Technology) SP 800-53 security and privacy controls for federal information systems .PARAMETER ControlFamily Specific control family to test: All, AccessControl, AuditAccountability, IdentificationAuthentication, SystemProtection, ConfigurationManagement .PARAMETER Revision NIST 800-53 revision: 4 or 5 (default: 5) .EXAMPLE Test-NISTCompliance Test-NISTCompliance -ControlFamily AccessControl -Revision 5 .OUTPUTS PSCustomObject with NIST compliance results #> [CmdletBinding()] param( [Parameter()] [ValidateSet('All', 'AccessControl', 'AuditAccountability', 'IdentificationAuthentication', 'SystemProtection', 'ConfigurationManagement')] [string]$ControlFamily = 'All', [Parameter()] [ValidateSet(4, 5)] [int]$Revision = 5 ) begin { Write-Host "=== NIST 800-53 COMPLIANCE TEST ===" -ForegroundColor Cyan Write-Host "Start Time: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" -ForegroundColor Yellow Write-Host "Revision: NIST 800-53 Rev $Revision" -ForegroundColor Yellow Write-Host "Control Family: $ControlFamily" -ForegroundColor Yellow $results = [PSCustomObject]@{ TestDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss" ComputerName = $env:COMPUTERNAME NISTRevision = $Revision ControlFamily = $ControlFamily Controls = @() Summary = @{ TotalControls = 0 Compliant = 0 NonCompliant = 0 PartiallyCompliant = 0 NotApplicable = 0 CompliancePercentage = 0 } Recommendations = @() } # Helper function to test registry value function Test-RegistryValue { param( [string]$Path, [string]$Name, [object]$ExpectedValue ) try { if (Test-Path $Path) { $value = Get-ItemProperty -Path $Path -Name $Name -ErrorAction SilentlyContinue | Select-Object -ExpandProperty $Name -ErrorAction SilentlyContinue return @{ Compliant = ($value -eq $ExpectedValue) ActualValue = $value ExpectedValue = $ExpectedValue } } return @{ Compliant = $false ActualValue = "Path not found" ExpectedValue = $ExpectedValue } } catch { return @{ Compliant = $false ActualValue = "Error" ExpectedValue = $ExpectedValue } } } } process { try { # 1. ACCESS CONTROL (AC) FAMILY if ($ControlFamily -in @('All', 'AccessControl')) { Write-Host "`n[1/5] Testing Access Control (AC) Controls..." -ForegroundColor Cyan try { # AC-2: Account Management $guestUser = Get-LocalUserByRid -Rid 501 -ErrorAction SilentlyContinue $guestKnown = ($null -ne $guestUser) -and ($null -ne $guestUser.Enabled) $guestDisabled = $guestKnown -and ($guestUser.Enabled -eq $false) $adminUser = Get-LocalUserByRid -Rid 500 -ErrorAction SilentlyContinue $adminKnown = ($null -ne $adminUser) -and ($null -ne $adminUser.Enabled) $adminDisabled = $adminKnown -and ($adminUser.Enabled -eq $false) $control = [PSCustomObject]@{ ControlID = 'AC-2' ControlName = 'Account Management' ControlFamily = 'AccessControl' Description = 'Guest and default Administrator accounts should be disabled' Expected = 'Guest and Administrator accounts disabled' Actual = "Guest: $(if(-not $guestKnown){'Unknown'}elseif($guestDisabled){'Disabled'}else{'Enabled'}), Admin: $(if(-not $adminKnown){'Unknown'}elseif($adminDisabled){'Disabled'}else{'Enabled'})" Status = if (-not $guestKnown -or -not $adminKnown) { 'NotApplicable' } elseif ($guestDisabled -and $adminDisabled) { 'Compliant' } elseif ($guestDisabled -or $adminDisabled) { 'PartiallyCompliant' } else { 'NonCompliant' } Impact = 'High' Remediation = 'Disable built-in Guest and Administrator accounts' } $results.Controls += $control # AC-7: Unsuccessful Logon Attempts $regTest = Test-RegistryValue -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' ` -Name 'MaxDevicePasswordFailedAttempts' -ExpectedValue 5 $control = [PSCustomObject]@{ ControlID = 'AC-7' ControlName = 'Unsuccessful Logon Attempts' ControlFamily = 'AccessControl' Description = 'System should lock after failed login attempts' Expected = '5 or fewer attempts' Actual = if ($regTest.ActualValue) { "$($regTest.ActualValue) attempts" } else { "Not configured" } Status = if ($regTest.Compliant) { 'Compliant' } else { 'NonCompliant' } Impact = 'High' Remediation = 'Configure account lockout after 5 failed attempts' } $results.Controls += $control # AC-11: Session Lock $regTest = Test-RegistryValue -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' ` -Name 'InactivityTimeoutSecs' -ExpectedValue 900 $control = [PSCustomObject]@{ ControlID = 'AC-11' ControlName = 'Session Lock' ControlFamily = 'AccessControl' Description = 'Automatic screen lock after inactivity' Expected = '15 minutes (900 seconds) or less' Actual = if ($regTest.ActualValue) { "$([math]::Round($regTest.ActualValue/60)) minutes" } else { "Not configured" } Status = if ($regTest.Compliant -or ($null -ne $regTest.ActualValue -and $regTest.ActualValue -le 900)) { 'Compliant' } else { 'NonCompliant' } Impact = 'Medium' Remediation = 'Configure screen lock timeout to 15 minutes or less' } $results.Controls += $control # AC-17: Remote Access $rdpSetting = Get-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server' -Name 'fDenyTSConnections' -ErrorAction SilentlyContinue $rdpEnabled = if ($rdpSetting) { $rdpSetting.fDenyTSConnections } else { $null } $control = [PSCustomObject]@{ ControlID = 'AC-17' ControlName = 'Remote Access' ControlFamily = 'AccessControl' Description = 'Remote access should be controlled and monitored' Expected = 'RDP disabled or properly secured' Actual = if ($rdpEnabled -eq 1) { "Disabled" } else { "Enabled" } Status = if ($rdpEnabled -eq 1) { 'Compliant' } else { 'PartiallyCompliant' } Impact = 'High' Remediation = 'Disable RDP if not needed, or ensure NLA and firewall rules' } $results.Controls += $control } catch { Write-Warning "Error testing Access Control: $_" } } # 2. AUDIT AND ACCOUNTABILITY (AU) FAMILY if ($ControlFamily -in @('All', 'AuditAccountability')) { Write-Host "`n[2/5] Testing Audit and Accountability (AU) Controls..." -ForegroundColor Cyan try { # AU-2: Audit Events $logonGuid = Get-AuditSubcategoryGuid -Name 'Logon' $logonAuditValue = $null if ($null -ne $logonGuid) { $logonTable = Get-AuditPolicyTable -Subcategory 'Logon' $logonKey = ($logonGuid -replace '[{}]', '').ToLowerInvariant() if ($logonTable.ContainsKey($logonKey)) { $logonAuditValue = $logonTable[$logonKey].Value } } $control = [PSCustomObject]@{ ControlID = 'AU-2' ControlName = 'Audit Events' ControlFamily = 'AuditAccountability' Description = 'System must audit significant security events' Expected = 'Audit policies configured' Actual = if ($null -eq $logonAuditValue) { 'Unable to determine' } elseif ($logonAuditValue -ge 1 -and $logonAuditValue -le 3) { "Configured" } else { "Not configured" } Status = if ($null -eq $logonAuditValue) { 'NotApplicable' } elseif ($logonAuditValue -ge 1 -and $logonAuditValue -le 3) { 'Compliant' } else { 'NonCompliant' } Impact = 'High' Remediation = 'Enable audit policies for security events' } $results.Controls += $control # AU-3: Content of Audit Records $securityLog = Get-WinEvent -ListLog Security -ErrorAction SilentlyContinue $eventLogSize = if ($securityLog) { $securityLog.MaximumSizeInBytes } else { 0 } $control = [PSCustomObject]@{ ControlID = 'AU-3' ControlName = 'Content of Audit Records' ControlFamily = 'AuditAccountability' Description = 'Audit records must contain sufficient detail' Expected = 'Security log size >= 100MB' Actual = "$([math]::Round($eventLogSize/1MB)) MB" Status = if ($eventLogSize -ge 100MB) { 'Compliant' } else { 'NonCompliant' } Impact = 'Medium' Remediation = 'Increase Security event log size to at least 100MB' } $results.Controls += $control # AU-9: Protection of Audit Information $logPath = "C:\Windows\System32\winevt\Logs\Security.evtx" if (Test-Path $logPath) { $acl = Get-Acl $logPath -ErrorAction SilentlyContinue $protected = @() if ($acl) { $protected = @($acl.Access | Where-Object { ((Test-IdentityReferenceIsSid -IdentityReference $_.IdentityReference -Sid 'S-1-5-32-545') -or (Test-IdentityReferenceIsSid -IdentityReference $_.IdentityReference -Sid 'S-1-5-11') -or (Test-IdentityReferenceIsSid -IdentityReference $_.IdentityReference -Sid 'S-1-1-0')) -and $_.FileSystemRights -match "FullControl|Modify|Write" }) } $control = [PSCustomObject]@{ ControlID = 'AU-9' ControlName = 'Protection of Audit Information' ControlFamily = 'AuditAccountability' Description = 'Audit logs must be protected from unauthorized access' Expected = 'Security log protected from normal users' Actual = if ($protected.Count -gt 0) { "Weak permissions detected" } else { "Properly protected" } Status = if ($protected.Count -eq 0) { 'Compliant' } else { 'NonCompliant' } Impact = 'High' Remediation = 'Restrict Security event log access to administrators only' } $results.Controls += $control } # AU-12: Audit Generation $processCreationGuid = Get-AuditSubcategoryGuid -Name 'Process Creation' $processAuditEntry = $null if ($null -ne $processCreationGuid) { $processTable = Get-AuditPolicyTable -Subcategory 'Process Creation' $processKey = ($processCreationGuid -replace '[{}]', '').ToLowerInvariant() if ($processTable.ContainsKey($processKey)) { $processAuditEntry = $processTable[$processKey] } } $control = [PSCustomObject]@{ ControlID = 'AU-12' ControlName = 'Audit Generation' ControlFamily = 'AuditAccountability' Description = 'System must generate audit records for defined events' Expected = 'Process creation auditing enabled' Actual = if ($null -eq $processAuditEntry -or $null -eq $processAuditEntry.Value) { 'Unable to determine' } elseif ($processAuditEntry.Success) { "Enabled" } else { "Disabled" } Status = if ($null -eq $processAuditEntry -or $null -eq $processAuditEntry.Value) { 'NotApplicable' } elseif ($processAuditEntry.Success) { 'Compliant' } else { 'NonCompliant' } Impact = 'Medium' Remediation = 'Enable process creation auditing' } $results.Controls += $control } catch { Write-Warning "Error testing Audit and Accountability: $_" } } # 3. IDENTIFICATION AND AUTHENTICATION (IA) FAMILY if ($ControlFamily -in @('All', 'IdentificationAuthentication')) { Write-Host "`n[3/5] Testing Identification and Authentication (IA) Controls..." -ForegroundColor Cyan try { # IA-2: Identification and Authentication $passwordPolicy = Get-LocalPasswordPolicy $minPasswordLength = $passwordPolicy.MinPasswordLength $control = [PSCustomObject]@{ ControlID = 'IA-2' ControlName = 'Identification and Authentication' ControlFamily = 'IdentificationAuthentication' Description = 'System must uniquely identify and authenticate users' Expected = 'Minimum password length >= 14 characters' Actual = if ($null -eq $minPasswordLength) { 'Unable to determine' } else { "$minPasswordLength characters" } Status = if ($null -eq $minPasswordLength) { 'NotApplicable' } elseif ($minPasswordLength -ge 14) { 'Compliant' } else { 'NonCompliant' } Impact = 'High' Remediation = 'Set minimum password length to 14 characters' } $results.Controls += $control # IA-5: Authenticator Management $maxAgeDays = $passwordPolicy.MaxPasswordAgeDays $control = [PSCustomObject]@{ ControlID = 'IA-5' ControlName = 'Authenticator Management' ControlFamily = 'IdentificationAuthentication' Description = 'Authenticators must be managed securely' Expected = 'Maximum password age <= 60 days' Actual = if ($null -eq $maxAgeDays) { 'Unable to determine' } elseif ($maxAgeDays -eq -1) { 'Never expires' } else { "$maxAgeDays days" } Status = if ($null -eq $maxAgeDays) { 'NotApplicable' } elseif ($maxAgeDays -gt 0 -and $maxAgeDays -le 60) { 'Compliant' } else { 'NonCompliant' } Impact = 'High' Remediation = 'Set maximum password age to 60 days or less' } $results.Controls += $control # IA-8: Identification and Authentication (Non-Organizational Users) $regTest = Test-RegistryValue -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa' ` -Name 'LimitBlankPasswordUse' -ExpectedValue 1 $control = [PSCustomObject]@{ ControlID = 'IA-8' ControlName = 'Non-Organizational User Authentication' ControlFamily = 'IdentificationAuthentication' Description = 'Blank passwords should not be allowed' Expected = 'Blank passwords disabled' Actual = if ($regTest.Compliant) { "Disabled" } else { "Allowed" } Status = if ($regTest.Compliant) { 'Compliant' } else { 'NonCompliant' } Impact = 'Critical' Remediation = 'Disable blank password usage' } $results.Controls += $control } catch { Write-Warning "Error testing Identification and Authentication: $_" } } # 4. SYSTEM AND COMMUNICATIONS PROTECTION (SC) FAMILY if ($ControlFamily -in @('All', 'SystemProtection')) { Write-Host "`n[4/5] Testing System and Communications Protection (SC) Controls..." -ForegroundColor Cyan try { # SC-7: Boundary Protection - FIX: Wrap in @() $firewallProfiles = Get-NetFirewallProfile -ErrorAction SilentlyContinue $disabledFirewalls = @($firewallProfiles | Where-Object { $null -ne $_ -and -not $_.Enabled }) $allFirewallsEnabled = ($null -ne $firewallProfiles) -and ($disabledFirewalls.Count -eq 0) $control = [PSCustomObject]@{ ControlID = 'SC-7' ControlName = 'Boundary Protection' ControlFamily = 'SystemProtection' Description = 'Firewall must protect system boundaries' Expected = 'All firewall profiles enabled' Actual = if ($null -eq $firewallProfiles) { "Unable to retrieve firewall profiles" } elseif ($allFirewallsEnabled) { "All enabled" } else { "Some disabled" } Status = if ($allFirewallsEnabled) { 'Compliant' } else { 'NonCompliant' } Impact = 'Critical' Remediation = 'Enable Windows Firewall for all profiles' } $results.Controls += $control # SC-8: Transmission Confidentiality and Integrity $regTest = Test-RegistryValue -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa' ` -Name 'NoLMHash' -ExpectedValue 1 $control = [PSCustomObject]@{ ControlID = 'SC-8' ControlName = 'Transmission Confidentiality' ControlFamily = 'SystemProtection' Description = 'Weak LAN Manager hashes should not be stored' Expected = 'LM hash storage disabled' Actual = if ($regTest.Compliant) { "Disabled" } else { "Enabled" } Status = if ($regTest.Compliant) { 'Compliant' } else { 'NonCompliant' } Impact = 'High' Remediation = 'Disable LAN Manager hash storage' } $results.Controls += $control # SC-28: Protection of Information at Rest $bitlockerStatus = Get-BitLockerVolume -MountPoint "C:" -ErrorAction SilentlyContinue $bitlockerEnabled = ($null -ne $bitlockerStatus) -and ($bitlockerStatus.ProtectionStatus -eq 'On') $control = [PSCustomObject]@{ ControlID = 'SC-28' ControlName = 'Protection of Information at Rest' ControlFamily = 'SystemProtection' Description = 'Data at rest should be encrypted' Expected = 'BitLocker enabled' Actual = if ($bitlockerEnabled) { "Enabled" } else { "Disabled" } Status = if ($bitlockerEnabled) { 'Compliant' } else { 'NonCompliant' } Impact = 'High' Remediation = 'Enable BitLocker encryption' } $results.Controls += $control } catch { Write-Warning "Error testing System Protection: $_" } } # 5. CONFIGURATION MANAGEMENT (CM) FAMILY if ($ControlFamily -in @('All', 'ConfigurationManagement')) { Write-Host "`n[5/5] Testing Configuration Management (CM) Controls..." -ForegroundColor Cyan try { # CM-6: Configuration Settings $defenderStatus = Get-MpComputerStatus -ErrorAction SilentlyContinue $defenderEnabled = ($null -ne $defenderStatus) -and $defenderStatus.AntivirusEnabled $control = [PSCustomObject]@{ ControlID = 'CM-6' ControlName = 'Configuration Settings' ControlFamily = 'ConfigurationManagement' Description = 'Security configuration settings must be established' Expected = 'Windows Defender enabled' Actual = if ($defenderEnabled) { "Enabled" } else { "Disabled" } Status = if ($defenderEnabled) { 'Compliant' } else { 'NonCompliant' } Impact = 'Critical' Remediation = 'Enable Windows Defender antivirus' } $results.Controls += $control # CM-7: Least Functionality - FIX: Wrap in @() $allServices = Get-Service -ErrorAction SilentlyContinue $unnecessaryServices = @($allServices | Where-Object { $_.Name -in @('TlntSvr', 'SNMP', 'SSDPSRV') -and $_.Status -eq 'Running' }) $control = [PSCustomObject]@{ ControlID = 'CM-7' ControlName = 'Least Functionality' ControlFamily = 'ConfigurationManagement' Description = 'Unnecessary services should be disabled' Expected = 'No unnecessary services running' Actual = if ($unnecessaryServices.Count -eq 0) { "Clean" } else { "$($unnecessaryServices.Count) unnecessary services" } Status = if ($unnecessaryServices.Count -eq 0) { 'Compliant' } else { 'NonCompliant' } Impact = 'Medium' Remediation = 'Disable unnecessary services (Telnet, SNMP, SSDP)' } $results.Controls += $control # CM-8: Information System Component Inventory $control = [PSCustomObject]@{ ControlID = 'CM-8' ControlName = 'System Component Inventory' ControlFamily = 'ConfigurationManagement' Description = 'Maintain accurate inventory of system components' Expected = 'Inventory maintained' Actual = 'Manual verification required' Status = 'NotApplicable' Impact = 'Low' Remediation = 'Implement automated inventory management' } $results.Controls += $control } catch { Write-Warning "Error testing Configuration Management: $_" } } # Calculate summary - FIX: All wrapped in @() $results.Summary.TotalControls = $results.Controls.Count $results.Summary.Compliant = @($results.Controls | Where-Object { $_.Status -eq 'Compliant' }).Count $results.Summary.NonCompliant = @($results.Controls | Where-Object { $_.Status -eq 'NonCompliant' }).Count $results.Summary.PartiallyCompliant = @($results.Controls | Where-Object { $_.Status -eq 'PartiallyCompliant' }).Count $results.Summary.NotApplicable = @($results.Controls | Where-Object { $_.Status -eq 'NotApplicable' }).Count $testableControls = $results.Summary.TotalControls - $results.Summary.NotApplicable if ($testableControls -gt 0) { $compliantCount = $results.Summary.Compliant + ($results.Summary.PartiallyCompliant * 0.5) $results.Summary.CompliancePercentage = [math]::Round(($compliantCount / $testableControls) * 100, 2) } # Generate recommendations - FIX: All wrapped in @() $criticalFailed = @($results.Controls | Where-Object { $_.Status -eq 'NonCompliant' -and $_.Impact -eq 'Critical' }) $highFailed = @($results.Controls | Where-Object { $_.Status -eq 'NonCompliant' -and $_.Impact -eq 'High' }) if ($criticalFailed.Count -gt 0) { $results.Recommendations += "CRITICAL: $($criticalFailed.Count) critical control(s) non-compliant" $results.Recommendations += "Address critical controls immediately" } if ($highFailed.Count -gt 0) { $results.Recommendations += "HIGH: $($highFailed.Count) high-impact control(s) non-compliant" } if ($results.Summary.CompliancePercentage -lt 80) { $results.Recommendations += "Compliance below 80% - implement remediation plan" } $results.Recommendations += "Review all non-compliant controls" $results.Recommendations += "Document compensating controls where applicable" } catch { Write-Error "Error during NIST compliance testing: $_" throw } } end { Write-Host "`n=== COMPLIANCE TEST COMPLETE ===" -ForegroundColor $( if ($results.Summary.CompliancePercentage -ge 80) { 'Green' } elseif ($results.Summary.CompliancePercentage -ge 60) { 'Yellow' } else { 'Red' } ) Write-Host "`nCompliance Summary:" -ForegroundColor Cyan Write-Host " Total Controls Tested: $($results.Summary.TotalControls)" -ForegroundColor White Write-Host " Compliant: $($results.Summary.Compliant)" -ForegroundColor Green Write-Host " Non-Compliant: $($results.Summary.NonCompliant)" -ForegroundColor Red Write-Host " Partially Compliant: $($results.Summary.PartiallyCompliant)" -ForegroundColor Yellow Write-Host " Not Applicable: $($results.Summary.NotApplicable)" -ForegroundColor Gray Write-Host " Compliance: $($results.Summary.CompliancePercentage)%" -ForegroundColor $( if ($results.Summary.CompliancePercentage -ge 80) { 'Green' } elseif ($results.Summary.CompliancePercentage -ge 60) { 'Yellow' } else { 'Red' } ) if ($results.Controls.Count -gt 0) { $nonCompliant = @($results.Controls | Where-Object { $_.Status -eq 'NonCompliant' }) if ($nonCompliant.Count -gt 0) { Write-Host "`nNon-Compliant Controls by Impact:" -ForegroundColor Cyan $failedByImpact = $nonCompliant | Group-Object Impact foreach ($group in $failedByImpact | Sort-Object Name) { Write-Host " $($group.Name): $($group.Count)" -ForegroundColor $( switch ($group.Name) { 'Critical' { 'Red' } 'High' { 'Red' } 'Medium' { 'Yellow' } default { 'White' } } ) } } } if ($results.Recommendations.Count -gt 0) { Write-Host "`nRecommendations:" -ForegroundColor Cyan $results.Recommendations | ForEach-Object { Write-Host " ! $_" -ForegroundColor Yellow } } Write-Host "`nNIST SP 800-53: https://csrc.nist.gov/publications/detail/sp/800-53/rev-5/final" -ForegroundColor Gray return $results } } |