Home / Blog / Incident Response
How to Investigate a Malicious Inbox Rule in Microsoft 365
A practical playbook for investigating a suspicious Microsoft 365 inbox rule: read the rule, scope the account, check what mail was exposed and contain it cleanly.
A new inbox rule is often the first visible sign of a business email compromise, because attackers use rules to hide replies and quietly copy mail out of the mailbox. This guide helps SOC analysts and incident responders work from a single rule alert to a clear answer: what the attacker did, what they could read, and how to remove their access. It focuses on Exchange Online, Microsoft Defender XDR and Microsoft Sentinel.
Scenario: Defender raises a suspicious inbox manipulation alert for finance.user@example.com. A rule named . moves any message containing "invoice" or "payment" to RSS Feeds and marks it as read. The rule was created from 203.0.113.50, an address the user has never signed in from. Nobody has reported anything yet, which is exactly why you need to move quickly.
Preserve the rule before you touch it
Your first instinct may be to delete the rule. Export it first, because the rule's name, keywords and destination are evidence. They show what the attacker cared about, and they give you search terms for the rest of the tenant.
Connect-ExchangeOnline
Get-InboxRule -Mailbox finance.user@example.com -IncludeHidden |
Select-Object Name, Enabled, Priority, RuleIdentity, Description, From,
SubjectContainsWords, BodyContainsWords, SubjectOrBodyContainsWords,
ForwardTo, ForwardAsAttachmentTo, RedirectTo, MoveToFolder, DeleteMessage, MarkAsRead |
Export-Csv .\inbox-rules-finance-user.csv -NoTypeInformation
Get-Mailbox finance.user@example.com |
Select-Object ForwardingSmtpAddress, ForwardingAddress, DeliverToMailboxAndForward
The second command matters because forwarding can be configured on the mailbox itself, with no inbox rule at all. Attackers who know that a rule review is coming may use both.
Watch out: Get-InboxRule does not work for members of the View-Only Organization Management role group or for the Global Reader role, so check your access before an incident. ATT&CK also notes that attackers can hide rules by changing their properties through MAPI. If activity continues after a clean result, examine the mailbox with a MAPI editor.
Find out when and how the rule was made
Next, get the audit record. Exchange records rule changes under several operations, and you need all of them. New-InboxRule and Set-InboxRule usually come from Outlook on the web or PowerShell. UpdateInboxRules records changes made from the Outlook client, and it may not show the same detail, so always compare it with the export above.
Defender XDR version
let user_id = "00000000-0000-0000-0000-000000000000";
CloudAppEvents
| where Timestamp > ago(30d)
| where AccountObjectId == user_id
| where Application == @"Microsoft Exchange Online"
| where ActionType in ("Set-Mailbox", "New-InboxRule", "Set-InboxRule", "UpdateInboxRules")
| project Timestamp, ActionType, CountryCode, City, ISP, IPAddress, RuleConfig = RawEventData.Parameters, RawEventData
| order by Timestamp asc
Sentinel version
OfficeActivity
| where TimeGenerated > ago(30d)
| where OfficeWorkload == "Exchange"
| where Operation in ("New-InboxRule", "Set-InboxRule", "UpdateInboxRules", "Set-Mailbox")
| where UserId =~ "finance.user@example.com"
| project TimeGenerated, Operation, UserId, ClientIP, ResultStatus, OfficeObjectId, Parameters
| order by TimeGenerated asc
Note: ClientIP in OfficeActivity can include a port number, so normalise it before you compare it with sign-in logs. The query above keeps the raw value for your notes.
Read the rule like an attacker would
Microsoft's alert playbooks focus on three things: keyword filters, the destination folder or address, and rules that act on every message. Compare what you see against what this user would plausibly set up for themselves.
External forwarding
HighForwardTo, RedirectTo or ForwardAsAttachmentTo pointing to an address outside the company, especially with no filter.
Search for the same destination across all mailboxes and trace what was sent there.
Financial keyword hiding
HighSubjectOrBodyContainsWords with terms like invoice, payment or bank, plus MoveToFolder and MarkAsRead.
Treat as likely payment fraud and alert the finance team through a separate channel.
Delete everything
HighDeleteMessage with no keyword filter, which hides replies and security warnings from the user.
Check the Deleted Items and Recoverable Items folders for what the rule removed.
Odd name or folder
MediumNames like . or .., or messages moved to rarely opened folders such as RSS Feeds or Archive.
Not proof on its own. Confirm with the source IP and the user's sign-in history.
Forward to a personal address
MediumThe user may have forwarded mail home, or the address may look personal but belong to the attacker.
Ask the user through a verified channel, and raise a policy breach if they did it themselves.
Scope the compromise
The rule is a symptom, so now look for the access behind it. Start from the IP address on the rule event. Check it across every user, because the same attacker often holds several accounts in one tenant.
let suspectIp = "203.0.113.50";
union SigninLogs, AADNonInteractiveUserSignInLogs
| where TimeGenerated > ago(14d)
| where IPAddress == suspectIp
| summarize Attempts = count(), Apps = make_set(AppDisplayName), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated) by UserPrincipalName, ResultType
| order by FirstSeen asc
Non-interactive sign-ins matter here. A stolen session or refresh token often shows up there, and looking only at interactive sign-ins can make the attacker's activity seem much shorter than it was.
Then find other mailboxes with the same rule logic. Search for the forwarding address, and for any keywords from the rule that look unusual.
let destination = "collector@example.net";
OfficeActivity
| where TimeGenerated > ago(30d)
| where OfficeWorkload == "Exchange"
| where Operation in ("New-InboxRule", "Set-InboxRule", "Set-Mailbox")
| where Parameters has destination
| project TimeGenerated, Operation, UserId, ClientIP, Parameters
Finally, check what happened to the account before and after the rule. Look for new authentication methods and application consents, which are common ways of keeping access after a password reset.
let account = "finance.user@example.com";
AuditLogs
| where TimeGenerated > ago(14d)
| where OperationName has_any ("security info", "Consent to application", "delegated permission grant")
| where tostring(InitiatedBy) has account or tostring(TargetResources) has account
| project TimeGenerated, OperationName, Result, InitiatedBy, TargetResources
| order by TimeGenerated asc
Work out what mail was exposed
Managers, legal and privacy teams will all ask this question. You need three sources to answer it well.
Search-UnifiedAuditLog -StartDate 2026-10-28 -EndDate 2026-11-04 `
-UserIds finance.user@example.com -Operations MailItemsAccessed -ResultSize 1000
Get-MessageTraceV2 -RecipientAddress collector@example.net `
-StartDate 2026-10-28 -EndDate 2026-11-04 -ResultSize 5000
Get-MessageTraceV2 -SenderAddress finance.user@example.com `
-StartDate 2026-10-28 -EndDate 2026-11-04 -ResultSize 5000
Get-MessageTraceV2 can search the last 90 days, but each query returns a maximum of 10 days of data. For a longer window, run several queries and combine the results. If you hit the result limit, narrow your dates, as the cmdlet is also throttled to 100 requests in five minutes.
Tip: Be careful how you word your conclusion. Say "no evidence of access in the available logs", and state your log coverage, rather than "no mail was read". MailItemsAccessed can be throttled for very active mailboxes, and audit retention depends on your licence and settings.
Contain the account in the right order
If you delete the rule first, you tell the attacker you have noticed, and they still have access. Containment works best as a short planned sequence, ideally completed in a single sitting.
- 1PreserveSave the rule export, mailbox forwarding settings and your query results to the case record.Analyst
- 2Cut accessReset the password and revoke all sessions so that existing refresh tokens stop working.Identity team
- 3Remove persistenceDisable the rules, clear mailbox forwarding, and review MFA methods and application consents.Messaging team
- 4MonitorWatch the account and the suspect IP for at least several days, and expect another attempt.SOC
# Revoke sessions (reset the password as part of the same step)
Connect-MgGraph -Scopes "User.RevokeSessions.All"
Revoke-MgUserSignInSession -UserId finance.user@example.com
# Disable the malicious rule, keeping it available for review
Disable-InboxRule -Mailbox finance.user@example.com -Identity "."
# Clear any mailbox level forwarding
Set-Mailbox finance.user@example.com -ForwardingSmtpAddress $null -ForwardingAddress $null
Microsoft notes that there may be a delay of a few minutes before tokens are revoked after you call revokeSignInSessions, so check for later activity rather than assuming it has stopped. Use the rule's RuleIdentity rather than the name if several rules share the same name.
Watch out: A password reset alone may not remove an attacker who registered their own MFA method or got a user to consent to an application. Complete the audit log review above before you hand the account back, or you may have to do this all over again.
Close the loop
The technical work is only part of a business email compromise. Before you close the case, work through the items below.
- Tell the finance team and any affected suppliers through a verified channel, such as a known phone number, not by replying to the mail thread.
- Check whether any payments or bank detail changes were requested or approved during the attacker's window of access.
- Give your legal and privacy teams the list of mail exposed, so that they can decide about any notification duties.
- Search the tenant for other sign-ins from the same IP and ISP, and for lures sent from the compromised mailbox to colleagues.
- Block external forwarding by default if you have not done so. ATT&CK lists this as a mitigation, and
Set-HostedOutboundSpamFilterPolicy -AutoForwardingMode Offis the Exchange Online setting to review. - Record the rule pattern in your hunting library, and turn it into a detection if your existing alert did not catch it first.
A simple hunting query can run on a schedule after the incident. It will produce some false positives, but it surfaces rules worth a quick look. Add terms that are relevant to your organisation.
OfficeActivity
| where TimeGenerated > ago(1d)
| where OfficeWorkload == "Exchange"
| where Operation in ("New-InboxRule", "Set-InboxRule")
| where Parameters has_any ("ForwardTo", "RedirectTo", "ForwardAsAttachmentTo", "DeleteMessage", "MoveToFolder", "MarkAsRead")
| where Parameters has_any ("invoice", "payment", "bank", "remittance", "security", "phish")
| project TimeGenerated, Operation, UserId, ClientIP, Parameters
Key takeaways
- Export the rule and mailbox forwarding settings before you change anything, including hidden rules with
-IncludeHidden. - Check all four audit operations:
New-InboxRule,Set-InboxRule,UpdateInboxRulesandSet-Mailbox. - Read the rule for external destinations, financial keywords, hidden folders and rules that apply to every message.
- Scope from the IP and the rule pattern across the whole tenant, including non-interactive sign-ins.
- Use MailItemsAccessed and message trace to describe exposure, and state your log coverage plainly.
- Reset, revoke and remove persistence together, then contact affected parties through a separate verified channel.
Save these queries as a ready-made playbook before you need them, and test that your team has the right roles to run Get-InboxRule. The time you save during the first hour of a real incident can be what separates a contained account from a fraudulent payment.