If you haven't heard, SANS suffered a "Data Incident" this summer, the disclosure was released on August 11. Details can be found in several locations:
This is well written up in several places:
https://www.sans.org/dataincident2020
IOC's discussed here:
https://www.sans.org/blog/sans-data-incident-2020-indicators-of-compromise/
Webcast is available in two places:
https://www.sans.org/webcasts/116375
https://www.youtube.com/watch?v=KZ3gcFe4_rE
This breach didn't affect me adversely, if my information was affected the disclosed information all falls into the category of information I advertise - - it's all on my website or in LinkedIn. But this did get me to thinking about other corporate networks where more sensitive customer information is routinely emailed around. An email forwarding rule in Office 365 could be a "forever data breach" in a situation link that - whether the rule was planted by an attacker or (mis)configured by an end-user, the results could be catastrophic. Whether it results in a breach of customer data, disclosure of intellectual property, or just a down-tick in a PCI audit - an email rule in the wrong place and at the wrong time can have a severe business impact.
So that being said, how can we look for these things if you have hundreds, thousands or tens-of-thousands of mailboxes to consider? In an Office 365 shop, and especially if I wrote the code, the answer is most likely going to be PowerShell!
Before we start, you'll need the msonline module:
A basic "get connected" script might look like this:
Import-Module MSOnline
$O365Cred = Get-Credential
$O365Session = New-PSSession –ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.outlook.com/powershell -Credential $O365Cred -Authentication Basic -AllowRedirection
Import-PSSession $O365Session
Connect-MsolService –Credential $O365Cred
|
Getting the list of mailboxes (so you have something to loop through) is as simple as "get-mailbox"
$mailboxnames = get-mailbox | select name
|
To get rules on a mailbox, "get-inboxrule" does the job:
Get-InboxRule -Mailbox "rob@coherentsecurity.com"
|
Often we're looking for "RedirectTo" or "ForwardTo" rules, but at this point your script may start to differ from mine, you might be looking for different types of rules. All the parameters that make up each rule are in fields, so it's easy to search and match on exactly what you are looking for. Normally I pull all configured rules, but I include the "redirectto" and "forwardto" fields so I can separate those out easily, usually in Excel.
If you are looking for other rule types, there are quite a few flags to help you sort / extract what you are looking for - this will give you the list of possibilities (the output of this command is edited to show only fields you might want to search/match on in your hunt for malicious rules):
Get-InboxRule -Mailbox "rob@coherentsecurity.com" | get-member
(again, only fields commonly of interest are shown)
Name MemberType Definition
---- ---------- ----------
BodyContainsWords Property Deserialized.Microsoft.Exchange.Data.MultiVal...
CopyToFolder Property {get;set;}
DeleteMessage Property System.Boolean {get;set;}
DeleteSystemCategory Property Deserialized.Microsoft.Exchange.Data.MultiVal...
Enabled Property System.Boolean {get;set;}
ExceptIfBodyContainsWords Property Deserialized.Microsoft.Exchange.Data.MultiVal...
ExceptIfFlaggedForAction Property {get;set;}
ExceptIfFrom Property {get;set;}
ExceptIfFromAddressContainsWords Property Deserialized.Microsoft.Exchange.Data.MultiVal...
ExceptIfFromSubscription Property {get;set;}
ExceptIfHasAttachment Property System.Boolean {get;set;}
ExceptIfHasClassification Property {get;set;}
ExceptIfHeaderContainsWords Property Deserialized.Microsoft.Exchange.Data.MultiVal...
ExceptIfMessageTypeMatches Property {get;set;}
ExceptIfMyNameInCcBox Property System.Boolean {get;set;}
ExceptIfMyNameInToBox Property System.Boolean {get;set;}
ExceptIfMyNameInToOrCcBox Property System.Boolean {get;set;}
ExceptIfMyNameNotInToBox Property System.Boolean {get;set;}
ExceptIfReceivedAfterDate Property {get;set;}
ExceptIfReceivedBeforeDate Property {get;set;}
ExceptIfRecipientAddressContainsWords Property Deserialized.Microsoft.Exchange.Data.MultiVal...
ExceptIfSentOnlyToMe Property System.Boolean {get;set;}
ExceptIfSentTo Property {get;set;}
ExceptIfSubjectContainsWords Property Deserialized.Microsoft.Exchange.Data.MultiVal...
ExceptIfSubjectOrBodyContainsWords Property Deserialized.Microsoft.Exchange.Data.MultiVal...
ExceptIfWithImportance Property {get;set;}
ExceptIfWithinSizeRangeMaximum Property {get;set;}
ExceptIfWithinSizeRangeMinimum Property {get;set;}
ExceptIfWithSensitivity Property {get;set;}
FlaggedForAction Property {get;set;}
ForwardAsAttachmentTo Property {get;set;}
ForwardTo Property {get;set;}
From Property {get;set;}
FromAddressContainsWords Property Deserialized.Microsoft.Exchange.Data.MultiVal...
FromSubscription Property {get;set;}
HasAttachment Property System.Boolean {get;set;}
HasClassification Property {get;set;}
HeaderContainsWords Property Deserialized.Microsoft.Exchange.Data.MultiVal...
MarkAsRead Property System.Boolean {get;set;}
MarkImportance Property {get;set;}
MessageTypeMatches Property {get;set;}
MoveToFolder Property System.String {get;set;}
MyNameInCcBox Property System.Boolean {get;set;}
MyNameInToBox Property System.Boolean {get;set;}
MyNameInToOrCcBox Property System.Boolean {get;set;}
MyNameNotInToBox Property System.Boolean {get;set;}
Priority Property System.Int32 {get;set;}
ReceivedAfterDate Property {get;set;}
ReceivedBeforeDate Property {get;set;}
RecipientAddressContainsWords Property Deserialized.Microsoft.Exchange.Data.MultiVal...
RedirectTo Property {get;set;}
SendTextMessageNotificationTo Property Deserialized.Microsoft.Exchange.Data.MultiVal...
SentOnlyToMe Property System.Boolean {get;set;}
SentTo Property {get;set;}
SubjectContainsWords Property Deserialized.Microsoft.Exchange.Data.MultiVal...
SubjectOrBodyContainsWords Property Deserialized.Microsoft.Exchange.Data.MultiVal...
WithImportance Property {get;set;}
WithinSizeRangeMaximum Property {get;set;}
WithinSizeRangeMinimum Property {get;set;}
WithSensitivity Property {get;set;}
|
However, once you have a match on whatever it is that you are looking for, the "Description" field is what you likely want in your report - that spells out in plain language what the rule is.
My regular script usually looks something like this:
# prep variables
$RuleList = @()
$timestamp = get-date -format "yyyy-MM-dd-HH-mm"
#See if the MSOnline Module is installed or not
if (Get-Module -ListAvailable -Name MSOnline) {
# no action, module is installed
}
else {
# note "install-module" requires local admin rights
install-module MSOnline
}
# import module and connect
Import-Module MSOnline
# collect credentials for an organization's O365 Administrator account
$O365Cred = Get-Credential
# Connect up to your org's instance
$O365Session = New-PSSession –ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.outlook.com/powershell -Credential $O365Cred -Authentication Basic -AllowRedirection
Import-PSSession $O365Session
Connect-MsolService –Credential $O365Cred
# collect all mailboxes in the enterprise
$mailboxes = get-mailbox
# Loop Through all mailboxes, check for rules
# note that will all the mailbox metadata, you can hunt for lots of other things too ....
foreach ($mb in $mailboxes) {
# main email for the mailboxes
$email = $mb.PrimarySmtpAddress
# is this a valid user-user?
if($mb.WindowsLiveID.length -ne 0) {
$rules = get-inboxrule -mailbox $email
if (($rules | measure-object).count) {
foreach( $r in $rules) {
$tempval = [pscustomobject]@{
UserName = $email
RuleName = $r.name
RuleEnabled = $r.Enabled
CopytoFolder = $r.CopyToFolder
MovetoFolder = $r.MoveToFolder
RedirectTo = $r.RedirectTo
ForwardTo = $r.ForwardTo
TextDescription = $r.Description
}
$RuleList += $tempval
}
}
}
}
# display results on screen, also save to a CSV with time/datestamp
$RuleList | out-Gridview
$filename = ".\MailRules-" + $timestamp + ".csv"
$RuleList | export-CSV $filename
|
The output (for just my mailbox, with one test rule) is shown below. In a more traditional organization it'll of course be much larger, with both more users and more rules.

This script applies to server rules only. If your user has checked the "run on this machine only" tick box when building the rule, or has created a rule that will only run inside of outlook (for instance, has selected "play a sound" or something else that will only run locally), then those rules are stored in Outlook and will not show up when you sweep your O365 instance for server-side rules.
Also, keep in mind that not all mail rules are malicious - often they can automate time-eating manual processes for a nice productivity boost. It'd be my opinion though that you at least want to evaluate the rules in your organization, especially forwarding rules, and especially if those rules forward to email addresses outside of your domain.
If you decide to kick the tires on this script (or if you decide to improve on it), by all means use our comment section to let us know what you found!
====== Update ======
Our reader Peter notes that for systems that use MFA for administrators (which is what we should ALL be doing), the 'ConnectMSolService" command should be used instead of "Get-Credential". This will give you a modern login form rather than just userid/password entry.
So this:
Import-Module MSOnline
$O365Cred = Get-Credential
$O365Session = New-PSSession –ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.outlook.com/powershell -Credential $O365Cred -Authentication Basic -AllowRedirection Import-PSSession $O365Session Connect-MsolService –Credential $O365Cred
|
Becomes something like this:
Import-Module MSOnline
Connect-MSolService
|
References:
https://docs.microsoft.com/en-us/microsoft-365/enterprise/connect-to-microsoft-365-powershell?view=o365-worldwide
(as described, note that you need .Net 3.5 installed for this to work)
===============
Rob VandenBrink
rob <at> coherentsecurity.com