Analyze/tests/18-DefenderFindings.ps1
|
#Health that is only observable from inside workloads, read from Microsoft Defender for Cloud assessments function Get-AssessmentFindings { #one finding per resource for the given assessment keys: Fail when any is Unhealthy, Pass when all reported ones are Healthy param([hashtable]$Keys, [string]$HealthyText, [string]$SubscriptionNotApplicable) $assessments = @(Get-IngestData 'defender/assessments' | Where-Object { $_ -and $Keys.ContainsKey(([string]$_.name).ToLowerInvariant()) }) $byResource = [ordered]@{} foreach ($assessment in $assessments) { $resourceId = $assessment.properties.resourceDetails.Id if (-not $resourceId) { $resourceId = $assessment.id -replace '(?i)/providers/Microsoft\.Security/assessments/[^/]+$', '' } $key = $resourceId.ToLowerInvariant() if (-not $byResource.Contains($key)) { $byResource[$key] = [pscustomobject]@{ Id = $resourceId; Items = [System.Collections.Generic.List[object]]::new() } } $byResource[$key].Items.Add($assessment) } if (-not $byResource.Count) { return New-SubscriptionFinding (New-NotApplicable $SubscriptionNotApplicable) } foreach ($entry in $byResource.Values) { $unhealthy = @($entry.Items | Where-Object { $_.properties.status.code -eq 'Unhealthy' } | ForEach-Object { $Keys[([string]$_.name).ToLowerInvariant()] } | Sort-Object -Unique) $healthy = @($entry.Items | Where-Object { $_.properties.status.code -eq 'Healthy' }) $evidence = [ordered]@{ unhealthy = $unhealthy; assessments = @($entry.Items | ForEach-Object { "$($Keys[([string]$_.name).ToLowerInvariant()]): $($_.properties.status.code)" } | Sort-Object) } $result = if ($unhealthy) { New-Fail ($unhealthy -join '; ') $evidence } elseif ($healthy) { New-Pass $HealthyText $evidence } else { New-NotApplicable 'Not applicable to this resource' $evidence } New-Finding -ResourceId $entry.Id -ResourceType ($entry.Id -replace '^.*/providers/([^/]+/[^/]+)/.*$', '$1') -Result $result } } Add-AzTest @{ Id = 'AZ-DFA-001' Title = 'Endpoint detection and response is healthy with current antivirus signatures' Category = 'Endpoint security' Service = 'Microsoft Defender for Cloud' Severity = 'High' Description = 'Reads the Defender for Cloud EDR assessments of machines: EDR configuration issues, antivirus component disabled, outdated signatures and scans older than 7 days.' Rationale = 'An installed but misconfigured or outdated EDR gives a false sense of protection; outdated signatures miss known malware.' Remediation = 'Resolve the listed issues on each machine (enable real-time protection, restore signature updates, run scans), following the remediation steps in the Defender for Cloud recommendation.' References = @('https://learn.microsoft.com/azure/defender-for-cloud/endpoint-detection-response') Defender = @{ 'dc5357d0-3858-4d17-a1a3-072840bff5be' = 'EDR configuration issues should be resolved on virtual machines'; '506d18a1-d571-4341-aad5-a7d363c5bbd4' = 'Anti-Virus component in your EDR is off or partially configured'; 'aafa7d27-01ae-40c6-a56c-1d0ef04b1d71' = 'Anti-Virus component of your EDR uses outdated signatures'; 'd44de051-1862-48f8-8476-192aee854699' = 'Anti-Virus scans of your EDR are out of 7 days' } Requires = @('defender/assessments') Run = { Get-AssessmentFindings -Keys @{ 'dc5357d0-3858-4d17-a1a3-072840bff5be' = 'EDR configuration issues'; '506d18a1-d571-4341-aad5-a7d363c5bbd4' = 'antivirus off or partially configured'; 'aafa7d27-01ae-40c6-a56c-1d0ef04b1d71' = 'outdated antivirus signatures'; 'd44de051-1862-48f8-8476-192aee854699' = 'antivirus scans older than 7 days' } -HealthyText 'EDR and antivirus healthy' -SubscriptionNotApplicable 'Defender for Cloud reports no EDR assessments (requires Defender for Servers)' } } Add-AzTest @{ Id = 'AZ-DFA-002' Title = 'Machines have system updates installed' Category = 'Posture and vulnerability management' Service = 'Microsoft Defender for Cloud' Severity = 'High' Description = "Reads the Defender for Cloud assessment 'System updates should be installed on your machines' (Azure Update Manager)." Rationale = 'Missing security updates are among the most exploited weaknesses; attackers weaponize published patches within days.' Remediation = 'Install the missing updates and configure Azure Update Manager maintenance configurations with patch orchestration for regular, automatic patching.' References = @('https://learn.microsoft.com/azure/update-manager/overview') Defender = @{ 'e1145ab1-eb4f-43d8-911b-36ddf771d13f' = 'System updates should be installed on your machines (powered by Azure Update Manager)' } Policy = @{ 'f85bf3e0-d513-442e-89c3-1784ad63382b' = 'System updates should be installed on your machines (powered by Update Center)' } Requires = @('defender/assessments') Run = { Get-AssessmentFindings -Keys @{ 'e1145ab1-eb4f-43d8-911b-36ddf771d13f' = 'missing system updates' } -HealthyText 'System updates installed' -SubscriptionNotApplicable 'Defender for Cloud reports no system update assessments' } } Add-AzTest @{ Id = 'AZ-DFA-003' Title = 'Vulnerability findings are resolved' Category = 'Posture and vulnerability management' Service = 'Microsoft Defender for Cloud' Severity = 'High' Description = 'Reads the Defender for Cloud vulnerability assessments of machines, container images, running containers, AKS, SQL databases and function apps.' Rationale = 'Known vulnerabilities in operating systems, images, packages and databases give attackers ready made exploits.' Remediation = 'Remediate the vulnerabilities listed in each Defender for Cloud recommendation (patch, rebuild images on patched base images, upgrade AKS), prioritizing exploitable and Internet exposed resources.' References = @('https://learn.microsoft.com/azure/defender-for-cloud/remediate-vulnerability-findings-vm') Defender = @{ '1195afff-c881-495e-9bc5-1486211ae03f' = 'Machines should have vulnerability findings resolved'; '44d12760-2cf2-4e6d-8613-8451c11c1abc' = 'Servers onboarded with MDE should have vulnerability findings resolved'; '33422d8f-ab1e-42be-bc9a-38685bb567b9' = 'Container images in Azure registry should have vulnerability findings resolved'; 'c5045ea3-afc6-4006-ab8f-86c8574dbf3d' = 'Containers running in Azure should have vulnerability findings resolved'; '5409cb02-6884-4de3-870d-c17b98bbe04f' = 'Vulnerable Azure Kubernetes Service should be updated to resolve vulnerability findings'; '82e20e14-edc5-4373-bfc4-f13121257c37' = 'SQL databases should have vulnerability findings resolved'; 'f97aa83c-9b63-4f9a-99f6-b22c4398f936' = 'SQL servers on machines should have vulnerability findings resolved'; 'afd071f0-ebaa-422b-bb2f-8a772a31db75' = 'Function apps should have vulnerability findings resolved' } Requires = @('defender/assessments') Run = { Get-AssessmentFindings -Keys @{ '1195afff-c881-495e-9bc5-1486211ae03f' = 'machine vulnerabilities'; '44d12760-2cf2-4e6d-8613-8451c11c1abc' = 'server vulnerabilities (MDE)'; '33422d8f-ab1e-42be-bc9a-38685bb567b9' = 'registry image vulnerabilities'; 'c5045ea3-afc6-4006-ab8f-86c8574dbf3d' = 'running container vulnerabilities'; '5409cb02-6884-4de3-870d-c17b98bbe04f' = 'AKS version vulnerabilities'; '82e20e14-edc5-4373-bfc4-f13121257c37' = 'SQL database vulnerabilities'; 'f97aa83c-9b63-4f9a-99f6-b22c4398f936' = 'SQL server on machine vulnerabilities'; 'afd071f0-ebaa-422b-bb2f-8a772a31db75' = 'function app vulnerabilities' } -HealthyText 'No open vulnerability findings' -SubscriptionNotApplicable 'Defender for Cloud reports no vulnerability assessments' } } Add-AzTest @{ Id = 'AZ-DFA-004' Title = 'Machines meet the Azure compute security baseline' Category = 'Posture and vulnerability management' Service = 'Microsoft Defender for Cloud' Severity = 'Medium' Description = 'Reads the Defender for Cloud assessments of operating system security configuration against the Azure compute security baseline (machine configuration).' Rationale = 'Operating system hardening gaps (weak protocols, missing audit policy, unsafe services) make exploitation and lateral movement easier.' Remediation = 'Remediate the failed baseline rules on each machine, preferably through machine configuration with auto remediation or your configuration management tooling.' References = @('https://learn.microsoft.com/azure/governance/policy/samples/guest-configuration-baseline-windows') Defender = @{ '1f655fb7-63ca-4980-91a3-56dbc2b715c6' = 'Vulnerabilities in security configuration on your Linux machines should be remediated (powered by Guest Configuration)'; '8c3d9ad0-3639-4686-9cd2-2b2ab2609bda' = 'Vulnerabilities in security configuration on your Windows machines should be remediated (powered by Guest Configuration)' } Requires = @('defender/assessments') Run = { Get-AssessmentFindings -Keys @{ '1f655fb7-63ca-4980-91a3-56dbc2b715c6' = 'Linux baseline deviations'; '8c3d9ad0-3639-4686-9cd2-2b2ab2609bda' = 'Windows baseline deviations' } -HealthyText 'Baseline compliant' -SubscriptionNotApplicable 'Defender for Cloud reports no security baseline assessments (requires the guest configuration extension)' } } Add-AzTest @{ Id = 'AZ-DFA-005' Title = 'Windows machines only use secure communication protocols' Category = 'Data protection' Service = 'Microsoft Defender for Cloud' Severity = 'High' Description = "Reads the Defender for Cloud assessment 'Windows servers should be configured to use secure communication protocols' (machine configuration): the minimum TLS version the operating system accepts." Rationale = 'Windows keeps SSL 3.0, TLS 1.0 and TLS 1.1 available unless they are turned off, and every service on the machine (RDP, IIS, SQL Server, WinRM) can then be downgraded to them.' Remediation = 'Disable SSL 3.0, TLS 1.0 and TLS 1.1 in SChannel (Group Policy, machine configuration with auto remediation or your configuration management), after checking that clients support TLS 1.2.' References = @('https://learn.microsoft.com/windows-server/security/tls/tls-registry-settings', 'https://learn.microsoft.com/azure/governance/machine-configuration/overview') Defender = @{ '87448ec1-55f6-3746-3f79-0f35beee76b4' = 'Windows servers should be configured to use secure communication protocols' } Policy = @{ '5752e6d6-1206-46d8-8ab1-ecc2f71a8112' = 'Windows machines should be configured to use secure communication protocols' } Requires = @('defender/assessments') Run = { Get-AssessmentFindings -Keys @{ '87448ec1-55f6-3746-3f79-0f35beee76b4' = 'insecure communication protocols allowed' } -HealthyText 'Only secure communication protocols' -SubscriptionNotApplicable 'Defender for Cloud reports no secure communication protocol assessments (requires the guest configuration extension)' } } |