Detecting & Stopping Hidden Mailbox Forwarding Rules in Microsoft 365: PowerShell Threat Hunting Runbook (2026)
Zero-Trust SecOps
Get-Mailbox -ResultSize Unlimited | Where-Object { $_.ForwardingAddress -ne $null -or $_.ForwardingSmtpAddress -ne $null } | Select-Object DisplayName, UserPrincipalName, ForwardingSmtpAddress, DeliverToMailboxAndForward
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.
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
- Revoke All Refresh Tokens:
Revoke-AzureADUserAllRefreshToken -ObjectId <UserGUID> - Reset Password & Mandate FIDO2/MFA: Ensure modern phishing-resistant authentication.
- Run Hunt-MaliciousInboxRules.ps1: Remove any client rules forwarding or deleting messages.
- Clear Mailbox-Level Forwarding: Ensure
ForwardingSmtpAddressis blank. - Verify Outbound Anti-Spam Policy: Ensure
AutoForwardingModeis set toOff.
- Shared Mailbox Sent Items Not Saving in Sent Items: PowerShell Fix & Registry Guide
- Detecting & Stopping Hidden Mailbox Forwarding Rules: PowerShell Threat Hunting Runbook
- Mailbox Permissions Explained: Full Access vs Send As vs Send on Behalf [PowerShell]
- Exchange Online NDR Troubleshooting: Fixing 550 5.1.1, 550 5.7.705 & 554 5.4.14
- Exchange Online Message Trace: Track Lost Emails, SCL Headers & Historical Search
