|

Detecting & Stopping Hidden Mailbox Forwarding Rules in Microsoft 365: PowerShell Threat Hunting Runbook (2026)

🛡️ 30-Second Fast Triage: One-Line Tenant Forwarding Audit

Zero-Trust SecOps

Audit Both Mailbox Forwarding AND Client Inbox Rules:
Get-Mailbox -ResultSize Unlimited | Where-Object { $_.ForwardingAddress -ne $null -or $_.ForwardingSmtpAddress -ne $null } | Select-Object DisplayName, UserPrincipalName, ForwardingSmtpAddress, DeliverToMailboxAndForward

Note: Forwarding happens at TWO levels: (1) Mailbox properties (above) and (2) Client-side Inbox Rules (e.g. Get-InboxRule). Both must be scanned to detect Business Email Compromise (BEC).

In modern enterprise cyberattacks, **Business Email Compromise (BEC)** remains the #1 vector for financial fraud and data exfiltration. When an adversary gains unauthorized access to a Microsoft 365 mailbox via credential stuffing or AiTM phishing, their very first operational persistence technique is rarely changing the password:

They create an automated forwarding rule—often named with a single period (.) or blank whitespace—that silently copies every inbound invoice, payroll memo, or customer correspondence to an external rogue email address.

Even if the SOC resets the user’s password and revokes their refresh tokens, the forwarding rule continues to siphon sensitive company emails silently in the background.

In this production threat-hunting runbook, we provide complete, battle-tested PowerShell automation scripts to audit mailbox-level forwarding, detect obfuscated and hidden client inbox rules, disable malicious rules, and enforce tenant-wide anti-forwarding policies in Exchange Online Protection.


1. The Two Vectors of Mailbox Forwarding in Microsoft 365

Security engineers frequently miss active forwarders because they only check one of the two mechanisms in Exchange Online:

Forwarding Type Where Configured Detection Cmdlet
1. Mailbox Level (SMTP Forwarding) Configured in Exchange Admin Center or via Set-Mailbox. Get-Mailbox | Select ForwardingSmtpAddress
2. Client Level (Inbox Rules) Configured by the user (or attacker) inside Outlook or OWA. Get-InboxRule -Mailbox <user>

2. Vector 1: Audit Mailbox-Level Forwarding Across the Entire Tenant

Run this script to immediately identify any mailbox configured to forward mail externally:

Connect-ExchangeOnline -ShowBanner:$false

$ForwardingMailboxes = Get-EXOMailbox -ResultSize Unlimited -Properties ForwardingAddress, ForwardingSmtpAddress, DeliverToMailboxAndForward | 
    Where-Object { $_.ForwardingAddress -ne $null -or $_.ForwardingSmtpAddress -ne $null }

if ($ForwardingMailboxes.Count -gt 0) {
    Write-Host "ALERT: Found $($ForwardingMailboxes.Count) mailboxes with active forwarding!" -ForegroundColor Red
    $ForwardingMailboxes | Select-Object DisplayName, UserPrincipalName, ForwardingSmtpAddress, ForwardingAddress, DeliverToMailboxAndForward | Format-Table -AutoSize
} else {
    Write-Host "CLEAN: No mailbox-level forwarding found." -ForegroundColor Green
}

How to Remediate Immediately:

Set-Mailbox -Identity "compromised@domain.com" -ForwardingSmtpAddress $null -ForwardingAddress $null

3. Vector 2: Hunting Hidden & External Client Inbox Rules

Adversaries know administrators look at mailbox properties, so 90% of BEC attacks leverage Client Inbox Rules instead. They configure rules with actions like ForwardTo, ForwardAsAttachmentTo, or RedirectTo, and often add an action to DeleteMessage so the legitimate user never sees the email arrive.

Use this multi-threaded PowerShell threat hunting script to scan every inbox rule in your tenant:

# ==============================================================================
# Script: Hunt-MaliciousInboxRules.ps1
# Description: Scans all tenant mailboxes for inbox rules forwarding to external domains.
# Author: Satya Pathak (SkypeExchange4U.com)
# ==============================================================================

Write-Host "Connecting to Exchange Online..." -ForegroundColor Cyan
Connect-ExchangeOnline -ShowBanner:$false

$AcceptedDomains = (Get-AcceptedDomain).DomainName.Domain

