Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/powershell/tests/Test-Assessment.41050.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Attack surface reduction rules block the specific behaviors that commodity malware, fileless threats, and human-operated intrusion sets reuse across campaigns. Each rule targets a narrow, well-documented execution pattern such as document applications creating child processes, script interpreters launching downloaded content, unsigned binaries performing bulk file operations, or processes attempting to read credential stores. When the rules are not set to block mode, an adversary who delivers a payload to an endpoint can exercise these patterns freely because no preventive control intervenes at the behavior layer. Audit mode records the activity but does not stop it, which means the security operations team sees the evidence only after the damage is done. The risk compounds because ASR rules operate as a set: leaving even a small number of rules in audit or disabled state creates predictable gaps that an attacker can target, knowing that the specific behavior will not be blocked. If you are new to ASR, Microsoft recommends starting in audit mode to understand the impact in your environment before switching rules to block - audit mode is a starting point, not the end state.

**Remediation action**

- [Attack surface reduction rules deployment](https://learn.microsoft.com/en-us/defender-endpoint/attack-surface-reduction-rules-deployment?wt.mc_id=zerotrustrecommendations_automation_content_cnl_csasci)
- [Plan attack surface reduction rules deployment](https://learn.microsoft.com/en-us/defender-endpoint/attack-surface-reduction-rules-deployment-plan?wt.mc_id=zerotrustrecommendations_automation_content_cnl_csasci)
- [Test attack surface reduction rules](https://learn.microsoft.com/en-us/defender-endpoint/attack-surface-reduction-rules-deployment-test?wt.mc_id=zerotrustrecommendations_automation_content_cnl_csasci)
- [Enable attack surface reduction rules](https://learn.microsoft.com/en-us/defender-endpoint/enable-attack-surface-reduction?wt.mc_id=zerotrustrecommendations_automation_content_cnl_csasci)
- [Attack surface reduction rules reference](https://learn.microsoft.com/en-us/defender-endpoint/attack-surface-reduction-rules-reference?wt.mc_id=zerotrustrecommendations_automation_content_cnl_csasci)

<!--- Results --->
%TestResult%
259 changes: 259 additions & 0 deletions src/powershell/tests/Test-Assessment.41050.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
<#
.SYNOPSIS
Attack surface reduction (ASR) rules are enabled in block mode.

.DESCRIPTION
Attack surface reduction rules block high-risk behaviors commonly reused by malware and
human-operated attacks. This check evaluates the pinned ASR Secure Score control set
(scid_2500 through scid_2518) by joining control profiles to the latest Microsoft Secure Score
snapshot and comparing each available score with its maximum score.

.NOTES
Test ID: 41050
Workshop Task ID: SECOPS-050
Category: Endpoint threat protection
Pillar: SecOps
Required Module: Microsoft.Graph.Authentication
Required Connection: Microsoft Graph
#>

function Test-Assessment-41050 {
[ZtTest(
Category = 'Endpoint threat protection',
CompatibleLicense = ('WINDEFATP'),
ImplementationCost = 'Medium',
Pillar = 'SecOps',
RiskLevel = 'High',
Service = ('Graph'),
SfiPillar = 'Monitor and detect cyberthreats',
TenantType = ('Workforce'),
TestId = 41050,
Title = 'Attack surface reduction (ASR) rules are enabled in block mode',
UserImpact = 'Medium'
)]
[CmdletBinding()]
param()

#region Data Collection
Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose

$activity = 'Checking ASR block-mode controls in Microsoft Secure Score'

$asrControlIds = @(
'scid_2500', 'scid_2501', 'scid_2502', 'scid_2503', 'scid_2504',
'scid_2505', 'scid_2506', 'scid_2507', 'scid_2508', 'scid_2509',
'scid_2510', 'scid_2511', 'scid_2512', 'scid_2513', 'scid_2514',
'scid_2515', 'scid_2516', 'scid_2517', 'scid_2518'
)

$controlProfileError = $null
$secureScoreError = $null

# Q1: Read all MDATP Secure Score control profiles, then intersect client-side with pinned IDs.
Write-ZtProgress -Activity $activity -Status 'Getting MDATP Secure Score control profiles'

$mdatpControlProfiles = @()
try {
$mdatpControlProfiles = @(Invoke-ZtGraphRequest -RelativeUri 'security/secureScoreControlProfiles' -Filter "service eq 'MDATP'" -ApiVersion beta -ErrorAction Stop)
}
Comment thread
praneeth-0000 marked this conversation as resolved.
catch {
$controlProfileError = $_
Write-PSFMessage "Failed to retrieve MDATP Secure Score control profiles: $_" -Tag Test -Level Warning
}

# Q2: Read the latest Secure Score snapshot; -DisablePaging returns the wrapper object.
Write-ZtProgress -Activity $activity -Status 'Getting latest Secure Score snapshot'

$latestSecureScore = $null
try {
$secureScoresResponse = Invoke-ZtGraphRequest -RelativeUri 'security/secureScores' -Top 1 -ApiVersion beta -DisablePaging -ErrorAction Stop
$secureScores = @($secureScoresResponse.value)
if ($secureScores.Count -gt 0) {
$latestSecureScore = $secureScores[0]
}
}
catch {
$secureScoreError = $_
Write-PSFMessage "Failed to retrieve latest Secure Score snapshot: $_" -Tag Test -Level Warning
}
#endregion Data Collection

#region Assessment Logic
$passed = $false
$customStatus = $null

foreach ($queryError in @($controlProfileError, $secureScoreError) | Where-Object { $null -ne $_ }) {
if ((Get-ZtHttpStatusCode -ErrorRecord $queryError) -in (401, 403)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Authorization failures are incorrectly reported as Not Applicable

if ((Get-ZtHttpStatusCode -ErrorRecord $queryError) -in (401, 403)) {
    Add-ZtTestResultDetail -SkippedBecause NotApplicable ...
    return
}

A 401/403 proves that the assessment could not read the tenant configuration. It does not prove that ASR is inapplicable.

This also contradicts the 41050 specification’s Investigate message:

ASR Secure Score data was not found; verify SecurityEvents.Read.All is granted...

The result will be suppressed as an applicability skip instead of surfacing the missing permission for investigation. The neighboring Secure Score implementation for 41060 also reports 401/403 through CustomStatus = 'Investigate'.

Required change: return an Investigate result for 401/403, preserving the permission-specific explanation. Reserve NotApplicable for evidence that the service or feature genuinely does not apply.

Add-ZtTestResultDetail -SkippedBecause NotApplicable -Result 'Microsoft Graph returned HTTP 401 or 403; grant `SecurityEvents.Read.All` and assign Security Reader or Security Administrator for delegated runs.'
return
}
}

$controlProfileById = @{}
foreach ($controlProfile in @($mdatpControlProfiles | Where-Object { $asrControlIds -contains $_.id })) {
if ($null -ne $controlProfile.id -and -not $controlProfileById.ContainsKey($controlProfile.id)) {
$controlProfileById[$controlProfile.id] = $controlProfile
}
}

if ($controlProfileError -or $secureScoreError -or $null -eq $latestSecureScore -or $controlProfileById.Count -eq 0) {
$params = @{
TestId = '41050'
Title = 'Attack surface reduction (ASR) rules are enabled in block mode'
Status = $false
Result = '⚠️ ASR Secure Score data was not found; verify `SecurityEvents.Read.All` is granted, Secure Score data is flowing, and at least one MDE device is onboarded.'
CustomStatus = 'Investigate'
}
Add-ZtTestResultDetail @params
return
}

$controlScoreByName = @{}
foreach ($controlScore in @($latestSecureScore.controlScores)) {
if ($null -ne $controlScore.controlName -and -not $controlScoreByName.ContainsKey($controlScore.controlName)) {
$controlScoreByName[$controlScore.controlName] = $controlScore
}
}

$evaluationResults = @()
foreach ($controlId in $asrControlIds) {
$controlProfile = if ($controlProfileById.ContainsKey($controlId)) { $controlProfileById[$controlId] } else { $null }
$matchingScore = if ($controlScoreByName.ContainsKey($controlId)) { $controlScoreByName[$controlId] } else { $null }

$score = if ($null -ne $matchingScore -and $null -ne $matchingScore.score) { $matchingScore.score } else { $null }
$maxScore = if ($null -ne $controlProfile -and $null -ne $controlProfile.maxScore) { $controlProfile.maxScore } else { $null }

$latestStateUpdate = @()
if ($null -ne $controlProfile) {
$latestStateUpdate = @($controlProfile.controlStateUpdates | Sort-Object { if ($_.updatedDateTime) { [datetime]$_.updatedDateTime } else { [datetime]::MinValue } } -Descending | Select-Object -First 1)
}
$isIgnored = $latestStateUpdate.Count -gt 0 -and $latestStateUpdate[0].state -eq 'ignored'

$scoreValue = $null
$scoreIsNumeric = $false
if ($null -ne $score) {
try {
$scoreValue = [double]$score
$scoreIsNumeric = $true
}
catch { }
}

$maxScoreValue = $null
$maxScoreIsNumeric = $false
if ($null -ne $maxScore) {
try {
$maxScoreValue = [double]$maxScore
$maxScoreIsNumeric = $true
}
catch { }
}

$status = if ($null -eq $controlProfile) {
'Investigate'
}
elseif ($null -eq $matchingScore) {
'N/A'
}
elseif ($isIgnored) {
'Skipped'
}
Comment thread
praneeth-0000 marked this conversation as resolved.
elseif (-not $scoreIsNumeric -or -not $maxScoreIsNumeric) {
'Investigate'
}
elseif ($scoreValue -ge $maxScoreValue) {
'Pass'
}
else {
'Fail'
}

$ruleName = if ($null -ne $controlProfile -and -not [string]::IsNullOrWhiteSpace($controlProfile.title)) {
$controlProfile.title
}
else {
$controlId
}

$evaluationResults += [PSCustomObject]@{
AsrRuleId = $controlId
AsrRuleName = $ruleName
ActionUrl = if ($null -ne $controlProfile) { $controlProfile.actionUrl } else { $null }
Score = if ($null -ne $score) { $score } else { 'N/A' }
MaxScore = if ($null -ne $maxScore) { $maxScore } else { 'N/A' }
ImplementationStatus = if ($null -ne $matchingScore -and -not [string]::IsNullOrWhiteSpace($matchingScore.implementationStatus)) { $matchingScore.implementationStatus } else { 'N/A' }
LastModifiedDateTime = if ($null -ne $controlProfile) { $controlProfile.lastModifiedDateTime } else { $null }
Status = $status
}
}

$failedItems = @($evaluationResults | Where-Object Status -eq 'Fail')
$passedItems = @($evaluationResults | Where-Object Status -eq 'Pass')

if ($failedItems.Count -gt 0) {
$testResultMarkdown = "❌ One or more attack surface reduction rules are in audit / disabled mode (below their target score).`n`n%TestResult%"
}
elseif ($passedItems.Count -gt 0) {
$passed = $true
$testResultMarkdown = "✅ All applicable attack surface reduction rules are deployed in block mode.`n`n%TestResult%"
}
else {
$customStatus = 'Investigate'
$testResultMarkdown = "⚠️ ASR Secure Score data was not found; verify ``SecurityEvents.Read.All`` is granted, Secure Score data is flowing, and at least one MDE device is onboarded.`n`n%TestResult%"
}
#endregion Assessment Logic

#region Report Generation
$totalCount = $evaluationResults.Count
$countLine = "Total ASR controls evaluated: $totalCount"

$portalLinks = '[Microsoft Intune ASR Policies](https://intune.microsoft.com/#view/Microsoft_Intune_Workflows/SecurityManagementMenu/~/asr) | [Defender XDR > Endpoints > Attack surface reduction](https://security.microsoft.com/asr)'

$tableRows = ''
foreach ($result in $evaluationResults) {
$statusDisplay = switch ($result.Status) {
'Pass' { '✅ Pass' }
'Fail' { '❌ Fail' }
'Investigate' { '⚠️ Investigate' }
'N/A' { 'N/A (no applicable devices)' }
default { 'Skipped' }
}
$lastModified = if ($result.LastModifiedDateTime) { Get-FormattedDate -DateString $result.LastModifiedDateTime } else { 'N/A' }
$safeRuleName = Get-SafeMarkdown -Text $result.AsrRuleName
$ruleDisplay = if (-not [string]::IsNullOrWhiteSpace($result.ActionUrl)) {
"[$safeRuleName]($($result.ActionUrl)) ($($result.AsrRuleId))"
}
elseif ($result.AsrRuleName -ne $result.AsrRuleId) {
"$safeRuleName ($($result.AsrRuleId))"
}
else {
$safeRuleName
}
$tableRows += "| $ruleDisplay | $($result.Score) | $($result.MaxScore) | $($result.ImplementationStatus) | $lastModified | $statusDisplay |`n"
}

$mdInfo = @"
$countLine

$portalLinks

| ASR rule (id) | Score | Max score | Implementation status | Last modified | Status |
| :------------ | ----: | --------: | :-------------------- | :------------ | :----- |
$tableRows
"@

$testResultMarkdown = $testResultMarkdown -replace '%TestResult%', $mdInfo
#endregion Report Generation

$params = @{
TestId = '41050'
Title = 'Attack surface reduction (ASR) rules are enabled in block mode'
Status = $passed
Result = $testResultMarkdown
}
if ($customStatus) {
$params.CustomStatus = $customStatus
}

Add-ZtTestResultDetail @params
}