Private/ADMonitor/Detections/Test-ADLdapQueryAnomaly.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 Test-ADLdapQueryAnomaly { [CmdletBinding()] param( [array]$RecentlyChanged = @() ) # LDAP query anomaly detection via baseline comparison # In a baseline comparison model, we detect high-volume enumeration artifacts: # - Unusually high number of recently-changed objects that suggest bulk LDAP enumeration tools # modified metadata (e.g., lastLogon timestamps updated by enumeration) # - Objects that appear in recently-changed with whenChanged very close together, # which can indicate automated tooling sweeping through AD $indicators = [System.Collections.Generic.List[PSCustomObject]]::new() if ($RecentlyChanged.Count -eq 0) { return @() } # Detect burst patterns in recently changed objects # Group changes by minute to find bursts $changesByMinute = @{} foreach ($obj in $RecentlyChanged) { if (-not $obj.whenChanged) { continue } try { $when = [datetime]::Parse($obj.whenChanged) $minuteKey = $when.ToString('yyyyMMddHHmm') if (-not $changesByMinute.ContainsKey($minuteKey)) { $changesByMinute[$minuteKey] = 0 } $changesByMinute[$minuteKey]++ } catch { } } # Flag if any single minute has more than 100 changes (potential enumeration) $burstMinutes = @($changesByMinute.GetEnumerator() | Where-Object { $_.Value -gt 100 }) if ($burstMinutes.Count -eq 0) { return @() } $totalBurstChanges = ($burstMinutes | Measure-Object -Property Value -Sum).Sum $peakMinute = ($burstMinutes | Sort-Object -Property Value -Descending | Select-Object -First 1) $detectionId = "adLdapQueryAnomaly_$([datetime]::UtcNow.ToString('yyyyMMddHHmm'))" $indicators.Add([PSCustomObject]@{ DetectionId = $detectionId DetectionName = 'LDAP Enumeration Burst Detected' DetectionType = 'adLdapQueryAnomaly' Description = "LDAP QUERY ANOMALY - $totalBurstChanges object changes detected in $($burstMinutes.Count) burst minute(s). Peak: $($peakMinute.Value) changes at $($peakMinute.Key). This pattern is consistent with automated AD enumeration tools (BloodHound, ADRecon, etc.)." Details = @{ BurstMinutes = $burstMinutes.Count TotalBurstChanges = $totalBurstChanges PeakMinute = $peakMinute.Key PeakCount = $peakMinute.Value } Count = $burstMinutes.Count Score = 0 Severity = '' }) return @($indicators) } |