Analyze/tests/03-DefenderForCloud.ps1
|
#Microsoft Defender for Cloud: plans, components, notifications and alerts function Get-DefenderPricing { param([string]$Name) return @(Get-IngestData 'defender/pricings') | Where-Object { $_ -and $_.name -eq $Name } | Select-Object -First 1 } function Get-PricingExtension { param($Pricing, [string]$Name) return @($Pricing.properties.extensions) | Where-Object { $_ -and $_.name -eq $Name } | Select-Object -First 1 } #one test per plan; Override returns $true/$false when a resource enables or disables protection itself, $null when it follows the plan $defenderPlans = @( @{ Id = 'AZ-DEF-001'; Plan = 'CloudPosture'; Name = 'Defender CSPM'; Severity = 'Medium'; Always = $true Policy = @{ '1f90fc71-a595-4066-8974-d4d0802e8ef0' = 'Microsoft Defender CSPM should be enabled' } Why = 'Defender CSPM adds attack path analysis, the cloud security explorer, agentless scanning and data security posture management on top of the free foundational posture.' } @{ Id = 'AZ-DEF-002'; Plan = 'VirtualMachines'; Name = 'Defender for Servers'; Severity = 'High' Types = @('Microsoft.Compute/virtualMachines', 'Microsoft.Compute/virtualMachineScaleSets', 'Microsoft.HybridCompute/machines') Policy = @{ '4da35fc9-c9e7-4960-aec9-797fe7d9051d' = 'Azure Defender for servers should be enabled' } Why = 'Defender for Servers provides Microsoft Defender for Endpoint (EDR), vulnerability management and threat detection for virtual machines and Arc machines.' } @{ Id = 'AZ-DEF-003'; Plan = 'Containers'; Name = 'Defender for Containers'; Severity = 'High'; Legacy = @('KubernetesService', 'ContainerRegistry') Types = @('Microsoft.ContainerService/managedClusters', 'Microsoft.ContainerRegistry/registries', 'Microsoft.Kubernetes/connectedClusters') Policy = @{ '1c988dd6-ade4-430f-a608-2a3e5b0a6d38' = 'Microsoft Defender for Containers should be enabled' } Why = 'Defender for Containers provides runtime threat detection for Kubernetes and vulnerability assessment of container images.' } @{ Id = 'AZ-DEF-004'; Plan = 'StorageAccounts'; Name = 'Defender for Storage'; Severity = 'High' Types = @('Microsoft.Storage/storageAccounts') Policy = @{ '640d2586-54d2-465f-877f-9ffc1d2109f4' = 'Microsoft Defender for Storage should be enabled' } Why = 'Defender for Storage detects unusual access, data exfiltration and malware uploads to storage accounts.' Override = { param($Record) $setting = Get-Child $Record 'providers/Microsoft.Security/defenderForStorageSettings/current' if ($setting -and $setting.properties.overrideSubscriptionLevelSettings) { return [bool]$setting.properties.isEnabled } return $null } } @{ Id = 'AZ-DEF-005'; Plan = 'AppServices'; Name = 'Defender for App Service'; Severity = 'Medium' Types = @('Microsoft.Web/sites') Policy = @{ '2913021d-f2fd-4f3d-b958-22354e2bdbcb' = 'Azure Defender for App Service should be enabled' } Why = 'Defender for App Service detects attacks against web applications and dangling DNS entries of decommissioned apps.' } @{ Id = 'AZ-DEF-006'; Plan = 'CosmosDbs'; Name = 'Defender for Azure Cosmos DB'; Severity = 'Medium' Types = @('Microsoft.DocumentDB/databaseAccounts') Policy = @{ 'adbe85b5-83e6-4350-ab58-bf3a4f736e5e' = 'Microsoft Defender for Azure Cosmos DB should be enabled' } Why = 'Defender for Azure Cosmos DB detects SQL injection, anomalous access and data exfiltration attempts.' Override = { param($Record) $setting = Get-Child $Record 'providers/Microsoft.Security/advancedThreatProtectionSettings/current' if ($setting -and $setting.properties.isEnabled) { return $true } return $null } } @{ Id = 'AZ-DEF-007'; Plan = 'OpenSourceRelationalDatabases'; Name = 'Defender for open-source relational databases'; Severity = 'Medium' Types = @('Microsoft.DBforPostgreSQL/flexibleServers', 'Microsoft.DBforPostgreSQL/servers', 'Microsoft.DBforMySQL/flexibleServers', 'Microsoft.DBforMySQL/servers') Policy = @{ '0a9fbe0d-c5c4-4da8-87d8-f4fd77338835' = 'Azure Defender for open-source relational databases should be enabled' } Why = 'Defender for open-source relational databases detects brute force, anomalous access and suspicious queries on PostgreSQL and MySQL servers.' Override = { param($Record) $setting = @(Get-Child $Record 'advancedThreatProtectionSettings') | Where-Object { $_ } | Select-Object -First 1 if ($setting -and $setting.properties.state -eq 'Enabled') { return $true } return $null } } @{ Id = 'AZ-DEF-008'; Plan = 'SqlServers'; Name = 'Defender for Azure SQL'; Severity = 'High' Types = @('Microsoft.Sql/servers', 'Microsoft.Sql/managedInstances', 'Microsoft.Synapse/workspaces') Policy = @{ '7fe3b40f-802b-4cdd-8bd4-fd799c948cc2' = 'Azure Defender for Azure SQL Database servers should be enabled'; 'abfb4388-5bf4-4ad7-ba82-2cd2f41ceae9' = 'Azure Defender for SQL should be enabled for unprotected Azure SQL servers'; 'abfb7388-5bf4-4ad7-ba99-2cd2f41cebb9' = 'Azure Defender for SQL should be enabled for unprotected SQL Managed Instances' } Why = 'Defender for SQL provides vulnerability assessment and detects SQL injection, brute force and anomalous database access.' Override = { param($Record) $setting = @(Get-Child $Record 'advancedThreatProtectionSettings') + @(Get-Child $Record 'securityAlertPolicies') | Where-Object { $_ -and $_.properties.state -eq 'Enabled' } | Select-Object -First 1 if ($setting) { return $true } return $null } } @{ Id = 'AZ-DEF-009'; Plan = 'SqlServerVirtualMachines'; Name = 'Defender for SQL servers on machines'; Severity = 'Medium' Types = @('Microsoft.SqlVirtualMachine/sqlVirtualMachines', 'Microsoft.AzureArcData/sqlServerInstances') Policy = @{ '6581d072-105e-4418-827f-bd446d56421b' = 'Azure Defender for SQL servers on machines should be enabled' } Why = 'Defender for SQL servers on machines protects SQL Server running on virtual machines and Arc enabled servers.' } @{ Id = 'AZ-DEF-010'; Plan = 'KeyVaults'; Name = 'Defender for Key Vault'; Severity = 'Medium' Types = @('Microsoft.KeyVault/vaults') Policy = @{ '0e6763cc-5078-4e64-889d-ff4d9a839047' = 'Azure Defender for Key Vault should be enabled' } Why = 'Defender for Key Vault detects unusual and potentially harmful access to secrets, keys and certificates.' } @{ Id = 'AZ-DEF-011'; Plan = 'Arm'; Name = 'Defender for Resource Manager'; Severity = 'Medium'; Always = $true Policy = @{ 'c3d20c29-b36d-48fe-808b-99a87530ad99' = 'Azure Defender for Resource Manager should be enabled' } Why = 'Defender for Resource Manager detects suspicious management operations such as the use of exploitation toolkits, unusual role assignments and suspicious control plane access.' } @{ Id = 'AZ-DEF-012'; Plan = 'Api'; Name = 'Defender for APIs'; Severity = 'Low' Types = @('Microsoft.ApiManagement/service') Policy = @{ '7926a6d1-b268-4586-8197-e8ae90c877d7' = 'Microsoft Defender for APIs should be enabled' } Why = 'Defender for APIs inventories APIs published through API Management and detects attacks against them.' } @{ Id = 'AZ-DEF-013'; Plan = 'AI'; Name = 'Defender for AI services'; Severity = 'Medium' Types = @('Microsoft.CognitiveServices/accounts') Why = 'Defender for AI services detects prompt injection (jailbreak), data leakage and credential theft attempts against Azure OpenAI and AI services.' } ) foreach ($plan in $defenderPlans) { Add-AzTest @{ Id = $plan.Id Title = "Microsoft $($plan.Name) is enabled" Category = 'Logging and threat detection' Service = 'Microsoft Defender for Cloud' Severity = $plan.Severity Description = if ($plan.Always) { "Checks that the $($plan.Name) plan is enabled on the subscription." } else { "Checks that the $($plan.Name) plan is enabled, and that each resource it protects is covered by the plan or by protection enabled on the resource itself." } Rationale = $plan.Why Remediation = "Enable the plan in Defender for Cloud > Environment settings > <subscription> > Defender plans (az security pricing create --name $($plan.Plan) --tier Standard)." References = @('https://learn.microsoft.com/azure/defender-for-cloud/defender-for-cloud-introduction') Policy = $plan.Policy Requires = @('defender/pricings') Config = $plan Run = { param($Test) $planCopy = $Test.Config $pricing = Get-DefenderPricing $planCopy.Plan $standard = $pricing.properties.pricingTier -eq 'Standard' $legacy = @($planCopy.Legacy | Where-Object { $_ } | Where-Object { (Get-DefenderPricing $_).properties.pricingTier -eq 'Standard' }) $evidence = [ordered]@{ plan = $planCopy.Plan; pricingTier = $pricing.properties.pricingTier; subPlan = $pricing.properties.subPlan; enabledLegacyPlans = $legacy } if (-not $pricing) { return New-SubscriptionFinding (New-Unknown "Plan $($planCopy.Plan) was not returned by the pricings API" $evidence) } if ($planCopy.Always) { if ($standard) { return New-SubscriptionFinding (New-Pass "$($planCopy.Name) is enabled" $evidence) } return New-SubscriptionFinding (New-Fail "$($planCopy.Name) is not enabled" $evidence) } $records = @(Get-AzResourceRecords -Type $planCopy.Types) if (-not $records) { if ($standard) { return New-SubscriptionFinding (New-Pass "$($planCopy.Name) is enabled (no resources in scope yet)" $evidence) } return New-SubscriptionFinding (New-NotApplicable "$($planCopy.Name) is off and there are no resources it would protect" $evidence) } foreach ($record in $records) { $override = if ($planCopy.Override) { & $planCopy.Override $record } else { $null } $resourceEvidence = [ordered]@{ planTier = $pricing.properties.pricingTier; resourceLevelProtection = $override } $result = if ($override -eq $false) { New-Fail 'Protection is disabled on the resource, overriding the subscription plan' $resourceEvidence } elseif ($standard -or $legacy) { New-Pass "Protected by the $($planCopy.Name) plan" $resourceEvidence } elseif ($override -eq $true) { New-Pass 'Protection is enabled on the resource' $resourceEvidence } else { New-Fail "Not protected: the $($planCopy.Name) plan is off" $resourceEvidence } New-Finding -Record $record -Result $result } } } } Add-AzTest @{ Id = 'AZ-DEF-014' Title = 'Defender for Servers endpoint protection (Defender for Endpoint integration) is on' Category = 'Endpoint security' Service = 'Microsoft Defender for Cloud' Severity = 'High' Description = 'Checks the WDATP integration setting that deploys Microsoft Defender for Endpoint to machines protected by Defender for Servers.' Rationale = 'Endpoint detection and response on servers is the primary control against malware, ransomware and hands-on-keyboard attacks; without the integration the Servers plan does not deploy it.' Remediation = "Enable 'Endpoint protection' under Defender plans > Servers > Settings (security setting WDATP set to enabled)." References = @('https://learn.microsoft.com/azure/defender-for-cloud/integration-defender-for-endpoint') Requires = @('defender/settings', 'defender/pricings') Run = { $setting = @(Get-IngestData 'defender/settings') | Where-Object { $_ -and $_.name -eq 'WDATP' } | Select-Object -First 1 $servers = (Get-DefenderPricing 'VirtualMachines').properties.pricingTier $evidence = [ordered]@{ wdatpEnabled = [bool]$setting.properties.enabled; serversPlan = $servers } if (-not $setting) { return New-SubscriptionFinding (New-Unknown 'The WDATP setting was not returned' $evidence) } if (-not $setting.properties.enabled) { return New-SubscriptionFinding (New-Fail 'Defender for Endpoint integration is off' $evidence) } if ($servers -ne 'Standard') { return New-SubscriptionFinding (New-Fail 'Defender for Endpoint integration is on but Defender for Servers is off, so nothing is deployed' $evidence) } New-SubscriptionFinding (New-Pass 'Defender for Endpoint integration is on' $evidence) } } $defenderServerComponents = @( @{ Id = 'AZ-DEF-015'; Extension = 'AgentlessVmScanning'; Name = 'Agentless scanning for machines'; Severity = 'Medium' Why = 'Agentless scanning inspects disk snapshots for vulnerabilities, secrets and malware without an agent, including machines where agents are missing or broken.' } @{ Id = 'AZ-DEF-016'; Extension = 'FileIntegrityMonitoring'; Name = 'File integrity monitoring'; Severity = 'Low' Why = 'File integrity monitoring detects changes to operating system files, registry keys and application binaries that indicate compromise or unauthorized change.' } ) foreach ($component in $defenderServerComponents) { Add-AzTest @{ Id = $component.Id Title = "Defender for Servers component '$($component.Name)' is on" Category = 'Posture and vulnerability management' Service = 'Microsoft Defender for Cloud' Severity = $component.Severity Description = "Checks that the $($component.Name) extension of the Defender for Servers plan (or Defender CSPM for agentless scanning) is enabled." Rationale = $component.Why Remediation = "Enable '$($component.Name)' under Defender plans > Servers > Settings (requires Defender for Servers Plan 2)." References = @('https://learn.microsoft.com/azure/defender-for-cloud/defender-for-servers-overview') Requires = @('defender/pricings') Config = $component Run = { param($Test) $componentCopy = $Test.Config $sources = @('VirtualMachines') if ($componentCopy.Extension -eq 'AgentlessVmScanning') { $sources += 'CloudPosture' } $enabledIn = @($sources | Where-Object { $pricing = Get-DefenderPricing $_ $extension = Get-PricingExtension $pricing $componentCopy.Extension $pricing.properties.pricingTier -eq 'Standard' -and $extension -and $extension.isEnabled -eq 'True' }) $evidence = [ordered]@{ enabledIn = $enabledIn; serversPlan = (Get-DefenderPricing 'VirtualMachines').properties.pricingTier; serversSubPlan = (Get-DefenderPricing 'VirtualMachines').properties.subPlan } if ($enabledIn) { return New-SubscriptionFinding (New-Pass "$($componentCopy.Name) is on ($($enabledIn -join ', '))" $evidence) } New-SubscriptionFinding (New-Fail "$($componentCopy.Name) is off" $evidence) } } } Add-AzTest @{ Id = 'AZ-DEF-026' Title = "Defender for AI services includes suspicious prompt evidence in alerts" Category = 'Logging and threat detection' Service = 'Microsoft Defender for Cloud' Severity = 'Low' Description = 'Checks that Defender for AI services is on and that its AIPromptEvidence extension (suspicious prompt evidence) is enabled, so alerts include the suspicious segments of prompts and responses, with sensitive data redacted.' Rationale = 'Without the evidence, an alert about prompt injection or data leakage only says that something happened. Analysts cannot judge the attack or its impact without asking the application owners for logs they may not keep.' Remediation = "Enable 'Suspicious prompt evidence' under Defender plans > AI services > Settings." References = @('https://learn.microsoft.com/azure/defender-for-cloud/gain-end-user-context-ai') Requires = @('defender/pricings') Run = { $pricing = Get-DefenderPricing 'AI' $extension = Get-PricingExtension $pricing 'AIPromptEvidence' $evidence = [ordered]@{ aiPlan = $pricing.properties.pricingTier; promptEvidence = $extension.isEnabled } if ($pricing.properties.pricingTier -ne 'Standard') { return New-SubscriptionFinding (New-Fail 'Defender for AI services is off' $evidence) } if ($extension -and $extension.isEnabled -eq 'True') { return New-SubscriptionFinding (New-Pass 'Suspicious prompt evidence is on' $evidence) } New-SubscriptionFinding (New-Fail 'Suspicious prompt evidence is off' $evidence) } } Add-AzTest @{ Id = 'AZ-DEF-017' Version = 2 Title = 'Vulnerability assessment for machines is configured' Category = 'Posture and vulnerability management' Service = 'Microsoft Defender for Cloud' Severity = 'Medium' Description = 'Checks that Defender for Servers is on and that a vulnerability assessment provider (Microsoft Defender Vulnerability Management) covers machines. Both Plan 1 and Plan 2 include Defender Vulnerability Management.' Rationale = 'Unknown vulnerabilities cannot be prioritized or patched. Built-in vulnerability management continuously finds missing patches and vulnerable software on servers.' Remediation = "Enable Defender for Servers and set 'Vulnerability assessment for machines' to Microsoft Defender Vulnerability Management." References = @('https://learn.microsoft.com/azure/defender-for-cloud/deploy-vulnerability-assessment-defender-vulnerability-management') Policy = @{ '501541f7-f7e7-4cd6-868c-4190fdad3ac9' = 'A vulnerability assessment solution should be enabled on your virtual machines' } Requires = @('defender/pricings') Run = { $servers = Get-DefenderPricing 'VirtualMachines' $evidence = [ordered]@{ serversPlan = $servers.properties.pricingTier; serversSubPlan = $servers.properties.subPlan } if ($servers.properties.pricingTier -ne 'Standard') { return New-SubscriptionFinding (New-Fail 'Defender for Servers is off, so no vulnerability assessment runs' $evidence) } #both Defender for Servers plans include Defender Vulnerability Management, so the plan itself already satisfies this if ($servers.properties.subPlan -in 'P1', 'P2') { return New-SubscriptionFinding (New-Pass "Defender for Servers $($servers.properties.subPlan) includes Defender Vulnerability Management" $evidence) } if (-not (Test-IngestSection 'defender/serverVulnerabilityAssessmentsSettings')) { return New-SubscriptionFinding (New-Unknown 'The vulnerability assessment setting could not be read and the plan does not report a sub plan' $evidence) } $setting = @(Get-IngestData 'defender/serverVulnerabilityAssessmentsSettings') | Where-Object { $_ } | Select-Object -First 1 $evidence.selectedProvider = $setting.properties.selectedProvider if ($evidence.selectedProvider) { return New-SubscriptionFinding (New-Pass "Vulnerability assessment provider $($evidence.selectedProvider)" $evidence) } New-SubscriptionFinding (New-Fail 'No vulnerability assessment provider is selected' $evidence) } } function Get-SecurityContact { return @(Get-IngestData 'defender/securityContacts') | Where-Object { $_ } | Select-Object -First 1 } Add-AzTest @{ Id = 'AZ-DEF-018' Title = 'A security contact email address is configured' Category = 'Incident response' Service = 'Microsoft Defender for Cloud' Severity = 'Medium' Description = 'Checks that Defender for Cloud email notifications have at least one additional email address.' Rationale = 'Microsoft and Defender for Cloud use the security contact to report compromised resources and high severity alerts. Without it, notifications may reach nobody who acts on them.' Remediation = "Set 'Additional email addresses' (a monitored security team mailbox) under Defender for Cloud > Environment settings > Email notifications." References = @('https://learn.microsoft.com/azure/defender-for-cloud/configure-email-notifications') Requires = @('defender/securityContacts') Run = { $contact = Get-SecurityContact $emails = @(([string]$contact.properties.emails) -split '[;,]' | ForEach-Object { $_.Trim() } | Where-Object { $_ }) $evidence = [ordered]@{ emails = $emails; isEnabled = $contact.properties.isEnabled } if ($emails) { return New-SubscriptionFinding (New-Pass "$($emails.Count) security contact address(es)" $evidence) } New-SubscriptionFinding (New-Fail 'No security contact email address' $evidence) } } Add-AzTest @{ Id = 'AZ-DEF-019' Title = 'Security alert notifications are sent to subscription owners' Category = 'Incident response' Service = 'Microsoft Defender for Cloud' Severity = 'Low' Description = "Checks that Defender for Cloud notifies users with the Owner role about alerts." Rationale = 'Owners are accountable for the subscription and must know when its resources are attacked, especially when no central SOC monitors the alerts.' Remediation = "Under Email notifications, select 'Owner' in 'All users with the following roles'." References = @('https://learn.microsoft.com/azure/defender-for-cloud/configure-email-notifications') Policy = @{ '0b15565f-aa9e-48ba-8619-45960f2c314d' = 'Email notification to subscription owner for high severity alerts should be enabled' } Requires = @('defender/securityContacts') Run = { $byRole = (Get-SecurityContact).properties.notificationsByRole $evidence = [ordered]@{ state = $byRole.state; roles = @($byRole.roles) } if ($byRole.state -eq 'On' -and @($byRole.roles) -contains 'Owner') { return New-SubscriptionFinding (New-Pass 'Owners are notified' $evidence) } New-SubscriptionFinding (New-Fail 'Owners are not notified about alerts' $evidence) } } Add-AzTest @{ Id = 'AZ-DEF-020' Title = 'Email notifications for security alerts are enabled' Category = 'Incident response' Service = 'Microsoft Defender for Cloud' Severity = 'Medium' Description = 'Checks that Defender for Cloud sends email notifications for alerts with a minimum severity of High or lower.' Rationale = 'Alert emails are the minimum notification path so that detected attacks are acted upon quickly.' Remediation = "Under Email notifications, enable 'Notify about alerts with the following severity (or higher)' and select High (or Medium)." References = @('https://learn.microsoft.com/azure/defender-for-cloud/configure-email-notifications') Policy = @{ '6e2593d9-add6-4083-9c9b-4b7d2188c899' = 'Email notification for high severity alerts should be enabled' } Requires = @('defender/securityContacts') Run = { $contact = Get-SecurityContact $source = @($contact.properties.notificationsSources) | Where-Object { $_ -and $_.sourceType -eq 'Alert' } | Select-Object -First 1 $evidence = [ordered]@{ isEnabled = $contact.properties.isEnabled; minimalSeverity = $source.minimalSeverity } if ($contact.properties.isEnabled -and $source.minimalSeverity -in 'High', 'Medium', 'Low') { return New-SubscriptionFinding (New-Pass "Alert emails for severity $($source.minimalSeverity) and higher" $evidence) } New-SubscriptionFinding (New-Fail 'Alert email notifications are not enabled' $evidence) } } Add-AzTest @{ Id = 'AZ-DEF-021' Title = 'Email notifications for attack paths are enabled' Category = 'Incident response' Service = 'Microsoft Defender for Cloud' Severity = 'Low' Description = 'Checks that Defender for Cloud sends email notifications for attack paths with a minimum risk level.' Rationale = 'Attack paths show exploitable chains from the internet to critical resources. Notifications make sure new high risk paths are handled promptly.' Remediation = "Under Email notifications, enable 'Notify about attack paths with the following risk level (or higher)' (requires Defender CSPM)." References = @('https://learn.microsoft.com/azure/defender-for-cloud/configure-email-notifications') Requires = @('defender/securityContacts') Run = { $contact = Get-SecurityContact $source = @($contact.properties.notificationsSources) | Where-Object { $_ -and $_.sourceType -eq 'AttackPath' } | Select-Object -First 1 $evidence = [ordered]@{ isEnabled = $contact.properties.isEnabled; minimalRiskLevel = $source.minimalRiskLevel } if ($contact.properties.isEnabled -and $source.minimalRiskLevel) { return New-SubscriptionFinding (New-Pass "Attack path emails for risk level $($source.minimalRiskLevel) and higher" $evidence) } New-SubscriptionFinding (New-Fail 'Attack path notifications are not enabled' $evidence) } } Add-AzTest @{ Id = 'AZ-DEF-022' Title = 'No active high severity security alerts' Category = 'Incident response' Service = 'Microsoft Defender for Cloud' Severity = 'High' Description = 'Lists Defender for Cloud alerts with severity High that are still active (not resolved or dismissed).' Rationale = 'An active high severity alert may be an ongoing compromise. Alerts must be triaged, investigated and closed.' Remediation = 'Investigate each alert (Defender for Cloud > Security alerts, or the Defender portal incidents), contain and remediate, then resolve or dismiss it with a reason.' References = @('https://learn.microsoft.com/azure/defender-for-cloud/managing-and-responding-alerts') Requires = @('defender/alerts') Run = { $alerts = @(Get-IngestData 'defender/alerts' | Where-Object { $_ -and $_.properties.severity -eq 'High' -and $_.properties.status -eq 'Active' }) if (-not $alerts) { return New-SubscriptionFinding (New-Pass 'No active high severity alerts') } foreach ($alert in $alerts) { $p = $alert.properties $evidence = [ordered]@{ alert = $p.alertDisplayName; startTime = Format-UtcDate $p.startTimeUtc; compromisedEntity = $p.compromisedEntity; resource = @($p.resourceIdentifiers | ForEach-Object { $_.azureResourceId } | Where-Object { $_ }) } New-Finding -ResourceId $alert.id -ResourceType $alert.type -ResourceName $p.alertDisplayName -Result (New-Fail "$($p.alertDisplayName) on $($p.compromisedEntity) since $($evidence.startTime)" $evidence) } } } Add-AzTest @{ Id = 'AZ-DEF-024' Title = 'Sensitive data discovery is enabled' Category = 'Data protection' Service = 'Microsoft Defender for Cloud' Severity = 'Medium' Description = 'Checks the sensitive data discovery extension of Defender CSPM or Defender for Storage.' Rationale = 'Knowing where sensitive data lives drives the prioritization of attack paths, alerts and protection; sensitive data discovery classifies data in storage and databases automatically.' Remediation = "Enable 'Sensitive data discovery' in the Defender CSPM or Defender for Storage plan settings." References = @('https://learn.microsoft.com/azure/defender-for-cloud/concept-data-security-posture') Requires = @('defender/pricings') Run = { $enabledIn = @('CloudPosture', 'StorageAccounts' | Where-Object { $pricing = Get-DefenderPricing $_ $extension = Get-PricingExtension $pricing 'SensitiveDataDiscovery' $pricing.properties.pricingTier -eq 'Standard' -and $extension.isEnabled -eq 'True' }) $evidence = [ordered]@{ enabledIn = $enabledIn } if ($enabledIn) { return New-SubscriptionFinding (New-Pass "Sensitive data discovery on ($($enabledIn -join ', '))" $evidence) } New-SubscriptionFinding (New-Fail 'Sensitive data discovery is off' $evidence) } } Add-AzTest @{ Id = 'AZ-DEF-025' Version = 2 Title = 'Defender for Storage malware scanning is enabled' Category = 'Data protection' Service = 'Microsoft Defender for Cloud' Severity = 'Medium' Description = 'Checks that on-upload malware scanning of Defender for Storage applies to each storage account (plan extension or account level override).' Rationale = 'Storage accounts that receive files from users or partners are a malware distribution path; scanning on upload detects malicious content before it is consumed.' Remediation = "Enable 'Malware scanning' in the Defender for Storage plan settings (with a monthly cap per account), or on the individual storage account." References = @('https://learn.microsoft.com/azure/defender-for-cloud/on-upload-malware-scanning') Requires = @('defender/pricings') ResourceTypes = @('Microsoft.Storage/storageAccounts') Evaluate = { param($Record) if (-not (Test-ChildCollected $Record 'providers/Microsoft.Security/defenderForStorageSettings/current')) { return New-Unknown 'The Defender for Storage setting of the account could not be read' } $setting = (Get-Child $Record 'providers/Microsoft.Security/defenderForStorageSettings/current').properties $pricing = Get-DefenderPricing 'StorageAccounts' $planScanning = $pricing.properties.pricingTier -eq 'Standard' -and (Get-PricingExtension $pricing 'OnUploadMalwareScanning').isEnabled -eq 'True' $evidence = [ordered]@{ planMalwareScanning = $planScanning; accountOverride = [bool]$setting.overrideSubscriptionLevelSettings; accountMalwareScanning = $setting.malwareScanning.onUpload.isEnabled } $enabled = if ($setting.overrideSubscriptionLevelSettings) { $setting.isEnabled -and $setting.malwareScanning.onUpload.isEnabled } else { $planScanning } if ($enabled) { return New-Pass 'Malware scanning on upload enabled' $evidence } New-Fail 'No malware scanning on upload' $evidence } } Add-AzTest @{ Id = 'AZ-DEF-023' Version = 2 Title = 'Security alerts are forwarded to a SIEM or automation' Category = 'Incident response' Service = 'Microsoft Defender for Cloud' Severity = 'Low' Description = 'Checks for a Defender for Cloud workflow automation or continuous export that handles security alerts, or the Microsoft Sentinel alert synchronization setting.' Rationale = 'Alerts that stay in the portal depend on someone looking. Forwarding them to a SIEM, SOAR or ticketing flow makes sure every alert is triaged.' Remediation = 'Connect Defender for Cloud to Microsoft Sentinel (or your SIEM) through the Defender XDR connector or continuous export, or create a workflow automation for alerts.' References = @('https://learn.microsoft.com/azure/defender-for-cloud/continuous-export') Requires = @('defender/automations', 'defender/settings') Run = { $automations = @(Get-IngestData 'defender/automations' | Where-Object { $_ -and $_.properties.isEnabled -and (@($_.properties.sources) | Where-Object { $_.eventSource -eq 'Alerts' }) }) $sentinel = @(Get-IngestData 'defender/settings') | Where-Object { $_ -and $_.name -eq 'Sentinel' -and $_.properties.enabled } $evidence = [ordered]@{ alertAutomations = @($automations | ForEach-Object name | Sort-Object); sentinelAlertSync = [bool]$sentinel } if ($automations -or $sentinel) { return New-SubscriptionFinding (New-Pass 'Alerts are forwarded' $evidence) } New-SubscriptionFinding (New-Fail 'No alert export, automation or Sentinel synchronization configured for this subscription' $evidence) } } |