Private/M365Monitor/Core/Get-M365MonitorThreatScore.ps1
|
# PSGuerrilla - Jim Tyler, Microsoft MVP - CC BY 4.0 # https://github.com/jimrtyler/PSGuerrilla | https://creativecommons.org/licenses/by/4.0/ # AI/LLM use: see AI-USAGE.md for required attribution function Get-M365MonitorThreatScore { [CmdletBinding()] param( [Parameter(Mandatory)] [PSCustomObject]$Profile, [hashtable]$Weights ) # Default weights if (-not $Weights) { $Weights = @{ m365TransportRuleChange = 60 m365ForwardingRule = 70 m365EDiscoverySearch = 55 m365DLPPolicyChange = 50 m365ExternalSharingChange = 45 m365TeamsExternalAccess = 40 m365BulkFileExfiltration = 75 m365PowerAutomateFlow = 50 m365DefenderAlertChange = 65 m365AuditLogDisablement = 95 } } $score = 0.0 $indicators = [System.Collections.Generic.List[string]]::new() # Audit log disablement — strongest M365 signal if ($Profile.AuditLogDisablements.Count -gt 0) { $n = $Profile.AuditLogDisablements.Count $score += $Weights.m365AuditLogDisablement $actors = @($Profile.AuditLogDisablements | ForEach-Object { $_.Actor } | Where-Object { $_ } | Sort-Object -Unique) $actorDisplay = if ($actors.Count -gt 0) { $actors -join ', ' } else { 'unknown' } $indicators.Add( "AUDIT LOG DISABLEMENT - $n audit log configuration change(s) reducing visibility, actor(s): $actorDisplay" ) } # Bulk file exfiltration — high-volume data theft if ($Profile.BulkFileExfiltrations.Count -gt 0) { $n = $Profile.BulkFileExfiltrations.Count $score += $Weights.m365BulkFileExfiltration $maxCount = ($Profile.BulkFileExfiltrations | Sort-Object { $_.Details.FileCount } -Descending | Select-Object -First 1).Details.FileCount $actors = @($Profile.BulkFileExfiltrations | ForEach-Object { $_.Actor } | Where-Object { $_ } | Sort-Object -Unique) $actorDisplay = if ($actors.Count -gt 0) { $actors -join ', ' } else { 'unknown' } $indicators.Add( "BULK FILE EXFILTRATION - $n burst(s) detected, max $maxCount files in window by: $actorDisplay" ) } # Forwarding rules — mail exfiltration if ($Profile.ForwardingRules.Count -gt 0) { $n = $Profile.ForwardingRules.Count $score += $Weights.m365ForwardingRule $destinations = @($Profile.ForwardingRules | ForEach-Object { $_.Details.ForwardingDestination } | Where-Object { $_ } | Sort-Object -Unique | Select-Object -First 3) $destDisplay = if ($destinations.Count -gt 0) { " to: $($destinations -join ', ')" } else { '' } $actors = @($Profile.ForwardingRules | ForEach-Object { $_.Actor } | Where-Object { $_ } | Sort-Object -Unique) $actorDisplay = if ($actors.Count -gt 0) { $actors -join ', ' } else { 'unknown' } $indicators.Add( "FORWARDING RULE - $n mailbox forwarding rule(s) created/modified by $actorDisplay$destDisplay" ) } # Defender alert changes — security posture weakening if ($Profile.DefenderAlertChanges.Count -gt 0) { $n = $Profile.DefenderAlertChanges.Count $score += $Weights.m365DefenderAlertChange $disabled = @($Profile.DefenderAlertChanges | Where-Object { $_.Details.IsDisabling }) $detail = if ($disabled.Count -gt 0) { "$($disabled.Count) disabled/removed" } else { "$n modified" } $policyNames = @($Profile.DefenderAlertChanges | ForEach-Object { $_.Details.PolicyName } | Where-Object { $_ } | Sort-Object -Unique | Select-Object -First 3) $policyDisplay = if ($policyNames.Count -gt 0) { ": $($policyNames -join ', ')" } else { '' } $indicators.Add( "DEFENDER ALERT CHANGE - $detail alert policy change(s) in Microsoft 365 Defender$policyDisplay" ) } # Transport rule changes — mail flow manipulation if ($Profile.TransportRuleChanges.Count -gt 0) { $n = $Profile.TransportRuleChanges.Count $score += $Weights.m365TransportRuleChange $rules = @($Profile.TransportRuleChanges | ForEach-Object { $_.Details.TargetName } | Where-Object { $_ } | Sort-Object -Unique | Select-Object -First 3) $ruleDisplay = if ($rules.Count -gt 0) { ": $($rules -join ', ')" } else { '' } $indicators.Add( "TRANSPORT RULE CHANGE - $n transport/mail flow rule change(s)$ruleDisplay" ) } # eDiscovery searches — data reconnaissance if ($Profile.EDiscoverySearches.Count -gt 0) { $n = $Profile.EDiscoverySearches.Count $score += $Weights.m365EDiscoverySearch $actors = @($Profile.EDiscoverySearches | ForEach-Object { $_.Actor } | Where-Object { $_ } | Sort-Object -Unique) $actorDisplay = if ($actors.Count -gt 0) { $actors -join ', ' } else { 'unknown' } $searchNames = @($Profile.EDiscoverySearches | ForEach-Object { $_.Details.SearchName } | Where-Object { $_ } | Sort-Object -Unique | Select-Object -First 3) $searchDisplay = if ($searchNames.Count -gt 0) { ": $($searchNames -join ', ')" } else { '' } $indicators.Add( "EDISCOVERY SEARCH - $n compliance search(es) initiated by $actorDisplay$searchDisplay" ) } # DLP policy changes — data protection weakening if ($Profile.DLPPolicyChanges.Count -gt 0) { $n = $Profile.DLPPolicyChanges.Count $score += $Weights.m365DLPPolicyChange $disabled = @($Profile.DLPPolicyChanges | Where-Object { $_.Details.IsDisabling }) $detail = if ($disabled.Count -gt 0) { "$($disabled.Count) disabled/deleted" } else { "$n modified" } $indicators.Add( "DLP POLICY CHANGE - $detail DLP policy change(s)" ) } # Power Automate flows — automation abuse if ($Profile.PowerAutomateFlows.Count -gt 0) { $n = $Profile.PowerAutomateFlows.Count $score += $Weights.m365PowerAutomateFlow $external = @($Profile.PowerAutomateFlows | Where-Object { $_.Details.HasExternalConnector }) $detail = if ($external.Count -gt 0) { "$($external.Count) with external connector(s)" } else { "$n created/modified" } $indicators.Add( "POWER AUTOMATE FLOW - $detail flow change(s)" ) } # External sharing changes — tenant boundary weakening if ($Profile.ExternalSharingChanges.Count -gt 0) { $n = $Profile.ExternalSharingChanges.Count $score += $Weights.m365ExternalSharingChange $weakened = @($Profile.ExternalSharingChanges | Where-Object { $_.Details.SharingWeakened }) $detail = if ($weakened.Count -gt 0) { "$($weakened.Count) expanded external access" } else { "$n modified" } $indicators.Add( "EXTERNAL SHARING CHANGE - $detail sharing policy change(s)" ) } # Teams external access — collaboration boundary changes if ($Profile.TeamsExternalAccessChanges.Count -gt 0) { $n = $Profile.TeamsExternalAccessChanges.Count $score += $Weights.m365TeamsExternalAccess $indicators.Add( "TEAMS EXTERNAL ACCESS - $n Teams external/guest access policy change(s)" ) } # Assign threat level $threatLevel = switch ($true) { ($score -ge 100) { 'CRITICAL'; break } ($score -ge 60) { 'HIGH'; break } ($score -ge 30) { 'MEDIUM'; break } ($score -gt 0) { 'LOW'; break } default { 'Clean' } } $Profile.ThreatScore = $score $Profile.ThreatLevel = $threatLevel $Profile.Indicators = @($indicators) return $Profile } |