diff --git a/.gitignore b/.gitignore index 0d24381..61b077c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ - -# ignore the settings folder and files for VSCode and PSS +# ignore the settings folder and files for VSCode and PSS .vscode/* *.psproj *TempPoint* @@ -19,4 +18,11 @@ Hawk/Hawk.psproj TestResults/* # ignore the publishing Directory -publish/* \ No newline at end of file +publish/* + +# Ignore all .csv, .json, .docx, and .xlsx files +*.csv +*.json +*.docx +*.doc +*.xlsx diff --git a/Hawk/Hawk.psd1 b/Hawk/Hawk.psd1 index 042e478..685b134 100644 --- a/Hawk/Hawk.psd1 +++ b/Hawk/Hawk.psd1 @@ -55,7 +55,6 @@ 'Get-HawkTenantInboxRules', 'Get-HawkTenantConsentGrant', 'Get-HawkTenantRBACChange', - 'Get-HawkTenantAzureAppAuditLog', 'Get-HawkUserAuthHistory', 'Get-HawkUserConfiguration', @@ -63,7 +62,11 @@ 'Get-HawkUserInboxRule', 'Get-HawkUserMailboxAuditing', 'Search-HawkTenantActivityByIP', - 'Search-HawkTenantEXOAuditLog', + 'Get-HawkTenantAdminInboxRuleCreation', + 'Get-HawkTenantAdminInboxRuleModification', + 'Get-HawkTenantAdminInboxRuleRemoval', + 'Get-HawkTenantAdminMailboxPermissionChange', + 'Get-HawkTenantAdminEmailForwardingChange', 'Show-HawkHelp', 'Start-HawkTenantInvestigation', 'Start-HawkUserInvestigation', diff --git a/Hawk/Resolving IP Locations b/Hawk/Resolving IP Locations deleted file mode 100644 index 46b134b..0000000 --- a/Hawk/Resolving IP Locations +++ /dev/null @@ -1 +0,0 @@ -ÿþ \ No newline at end of file diff --git a/Hawk/changelog.md b/Hawk/changelog.md index 34f7fb4..1350ffa 100644 --- a/Hawk/changelog.md +++ b/Hawk/changelog.md @@ -60,18 +60,21 @@ - Removed Robust Cloud Command from build as it was not being used in the code base anymore - Updated PowerShell API key in GitHub to fix build.yml issue where the Hawk would not publish to gallery on merge to main -## 3.2.3 (2024-12-09) - -- **Migration to Microsoft Graph**: Replaced all AzureAD functionality with Microsoft Graph commands, including updates to functions like `Get-HawkTenantAppAndSPNCredentialDetails` (now using `Get-MgServicePrincipal` and `Get-MgApplication`). - -- **Directory Role Management**: Updated `Get-HawkTenantAZAdmins` to use Microsoft Graph (`Get-MgRoleDefinition` and `Get-MgRoleAssignment`), renamed to `Get-HawkTenantEntraIDAdmin`, and enhanced output for better role tracking. - -- **Consent Grant Updates**: Migrated `Get-HawkTenantConsentGrant` to Graph commands (`Get-MgOauth2PermissionGrant` and `Get-MgServicePrincipalAppRoleAssignment`), ensuring consistent output and backward compatibility. - -- **Removed AzureAD Dependencies**: Eliminated AzureAD references in the Hawk.psd1 manifest and removed the deprecated `Test-AzureADConnection.ps1`. Updated manifest to rely solely on Microsoft Graph modules (v2.25.0). - -- **Simplified Authentication**: Streamlined Graph API connections by removing unnecessary commands like `Select-MgProfile` and improving `Test-GraphConnection` for default behaviors. - -- **Improved Logging and Naming**: Standardized log outputs (e.g., `AzureADUsers` to `EntraIDUsers`) and aligned function outputs with updated naming conventions. - -- This release completes the migration to Microsoft Graph, fully deprecating AzureAD and aligning Hawk with modern Microsoft standards. +## 3.2.3 (2024-12-20) + +- Replaced all AzureAD functionality with Microsoft Graph commands. +- Updated Get-HawkTenantAZAdmins to use Microsoft Graph. +- Migrated Get-HawkTenantConsentGrant to Graph commands. +- Removed AzureAD Dependencies: Eliminated AzureAD references in the Hawk.psd1 manifest and removed the deprecated Test-AzureADConnection.ps1. +- Simplified Authentication: Streamlined Graph API connections by removing unnecessary commands like Select-MgProfile and improving Test-GraphConnection. +- Improved Logging and Naming: Standardized log outputs (e.g., AzureADUsers to EntraIDUsers) and aligned function outputs with updated naming conventions. +- Removed Search-HawkTenantEXOAuditLog as it was deprecated and replaced with modern, modular functions, as listed below: +- Added Get-HawkTenantAdminInboxRuleModification, which retrieves audit log entries for inbox rules that were historically modified within the tenant. +- Added Get-HawkTenantAdminEmailForwardingChange, which retrieves audit log entries for email forwarding changes made within the tenant. +- Added Get-HawkTenantAdminInboxRuleCreation, which retrieves audit log entries for inbox rules that were historically created within the tenant. +- Added Get-HawkTenantAdminInboxRuleRemoval, which retrieves audit log entries for inbox rules that were removed within the tenant. +- Added Get-HawkTenantAdminMailboxPermissionChange, which retrieves audit log entries for mailbox permission changes within the tenant. +- Added internal helper function Test-SuspiciousInboxRule, which detects suspicious inbox rule patterns. +- Removed ability to detect RBAC Application Impersonation as this is being deprecated / removed in FEB 2025. +- Updated Out-Log file, adding -Information parameter for tagging prompts with INFO for status updates. +- Updated Out-Log file, modifying -Notice parameter for tagging prompts with INVESTIGATE in brackets instead of asterisks, for uniformity purposes. diff --git a/Hawk/functions/Tenant/Get-HawkTenantAdminEmailForwardingChange.ps1 b/Hawk/functions/Tenant/Get-HawkTenantAdminEmailForwardingChange.ps1 new file mode 100644 index 0000000..b22eeb8 --- /dev/null +++ b/Hawk/functions/Tenant/Get-HawkTenantAdminEmailForwardingChange.ps1 @@ -0,0 +1,216 @@ +Function Get-HawkTenantAdminEmailForwardingChange { + <# + .SYNOPSIS + Retrieves audit log entries for email forwarding changes made within the tenant. + + .DESCRIPTION + This function queries the Microsoft 365 Unified Audit Log for events related to email + forwarding configuration changes (Set-Mailbox with forwarding parameters). It focuses on + tracking when and by whom forwarding rules were added or modified, helping identify potential + unauthorized data exfiltration attempts. + + Key points: + - Monitors changes to both ForwardingAddress and ForwardingSMTPAddress settings + - Resolves recipient information for ForwardingAddress values + - Flags all forwarding changes for review as potential security concerns + - Provides historical context for forwarding configuration changes + + .OUTPUTS + File: Simple_Forwarding_Changes.csv/.json + Path: \Tenant + Description: Simplified view of forwarding configuration changes. + + File: Forwarding_Changes.csv/.json + Path: \Tenant + Description: Detailed audit log data for forwarding changes. + + File: Forwarding_Recipients.csv/.json + Path: \Tenant + Description: List of unique forwarding destinations configured. + + .EXAMPLE + Get-HawkTenantAdminEmailForwardingChange + + Retrieves all email forwarding configuration changes from the audit logs within the specified + search window. + #> + [CmdletBinding()] + param() + + # Test the Exchange Online connection to ensure the environment is ready for operations. + Test-EXOConnection + # Log the execution of the function for audit and telemetry purposes. + Send-AIEvent -Event "CmdRun" + + # Initialize timing variables for status updates + $startTime = Get-Date + $lastUpdate = $startTime + + # Log the start of the analysis process for email forwarding configuration changes. + Out-LogFile "Analyzing email forwarding configuration changes from audit logs" -Action + + # Ensure the tenant-specific folder exists to store output files. If not, create it. + $TenantPath = Join-Path -Path $Hawk.FilePath -ChildPath "Tenant" + if (-not (Test-Path -Path $TenantPath)) { + New-Item -Path $TenantPath -ItemType Directory -Force | Out-Null + } + + try { + # Define both operations and broader search terms to cast a wider net. + $searchCommand = @" +Search-UnifiedAuditLog -RecordType ExchangeAdmin -Operations @( + 'Set-Mailbox', + 'Set-MailUser', + 'Set-RemoteMailbox', + 'Enable-RemoteMailbox' +) +"@ + + # Fetch all specified operations from the audit log + [array]$AllMailboxChanges = Get-AllUnifiedAuditLogEntry -UnifiedSearch $searchCommand + + # Log search completion time + Out-LogFile "Unified Audit Log search completed" -Information + + Out-LogFile "Filtering results for forwarding changes..." -Action + + # Enhanced filtering to catch more types of forwarding changes + [array]$ForwardingChanges = $AllMailboxChanges | Where-Object { + $auditData = $_.AuditData | ConvertFrom-Json + $parameters = $auditData.Parameters + ($parameters | Where-Object { + $_.Name -in @( + 'ForwardingAddress', + 'ForwardingSMTPAddress', + 'ExternalEmailAddress', + 'PrimarySmtpAddress', + 'RedirectTo', # Added from other LLM suggestion + 'DeliverToMailboxAndForward', # Corrected parameter name + 'DeliverToAndForward' # Alternative parameter name + ) -or + # Check for parameter changes enabling forwarding + ($_.Name -eq 'DeliverToMailboxAndForward' -and $_.Value -eq 'True') -or + ($_.Name -eq 'DeliverToAndForward' -and $_.Value -eq 'True') + }) + } + + Out-LogFile "Completed filtering for forwarding changes" -Information + + if ($ForwardingChanges.Count -gt 0) { + # Log the number of forwarding configuration changes found. + Out-LogFile ("Found " + $ForwardingChanges.Count + " change(s) to user email forwarding") -Information + + # Write raw JSON data for detailed reference and potential troubleshooting. + $RawJsonPath = Join-Path -Path $TenantPath -ChildPath "Forwarding_Changes_Raw.json" + $ForwardingChanges | Select-Object -ExpandProperty AuditData | Out-File -FilePath $RawJsonPath + + # Parse the audit data into a simpler format for further processing and output. + $ParsedChanges = $ForwardingChanges | Get-SimpleUnifiedAuditLog + if ($ParsedChanges) { + # Write the simplified data for quick analysis and review. + $ParsedChanges | Out-MultipleFileType -FilePrefix "Simple_Forwarding_Changes" -csv -json -Notice + + # Write the full audit log data for comprehensive records. + $ForwardingChanges | Out-MultipleFileType -FilePrefix "Forwarding_Changes" -csv -json -Notice + + # Initialize an array to store processed forwarding destination data. + $ForwardingDestinations = @() + + Out-LogFile "Beginning detailed analysis of forwarding changes..." -Action + foreach ($change in $ParsedChanges) { + # Add a status update every 30 seconds + $currentTime = Get-Date + if (($currentTime - $lastUpdate).TotalSeconds -ge 30) { + Out-LogFile "Processing forwarding changes... ($($ForwardingDestinations.Count) destinations found so far)" -Action + $lastUpdate = $currentTime + } + + $targetUser = $change.ObjectId + + # Process ForwardingSMTPAddress changes if detected in the audit log. + if ($change.Parameters -match "ForwardingSMTPAddress") { + $smtpAddress = ($change.Parameters | Select-String -Pattern "ForwardingSMTPAddress:\s*([^,]+)").Matches.Groups[1].Value + if ($smtpAddress) { + # Add the SMTP forwarding configuration to the destinations array. + $ForwardingDestinations += [PSCustomObject]@{ + UserModified = $targetUser + TargetSMTPAddress = $smtpAddress.Split(":")[-1].Trim() # Remove "SMTP:" prefix if present. + ChangeType = "SMTP Forwarding" + ModifiedBy = $change.UserId + ModifiedTime = $change.CreationTime + } + } + } + + # Process ForwardingAddress changes if detected in the audit log. + if ($change.Parameters -match "ForwardingAddress") { + $forwardingAddress = ($change.Parameters | Select-String -Pattern "ForwardingAddress:\s*([^,]+)").Matches.Groups[1].Value + if ($forwardingAddress) { + try { + # Attempt to resolve the recipient details from Exchange Online. + $recipient = Get-EXORecipient $forwardingAddress -ErrorAction Stop + + # Determine the recipient's type and extract the appropriate address. + $targetAddress = switch ($recipient.RecipientType) { + "MailContact" { $recipient.ExternalEmailAddress.Split(":")[-1] } + default { $recipient.PrimarySmtpAddress } + } + + # Add the recipient forwarding configuration to the destinations array. + $ForwardingDestinations += [PSCustomObject]@{ + UserModified = $targetUser + TargetSMTPAddress = $targetAddress + ChangeType = "Recipient Forwarding" + ModifiedBy = $change.UserId + ModifiedTime = $change.CreationTime + } + } + catch { + # Log a warning if the recipient cannot be resolved. + Out-LogFile "Unable to resolve forwarding recipient: $forwardingAddress" -Notice + # Add an unresolved entry for transparency in the output. + $ForwardingDestinations += [PSCustomObject]@{ + UserModified = $targetUser + TargetSMTPAddress = "UNRESOLVED:$forwardingAddress" + ChangeType = "Recipient Forwarding (Unresolved)" + ModifiedBy = $change.UserId + ModifiedTime = $change.CreationTime + } + } + } + } + } + + + Out-LogFile "Completed processing forwarding changes" -Information + + if ($ForwardingDestinations.Count -gt 0) { + # Log the total number of forwarding destinations detected. + Out-LogFile ("Found " + $ForwardingDestinations.Count + " forwarding destinations configured") -Information + # Write the forwarding destinations data to files for review. + $ForwardingDestinations | Out-MultipleFileType -FilePrefix "Forwarding_Recipients" -csv -json -Notice + + # Log details about each forwarding destination for detailed auditing. + foreach ($dest in $ForwardingDestinations) { + Out-LogFile "Forwarding configured: $($dest.UserModified) -> $($dest.TargetSMTPAddress) ($($dest.ChangeType)) by $($dest.ModifiedBy) at $($dest.ModifiedTime)" -Notice + } + } + } + else { + # Log a warning if the parsing of audit data fails. + Out-LogFile "Error: Failed to parse forwarding change audit data" -Notice + } + } + else { + # Log a message if no forwarding changes are found in the logs. + Out-LogFile "No forwarding changes found in filtered results" -Information + Out-LogFile "Retrieved $($AllMailboxChanges.Count) total operations, but none involved forwarding changes" -Information + } + } + catch { + # Log an error if the analysis encounters an exception. + Out-LogFile "Error analyzing email forwarding changes: $($_.Exception.Message)" -Notice + Write-Error -ErrorRecord $_ -ErrorAction Continue + } +} + diff --git a/Hawk/functions/Tenant/Get-HawkTenantAdminInboxRuleCreation.ps1 b/Hawk/functions/Tenant/Get-HawkTenantAdminInboxRuleCreation.ps1 new file mode 100644 index 0000000..5d3b417 --- /dev/null +++ b/Hawk/functions/Tenant/Get-HawkTenantAdminInboxRuleCreation.ps1 @@ -0,0 +1,119 @@ +Function Get-HawkTenantAdminInboxRuleCreation { + <# + .SYNOPSIS + Retrieves audit log entries for inbox rules that were historically created within the tenant. + + .DESCRIPTION + This function queries the Microsoft 365 Unified Audit Log for events classified as inbox + rule creation (New-InboxRule). It focuses on historical record-keeping and identifying + potentially suspicious rules that were created. The logged events do not indicate the + specific method or interface used to create the rules. + + Key points: + - Displays creation events for inbox rules, including who created them and when. + - Flags created rules that appear suspicious (e.g., rules that forward externally, delete + messages, or filter based on suspicious keywords). + - Does not confirm whether the rules are currently active or still exist. + + For current, active rules, use Get-HawkTenantInboxRules. + + .OUTPUTS + File: Simple_Admin_Inbox_Rules_Creation.csv/.json + Path: \Tenant + Description: Simplified view of created inbox rule events. + + File: Admin_Inbox_Rules_Creation.csv/.json + Path: \Tenant + Description: Detailed audit log data for created inbox rules. + + File: _Investigate_Admin_Inbox_Rules_Creation.csv/.json + Path: \Tenant + Description: A subset of historically created rules flagged as suspicious. + + File: Investigate_Admin_Inbox_Rules_Creation_Raw.json + Path: \Tenant + Description: Raw audit data for suspicious created rules. + .EXAMPLE + Get-HawkTenantAdminInboxRuleCreation + + Retrieves events for all admin inbox rules created and available within the audit logs within the configured search window. + + Remarks: This basic example pulls all inbox rule creations from the audit log and analyzes them for + suspicious patterns. Output files will be created in the configured Hawk output directory under + the Tenant subfolder. + #> + [CmdletBinding()] + param() + + Test-EXOConnection + Send-AIEvent -Event "CmdRun" + + Out-LogFile "Analyzing admin inbox rule creation from audit logs" -Action + + # Create tenant folder if it doesn't exist + $TenantPath = Join-Path -Path $Hawk.FilePath -ChildPath "Tenant" + if (-not (Test-Path -Path $TenantPath)) { + New-Item -Path $TenantPath -ItemType Directory -Force | Out-Null + } + + try { + # Search for new inbox rules + Out-LogFile "Searching audit logs for inbox rule creation events" -Action + $searchCommand = "Search-UnifiedAuditLog -RecordType ExchangeAdmin -Operations 'New-InboxRule'" + [array]$NewInboxRules = Get-AllUnifiedAuditLogEntry -UnifiedSearch $searchCommand + + if ($NewInboxRules.Count -gt 0) { + Out-LogFile ("Found " + $NewInboxRules.Count + " admin inbox rule changes in audit logs") -Information + + # Write raw audit data with action flag + $RawJsonPath = Join-Path -Path $TenantPath -ChildPath "Admin_Inbox_Rules_Creation_Raw.json" + Out-LogFile "Writing raw audit data to: $RawJsonPath" -Action + $NewInboxRules | Select-Object -ExpandProperty AuditData | Out-File -FilePath $RawJsonPath + + # Process and output the results + $ParsedRules = $NewInboxRules | Get-SimpleUnifiedAuditLog + if ($ParsedRules) { + Out-LogFile "Writing parsed admin inbox rule creation data" -Action + $ParsedRules | Out-MultipleFileType -FilePrefix "Simple_Admin_Inbox_Rules_Creation" -csv -json + $NewInboxRules | Out-MultipleFileType -FilePrefix "Admin_Inbox_Rules_Creation" -csv -json + + # Check for suspicious rules using the helper function + $SuspiciousRules = $ParsedRules | Where-Object { + $reasons = @() + Test-SuspiciousInboxRule -Rule $_ -Reasons ([ref]$reasons) + } + + if ($SuspiciousRules) { + Out-LogFile "Found suspicious admin inbox rule creation requiring investigation" -Notice + + Out-LogFile "Writing suspicious rule creation data" -Action + $SuspiciousRules | Out-MultipleFileType -FilePrefix "_Investigate_Admin_Inbox_Rules_Creation" -csv -json -Notice + + # Write raw data for suspicious rules with action flag + $RawSuspiciousPath = Join-Path -Path $TenantPath -ChildPath "Investigate_Admin_Inbox_Rules_Creation_Raw.json" + Out-LogFile "Writing raw suspicious rule data to: $RawSuspiciousPath" -Action + $SuspiciousRules | ConvertTo-Json -Depth 10 | Out-File -FilePath $RawSuspiciousPath + + # Log details about why each rule was flagged + foreach ($rule in $SuspiciousRules) { + $reasons = @() + if (Test-SuspiciousInboxRule -Rule $rule -Reasons ([ref]$reasons)) { + Out-LogFile "Found suspicious rule creation: '$($rule.Param_Name)' created by $($rule.UserId) at $($rule.CreationTime)" -Notice + Out-LogFile "Reasons for investigation: $($reasons -join '; ')" -Notice + } + } + } + } + else { + Out-LogFile "Error: Failed to parse inbox rule audit data" -Notice + } + } + else { + Out-LogFile "No admin inbox rule creation events found in audit logs" + } + } + catch { + Out-LogFile "Error analyzing admin inbox rule creation: $($_.Exception.Message)" -Notice + Write-Error -ErrorRecord $_ -ErrorAction Continue + } +} \ No newline at end of file diff --git a/Hawk/functions/Tenant/Get-HawkTenantAdminInboxRuleModification.ps1 b/Hawk/functions/Tenant/Get-HawkTenantAdminInboxRuleModification.ps1 new file mode 100644 index 0000000..74257e8 --- /dev/null +++ b/Hawk/functions/Tenant/Get-HawkTenantAdminInboxRuleModification.ps1 @@ -0,0 +1,122 @@ +Function Get-HawkTenantAdminInboxRuleModification { + <# + .SYNOPSIS + Retrieves audit log entries for inbox rules that were historically modified within the tenant. + + .DESCRIPTION + This function queries the Microsoft 365 Unified Audit Logs for events classified as + inbox rule modification (Set-InboxRule). It focuses on past changes to existing rules, + helping identify suspicious modifications (e.g., forwarding to external addresses, + enabling deletion, or targeting sensitive keywords). + + The logged events do not indicate how or where the modification took place, only that + an inbox rule was changed at a given time by a specific account. + + Key points: + - Shows modification events for inbox rules, including who modified them and when. + - Flags modifications that may be suspicious based on predefined criteria. + - Does not indicate whether the rules are currently active or still exist. + + For current, active rules, use Get-HawkTenantInboxRules. + + .OUTPUTS + File: Simple_Admin_Inbox_Rules_Modification.csv/.json + Path: \Tenant + Description: Simplified view of inbox rule modification events. + + File: Admin_Inbox_Rules_Modification.csv/.json + Path: \Tenant + Description: Detailed audit log data for modified inbox rules. + + File: _Investigate_Admin_Inbox_Rules_Modification.csv/.json + Path: \Tenant + Description: A subset of historically modified rules flagged as suspicious. + + File: Investigate_Admin_Inbox_Rules_Modification_Raw.json + Path: \Tenant + Description: Raw audit data for suspicious rule modifications. + .EXAMPLE + Get-HawkTenantAdminInboxRuleModification + + Retrieves events for all admin inbox rules modified and available within the audit logs within the configured search window. + + Remarks: This basic example pulls all inbox rule modification logs from the audit log and analyzes them for + suspicious patterns. Output files will be created in the configured Hawk output directory under + the Tenant subfolder. + #> + #> + [CmdletBinding()] + param() + + Test-EXOConnection + Send-AIEvent -Event "CmdRun" + + Out-LogFile "Analyzing admin inbox rule modifications from audit logs" -Action + + # Create tenant folder if it doesn't exist + $TenantPath = Join-Path -Path $Hawk.FilePath -ChildPath "Tenant" + if (-not (Test-Path -Path $TenantPath)) { + New-Item -Path $TenantPath -ItemType Directory -Force | Out-Null + } + + try { + # Search for modified inbox rules + Out-LogFile "Searching audit logs for inbox rule modification events" -Action + $searchCommand = "Search-UnifiedAuditLog -RecordType ExchangeAdmin -Operations 'Set-InboxRule'" + [array]$ModifiedInboxRules = Get-AllUnifiedAuditLogEntry -UnifiedSearch $searchCommand + + if ($ModifiedInboxRules.Count -gt 0) { + Out-LogFile ("Found " + $ModifiedInboxRules.Count + " admin inbox rule modifications in audit logs") -Action + + # Write raw audit data with action flag + $RawJsonPath = Join-Path -Path $TenantPath -ChildPath "Admin_Inbox_Rules_Modification_Raw.json" + Out-LogFile "Writing raw audit data to: $RawJsonPath" -Action + $ModifiedInboxRules | Select-Object -ExpandProperty AuditData | Out-File -FilePath $RawJsonPath + + # Process and output the results + $ParsedRules = $ModifiedInboxRules | Get-SimpleUnifiedAuditLog + if ($ParsedRules) { + Out-LogFile "Writing parsed admin inbox rule modification data" -Action + $ParsedRules | Out-MultipleFileType -FilePrefix "Simple_Admin_Inbox_Rules_Modification" -csv -json + $ModifiedInboxRules | Out-MultipleFileType -FilePrefix "Admin_Inbox_Rules_Modification" -csv -json + + # Check for suspicious modifications using the helper function + $SuspiciousModifications = $ParsedRules | Where-Object { + $reasons = @() + Test-SuspiciousInboxRule -Rule $_ -Reasons ([ref]$reasons) + } + + if ($SuspiciousModifications) { + Out-LogFile "Found suspicious rule modifications requiring investigation" -Notice + + Out-LogFile "Writing suspicious rule modification data" -Action + $SuspiciousModifications | Out-MultipleFileType -FilePrefix "_Investigate_Admin_Inbox_Rules_Modification" -csv -json -Notice + + # Write raw data for suspicious modifications with action flag + $RawSuspiciousPath = Join-Path -Path $TenantPath -ChildPath "Investigate_Admin_Inbox_Rules_Modification_Raw.json" + Out-LogFile "Writing raw suspicious modification data to: $RawSuspiciousPath" -Action + $SuspiciousModifications | ConvertTo-Json -Depth 10 | Out-File -FilePath $RawSuspiciousPath + + # Log details about why each modification was flagged + foreach ($rule in $SuspiciousModifications) { + $reasons = @() + if (Test-SuspiciousInboxRule -Rule $rule -Reasons ([ref]$reasons)) { + Out-LogFile "Found suspicious rule modification: '$($rule.Param_Name)' modified by $($rule.UserId) at $($rule.CreationTime)" -Notice + Out-LogFile "Reasons for investigation: $($reasons -join '; ')" -Notice + } + } + } + } + else { + Out-LogFile "Error: Failed to parse inbox rule audit data" -Notice + } + } + else { + Out-LogFile "No inbox rule modifications found in audit logs" + } + } + catch { + Out-LogFile "Error analyzing admin inbox rule modifications: $($_.Exception.Message)" -Notice + Write-Error -ErrorRecord $_ -ErrorAction Continue + } +} \ No newline at end of file diff --git a/Hawk/functions/Tenant/Get-HawkTenantAdminInboxRuleRemoval.ps1 b/Hawk/functions/Tenant/Get-HawkTenantAdminInboxRuleRemoval.ps1 new file mode 100644 index 0000000..4b54332 --- /dev/null +++ b/Hawk/functions/Tenant/Get-HawkTenantAdminInboxRuleRemoval.ps1 @@ -0,0 +1,121 @@ +Function Get-HawkTenantAdminInboxRuleRemoval { + <# + .SYNOPSIS + Retrieves audit log entries for inbox rules that were removed within the tenant. + + .DESCRIPTION + This function queries the Microsoft 365 Unified Audit Log for events classified as inbox + rule removal (Remove-InboxRule). It focuses on historical record-keeping and identifying + when inbox rules were removed and by whom. The logged events do not indicate the + specific method or interface used to remove the rules. + + Key points: + - Displays removal events for inbox rules, including who removed them and when. + - Flags removals that might be suspicious (e.g., rules that were forwarding externally). + - Provides historical context for rule removals during investigations. + + For current, active rules, use Get-HawkTenantInboxRules. + + .OUTPUTS + File: Simple_Admin_Inbox_Rules_Removal.csv/.json + Path: \Tenant + Description: Simplified view of removed inbox rule events. + + File: Admin_Inbox_Rules_Removal.csv/.json + Path: \Tenant + Description: Detailed audit log data for removed inbox rules. + + File: _Investigate_Admin_Inbox_Rules_Removal.csv/.json + Path: \Tenant + Description: A subset of historically removed rules flagged as suspicious. + + File: Investigate_Admin_Inbox_Rules_Removal_Raw.json + Path: \Tenant + Description: Raw audit data for suspicious removed rules. + + .EXAMPLE + Get-HawkTenantAdminInboxRuleRemoval + + Retrieves events for all removed inbox rules from the audit logs within the specified + search window, highlighting any that appear suspicious. + #> + [CmdletBinding()] + param() + + Test-EXOConnection + Send-AIEvent -Event "CmdRun" + + Out-LogFile "Analyzing admin inbox rule removals from audit logs" -Action + + # Create tenant folder if it doesn't exist + $TenantPath = Join-Path -Path $Hawk.FilePath -ChildPath "Tenant" + if (-not (Test-Path -Path $TenantPath)) { + New-Item -Path $TenantPath -ItemType Directory -Force | Out-Null + } + + try { + # Search for removed inbox rules + Out-LogFile "Searching audit logs for inbox rule removals" -action + $searchCommand = "Search-UnifiedAuditLog -RecordType ExchangeAdmin -Operations 'Remove-InboxRule'" + [array]$RemovedInboxRules = Get-AllUnifiedAuditLogEntry -UnifiedSearch $searchCommand + + if ($RemovedInboxRules.Count -gt 0) { + Out-LogFile ("Found " + $RemovedInboxRules.Count + " admin inbox rule removals in audit logs") -Information + + # Write raw audit data for reference + $RawJsonPath = Join-Path -Path $TenantPath -ChildPath "Admin_Inbox_Rules_Removal_Raw.json" + $RemovedInboxRules | Select-Object -ExpandProperty AuditData | Out-File -FilePath $RawJsonPath + + # Process and output the results + $ParsedRules = $RemovedInboxRules | Get-SimpleUnifiedAuditLog + if ($ParsedRules) { + # Output simple format for easy analysis + $ParsedRules | Out-MultipleFileType -FilePrefix "Simple_Admin_Inbox_Rules_Removal" -csv -json + + # Output full audit logs for complete record + $RemovedInboxRules | Out-MultipleFileType -FilePrefix "Admin_Inbox_Rules_Removal" -csv -json + + # Check for suspicious removals + $SuspiciousRemovals = $ParsedRules | Where-Object { + $reasons = @() + Test-SuspiciousInboxRule -Rule $_ -Reasons ([ref]$reasons) + } + + if ($SuspiciousRemovals) { + Out-LogFile "Found suspicious admin inbox rule removals requiring investigation" -Notice + + # Output files with timestamps + $csvPath = Join-Path -Path $TenantPath -ChildPath "_Investigate_Admin_Inbox_Rules_Removal.csv" + $jsonPath = Join-Path -Path $TenantPath -ChildPath "_Investigate_Admin_Inbox_Rules_Removal.json" + Out-LogFile "Additional Information: $csvPath" -Notice + Out-LogFile "Additional Information: $jsonPath" -Notice + + $SuspiciousRemovals | Out-MultipleFileType -FilePrefix "_Investigate_Admin_Inbox_Rules_Removal" -csv -json -Notice + + # Write raw data for suspicious rules + $RawSuspiciousPath = Join-Path -Path $TenantPath -ChildPath "Investigate_Admin_Inbox_Rules_Removal_Raw.json" + $SuspiciousRemovals | ConvertTo-Json -Depth 10 | Out-File -FilePath $RawSuspiciousPath + + # Log details about why each removal was flagged + foreach ($rule in $SuspiciousRemovals) { + $reasons = @() + if (Test-SuspiciousInboxRule -Rule $rule -Reasons ([ref]$reasons)) { + Out-LogFile "Found suspicious rule removal: '$($rule.Param_Name)' removed by $($rule.UserId) at $($rule.CreationTime)" -Notice + Out-LogFile "Reasons for investigation: $($reasons -join '; ')" -Notice + } + } + } + } + else { + Out-LogFile "Error: Failed to parse inbox rule removal audit data" -Notice + } + } + else { + Out-LogFile "No inbox rule removals found in audit logs" + } + } + catch { + Out-LogFile "Error analyzing admin inbox rule removals: $($_.Exception.Message)" -Notice + Write-Error -ErrorRecord $_ -ErrorAction Continue + } +} \ No newline at end of file diff --git a/Hawk/functions/Tenant/Get-HawkTenantAdminMailboxPermissionChange.ps1 b/Hawk/functions/Tenant/Get-HawkTenantAdminMailboxPermissionChange.ps1 new file mode 100644 index 0000000..d56588f --- /dev/null +++ b/Hawk/functions/Tenant/Get-HawkTenantAdminMailboxPermissionChange.ps1 @@ -0,0 +1,108 @@ +Function Get-HawkTenantAdminMailboxPermissionChange { + <# + .SYNOPSIS + Retrieves audit log entries for mailbox permission changes within the tenant. + + .DESCRIPTION + Searches the Unified Audit Log for mailbox permission changes and flags any grants + of FullAccess, SendAs, or Send on Behalf permissions for investigations. + Excludes normal system operations on Discovery Search Mailboxes. + + .OUTPUTS + File: Simple_Mailbox_Permission_Change.csv/.json + Path: \Tenant + Description: Simplified view of mailbox permission changes. + + File: Mailbox_Permission_Change.csv/.json + Path: \Tenant + Description: Detailed audit log data for permission changes. + + File: _Investigate_Mailbox_Permission_Change.csv/.json + Path: \Tenant + Description: Permission changes that granted sensitive rights. + + .EXAMPLE + Get-HawkTenantAdminMailboxPermissionChange + + Retrieves mailbox permission change events from the audit logs. + #> + [CmdletBinding()] + param() + + Test-EXOConnection + Send-AIEvent -Event "CmdRun" + + Out-LogFile "Analyzing mailbox permission changes from audit logs" -Action + + # Create tenant folder if it doesn't exist + $TenantPath = Join-Path -Path $Hawk.FilePath -ChildPath "Tenant" + if (-not (Test-Path -Path $TenantPath)) { + New-Item -Path $TenantPath -ItemType Directory -Force | Out-Null + } + + try { + # Search for mailbox permission changes + Out-LogFile "Searching audit logs for mailbox permission changes" -action + $searchCommand = "Search-UnifiedAuditLog -RecordType ExchangeAdmin -Operations 'Add-MailboxPermission','Add-RecipientPermission','Add-ADPermission'" + [array]$PermissionChanges = Get-AllUnifiedAuditLogEntry -UnifiedSearch $searchCommand + + if ($PermissionChanges.Count -gt 0) { + Out-LogFile ("Found " + $PermissionChanges.Count + " mailbox permission changes in audit logs") + + # Process and output the results + $ParsedChanges = $PermissionChanges | Get-SimpleUnifiedAuditLog + if ($ParsedChanges) { + # Output simple format for easy analysis + $ParsedChanges | Out-MultipleFileType -FilePrefix "Simple_Mailbox_Permission_Change" -csv -json + + # Output full audit logs for complete record + $PermissionChanges | Out-MultipleFileType -FilePrefix "Mailbox_Permission_Change" -csv -json + + # Check for sensitive permissions, excluding Discovery Search Mailbox system operations + $SensitiveGrants = $ParsedChanges | Where-Object { + # First check if this is potentially sensitive permission + ($_.Param_AccessRights -match 'FullAccess|SendAs' -or + $_.Operation -eq 'Add-ADPermission' -or + $_.Operation -match 'Add-RecipientPermission') -and + # Then exclude DiscoverySearchMailbox system operations + -not ( + $_.UserId -eq "NT AUTHORITY\SYSTEM (Microsoft.Exchange.ServiceHost)" -and + $_.ObjectId -like "*DiscoverySearchMailbox*" -and + $_.Param_User -like "*Discovery Management*" + ) + } + + if ($SensitiveGrants) { + Out-LogFile "Found sensitive permission grants requiring investigation" -Notice + $SensitiveGrants | Out-MultipleFileType -FilePrefix "_Investigate_Mailbox_Permission_Change" -csv -json -Notice + + # Log details about sensitive permission grants + foreach ($change in $SensitiveGrants) { + $permType = if ($change.Param_AccessRights -match 'FullAccess') { + "FullAccess" + } elseif ($change.Param_AccessRights -match 'SendAs' -or + $change.Operation -eq 'Add-ADPermission' -or + $change.Operation -match 'Add-RecipientPermission') { + "SendAs/Send on Behalf" + } else { + "Other sensitive permission" + } + + Out-LogFile "Permission change by $($change.UserId) at $($change.CreationTime)" -Notice + Out-LogFile "Details: Granted $permType to $($change.Param_User) on mailbox $($change.Param_Identity)" -Notice + } + } + } + else { + Out-LogFile "Error: Failed to parse mailbox permission audit data" -Notice + } + } + else { + Out-LogFile "No mailbox permission changes found in audit logs" + } + } + catch { + Out-LogFile "Error analyzing mailbox permission changes: $($_.Exception.Message)" -Notice + Write-Error -ErrorRecord $_ -ErrorAction Continue + } +} \ No newline at end of file diff --git a/Hawk/functions/Tenant/Get-HawkTenantEntraIDAdmin.ps1 b/Hawk/functions/Tenant/Get-HawkTenantEntraIDAdmin.ps1 index 1c7a20c..8cb7735 100644 --- a/Hawk/functions/Tenant/Get-HawkTenantEntraIDAdmin.ps1 +++ b/Hawk/functions/Tenant/Get-HawkTenantEntraIDAdmin.ps1 @@ -35,7 +35,7 @@ try { # Retrieve all directory roles from Microsoft Graph $directoryRoles = Get-MgDirectoryRole -ErrorAction Stop - Out-LogFile "Retrieved $(($directoryRoles | Measure-Object).Count) directory roles" + Out-LogFile "Retrieved $(($directoryRoles | Measure-Object).Count) directory roles" -Information # Process each role and its members $roles = foreach ($role in $directoryRoles) { diff --git a/Hawk/functions/Tenant/Get-HawkTenantInboxRules.ps1 b/Hawk/functions/Tenant/Get-HawkTenantInboxRules.ps1 index c7e0b98..33b77c6 100644 --- a/Hawk/functions/Tenant/Get-HawkTenantInboxRules.ps1 +++ b/Hawk/functions/Tenant/Get-HawkTenantInboxRules.ps1 @@ -1,41 +1,54 @@ Function Get-HawkTenantInboxRules { <# -.SYNOPSIS - Gets inbox rules and forwarding directly from all mailboxes in the org. -.DESCRIPTION - Uses Start-RobustCloudCommand to gather data from each mailbox in the org. - Gathers inbox rules with Get-HawkUserInboxRule - Gathers forwarding with Get-HawkUserEmailForwarding -.PARAMETER CSVPath - Path to a CSV file with a list of users to run against. - CSV header should have DisplayName,PrimarySMTPAddress at minimum - -.PARAMETER UserPrincipalName - The UPN of the user that will authenticate against Exchange Online. - -.OUTPUTS - See Help for Get-HawkUserInboxRule for inbox rule output - See Help for Get-HawkUserEmailForwarding for email forwarding output - - File: Robust.log - Path: \ - Description: Logfile for Start-RobustCloudCommand - -.EXAMPLE - Start-HawkTenantInboxRules -UserPrincipalName userx@tenantdomain.onmicrosoft.com - - Runs Get-HawkUserInboxRule and Get-HawkUserEmailForwarding against all mailboxes in the org. The UserPrincipalName - is the Admin/User who is running the cmdlet. - -.EXAMPLE - Start-HawkTenantInboxRules -csvpath c:\temp\myusers.csv - - Runs Get-HawkUserInboxRule and Get-HawkUserEmailForwarding against all mailboxes listed in myusers.csv.The UserPrincipalName - is the Admin/User who is running the cmdlet. - -.LINK - https://gallery.technet.microsoft.com/office/Start-RobustCloudCommand-69fb349e -#> + .SYNOPSIS + Retrieves the currently active inbox rules and forwarding settings from all (or specified) mailboxes. + + .DESCRIPTION + This function directly queries each mailbox in the organization to list its currently configured + inbox rules and email forwarding settings. It provides a real-time snapshot of what rules are + active right now, as opposed to historical audit data. + + Key points: + - Directly collects the current state of each mailbox’s rules using Get-HawkUserInboxRule. + - Also gathers forwarding settings from Get-HawkUserEmailForwarding. + - Does not rely on audit logs; instead, uses live mailbox data. + + For historical records of when rules were created and past suspicious activity, use Get-HawkTenantInboxRuleHistory. + + .PARAMETER CSVPath + A CSV file specifying a list of users to query. + Expected columns: DisplayName, PrimarySMTPAddress (minimum). + + .PARAMETER UserPrincipalName + The UPN of the admin or account used to authenticate against Exchange Online. + + .OUTPUTS + This function calls Get-HawkUserInboxRule and Get-HawkUserEmailForwarding. + For detailed information about the output, see their respective help documentation. + + File: Robust.log + Path: \ + Description: The log file generated by Start-RobustCloudCommand, which is used to retrieve + the rules and forwarding information from each mailbox. + + .EXAMPLE + Start-HawkTenantInboxRules -UserPrincipalName userx@tenantdomain.onmicrosoft.com + + Retrieves the current inbox rules and forwarding for all mailboxes in the organization. + + .EXAMPLE + Start-HawkTenantInboxRules -csvpath c:\temp\myusers.csv -UserPrincipalName admin@tenantdomain.onmicrosoft.com + + Retrieves the current inbox rules and forwarding for all mailboxes listed in myusers.csv. + + .LINK + https://gallery.technet.microsoft.com/office/Start-RobustCloudCommand-69fb349e + + .NOTES + - This function shows the current (live) rules and forwarding settings. + - For historical data on when rules were created, refer to Get-HawkTenantInboxRuleHistory. + #> + param ( [string]$CSVPath, diff --git a/Hawk/functions/Tenant/Search-HawkTenantEXOAuditLog.ps1 b/Hawk/functions/Tenant/Search-HawkTenantEXOAuditLog.ps1 deleted file mode 100644 index 1470cb4..0000000 --- a/Hawk/functions/Tenant/Search-HawkTenantEXOAuditLog.ps1 +++ /dev/null @@ -1,234 +0,0 @@ -Function Search-HawkTenantEXOAuditLog { - <# -.SYNOPSIS - Searches the admin audit logs for possible bad actor activities -.DESCRIPTION - Searches the Exchange admin audkit logs for a number of possible bad actor activies. - * New inbox rules - * Changes to user forwarding configurations - * Changes to user mailbox permissions - * Granting of impersonation rights -.OUTPUTS - - File: Simple_New_InboxRule.csv - Path: \ - Description: cmdlets to create any new inbox rules in a simple to read format - - File: New_InboxRules.xml - Path: \XML - Description: Search results for any new inbox rules in CLI XML format - - File: _Investigate_Simple_New_InboxRule.csv - Path: \ - Description: cmdlets to create inbox rules that forward or delete email in a simple format - - File: _Investigate_New_InboxRules.xml - Path: \XML - Description: Search results for newly created inbox rules that forward or delete email in CLI XML - - File: _Investigate_New_InboxRules.txt - Path: \ - Description: Search results of newly created inbox rules that forward or delete email - - File: Simple_Forwarding_Changes.csv - Path: \ - Description: cmdlets that change forwarding settings in a simple to read format - - File: Forwarding_Changes.xml - Path: \XML - Description: Search results for cmdlets that change forwarding settings in CLI XML - - File: Forwarding_Recipients.csv - Path: \ - Description: List of unique Email addresses that were setup to recieve email via forwarding - - File: Simple_Mailbox_Permissions.csv - Path: \ - Description: Cmdlets that add permissions to users in a simple to read format - - File: Mailbox_Permissions.xml - Path: \XML - Description: Search results for cmdlets that change permissions in CLI XML - - File: _Investigate_Impersonation_Roles.csv - Path: \ - Description: List all users with impersonation rights if we find more than the default of one - - File: _Investigate_Impersonation_Roles.csv - Path: \XML - Description: List all users with impersonation rights if we find more than the default of one as CLI XML - - File: Impersonation_Rights.csv - Path: \ - Description: List all users with impersonation rights if we only find the default one - - File: Impersonation_Rights.csv - Path: \XML - Description: List all users with impersonation rights if we only find the default one as CLI XML -.EXAMPLE - Search-HawkTenantEXOAuditLog - - Searches the tenant audit logs looking for changes that could have been made in the tenant. -#> - - Test-EXOConnection - Send-AIEvent -Event "CmdRun" - - Out-LogFile "Searching EXO Audit Logs" -Action - Out-LogFile "Searching Entire Admin Audit Log for Specific cmdlets" - - #Make sure our values are null - $TenantInboxRules = $Null - $TenantSetInboxRules = $Null - $TenantRemoveInboxRules = $Null - - - # Search for the creation of ANY inbox rules - Out-LogFile "Searching for ALL Inbox Rules Created in the Shell" -action - [array]$TenantInboxRules = Search-UnifiedAuditLog -RecordType ExchangeAdmin -Operations New-InboxRule -StartDate $Hawk.StartDate -EndDate $Hawk.EndDate - - # If we found anything report it and log it - if ($TenantInboxRules.count -gt 0) { - - Out-LogFile ("Found " + $TenantInboxRules.count + " Inbox Rule(s) created from PowerShell") - $TenantInboxRules | Get-SimpleAdminAuditLog | Out-MultipleFileType -fileprefix "Simple_New_InboxRule" -csv -json - $TenantInboxRules | Out-MultipleFileType -fileprefix "New_InboxRules" -csv -json - } - - # Search for the Modification of ANY inbox rules - Out-LogFile "Searching for ALL Inbox Rules Modified in the Shell" -action - [array]$TenantSetInboxRules = Search-AdminAuditLog -Cmdlets Set-InboxRule -StartDate $Hawk.StartDate -EndDate $Hawk.EndDate - - # If we found anything report it and log it - if ($TenantSetInboxRules.count -gt 0) { - - Out-LogFile ("Found " + $TenantSetInboxRules.count + " Inbox Rule(s) created from PowerShell") - $TenantSetInboxRules | Get-SimpleAdminAuditLog | Out-MultipleFileType -fileprefix "Simple_Set_InboxRule" -csv -json - $TenantSetInboxRules | Out-MultipleFileType -fileprefix "Set_InboxRules" -csv -json - } - - # Search for the Modification of ANY inbox rules - Out-LogFile "Searching for ALL Inbox Rules Removed in the Shell" -action - [array]$TenantRemoveInboxRules = Search-AdminAuditLog -Cmdlets Remove-InboxRule -StartDate $Hawk.StartDate -EndDate $Hawk.EndDate - - # If we found anything report it and log it - if ($TenantRemoveInboxRules.count -gt 0) { - - Out-LogFile ("Found " + $TenantRemoveInboxRules.count + " Inbox Rule(s) created from PowerShell") - $TenantRemoveInboxRules | Get-SimpleAdminAuditLog | Out-MultipleFileType -fileprefix "Simple_Remove_InboxRule" -csv -json - $TenantRemoveInboxRules | Out-MultipleFileType -fileprefix "Remove_InboxRules" -csv -json - } - - # Searching for interesting inbox rules - Out-LogFile "Searching for Interesting Inbox Rules Created in the Shell" -action - [array]$InvestigateInboxRules = Search-AdminAuditLog -StartDate $Hawk.StartDate -EndDate $Hawk.EndDate -cmdlets New-InboxRule -Parameters ForwardTo, ForwardAsAttachmentTo, RedirectTo, DeleteMessage - - # if we found a rule report it and output it to the _Investigate files - if ($InvestigateInboxRules.count -gt 0) { - Out-LogFile ("Found " + $InvestigateInboxRules.count + " Inbox Rules that should be investigated further.") -notice - $InvestigateInboxRules | Get-SimpleAdminAuditLog | Out-MultipleFileType -fileprefix "_Investigate_Simple_New_InboxRule" -csv -json -Notice - $InvestigateInboxRules | Out-MultipleFileType -fileprefix "_Investigate_New_InboxRules" -xml -txt -Notice - } - - # Look for changes to user forwarding - Out-LogFile "Searching for user Forwarding Changes" -action - [array]$TenantForwardingChanges = Search-AdminAuditLog -Cmdlets Set-Mailbox -Parameters ForwardingAddress, ForwardingSMTPAddress -StartDate $Hawk.StartDate -EndDate $Hawk.EndDate - - if ($TenantForwardingChanges.count -gt 0) { - Out-LogFile ("Found " + $TenantForwardingChanges.count + " Change(s) to user Email Forwarding") -notice - $TenantForwardingChanges | Get-SimpleAdminAuditLog | Out-MultipleFileType -FilePrefix "Simple_Forwarding_Changes" -csv -json -Notice - $TenantForwardingChanges | Out-MultipleFileType -FilePrefix "Forwarding_Changes" -xml -Notice - - # Make sure our output array is null - [array]$Output = $null - - # Checking if addresses were added or removed - # If added compile a list - Foreach ($Change in $TenantForwardingChanges) { - - # Get the user object modified - $user = ($Change.CmdletParameters | Where-Object ($_.name -eq "Identity")).value - - # Check the ForwardingSMTPAddresses first - if ([string]::IsNullOrEmpty(($Change.CmdletParameters | Where-Object { $_.name -eq "ForwardingSMTPAddress" }).value)) { } - # If not null then push the email address into $output - else { - [array]$Output = $Output + ($Change.CmdletParameters | Where-Object { $_.name -eq "ForwardingSMTPAddress" }) | Select-Object -Property @{Name = "UserModified"; Expression = { $user } }, @{Name = "TargetSMTPAddress"; Expression = { $_.value.split(":")[1] } } - } - - # Check ForwardingAddress - if ([string]::IsNullOrEmpty(($Change.CmdletParameters | Where-Object { $_.name -eq "ForwardingAddress" }).value)) { } - else { - # Here we get back a recipient object in EXO not an SMTP address - # So we need to go track down the recipient object - $recipient = Get-EXORecipient (($Change.CmdletParameters | Where-Object { $_.name -eq "ForwardingAddress" }).value) -ErrorAction SilentlyContinue - - # If we can't resolve the recipient we need to log that - if ($null -eq $recipient) { - Out-LogFile ("Unable to resolve forwarding Target Recipient " + ($Change.CmdletParameters | Where-Object { $_.name -eq "ForwardingAddress" })) -notice - } - # If we can resolve it then we need to push the address the mail was being set to into $output - else { - # Determine the type of recipient and handle as needed to get out the SMTP address - Switch ($recipient.RecipientType) { - # For mailcontact we needed the external email address - MailContact { - [array]$Output += $recipient | Select-Object -Property @{Name = "UserModified"; Expression = { $user } }; @{Name = "TargetSMTPAddress"; Expression = { $_.ExternalEmailAddress.split(":")[1] } } - } - # For all others I believe primary will work - Default { - [array]$Output += $recipient | Select-Object -Property @{Name = "UserModified"; Expression = { $user } }; @{Name = "TargetSMTPAddress"; Expression = { $_.PrimarySmtpAddress } } - } - } - } - } - } - - # Output our email address user modified pairs - Out-logfile ("Found " + $Output.count + " email addresses set to be forwarded mail") -notice - $Output | Out-MultipleFileType -FilePrefix "Forwarding_Recipients" -csv -json -Notice - - } - - # Look for changes to mailbox permissions - Out-LogFile "Searching for Mailbox Permissions Changes" -Action - [array]$TenantMailboxPermissionChanges = Search-AdminAuditLog -StartDate $Hawk.StartDate -EndDate $Hawk.EndDate -cmdlets Add-MailboxPermission - - if ($TenantMailboxPermissionChanges.count -gt 0) { - Out-LogFile ("Found " + $TenantMailboxPermissionChanges.count + " changes to mailbox permissions") - $TenantMailboxPermissionChanges | Get-SimpleAdminAuditLog | Out-MultipleFileType -fileprefix "Simple_Mailbox_Permissions" -csv -json - $TenantMailboxPermissionChanges | Out-MultipleFileType -fileprefix "Mailbox_Permissions" -xml - - ## TODO: Possibly check who was added with permissions and see how old their accounts are - } - - # Look for change to impersonation access - Out-LogFile "Searching Impersonation Access" -action - [array]$TenantImpersonatingRoles = Get-ManagementRoleEntry "*\Impersonate-ExchangeUser" - if ($TenantImpersonatingRoles.count -gt 1) { - Out-LogFile ("Found " + $TenantImpersonatingRoles.count + " Impersonation Roles. Default is 1") -notice - $TenantImpersonatingRoles | Out-MultipleFileType -fileprefix "_Investigate_Impersonation_Roles" -csv -json -xml -Notice - } - elseif ($TenantImpersonatingRoles.count -eq 0) { } - else { - $TenantImpersonatingRoles | Out-MultipleFileType -fileprefix "Impersonation_Roles" -csv -json -xml - } - - $Output = $null - # Search all impersonation roles for users that have access - foreach ($Role in $TenantImpersonatingRoles) { - [array]$Output += Get-ManagementRoleAssignment -Role $Role.role -GetEffectiveUsers -Delegating:$false - } - - if ($Output.count -gt 1) { - Out-LogFile ("Found " + $Output.count + " Users/Groups with Impersonation rights. Default is 1") -notice - $Output | Out-MultipleFileType -fileprefix "Impersonation_Rights" -csv -json -xml - $Output | Out-MultipleFileType -fileprefix "_Investigate_Impersonation_Rights" -csv -json -xml -Notice - } - elseif ($Output.count -eq 1) { - Out-LogFile ("Found default number of Impersonation users") - $Output | Out-MultipleFileType -fileprefix "Impersonation_Rights" -csv -json -xml - } - else { } - -} \ No newline at end of file diff --git a/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 b/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 index d0705e4..b4b6a3d 100644 --- a/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 +++ b/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 @@ -51,11 +51,32 @@ Get-HawkTenantEDiscoveryConfiguration } - if ($PSCmdlet.ShouldProcess("Exchange Audit Log", "Search audit logs")) { - Out-LogFile "Running Search-HawkTenantEXOAuditLog" -action - Search-HawkTenantEXOAuditLog + if ($PSCmdlet.ShouldProcess("Admin Inbox Rule Creation Audit Log", "Search Admin Inbox Rule Creation")) { + Out-LogFile "Running Get-HawkTenantAdminInboxRuleCreation" -action + Get-HawkTenantAdminInboxRuleCreation } + if ($PSCmdlet.ShouldProcess("Admin Inbox Rule Modification Audit Log", "Search Admin Inbox Rule Modification")) { + Out-LogFile "Running Get-HawkTenantInboxRuleModification" -action + Get-HawkTenantAdminInboxRuleModification + } + + if ($PSCmdlet.ShouldProcess("Admin Inbox Rule Removal Audit Log", "Search Admin Inbox Rule Removal")) { + Out-LogFile "Running Get-HawkTenantAdminInboxRuleRemoval" -action + Get-HawkTenantAdminInboxRuleRemoval + } + + if ($PSCmdlet.ShouldProcess("Admin Inbox Rule Permission Change Audit Log", "Search Admin Inbox Permission Changes")) { + Out-LogFile "Running Get-HawkTenantAdminMailboxPermissionChange" -action + Get-HawkTenantAdminMailboxPermissionChange + } + + if ($PSCmdlet.ShouldProcess("Admin Email Forwarding Change Change Audit Log", "Search Admin Email Forwarding Changes")) { + Out-LogFile "Running Get-HawkTenantAdminEmailForwardingChange" -action + Get-HawkTenantAdminEmailForwardingChange + } + + if ($PSCmdlet.ShouldProcess("EDiscovery Logs", "Get eDiscovery logs")) { Out-LogFile "Running Get-HawkTenantEDiscoveryLogs" -action Get-HawkTenantEDiscoveryLogs @@ -86,7 +107,7 @@ Get-HawkTenantConsentGrant } - if ($PSCmdlet.ShouldProcess("Azure Admins", "Get Entra ID admin list")) { + if ($PSCmdlet.ShouldProcess("Entra ID Admins", "Get Entra ID admin list")) { Out-LogFile "Running Get-HawkTenantEntraIDAdmin" -action Get-HawkTenantEntraIDAdmin } diff --git a/Hawk/internal/functions/Get-AllUnifiedAuditLogEntry.ps1 b/Hawk/internal/functions/Get-AllUnifiedAuditLogEntry.ps1 index 10d5e03..cf466ff 100644 --- a/Hawk/internal/functions/Get-AllUnifiedAuditLogEntry.ps1 +++ b/Hawk/internal/functions/Get-AllUnifiedAuditLogEntry.ps1 @@ -36,8 +36,8 @@ # build our search command to execute $cmd = $UnifiedSearch + " -StartDate `'" + (get-date ($StartDate) -UFormat %m/%d/%Y) + "`' -EndDate `'" + (get-date ($endDate) -UFormat %m/%d/%Y) + "`' -SessionCommand ReturnLargeSet -resultsize 5000 -sessionid " + (Get-Date -UFormat %H%M%S) - Out-LogFile ("Running Unified Audit Log Search") - Out-Logfile $cmd + Out-LogFile ("Running Unified Audit Log Search") -Action + Out-Logfile $cmd -NoDisplay # Run the initial command $Output = $null @@ -46,9 +46,12 @@ # Setup our run variable $Run = $true + # Convert the command string into a scriptblock to avoid Invoke-Expression + $searchScript = [ScriptBlock]::Create($cmd) + # Since we have more than 1k results we need to keep returning results until we have them all while ($Run) { - $Output += (Invoke-Expression $cmd) + $Output += & $searchScript # Check for null results if so warn and stop if ($null -eq $Output) { @@ -67,12 +70,12 @@ } # if our resultindex = our resultcount then we have everything and should stop elseif ($Output[-1].Resultindex -ge $Output[-1].ResultCount) { - Out-LogFile ("Retrieved all results.") + Out-LogFile ("Retrieved all results.") -Information $Run = $false } # Output the current progress - Out-LogFile ("Retrieved:" + $Output[-1].ResultIndex.tostring().PadRight(5, " ") + " Total: " + $Output[-1].ResultCount) + Out-LogFile ("Retrieved:" + $Output[-1].ResultIndex.tostring().PadRight(5, " ") + " Total: " + $Output[-1].ResultCount) -Information } } diff --git a/Hawk/internal/functions/Out-LogFile.ps1 b/Hawk/internal/functions/Out-LogFile.ps1 index 11fc834..1ee85bf 100644 --- a/Hawk/internal/functions/Out-LogFile.ps1 +++ b/Hawk/internal/functions/Out-LogFile.ps1 @@ -1,32 +1,88 @@ -<# -.SYNOPSIS - Writes output to a log file with a time date stamp -.DESCRIPTION - Writes output to a log file with a time date stamp -.PARAMETER string - Log Message -.PARAMETER action - What is happening -.PARAMETER notice - Verbose notification -.PARAMETER silentnotice - Silent notification -.EXAMPLE - Out-LogFile - Sends messages to the log file -.NOTES - This is will depracted soon. -#> -Function Out-LogFile { +Function Out-LogFile { + <# + .SYNOPSIS + Writes output to a log file with a time date stamp. + .DESCRIPTION + Writes output to a log file with a time date stamp and appropriate prefixes + based on the type of message. By default, messages are also displayed on the screen + unless the -NoDisplay switch is used. + + Message types: + - Action: Represent ongoing operations or procedures. + - Investigate (notice, silentnotice): Represent events that require attention or hold + investigative value. + - Information: Represent successful completion or informational status updates + that do not require action or investigation. + + .PARAMETER string + The log message to be written. + + .PARAMETER action + Switch indicating the log entry is describing an action being performed. + + .PARAMETER notice + Switch indicating the log entry requires investigation or special attention. + + .PARAMETER silentnotice + Switch indicating additional investigative information that should not be + displayed on the screen. This is logged to the file but suppressed in console output. + + .PARAMETER NoDisplay + Switch indicating the message should only be written to the log file, + not displayed in the console. + + .PARAMETER Information + Switch indicating the log entry provides informational status or completion messages, + for example: "Retrieved all results" or "Completed data export successfully." + + .EXAMPLE + Out-LogFile "Routine scan completed." + + Writes a simple log message with a timestamp to the log file and displays it on the screen. + + .EXAMPLE + Out-LogFile "Starting mailbox export operation" -action + + Writes a log message indicating an action is being performed. + The output is prefixed with [ACTION] in the log file. + + .EXAMPLE + Out-LogFile "Detected suspicious login attempt from external IP" -notice + + Writes a log message indicating a situation requiring investigation. + The output is prefixed with [INVESTIGATE] and also recorded in a separate _Investigate.txt file. + + .EXAMPLE + Out-LogFile "User mailbox configuration details" -silentnotice + + Writes investigative detail to the log and _Investigate.txt file without printing to the console. + This is useful for adding detail to a previously logged [INVESTIGATE] event without cluttering the console. + + .EXAMPLE + Out-LogFile "Retrieved all results successfully" -Information + + Writes a log message indicating a successful or informational event. + The output is prefixed with [INFO], suitable for status updates or completion notices. + + .EXAMPLE + Out-LogFile "Executing periodic health check" -NoDisplay + + Writes a log message to the file without displaying it on the console, + useful for routine logging that doesn't need immediate user visibility. + #> + [CmdletBinding()] Param ( + [Parameter(Mandatory = $true)] [string]$string, [switch]$action, [switch]$notice, - [switch]$silentnotice - ) + [switch]$silentnotice, + [switch]$NoDisplay, + [switch]$Information + ) - Write-PSFMessage -Message $string -ModuleName Hawk -FunctionName (Get-PSCallstack)[1].FunctionName + Write-PSFMessage -Message $string -ModuleName Hawk -FunctionName (Get-PSCallstack)[1].FunctionName # Make sure we have the Hawk Global Object if ([string]::IsNullOrEmpty($Hawk.FilePath)) { @@ -35,52 +91,49 @@ Function Out-LogFile { # Get our log file path $LogFile = Join-path $Hawk.FilePath "Hawk.log" - $ScreenOutput = $true + $ScreenOutput = -not $NoDisplay $LogOutput = $true # Get the current date - [string]$date = Get-Date -Format G + [string]$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" + [string]$logstring = "" - # Deal with each switch and what log string it should put out and if any special output - - # Action indicates that we are starting to do something + # Build the log string based on the type of message if ($action) { - [string]$logstring = ( "[" + $date + "] - [ACTION] - " + $string) - + $logstring = "[$timestamp] - [ACTION] - $string" } - # If notice is true the we should write this to interesting.txt as well elseif ($notice) { - [string]$logstring = ( "[" + $date + "] - ## INVESTIGATE ## - " + $string) + $logstring = "[$timestamp] - [INVESTIGATE] - $string" - # Build the file name for Investigate stuff log + # Write to the investigation file [string]$InvestigateFile = Join-Path (Split-Path $LogFile -Parent) "_Investigate.txt" $logstring | Out-File -FilePath $InvestigateFile -Append } - # For silent we need to supress the screen output elseif ($silentnotice) { - [string]$logstring = ( "Addtional Information: " + $string) - # Build the file name for Investigate stuff log + $logstring = "[$timestamp] - [INVESTIGATE] - Additional Information: $string" + + # Write to the investigation file [string]$InvestigateFile = Join-Path (Split-Path $LogFile -Parent) "_Investigate.txt" $logstring | Out-File -FilePath $InvestigateFile -Append - # Supress screen and normal log output + # Suppress regular output for silentnotice $ScreenOutput = $false $LogOutput = $false - } - # Normal output + elseif ($Information) { + $logstring = "[$timestamp] - [INFO] - $string" + } else { - [string]$logstring = ( "[" + $date + "] - " + $string) + $logstring = "[$timestamp] - $string" } - # Write everything to our log file + # Write to log file if enabled if ($LogOutput) { $logstring | Out-File -FilePath $LogFile -Append } - # Output to the screen + # Write to screen if enabled if ($ScreenOutput) { Write-Information -MessageData $logstring -InformationAction Continue } - -} \ No newline at end of file +} diff --git a/Hawk/internal/functions/Out-MultipleFileType.ps1 b/Hawk/internal/functions/Out-MultipleFileType.ps1 index b471a39..b028d08 100644 --- a/Hawk/internal/functions/Out-MultipleFileType.ps1 +++ b/Hawk/internal/functions/Out-MultipleFileType.ps1 @@ -113,7 +113,7 @@ Function Out-MultipleFileType { else { $filename = Join-Path $xmlPath ($FilePrefix + ".xml") } - Out-LogFile ("Writing Data to " + $filename) + Out-LogFile ("Writing Data to " + $filename) -Action # Output our objects to clixml $AllObject | Export-Clixml $filename @@ -143,7 +143,7 @@ Function Out-MultipleFileType { # Otherwise overwrite else { - Out-LogFile ("Writing Data to " + $filename) + Out-LogFile ("Writing Data to " + $filename) -Action $AllObject | Export-Csv $filename -NoTypeInformation -Encoding UTF8 } @@ -169,7 +169,7 @@ Function Out-MultipleFileType { # Otherwise overwrite else { - Out-LogFile ("Writing Data to " + $filename) + Out-LogFile ("Writing Data to " + $filename) -Action $AllObject | Format-List * | Out-File $filename } @@ -198,7 +198,7 @@ Function Out-MultipleFileType { # Otherwise overwrite else { - Out-LogFile ("Writing Data to " + $filename) + Out-LogFile ("Writing Data to " + $filename) -Action $AllObject | ConvertTo-Json -Depth 100 | Out-File -FilePath $filename } diff --git a/Hawk/internal/functions/Test-SuspiciousInboxRule.ps1 b/Hawk/internal/functions/Test-SuspiciousInboxRule.ps1 new file mode 100644 index 0000000..1f4e530 --- /dev/null +++ b/Hawk/internal/functions/Test-SuspiciousInboxRule.ps1 @@ -0,0 +1,78 @@ +Function Test-SuspiciousInboxRule { + <# + .SYNOPSIS + Internal helper function to detect suspicious inbox rule patterns. + + .DESCRIPTION + Analyzes inbox rule properties to identify potentially suspicious configurations + like external forwarding, message deletion, or targeting of security-related content. + Used by both rule creation and modification audit functions. + + .PARAMETER Rule + The parsed inbox rule object to analyze. + + .PARAMETER Reasons + [ref] array to store the reasons why a rule was flagged as suspicious. + + .OUTPUTS + Boolean indicating if the rule matches suspicious patterns. + Populates the Reasons array parameter with explanations if suspicious. + + .EXAMPLE + $reasons = @() + $isSuspicious = Test-SuspiciousInboxRule -Rule $ruleObject -Reasons ([ref]$reasons) + #> + [CmdletBinding()] + [OutputType([bool])] + param ( + [Parameter(Mandatory = $true)] + [object]$Rule, + + [Parameter(Mandatory = $true)] + [ref]$Reasons + ) + + $isSuspicious = $false + $suspiciousReasons = @() + + # Check forwarding/redirection configurations + if ($Rule.Param_ForwardTo) { + $isSuspicious = $true + $suspiciousReasons += "forwards to: $($Rule.Param_ForwardTo)" + } + if ($Rule.Param_ForwardAsAttachmentTo) { + $isSuspicious = $true + $suspiciousReasons += "forwards as attachment to: $($Rule.Param_ForwardAsAttachmentTo)" + } + if ($Rule.Param_RedirectTo) { + $isSuspicious = $true + $suspiciousReasons += "redirects to: $($Rule.Param_RedirectTo)" + } + + # Check deletion/move to deleted items + if ($Rule.Param_DeleteMessage) { + $isSuspicious = $true + $suspiciousReasons += "deletes messages" + } + if ($Rule.Param_MoveToFolder -eq 'Deleted Items') { + $isSuspicious = $true + $suspiciousReasons += "moves to Deleted Items" + } + + # Check for suspicious keywords in subject filters + if ($Rule.Param_SubjectContainsWords -match 'password|credentials|login|secure|security') { + $isSuspicious = $true + $suspiciousReasons += "suspicious subject filter: $($Rule.Param_SubjectContainsWords)" + } + + # Check for targeting of security-related senders + if ($Rule.Param_From -match 'security|admin|support|microsoft|helpdesk') { + $isSuspicious = $true + $suspiciousReasons += "targets security sender: $($Rule.Param_From)" + } + + # Update the reasons array with our findings + $Reasons.Value = $suspiciousReasons + + return $isSuspicious +} \ No newline at end of file