Private/M365Monitor/Core/New-M365ChangeProfile.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 New-M365ChangeProfile { <# .SYNOPSIS Builds an M365 change profile from categorized audit events by running all detection functions. .DESCRIPTION Analyzes categorized M365 audit events through each Test-M365* detection function, collects all flagged changes, and returns a unified change profile with threat indicators. .PARAMETER CategorizedEvents Hashtable of categorized events from Get-M365AuditEvents. .PARAMETER DetectionConfig Hashtable of detection configuration overrides (thresholds, patterns, etc.). #> [CmdletBinding()] param( [Parameter(Mandatory)] [hashtable]$CategorizedEvents, [hashtable]$DetectionConfig = @{}, [hashtable]$DetectionFilter = @{} ) # Helper: check if a detection signal is enabled in the filter function Test-DetectionEnabled([string]$SignalKey) { if (-not $DetectionFilter -or $DetectionFilter.Count -eq 0) { return $true } return $DetectionFilter[$SignalKey] -ne $false } $profile = [PSCustomObject]@{ PSTypeName = 'PSGuerrilla.M365ChangeProfile' TenantId = '' ThreatLevel = 'Clean' ThreatScore = 0.0 Indicators = @() TransportRuleChanges = [System.Collections.Generic.List[PSCustomObject]]::new() ForwardingRules = [System.Collections.Generic.List[PSCustomObject]]::new() EDiscoverySearches = [System.Collections.Generic.List[PSCustomObject]]::new() DLPPolicyChanges = [System.Collections.Generic.List[PSCustomObject]]::new() ExternalSharingChanges = [System.Collections.Generic.List[PSCustomObject]]::new() TeamsExternalAccessChanges = [System.Collections.Generic.List[PSCustomObject]]::new() BulkFileExfiltrations = [System.Collections.Generic.List[PSCustomObject]]::new() PowerAutomateFlows = [System.Collections.Generic.List[PSCustomObject]]::new() DefenderAlertChanges = [System.Collections.Generic.List[PSCustomObject]]::new() AuditLogDisablements = [System.Collections.Generic.List[PSCustomObject]]::new() SecurityAlerts = @() } # ── Transport Rule Changes ─────────────────────────────────────────── if ((Test-DetectionEnabled 'transportRuleChanges') -and $CategorizedEvents.ExchangeTransportRules -and $CategorizedEvents.ExchangeTransportRules.Count -gt 0) { $detected = Test-M365TransportRuleChange -Events @($CategorizedEvents.ExchangeTransportRules) foreach ($item in $detected) { $profile.TransportRuleChanges.Add($item) } } # ── Forwarding Rules ───────────────────────────────────────────────── if ((Test-DetectionEnabled 'forwardingRules') -and $CategorizedEvents.ExchangeForwardingRules -and $CategorizedEvents.ExchangeForwardingRules.Count -gt 0) { $detected = Test-M365ForwardingRule -Events @($CategorizedEvents.ExchangeForwardingRules) foreach ($item in $detected) { $profile.ForwardingRules.Add($item) } } # ── eDiscovery Searches ────────────────────────────────────────────── if ((Test-DetectionEnabled 'eDiscoverySearches') -and $CategorizedEvents.EDiscoverySearches -and $CategorizedEvents.EDiscoverySearches.Count -gt 0) { $detected = Test-M365EDiscoverySearch -Events @($CategorizedEvents.EDiscoverySearches) foreach ($item in $detected) { $profile.EDiscoverySearches.Add($item) } } # ── DLP Policy Changes ─────────────────────────────────────────────── if ((Test-DetectionEnabled 'dlpPolicyChanges') -and $CategorizedEvents.DLPPolicyChanges -and $CategorizedEvents.DLPPolicyChanges.Count -gt 0) { $detected = Test-M365DLPPolicyChange -Events @($CategorizedEvents.DLPPolicyChanges) foreach ($item in $detected) { $profile.DLPPolicyChanges.Add($item) } } # ── External Sharing Changes ───────────────────────────────────────── if ((Test-DetectionEnabled 'externalSharingChanges') -and $CategorizedEvents.SharePointSharingChanges -and $CategorizedEvents.SharePointSharingChanges.Count -gt 0) { $detected = Test-M365ExternalSharingChange -Events @($CategorizedEvents.SharePointSharingChanges) foreach ($item in $detected) { $profile.ExternalSharingChanges.Add($item) } } # ── Teams External Access Changes ──────────────────────────────────── if ((Test-DetectionEnabled 'teamsExternalAccess') -and $CategorizedEvents.TeamsAccessChanges -and $CategorizedEvents.TeamsAccessChanges.Count -gt 0) { $detected = Test-M365TeamsExternalAccess -Events @($CategorizedEvents.TeamsAccessChanges) foreach ($item in $detected) { $profile.TeamsExternalAccessChanges.Add($item) } } # ── Bulk File Exfiltration ─────────────────────────────────────────── if ((Test-DetectionEnabled 'bulkFileExfiltration') -and $CategorizedEvents.SharePointFileOperations -and $CategorizedEvents.SharePointFileOperations.Count -gt 0) { $threshold = if ($DetectionConfig.bulkExfiltrationThreshold) { $DetectionConfig.bulkExfiltrationThreshold } else { 100 } $windowMin = if ($DetectionConfig.bulkExfiltrationWindowMinutes) { $DetectionConfig.bulkExfiltrationWindowMinutes } else { 30 } $detected = Test-M365BulkFileExfiltration ` -Events @($CategorizedEvents.SharePointFileOperations) ` -Threshold $threshold ` -WindowMinutes $windowMin foreach ($item in $detected) { $profile.BulkFileExfiltrations.Add($item) } } # ── Power Automate Flows ───────────────────────────────────────────── if ((Test-DetectionEnabled 'powerAutomateFlows') -and $CategorizedEvents.PowerPlatformFlows -and $CategorizedEvents.PowerPlatformFlows.Count -gt 0) { $extPatterns = if ($DetectionConfig.externalConnectorPatterns) { $DetectionConfig.externalConnectorPatterns } else { @() } $detected = Test-M365PowerAutomateFlow ` -Events @($CategorizedEvents.PowerPlatformFlows) ` -ExternalConnectorPatterns $extPatterns foreach ($item in $detected) { $profile.PowerAutomateFlows.Add($item) } } # ── Defender Alert Changes ─────────────────────────────────────────── if ((Test-DetectionEnabled 'defenderAlertChanges') -and $CategorizedEvents.DefenderAlertChanges -and $CategorizedEvents.DefenderAlertChanges.Count -gt 0) { $detected = Test-M365DefenderAlertChange -Events @($CategorizedEvents.DefenderAlertChanges) foreach ($item in $detected) { $profile.DefenderAlertChanges.Add($item) } } # ── Audit Log Disablements ─────────────────────────────────────────── if ((Test-DetectionEnabled 'auditLogDisablement') -and $CategorizedEvents.AuditLogChanges -and $CategorizedEvents.AuditLogChanges.Count -gt 0) { $detected = Test-M365AuditLogDisablement -Events @($CategorizedEvents.AuditLogChanges) foreach ($item in $detected) { $profile.AuditLogDisablements.Add($item) } } # ── Attach security alerts as-is ───────────────────────────────────── if ($CategorizedEvents.SecurityAlerts -and $CategorizedEvents.SecurityAlerts.Count -gt 0) { $profile.SecurityAlerts = @($CategorizedEvents.SecurityAlerts) } # ── Score the profile ──────────────────────────────────────────────── $profile = Get-M365MonitorThreatScore -Profile $profile return $profile } |