functions/Tenant/Get-OspreyTenantExchangeLogs.ps1

<#
.DESCRIPTION
    Searches the Exchange admin audit logs for a number of possible bad actor activities.
    * New/modified/deleted inbox rules
    * Changes to user forwarding configurations
    * Changes to user mailbox permissions
    * Granting of impersonation rights
    * RBAC changes
.OUTPUTS
    New_Inboxrule.csv
    _Investigate_New_Inboxrule
    Set_InboxRule.csv
    Remove_InboxRules.csv
    Forwarding_Changes.csv
    Impersonation_Roles.csv / Impersonation_Roles.json / Impersonation_Roles.xml
    Impersonation_Rights.csv / Impersonation_Rights.json / Impersonation_Rights.xml
    RBAC_Changes.csv / RBAC_Changes.json / RBAC_Changes.xml
#>
 
Function Get-OspreyTenantExchangeLogs {

    Test-EXOConnection
    Test-GraphConnection
    $InformationPreference = "Continue"

    Out-Logfile "Searching Unified Audit Log for Exchange-related activities."

    # Make sure our values are null
    $TenantNewInboxRules = $Null
    $TenantSetInboxRules = $Null
    $TenantRemoveInboxRules = $Null

    Out-LogFile "Searching for ALL Inbox Rules Created, Modified, or Deleted during the investigation period." -action

    ##Search for the creation of ANY inbox rules##

    $TenantNewInboxRules = Get-AllUnifiedAuditLogEntry -UnifiedSearch ("Search-UnifiedAuditLog -Operations New-InboxRule")

    # If null we found no rules
    if ($null -eq $TenantNewInboxRules) {
        Out-LogFile "No Inbox Rules created during the investigation period found."
    }
    # If not null then we must have found some events so flag them
    else {
        Out-LogFile "New inbox rules have been found"
        # Go thru each rule and prepare it to output to CSV

        $NewRuleReport = foreach ($rule in $TenantNewInboxRules) {
            #throwing all new inbox rules created into custom object
            $rule1 = $rule.auditdata | ConvertFrom-Json
            [PSCustomObject]@{
                CreationTime               = $rule1.CreationTime
                Id                         = $rule1.id
                Operation                  = $rule1.Operation
                UserID                     = $rule1.UserID
                ClientIP                   = $rule1.ClientIP
                RuleName                   = $rule1.Parameters | Where-Object name -eq name | Select-Object -expandproperty value
                SentTo                     = $rule1.Parameters | Where-Object name -eq SentTo | Select-Object -expandproperty value
                ReceivedFrom               = $rule1.Parameters | Where-Object name -eq From | Select-Object -expandproperty value
                FromAddressContains        = $rule1.Parameters | Where-Object name -eq FromAddressContains | Select-Object -expandproperty value
                MoveToFolder               = $rule1.Parameters | Where-Object name -eq MoveToFolder | Select-Object -expandproperty value
                MarkAsRead                 = $rule1.Parameters | Where-Object name -eq MarkAsRead | Select-Object -expandproperty value
                DeleteMessage              = $rule1.Parameters | Where-Object name -eq DeleteMessage | Select-Object -expandproperty value
                SubjectContainsWords       = $rule1.Parameters | Where-Object name -eq SubjectContainsWords | Select-Object -expandproperty value
                SubjectOrBodyContainsWords = $rule1.Parameters | Where-Object name -eq SubjectOrBodyContainsWords | Select-Object -expandproperty value
                ForwardTo                  = $rule1.Parameters | Where-Object name -eq ForwardTo | Select-Object -expandproperty value
            }
        }
        $NewRuleReport | Out-MultipleFileType -fileprefix "New_InboxRule" -csv
        $InvestigateLog = @()
        Foreach ($rule in $NewRuleReport) {
            $Investigate = $false
            if ($rule.DeleteMessage -eq $true) { $Investigate = $true }
            if (!([string]::IsNullOrEmpty($rule.ForwardAsAttachmentTo))) { $Investigate = $true }
            if (!([string]::IsNullOrEmpty($rule.ForwardTo))) { $Investigate = $true }
            if (!([string]::IsNullOrEmpty($rule.RedirectTo))) { $Investigate = $true }
            if ($rule.MoveToFolder -in "Archive", "Conversation History", "RSS Subscription") { $Investigate = $true }
            
            if ($Investigate -eq $true) {
                $InvestigateLog += $rule
                Out-LogFile ("Possible Investigate inbox rule found! ID:" + $rule.Id) -notice
            }
        }
        #if investigation-worthy rules were found, output those to csv.
        if ($null -ne $InvestigateLog) {
            $InvestigateLog | Out-MultipleFileType -fileprefix "_Investigate_New_InboxRule" -csv -notice
        }
    }


    ##Search for the Modification of ANY inbox rules##

    $TenantSetInboxRules = Get-AllUnifiedAuditLogEntry -UnifiedSearch ("Search-UnifiedAuditLog -Operations Set-InboxRule")

    # If null we found no rules modified
    if ($null -eq $TenantSetinboxRules) {
        Out-LogFile "No Inbox Rules modified during the investigation period found."
    }
    # If not null then we must have found some events so flag them
    else {
        Out-LogFile "Modified inbox rules have been found"
        # Go thru each rule and prepare it to output to CSV

        $SetRuleReport = foreach ($rule in $TenantSetInboxRules) {
            #throwing all edited inbox rules created into custom object
            $rule1 = $rule.auditdata | ConvertFrom-Json
            [PSCustomObject]@{
                CreationTime               = $rule1.CreationTime
                Id                         = $rule1.id
                Operation                  = $rule1.Operation
                UserID                     = $rule1.UserID
                ClientIP                   = $rule1.ClientIP
                RuleName                   = $rule1.Parameters | Where-Object name -eq name | Select-Object -expandproperty value
                SentTo                     = $rule1.Parameters | Where-Object name -eq SentTo | Select-Object -expandproperty value
                ReceivedFrom               = $rule1.Parameters | Where-Object name -eq From | Select-Object -expandproperty value
                FromAddressContains        = $rule1.Parameters | Where-Object name -eq FromAddressContains | Select-Object -expandproperty value
                MoveToFolder               = $rule1.Parameters | Where-Object name -eq MoveToFolder | Select-Object -expandproperty value
                MarkAsRead                 = $rule1.Parameters | Where-Object name -eq MarkAsRead | Select-Object -expandproperty value
                DeleteMessage              = $rule1.Parameters | Where-Object name -eq DeleteMessage | Select-Object -expandproperty value
                SubjectContainsWords       = $rule1.Parameters | Where-Object name -eq SubjectContainsWords | Select-Object -expandproperty value
                SubjectOrBodyContainsWords = $rule1.Parameters | Where-Object name -eq SubjectOrBodyContainsWords | Select-Object -expandproperty value
                ForwardTo                  = $rule1.Parameters | Where-Object name -eq ForwardTo | Select-Object -expandproperty value
            }
        }
        $SetRuleReport | Out-MultipleFileType -fileprefix "Set_InboxRule" -csv
    }

    ##Search for the deletion of ALL Inbox Rules##

    #This kinda sucks as the remove-inboxrule record doesn't have a lot of information :c
    $TenantRemoveInboxRules = Get-AllUnifiedAuditLogEntry -UnifiedSearch ("Search-UnifiedAuditLog -Operations Remove-InboxRule")

    if ($null -eq $TenantRemoveinboxRules) {
        Out-LogFile "No Inbox Rules deleted during the investigation period found."
    }
    # If not null then we must have found some events so flag them
    else {
        Out-LogFile "Deleted inbox rules have been found"
        # Go thru each rule and prepare it to output to CSV

        $RemoveRuleReport = foreach ($rule in $TenantRemoveInboxRules) {
            #throwing all new inbox rules created into custom object
            $rule1 = $rule.auditdata | ConvertFrom-Json
            [PSCustomObject]@{
                CreationTime = $rule1.CreationTime
                Id           = $rule1.id
                Operation    = $rule1.Operation
                UserID       = $rule1.UserID
                ClientIP     = $rule1.ClientIP
                Identity     = $rule1.Parameters | Where-Object name -eq Identity | Select-Object -expandproperty value
            }
        }
        $RemoveRuleReport | Out-MultipleFileType -fileprefix "Remove_InboxRule" -csv
    }


    ##Look for changes to user forwarding##

    Out-LogFile "Searching for changes to user forwarding" -action
    # Getting records from UAL where user forwarding was changed, either enabled or disabled

    $TenantForwardingChanges = Get-AllUnifiedAuditLogEntry -UnifiedSearch ("Search-UnifiedAuditLog -Operations Set-Mailbox -FreeText ForwardingSmtpAddress")
    # If null we found forwarding changes
    if ($null -eq $TenantForwardingChanges) {
        Out-LogFile "No forwarding changes during the investigation period found."
    }
    # If not null then we must have found some events so flag them
    else {
        Out-LogFile "Forwarding changes have been found"
        # Go thru each log and prepare it to output to CSV
        $ForwardingChangeReport = Foreach ($log in $TenantForwardingChanges) {
            $log1 = $log.auditdata | ConvertFrom-Json
            [PSCustomObject]@{
                CreationTime      = $log1.CreationTime
                Id                = $log1.id
                Operation         = $log1.Operation
                UserID            = $log1.UserID
                ClientIP          = $log1.ClientIP
                ForwardingStatus  = $log1.Parameters | Where-Object name -eq DeliverToMailboxAndForward | Select-Object -expandproperty value
                ForwardingAddress = $log1.Parameters | Where-Object name -eq ForwardingSmtpAddress | Select-Object -expandproperty value
            }
        }
        $ForwardingChangeReport | Out-MultipleFileType -fileprefix "Forwarding_Changes" -csv
    }
    

    ##Look for changes to mailbox permissions##

    Out-LogFile "Searching for changes to mailbox permissions" -Action
    $TenantMailboxPermissionChanges = Get-AllUnifiedAuditLogEntry -UnifiedSearch ("Search-UnifiedAuditLog -Operations Add-MailboxPermission")

    #Expanding changes and exporting raw
    $MailboxChangesExpanded = $TenantMailboxPermissionChanges | Select-object -ExpandProperty AuditData | ConvertFrom-Json
    $MailboxChangesExpanded | Out-MultipleFileType -fileprefix "Unfiltered_Mailbox_Permission_Changes" -csv -json

    #Filtering out system changes
    $MailboxChangesFiltered = $MailboxChangesExpanded | Where-Object { $_.UserId -notlike "NT AUTHORITY\SYSTEM*" }

    if ($null -eq $MailboxChangesFiltered) {
        Out-LogFile "No permission changes during the investigation period found."
    }
    # If not null then we must have found some events so flag them
    else {
        Out-LogFile "Mailbox permission changes have been found."
        # Go thru each log and prepare it to output to CSV
        $PermissionChangesReport = foreach ($change in $MailboxChangesFiltered) {
            $TargetID = $change.Parameters | Where-Object Name -eq Identity | Select-Object -expandproperty Value
            $AccessID = $change.Parameters | Where-Object Name -eq User | Select-Object -expandproperty Value
            $TargetName = Get-MgUser -userid $TargetID -erroraction SilentlyContinue | Select-Object -ExpandProperty DisplayName
            $TargetUPN = Get-MgUser -userid $TargetID -erroraction SilentlyContinue | Select-Object -ExpandProperty UserPrincipalName
            $UserWithAccessName = Get-MgUser -userid $AccessID  -erroraction SilentlyContinue | Select-Object -ExpandProperty DisplayName
            $UserWithAccessUPN = Get-MgUser -userid $AccessID -erroraction SilentlyContinue | Select-Object -ExpandProperty UserPrincipalName
            [PSCustomObject]@{
                CreationTime       = $change.CreationTime
                ID                 = $change.Id
                Operation          = $change.Operation
                UserMakingChange   = $change.UserId
                ClientIP           = $change.ClientIP
                TargetName         = $TargetName
                TargetUPN          = $TargetUPN
                UserWithAccessName = $UserWithAccessName
                UserWithAccessUPN  = $UserWithAccessUPN
                AccessRights       = $change.Parameters | Where-Object Name -eq AccessRights | Select-Object -expandproperty Value
            }
            if ($null -in $TargetName, $TargetUPN, $UserWithAccessName, $UserWithAccessUPN) { 
                Out-Logfile ("Warning, failed to extract target or user information from record ID: " + $change.Id)
            }
        }
        $PermissionChangesReport | Out-MultipleFileType -fileprefix "Mailbox_Permission_Changes" -csv 
    }


    ##Looking for changes to impersonation access##

    Out-LogFile "Searching Impersonation Access" -action
    [array]$TenantImpersonatingRoles = Get-ManagementRoleEntry "*\Impersonate-ExchangeUser"
    $TenantImpersonatingRoles | Out-MultipleFileType -fileprefix "Impersonation_Roles" -csv -json -xml
    if ($TenantImpersonatingRoles.count -gt 1) {
        Out-LogFile ("Found " + $TenantImpersonatingRoles.count + " Impersonation Roles. Default is 1") -notice
    }

    $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
    }
    $Output | Out-MultipleFileType -fileprefix "Impersonation_Rights" -csv -json -xml
    if ($Output.count -gt 1) {
        Out-LogFile ("Found " + $Output.count + " Users/Groups with Impersonation rights. Default is 1") -notice
    }
    elseif ($Output.count -eq 1) {
        Out-LogFile ("Found default number of Impersonation users")
    }


    ##Look for any changes to RBAC##

    Out-LogFile "Gathering any changes to RBAC configuration" -action
    $RBACOps = ('Add-ManagementRoleEntry,Add-RoleGroupMember,New-ManagementRole,New-ManagementRoleAssignment,New-ManagementScope,New-RoleAssignmentPolicy,New-RoleGroup,Remove-ManagementRole,Remove-ManagementRoleAssignment,Remove-ManagementRoleEntry,Remove-ManagementScope,Remove-RoleAssignmentPolicy,Remove-RoleGroup,Remove-RoleGroupMember,Set-ManagementRoleAssignment,Set-ManagementRoleEntry,Set-ManagementScope,Set-RoleAssignmentPolicy,Set-RoleGroup,Update-RoleGroupMember')
    [array]$RBACChanges = Get-AllUnifiedAuditLogEntry -UnifiedSearch ("Search-UnifiedAuditLog -operations $RBACOps")

    # If there are any results push them to an output file
    if ($RBACChanges.Count -gt 0) {
        Out-LogFile ("Found " + $RBACChanges.Count + " Changes made to Roles Based Access Control") -notice
        $RBACChanges | Out-MultipleFileType -FilePrefix "RBAC_Changes" -csv -xml -json
    }
    # Otherwise report no results found
    else {
        Out-Logfile "No RBAC Changes found."
    }
}