Detect suspicious mailbox rules and forwarding after compromise
For developers and responders investigating a potentially compromised mailbox. This walks you through finding hidden inbox rules, forwarding settings, and delegate access in Microsoft 365 and Google Workspace, then proving the malicious persistence is gone.
TL;DR — After a mailbox compromise, attackers often leave persistence by adding inbox rules, SMTP forwarding, delegates, or hidden rules that keep exfiltrating mail after the password reset. The fastest win is to enumerate forwarding and rules from the admin/API side, remove anything you cannot attribute, then verify with a test message that no copy leaves the tenant. Reading time: ~5 min
Goal
When you finish, you will have a verified list of mailbox rules, forwarding settings, and delegate permissions for the affected account, removed any suspicious entries, and confirmed with a test message that mail is no longer being silently redirected.
Prerequisites
- Admin access to the mail platform for the affected mailbox:
- Microsoft 365/Exchange Online: Exchange admin or a role that can run mailbox/rule cmdlets
- Google Workspace: super admin or delegated admin with Gmail settings access
- The affected mailbox address, example:
alice@example.com - A shell with one of these toolsets:
- PowerShell 7.x — check with
pwsh --version - Google Cloud SDK or
curl+ OAuth token tooling if you use the Gmail API
- PowerShell 7.x — check with
- For Microsoft 365 PowerShell:
- Exchange Online module installed — check with
Get-Module ExchangeOnlineManagement -ListAvailable
- Exchange Online module installed — check with
- For Google Workspace API checks:
jq >= 1.6— check withjq --version
- A known-good external mailbox you control for verification, example:
security-test@external.tld - The compromise window or at least the date the account was reset; you will use it to judge whether a rule is expected
Steps
Step 1: Enumerate forwarding and inbox rules in Microsoft 365
Run these commands from PowerShell if the mailbox is in Exchange Online.
pwsh
Connect-ExchangeOnline
$mailbox = "alice@example.com"
Get-EXOMailbox -Identity $mailbox | Format-List DisplayName,PrimarySmtpAddress,ForwardingAddress,ForwardingSmtpAddress,DeliverToMailboxAndForward
Get-InboxRule -Mailbox $mailbox | Select-Object Name,Enabled,Priority,Description,RedirectTo,ForwardTo,ForwardAsAttachmentTo,DeleteMessage,MoveToFolder,MarkAsRead,StopProcessingRules | Format-Table -AutoSize
Get-EXOMailboxPermission -Identity $mailbox | Where-Object { $_.User -notlike "NT AUTHORITY\\SELF" } | Select-Object User,AccessRights,IsInherited,Deny | Format-Table -AutoSize
Get-EXORecipientPermission -Identity $mailbox | Select-Object Trustee,AccessRights,IsInherited | Format-Table -AutoSize
Success looks like mailbox properties plus a table of rules; suspicious lines usually have ForwardTo, RedirectTo, ForwardAsAttachmentTo, or unknown delegates.
Example output shape:
DisplayName : Alice Example
PrimarySmtpAddress : alice@example.com
ForwardingAddress :
ForwardingSmtpAddress : attacker.collector@proton.me
DeliverToMailboxAndForward : True
Name Enabled Priority Description RedirectTo ForwardTo
---- ------- -------- ----------- ---------- ---------
RSS True 1 If from boss move to Archive
invoice-export True 2 If subject contains invoice {acctdump@outlook.com}
Step 2: Check for hidden rules and transport-level forwarding in Microsoft 365
Attackers sometimes create rules not obvious in Outlook. Use MFCMAPI or EWS only if you already have that workflow; from admin PowerShell, first clear the common persistence points you can inspect directly.
⚠️ Removing forwarding or delegates can interrupt legitimate business workflows. Before deleting, export the current state to a file and get an owner sign-off if the mailbox is shared or executive.
$mailbox = "alice@example.com"
Get-InboxRule -Mailbox $mailbox | Export-Csv -NoTypeInformation ".\$($mailbox)-inbox-rules.csv"
Get-EXOMailbox -Identity $mailbox | Export-Clixml ".\$($mailbox)-mailbox.xml"
Get-TransportRule | Select-Object Name,State,Mode,Comments,Description | Format-Table -AutoSize
Success looks like two local export files and a transport rule list you can review for org-wide forwarding or redirection.
Step 3: Remove suspicious Microsoft 365 rules, forwarding, and delegates
Use the exact rule names and addresses you found. Replace the examples below with the actual suspicious values.
$mailbox = "alice@example.com"
Disable-InboxRule -Mailbox $mailbox -Identity "invoice-export"
Remove-InboxRule -Mailbox $mailbox -Identity "invoice-export"
Set-Mailbox -Identity $mailbox -ForwardingSmtpAddress $null -ForwardingAddress $null -DeliverToMailboxAndForward $false
Remove-MailboxPermission -Identity $mailbox -User "unknown.user@example.com" -AccessRights FullAccess -InheritanceType All -Confirm:$false
Remove-RecipientPermission -Identity $mailbox -Trustee "unknown.user@example.com" -AccessRights SendAs -Confirm:$false
Success looks like no errors and subsequent Get-* commands returning blank forwarding fields and no suspicious rule/delegate entries.
Step 4: Enumerate forwarding, filters, and delegates in Google Workspace
If the mailbox is Gmail in Google Workspace, use the Gmail API. First obtain an OAuth access token with Gmail settings scopes using your standard admin workflow, then run these commands.
export TOKEN="ya29...."
export USER="alice@example.com"
curl -s -H "Authorization: Bearer $TOKEN" \
"https://gmail.googleapis.com/gmail/v1/users/$USER/settings/autoForwarding" | jq
curl -s -H "Authorization: Bearer $TOKEN" \
"https://gmail.googleapis.com/gmail/v1/users/$USER/settings/forwardingAddresses" | jq
curl -s -H "Authorization: Bearer $TOKEN" \
"https://gmail.googleapis.com/gmail/v1/users/$USER/settings/filters" | jq '.filter[] | {id,criteria,action}'
curl -s -H "Authorization: Bearer $TOKEN" \
"https://gmail.googleapis.com/gmail/v1/users/$USER/settings/delegates" | jq
Success looks like JSON objects. Suspicious entries usually show enabled: true under autoForwarding, unknown addresses in forwardingAddresses, filters with forward, removeLabelIds: ["INBOX"], or unknown delegates.
Example output shape:
{
"enabled": true,
"emailAddress": "collector@external.tld",
"disposition": "leaveInInbox"
}
Step 5: Remove suspicious Gmail forwarding, filters, and delegates
Delete only the entries you cannot attribute. Use the IDs returned in the previous step.
export FILTER_ID="1234567890"
export FWD_ADDR="collector@external.tld"
export DELEGATE="unknown.user@example.com"
curl -s -X PATCH -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
"https://gmail.googleapis.com/gmail/v1/users/$USER/settings/autoForwarding" \
-d '{"enabled":false}' | jq
curl -s -X DELETE -H "Authorization: Bearer $TOKEN" \
"https://gmail.googleapis.com/gmail/v1/users/$USER/settings/filters/$FILTER_ID" -i
curl -s -X DELETE -H "Authorization: Bearer $TOKEN" \
"https://gmail.googleapis.com/gmail/v1/users/$USER/settings/forwardingAddresses/$FWD_ADDR" -i
curl -s -X DELETE -H "Authorization: Bearer $TOKEN" \
"https://gmail.googleapis.com/gmail/v1/users/$USER/settings/delegates/$DELEGATE" -i
Success looks like HTTP/2 204 for deletes and a JSON response with "enabled": false for auto-forwarding.
Step 6: Send a verification message and confirm no exfil path remains
Use a unique subject so you can search logs and mailbox contents.
View as text
[Admin Mailbox] ---- 1. send test message ----> [Affected Mailbox] [Affected Mailbox] ---- 2. inbox rule/filter eval ----> [Mail Platform] [Mail Platform] ---- 3. no forward/redirect ----> [No External Copy]
Send a message from an internal admin mailbox to the affected user with subject XR-VERIFY-2026-10-30-001, then search for the same subject in the affected mailbox and in any previously suspicious external destination you control.
For Microsoft 365, re-run:
$mailbox = "alice@example.com"
Get-EXOMailbox -Identity $mailbox | Format-List ForwardingAddress,ForwardingSmtpAddress,DeliverToMailboxAndForward
Get-InboxRule -Mailbox $mailbox | Select-Object Name,Enabled,RedirectTo,ForwardTo,ForwardAsAttachmentTo | Format-Table -AutoSize
For Gmail, re-run:
curl -s -H "Authorization: Bearer $TOKEN" \
"https://gmail.googleapis.com/gmail/v1/users/$USER/settings/autoForwarding" | jq
curl -s -H "Authorization: Bearer $TOKEN" \
"https://gmail.googleapis.com/gmail/v1/users/$USER/settings/filters" | jq
Success looks like empty forwarding targets, no suspicious rules/filters, and only one delivered copy in the affected mailbox.
Verify it works
Run the platform-specific readback commands again and confirm the exact absence of forwarding.
Microsoft 365 expected shape:
ForwardingAddress :
ForwardingSmtpAddress :
DeliverToMailboxAndForward : False
Name Enabled RedirectTo ForwardTo ForwardAsAttachmentTo
---- ------- ---------- --------- ---------------------
RSS True
Google Workspace expected shape:
{
"enabled": false,
"disposition": "leaveInInbox"
}
For delete calls, expected HTTP response shape:
HTTP/2 204
content-type: application/json; charset=UTF-8
End-to-end proof is: the test message with subject XR-VERIFY-2026-10-30-001 appears in the affected mailbox, and no copy appears at any previously suspicious destination.
Common pitfalls
You only reset the password and never checked forwarding
Mistake: stopping after credential reset or MFA reset. Symptom: the user regains access, but mail still leaks to an external address. Fix: run the exact mailbox/rule enumeration commands above before closing the incident.
You removed inbox rules but left mailbox-level forwarding enabled
Mistake: deleting visible rules and assuming that covers forwarding. Symptom: Get-InboxRule is clean, but mail still forwards because ForwardingSmtpAddress or Gmail autoForwarding.enabled is still set. Fix: clear Set-Mailbox -ForwardingSmtpAddress $null -ForwardingAddress $null -DeliverToMailboxAndForward $false or PATCH Gmail auto-forwarding to false.
You ignored delegates and SendAs permissions
Mistake: checking only filters/rules. Symptom: no forwarding rule exists, but another account can still read or send as the user. Fix: enumerate and remove FullAccess/SendAs or Gmail delegates with the commands above.
You trusted the GUI and missed hidden or stale state
Mistake: relying on one admin screen or one user client view. Symptom: Outlook or Gmail UI looks clean, but API/PowerShell still returns forwarding or a filter. Fix: use admin-side readback commands/API responses as the source of truth and export before/after state.
You deleted a legitimate business rule without recording it
Mistake: removing everything that looks unfamiliar from a shared mailbox or executive assistant workflow. Symptom: support noise later because routing or filing stopped working. Fix: export rules and permissions first, then remove only entries that are unowned, recently added, or point to unknown external recipients.
This article was written by an AI system and published pending human review. Verify anything you intend to act on.
Have a project in mind?
Get an instant AI price estimate for it, or talk directly to our team.
One email a month on what we learn building with AI