Private/Core/New-UserCompromiseProfile.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-UserCompromiseProfile { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Email, [hashtable[]]$LoginEvents = @(), [hashtable[]]$TokenEvents = @(), [hashtable[]]$AccountEvents = @(), [hashtable[]]$AdminEvents = @(), [hashtable[]]$DriveEvents = @(), [hashtable]$GeoData = @{}, [bool]$IsKnownCompromised = $false, [bool]$WasRemediated = $false, [hashtable]$DetectionConfig = @{}, [hashtable]$DetectionFilter = @{}, [hashtable]$PreviousDevices = @{}, [string]$InternalDomain = '' ) # Helper: check if a detection signal is enabled in the filter # If DetectionFilter is null/empty, all signals are enabled (backwards compatible) function Test-DetectionEnabled([string]$SignalKey) { if (-not $DetectionFilter -or $DetectionFilter.Count -eq 0) { return $true } return $DetectionFilter[$SignalKey] -ne $false } $profile = [PSCustomObject]@{ PSTypeName = 'PSGuerrilla.UserProfile' Email = $Email ThreatLevel = 'Clean' ThreatScore = 0.0 IsKnownCompromised = $IsKnownCompromised WasRemediated = $WasRemediated Indicators = @() KnownAttackerIpLogins = [System.Collections.Generic.List[PSCustomObject]]::new() CloudIpLogins = [System.Collections.Generic.List[PSCustomObject]]::new() ReauthFromCloud = [System.Collections.Generic.List[PSCustomObject]]::new() RiskyActions = [System.Collections.Generic.List[PSCustomObject]]::new() SuspiciousCountryLogins = [System.Collections.Generic.List[PSCustomObject]]::new() SuspiciousOAuthGrants = [System.Collections.Generic.List[PSCustomObject]]::new() ImpossibleTravel = @() ConcurrentSessions = @() UserAgentAnomalies = @() BruteForce = $null AfterHoursLogins = @() NewDevices = @() IpClassifications = @{} TotalLoginEvents = $LoginEvents.Count LoginEvents = $LoginEvents TokenEvents = $TokenEvents AccountEvents = $AccountEvents AdminEvents = $AdminEvents DriveEvents = $DriveEvents # Phase 4.1: Expanded monitoring signals AdminPrivilegeEscalations = [System.Collections.Generic.List[PSCustomObject]]::new() EmailForwardingRules = [System.Collections.Generic.List[PSCustomObject]]::new() DriveExternalShares = [System.Collections.Generic.List[PSCustomObject]]::new() BulkFileDownloads = @() HighRiskOAuthApps = [System.Collections.Generic.List[PSCustomObject]]::new() UserSuspensions = [System.Collections.Generic.List[PSCustomObject]]::new() TwoSvDisablements = [System.Collections.Generic.List[PSCustomObject]]::new() DomainWideDelegations = [System.Collections.Generic.List[PSCustomObject]]::new() WorkspaceSettingChanges = [System.Collections.Generic.List[PSCustomObject]]::new() } # Suspicious country set $suspiciousCountryCodes = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) foreach ($code in $script:SuspiciousCountries.codes) { [void]$suspiciousCountryCodes.Add($code) } # Analyze login events foreach ($event in $LoginEvents) { $ip = $event.IpAddress if (-not $ip) { continue } $ipClass = Get-CloudIpClassification -IpAddress $ip $geoCountry = if ($GeoData.ContainsKey($ip) -and $GeoData[$ip]) { $GeoData[$ip].CountryCode } else { '' } # Build enriched event object $enrichedEvent = [PSCustomObject]@{ Timestamp = $event.Timestamp User = $event.User EventName = $event.EventName IpAddress = $ip IpClass = $ipClass GeoCountry = $geoCountry Source = $event.Source Params = $event.Params } # Track IP classifications if (-not $profile.IpClassifications.ContainsKey($ip)) { $profile.IpClassifications[$ip] = @{ Class = $ipClass Country = $geoCountry Events = [System.Collections.Generic.List[string]]::new() } } $profile.IpClassifications[$ip].Events.Add($event.EventName) $loginType = $event.Params['login_type'] $eventName = $event.EventName # Signal 1: Known attacker IP if ((Test-DetectionEnabled 'knownAttackerIps') -and $ipClass -eq 'known_attacker') { $profile.KnownAttackerIpLogins.Add($enrichedEvent) } # Signal 2: Cloud provider IP login (any cloud provider or known attacker) $isCloudIp = $ipClass -and ($ipClass -eq 'known_attacker' -or $script:CloudProviderClasses.Contains($ipClass)) if ((Test-DetectionEnabled 'cloudIpLogins') -and $isCloudIp) { $profile.CloudIpLogins.Add($enrichedEvent) } # Signal 3: Reauth from cloud IP (exact attack pattern) if ((Test-DetectionEnabled 'reauthFromCloudIp') -and $isCloudIp -and $loginType -eq 'reauth') { $profile.ReauthFromCloud.Add($enrichedEvent) } # Signal 4: Risky sensitive action if ((Test-DetectionEnabled 'riskySensitiveActions') -and $eventName -eq 'risky_sensitive_action_allowed') { $profile.RiskyActions.Add($enrichedEvent) } # Signal 5: Suspicious country login if ((Test-DetectionEnabled 'suspiciousCountryLogins') -and $geoCountry -and $suspiciousCountryCodes.Contains($geoCountry)) { $profile.SuspiciousCountryLogins.Add($enrichedEvent) } } # Analyze token/OAuth events foreach ($event in $TokenEvents) { $ip = $event.IpAddress $ipClass = if ($ip) { Get-CloudIpClassification -IpAddress $ip } else { '' } $eventName = $event.EventName $enrichedEvent = [PSCustomObject]@{ Timestamp = $event.Timestamp User = $event.User EventName = $eventName IpAddress = $ip IpClass = $ipClass GeoCountry = '' Source = $event.Source Params = $event.Params } # OAuth authorize from cloud IP (any cloud provider or known attacker) $isCloudToken = $ipClass -and ($ipClass -eq 'known_attacker' -or $script:CloudProviderClasses.Contains($ipClass)) if ((Test-DetectionEnabled 'oauthFromCloudIp') -and $eventName -eq 'authorize' -and $isCloudToken) { $profile.SuspiciousOAuthGrants.Add($enrichedEvent) } } # --- New detection signals --- # Impossible travel if ((Test-DetectionEnabled 'impossibleTravel') -and $LoginEvents.Count -ge 2 -and $GeoData.Count -gt 0) { $maxSpeed = if ($DetectionConfig.impossibleTravelSpeedKmh) { $DetectionConfig.impossibleTravelSpeedKmh } else { 900 } $profile.ImpossibleTravel = @(Test-ImpossibleTravel -LoginEvents $LoginEvents -GeoData $GeoData -MaxSpeedKmh $maxSpeed) } # Concurrent sessions if ((Test-DetectionEnabled 'concurrentSessions') -and $LoginEvents.Count -ge 2) { $windowMin = if ($DetectionConfig.concurrentSessionWindowMinutes) { $DetectionConfig.concurrentSessionWindowMinutes } else { 5 } $profile.ConcurrentSessions = @(Test-ConcurrentSessions -LoginEvents $LoginEvents -WindowMinutes $windowMin) } # User agent anomalies if ((Test-DetectionEnabled 'userAgentAnomalies') -and $LoginEvents.Count -gt 0) { $profile.UserAgentAnomalies = @(Test-UserAgentAnomaly -LoginEvents $LoginEvents) } # Brute force if ((Test-DetectionEnabled 'bruteForce') -and $LoginEvents.Count -gt 0) { $failThreshold = if ($DetectionConfig.bruteForceFailureThreshold) { $DetectionConfig.bruteForceFailureThreshold } else { 5 } $failWindow = if ($DetectionConfig.bruteForceWindowMinutes) { $DetectionConfig.bruteForceWindowMinutes } else { 10 } $profile.BruteForce = Test-BruteForce -LoginEvents $LoginEvents -FailureThreshold $failThreshold -WindowMinutes $failWindow } # After-hours logins if ((Test-DetectionEnabled 'afterHoursLogins') -and $LoginEvents.Count -gt 0) { $bhStart = if ($DetectionConfig.businessHoursStart) { $DetectionConfig.businessHoursStart } else { 7 } $bhEnd = if ($DetectionConfig.businessHoursEnd) { $DetectionConfig.businessHoursEnd } else { 19 } $bhTz = if ($DetectionConfig.businessHoursTimezone) { $DetectionConfig.businessHoursTimezone } else { 'UTC' } $bhDays = if ($DetectionConfig.businessDays) { $DetectionConfig.businessDays } else { @('Monday','Tuesday','Wednesday','Thursday','Friday') } $profile.AfterHoursLogins = @(Test-AfterHoursLogin -LoginEvents $LoginEvents ` -BusinessHoursStart $bhStart -BusinessHoursEnd $bhEnd -Timezone $bhTz -BusinessDays $bhDays) } # New device detection if ((Test-DetectionEnabled 'newDeviceDetection') -and $LoginEvents.Count -gt 0) { $profile.NewDevices = @(Test-NewDevice -LoginEvents $LoginEvents -PreviousDevices $PreviousDevices) } # --- Phase 4.1: Expanded Google Workspace detection signals --- # Admin privilege escalation if ((Test-DetectionEnabled 'adminPrivilegeEscalations') -and $AdminEvents.Count -gt 0) { $profile.AdminPrivilegeEscalations = [System.Collections.Generic.List[PSCustomObject]]::new() foreach ($item in (Test-AdminAction -AdminEvents $AdminEvents)) { $profile.AdminPrivilegeEscalations.Add($item) } } # Email forwarding rule creation if ((Test-DetectionEnabled 'emailForwardingRules') -and $AdminEvents.Count -gt 0) { $profile.EmailForwardingRules = [System.Collections.Generic.List[PSCustomObject]]::new() foreach ($item in (Test-EmailForwarding -AdminEvents $AdminEvents)) { $profile.EmailForwardingRules.Add($item) } } # Drive external sharing if ((Test-DetectionEnabled 'driveExternalSharing') -and $DriveEvents.Count -gt 0) { $profile.DriveExternalShares = [System.Collections.Generic.List[PSCustomObject]]::new() foreach ($item in (Test-DriveExternalSharing -DriveEvents $DriveEvents -InternalDomain $InternalDomain)) { $profile.DriveExternalShares.Add($item) } } # Bulk file download detection if ((Test-DetectionEnabled 'bulkFileDownloads') -and $DriveEvents.Count -gt 0) { $bulkThreshold = if ($DetectionConfig.bulkDownloadThreshold) { $DetectionConfig.bulkDownloadThreshold } else { 50 } $bulkWindow = if ($DetectionConfig.bulkDownloadWindowMinutes) { $DetectionConfig.bulkDownloadWindowMinutes } else { 10 } $profile.BulkFileDownloads = @(Test-BulkFileDownload -DriveEvents $DriveEvents -Threshold $bulkThreshold -WindowMinutes $bulkWindow) } # High-risk OAuth app detection if ((Test-DetectionEnabled 'highRiskOAuthApps') -and $TokenEvents.Count -gt 0) { $highRiskPatterns = if ($DetectionConfig.highRiskOAuthAppPatterns) { $DetectionConfig.highRiskOAuthAppPatterns } else { @() } $profile.HighRiskOAuthApps = [System.Collections.Generic.List[PSCustomObject]]::new() foreach ($item in (Test-HighRiskOAuthApp -TokenEvents $TokenEvents -HighRiskPatterns $highRiskPatterns)) { $profile.HighRiskOAuthApps.Add($item) } } # User suspension/deletion monitoring if ((Test-DetectionEnabled 'userSuspensions') -and $AdminEvents.Count -gt 0) { $profile.UserSuspensions = [System.Collections.Generic.List[PSCustomObject]]::new() foreach ($item in (Test-UserSuspension -AdminEvents $AdminEvents)) { $profile.UserSuspensions.Add($item) } } # 2SV disablement monitoring if ((Test-DetectionEnabled 'twoStepDisablement') -and $AdminEvents.Count -gt 0) { $profile.TwoSvDisablements = [System.Collections.Generic.List[PSCustomObject]]::new() foreach ($item in (Test-2svDisablement -AdminEvents $AdminEvents)) { $profile.TwoSvDisablements.Add($item) } } # Domain-wide delegation monitoring if ((Test-DetectionEnabled 'domainWideDelegation') -and $AdminEvents.Count -gt 0) { $profile.DomainWideDelegations = [System.Collections.Generic.List[PSCustomObject]]::new() foreach ($item in (Test-DomainWideDelegation -AdminEvents $AdminEvents)) { $profile.DomainWideDelegations.Add($item) } } # Workspace setting change monitoring if ((Test-DetectionEnabled 'workspaceSettingChanges') -and $AdminEvents.Count -gt 0) { $profile.WorkspaceSettingChanges = [System.Collections.Generic.List[PSCustomObject]]::new() foreach ($item in (Test-WorkspaceSettingChange -AdminEvents $AdminEvents)) { $profile.WorkspaceSettingChanges.Add($item) } } # Score the profile $profile = Get-ThreatScore -Profile $profile return $profile } |