Detection rules › Kusto
Azure Security Benchmark Posture Changed
This rule monitors Azure policies aligned with the Azure Security Benchmark regulatory compliance initiative and triggers when policy compliance falls below 70% within a 7-day time window.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Discovery | T1082 System Information Discovery |
Rule body kusto
id: 0610e72f-ceaf-42d1-879e-952a1bd8d07a
name: Azure Security Benchmark Posture Changed
description: This rule monitors Azure policies aligned with the Azure Security Benchmark regulatory compliance initiative
and triggers when policy compliance falls below 70% within a 7-day time window.
severity: Medium
requiredDataConnectors:
- connectorId: AzureSecurityCenter
dataTypes:
- SecurityRecommendation
- SecurityRegulatoryCompliance
queryFrequency: 7d
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
relevantTechniques:
- T1082
query: |
let ComplianceDomainLookup = SecurityRecommendation
| join kind=fullouter (SecurityRegulatoryCompliance | where ComplianceStandard == "Azure-Security-Benchmark") on RecommendationName
| summarize arg_max(TimeGenerated, *) by AssessedResourceId, RecommendationName
| extend ComplianceDomain = iff(ComplianceControl contains "NS.", "Network Security", iff(ComplianceControl contains "IM.", "Identity Management", iff(ComplianceControl contains "PA.", "Privileged Access", iff(ComplianceControl contains "DP.", "Data Protection", iff(ComplianceControl contains "AM.", "Asset Management", iff(ComplianceControl contains "LT.", "Logging & Threat Detection", iff(ComplianceControl contains "IR.", "Incident Response", iff(ComplianceControl contains "PV.", "Posture & Vulnerability Management", iff(ComplianceControl contains "ES.", "Endpoint Security", iff(ComplianceControl contains "BR.", "Backup & Recovery", iff(ComplianceControl startswith "DS.", "DevOps Security", iff(ComplianceControl contains "GS.", "Governance & Strategy", "Other"))))))))))));
SecurityRecommendation
| join kind=fullouter (SecurityRegulatoryCompliance | where ComplianceStandard == "Azure-Security-Benchmark") on RecommendationName
| summarize arg_max(TimeGenerated, *) by AssessedResourceId, RecommendationName
| extend ComplianceDomain = iff(ComplianceControl contains "NS.", "Network Security", iff(ComplianceControl contains "IM.", "Identity Management", iff(ComplianceControl contains "PA.", "Privileged Access", iff(ComplianceControl contains "DP.", "Data Protection", iff(ComplianceControl contains "AM.", "Asset Management", iff(ComplianceControl contains "LT.", "Logging & Threat Detection", iff(ComplianceControl contains "IR.", "Incident Response", iff(ComplianceControl contains "PV.", "Posture & Vulnerability Management", iff(ComplianceControl contains "ES.", "Endpoint Security", iff(ComplianceControl contains "BR.", "Backup & Recovery", iff(ComplianceControl startswith "DS.", "DevOps Security", iff(ComplianceControl contains "GS.", "Governance & Strategy", "Other"))))))))))))
| summarize Failed = countif(RecommendationState == "Unhealthy"), Passed = countif(RecommendationState == "Healthy"), Total = countif(RecommendationState == "Healthy" or RecommendationState == "Unhealthy") by ComplianceDomain
| extend PassedControlsPercentage = iff(Total > 0, (Passed / todouble(Total)) * 100.0, real(null))
| join kind=leftouter (ComplianceDomainLookup) on ComplianceDomain
| project ComplianceDomain, Total, PassedControlsPercentage, Passed, Failed, LastEvaluated = TimeGenerated
| summarize arg_max(LastEvaluated, *) by ComplianceDomain, Total, PassedControlsPercentage, Passed, Failed
| where PassedControlsPercentage < 70
| sort by PassedControlsPercentage asc, Passed desc
| extend RemediationLink = strcat("https://portal.azure.com/#blade/Microsoft_Azure_Security/SecurityMenuBlade/22")
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: RemediationLink
alertDetailsOverride:
alertDisplayNameFormat: Azure Security Benchmark posture below threshold for {{ComplianceDomain}}
customDetails:
ComplianceDomain: ComplianceDomain
TotalControls: Total
PassedControls: Passed
FailedControls: Failed
version: 1.0.2
kind: Scheduled
Stages and Predicates
Let binding: ComplianceDomainLookup
let ComplianceDomainLookup = SecurityRecommendation
| join kind=fullouter (SecurityRegulatoryCompliance | where ComplianceStandard == "Azure-Security-Benchmark") on RecommendationName
| summarize arg_max(TimeGenerated, *) by AssessedResourceId, RecommendationName
| extend ComplianceDomain = iff(ComplianceControl contains "NS.", "Network Security", iff(ComplianceControl contains "IM.", "Identity Management", iff(ComplianceControl contains "PA.", "Privileged Access", iff(ComplianceControl contains "DP.", "Data Protection", iff(ComplianceControl contains "AM.", "Asset Management", iff(ComplianceControl contains "LT.", "Logging & Threat Detection", iff(ComplianceControl contains "IR.", "Incident Response", iff(ComplianceControl contains "PV.", "Posture & Vulnerability Management", iff(ComplianceControl contains "ES.", "Endpoint Security", iff(ComplianceControl contains "BR.", "Backup & Recovery", iff(ComplianceControl startswith "DS.", "DevOps Security", iff(ComplianceControl contains "GS.", "Governance & Strategy", "Other"))))))))))));
Stage 1: source
SecurityRecommendation
Stage 2: join
| join kind=fullouter (SecurityRegulatoryCompliance | where ComplianceStandard == "Azure-Security-Benchmark") on RecommendationName
Stage 3: summarize
| summarize arg_max(TimeGenerated, *) by AssessedResourceId, RecommendationName
Stage 4: extend
| extend ComplianceDomain = iff(ComplianceControl contains "NS.", "Network Security", iff(ComplianceControl contains "IM.", "Identity Management", iff(ComplianceControl contains "PA.", "Privileged Access", iff(ComplianceControl contains "DP.", "Data Protection", iff(ComplianceControl contains "AM.", "Asset Management", iff(ComplianceControl contains "LT.", "Logging & Threat Detection", iff(ComplianceControl contains "IR.", "Incident Response", iff(ComplianceControl contains "PV.", "Posture & Vulnerability Management", iff(ComplianceControl contains "ES.", "Endpoint Security", iff(ComplianceControl contains "BR.", "Backup & Recovery", iff(ComplianceControl startswith "DS.", "DevOps Security", iff(ComplianceControl contains "GS.", "Governance & Strategy", "Other"))))))))))))
ComplianceDomain =ComplianceControl contains "NS.""Network Security"iff((ComplianceControl contains "IM."), "Identity Management", iff((ComplianceControl contains "PA."), "Privileged Access", iff((ComplianceControl contains "DP."), "Data Protection", iff((ComplianceControl contains "AM."), "Asset Management", iff((ComplianceControl contains "LT."), "Logging & Threat Detection", iff((ComplianceControl contains "IR."), "Incident Response", iff((ComplianceControl contains "PV."), "Posture & Vulnerability Management", iff((ComplianceControl contains "ES."), "Endpoint Security", iff((ComplianceControl contains "BR."), "Backup & Recovery", iff((ComplianceControl startswith "DS."), "DevOps Security", iff((ComplianceControl contains "GS."), "Governance & Strategy", "Other")))))))))))Stage 5: summarize
| summarize Failed = countif(RecommendationState == "Unhealthy"), Passed = countif(RecommendationState == "Healthy"), Total = countif(RecommendationState == "Healthy" or RecommendationState == "Unhealthy") by ComplianceDomain
Stage 6: extend
| extend PassedControlsPercentage = iff(Total > 0, (Passed / todouble(Total)) * 100.0, real(null))
PassedControlsPercentage =Total > 0((Passed / todouble(Total)) * 100.0)real(null)Stage 7: join
| join kind=leftouter (ComplianceDomainLookup) on ComplianceDomain
Stage 8: project
| project ComplianceDomain, Total, PassedControlsPercentage, Passed, Failed, LastEvaluated = TimeGenerated
Stage 9: summarize
| summarize arg_max(LastEvaluated, *) by ComplianceDomain, Total, PassedControlsPercentage, Passed, Failed
Stage 10: where
| where PassedControlsPercentage < 70
Stage 11: sort
| sort by PassedControlsPercentage asc, Passed desc
Stage 12: extend
| extend RemediationLink = strcat("https://portal.azure.com/#blade/Microsoft_Azure_Security/SecurityMenuBlade/22")
Indicators
Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.
| Field | Kind | Values |
|---|---|---|
ComplianceStandard | eq |
|
PassedControlsPercentage | lt |
|
Output fields
Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.
| Field | Source |
|---|---|
ComplianceDomain | summarize |
Failed | summarize |
Passed | summarize |
PassedControlsPercentage | summarize |
Total | summarize |
RemediationLink | extend |