Write-Host "Scanning all user mailboxes for suspicious forwarding rules..." -ForegroundColor Yellow
$AllMailboxes = Get-EXOMailbox -RecipientTypeDetails UserMailbox -ResultSize Unlimited

$SuspiciousRules = [System.Collections.Generic.List[PSCustomObject]]::new()

foreach ($MBX in $AllMailboxes) {
    try {
        $Rules = Get-InboxRule -Mailbox $MBX.UserPrincipalName -ErrorAction SilentlyContinue
        
        foreach ($Rule in $Rules) {
            # Collect all forward recipients
            $Recipients = @()
            if ($Rule.ForwardTo) { $Recipients += $Rule.ForwardTo }
            if ($Rule.ForwardAsAttachmentTo) { $Recipients += $Rule.ForwardAsAttachmentTo }
            if ($Rule.RedirectTo) { $Recipients += $Rule.RedirectTo }
            
            foreach ($Recipient in $Recipients) {
                # Extract email address
                $TargetEmail = if ($Recipient -match "<(.*?)>") { $Matches[1] } else { $Recipient }
                
                # Check if recipient is external
                $Domain = ($TargetEmail -split "@")[-1]
                if ($Domain -and ($AcceptedDomains -notcontains $Domain)) {
                    $SuspiciousRules.Add([PSCustomObject]@{
                        UserMailbox      = $MBX.UserPrincipalName
                        RuleName         = $Rule.Name
                        RuleEnabled      = $Rule.Enabled
                        ExternalTarget   = $TargetEmail
                        Action           = if ($Rule.ForwardTo) { "ForwardTo" } elseif ($Rule.RedirectTo) { "RedirectTo" } else { "ForwardAsAttachment" }
                        DeletesMessage   = $Rule.DeleteMessage
                    })
                }
            }
        }
    }
    catch {
        Write-Warning "Could not read rules for $($MBX.UserPrincipalName): $_"
    }
}

if ($SuspiciousRules.Count -gt 0) {
    Write-Host "`nCRITICAL: Found $($SuspiciousRules.Count) suspicious external forwarding rules!" -ForegroundColor Red
    $SuspiciousRules | Format-Table -AutoSize
    
    # Export to CSV for incident response documentation
    $SuspiciousRules | Export-Csv -Path "M365_Suspicious_Rules_Report_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv" -NoTypeInformation
    Write-Host "Report exported to CSV." -ForegroundColor Green
} else {
    Write-Host "`nCLEAN: No external forwarding inbox rules detected." -ForegroundColor Green
}

4. How to Delete Malicious Rules in Seconds

When you identify a malicious rule ID from the output above, remove it immediately using PowerShell:

# Disable the rule immediately
Disable-InboxRule -Mailbox "compromised@domain.com" -Identity "<RuleId-or-Name>"

# Permanently delete the rule
Remove-InboxRule -Mailbox "compromised@domain.com" -Identity "<RuleId-or-Name>" -Confirm:$false

5. Tenant-Wide Prevention: Enforce Anti-Forwarding in Outbound Anti-Spam Policy

Rather than reacting to malicious rules after data has already leaked, Microsoft 365 allows you to block all external automatic forwarding globally at the transport layer.

The Best Practice: Set AutoForwardingMode to Off

When configured, if a compromised account attempts to auto-forward an email to Gmail or Yahoo, Exchange Online Protection silently drops the forward and generates NDR 550 5.7.520 Access denied, Your organization does not allow external forwarding.

PowerShell to Enforce Tenant-Wide Block:

# Check current outbound anti-spam policy
Get-HostedOutboundSpamFilterPolicy -Identity "Default" | Select-Object Name, AutoForwardingMode

# Enforce block
Set-HostedOutboundSpamFilterPolicy -Identity "Default" -AutoForwardingMode Off

6. Incident Response Checklist for Compromised Mailboxes

  1. Revoke All Refresh Tokens: Revoke-AzureADUserAllRefreshToken -ObjectId <UserGUID>
  2. Reset Password & Mandate FIDO2/MFA: Ensure modern phishing-resistant authentication.
  3. Run Hunt-MaliciousInboxRules.ps1: Remove any client rules forwarding or deleting messages.
  4. Clear Mailbox-Level Forwarding: Ensure ForwardingSmtpAddress is blank.
  5. Verify Outbound Anti-Spam Policy: Ensure AutoForwardingMode is set to Off.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *