Threat Level: green Handler on Duty: Xavier Mertens

SANS ISC: InfoSec Handlers Diary Blog InfoSec Handlers Diary Blog

Sign Up for Free!   Forgot Password?
Log In or Sign Up for Free!

Office 365 Mail Forwarding Rules (and other Mail Rules too)

Published: 2020-08-20
Last Updated: 2020-08-20 15:43:34 UTC
by Rob VandenBrink (Version: 1)
0 comment(s)

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:

IOC's discussed here:

Webcast is available in two places:

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:

install-module msonline

A basic "get connected" script might look like this:

Import-Module MSOnline
$O365Cred = Get-Credential
$O365Session = New-PSSession –ConfigurationName Microsoft.Exchange -ConnectionUri -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 ""

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 "" | 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 -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 = $           

                     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 -Credential $O365Cred -Authentication Basic -AllowRedirection Import-PSSession $O365Session Connect-MsolService –Credential $O365Cred

Becomes something like this:

Import-Module MSOnline


(as described, note that you need .Net 3.5 installed for this to work)

Rob VandenBrink
rob <at>

0 comment(s)
Diary Archives