O365 Secure Score & Azure Automation (Part 2) – External Forwarding Report

If you would like to read the first part in this article series, please go to O365 Secure Score & Azure Automation (Part 1) – Enable Mailbox Auditing.



We introduced Azure Automation as a component to adopt security features in Office 365 (and Azure). Many features are not enabled just by “a tick in the portal” – they require repeated actions on each service, mailbox, user, etc.

A good example of that is enabling audit logging on mailboxes in Exchange Online. In the last article we showed how an Azure Automation Runbook can enable mailbox audit logging for all mailboxes and make sure all future mailboxes are compliant.

In this episode…

We will have a look at the Secure Score action named “Review mailbox forwarding rules weekly”.

So we want to avoid the situation where confidential information is automatically forwarded to external recipients.

While there are lots of legitimate uses of mail forwarding rules to other locations, it is also a very popular data exfiltration tactic for attackers

I have seen this myself: An employee got a call from a “trustworthy tech-guy” and were asked to import an inbox rule in Outlook. All e-mails were forwarded to a Gmail account for some time. Not good!

The Secure Score action has a PowerShell script associated. This script covers mailbox forwarding, which cover both levels of forwarding: “mailbox level” forwarding and “inbox level” 🙂

Unfortunately, the associated script is designed to dump CSV files in your system32 folder, which you then have to review.

Did I mention the script dumps all inbox rules and mailboxes with mailbox forwarding, even if the recipient is internal?

We don’t want to dump CSV files in system32, and we certainly don’t want them if they are cluttered with unnecessary data.

Note: The associated PowerShell script also dumps all non-owner mailbox delegation into a CSV file. Mailbox delegation will not be covered by the Azure Automation runbook in this article. Maybe in a future article/script.


Azure Automation Account available

Same as the prerequisites in the last article. Please provision an account before going any further.

Exchange Online Service Account

If you haven’t already created one, do so – please follow the instructions in previous article.

Create Exchange Online RBAC Group

If you haven’t already created one, do so – please follow the instructions in previous article.

Note: We will create and add two more roles to the group later in this article.

Create Azure Automation Credential

If you haven’t already created one, do so – please follow the instructions in previous article.


Exchange Online RBAC

In the last article, we made use of the built-in management role called “Audit”. It gave the service account sufficient permission to execute the audit logging related PowerShell cmdlets.

Now we need an additional set of cmdlets. More specifically, Get-InboxRule and Get-AcceptedDomain.

A quick way to check which management roles include a cmdlet, is by running following command (when connected to Exchange Online in your Office 365 tenant):

Get-ManagementRoleEntry -Identity "*\cmdlet"


Let’s check what roles has Get-InboxRule:

Get-ManagementRoleEntry -Identity "*\Get-InboxRule"

The output:

Okay. We want to go with the principle of least privilege. We only need the command Get-InboxRule and we certainly don’t want permissions for creating, modifying or deleting anything.

The following roles all include a number of state-changing cmdlets (Set-, New-, Remove-, etc.):

  • Mail Recipients
  • Security Group Creation and Membership
  • User Options

The role “MyBaseOptions” is only scoped for mailbox-owner. So, that’s not an option.

Then we have “View-Only Recipients”. Sounds right. But trust me. It won’t fly.

Even though we only require a “read-scope”, you will end up getting the “bloody” text above when executing Get-InboxRule against any mailbox (except the Service Account’s own mailbox).

For some reason, to run the cmdlet against other mailboxes you need “write-scope access” on a “management role level”. Which we don’t have with “View-Only Recipients”.

With Get-AcceptedDomain we have the same “state-changing” cmdlet issue: plenty of built-in roles that grant enough permissions to execute Get-AcceptedDomain, but it also contains a long list of “Set-‘s” (and other powerful cmdlets):

Maybe we could use the Role “View-Only Configuration”?

(Note: the list goes on)

Nah – too many cmdlets / permissions.

Now what?

Create Custom Role

The beauty of role-based access control: custom roles.

Let’s create two new roles: one only with Get-InboxRule (with sufficient read/write scope) and one with Get-AcceptedDomain.

$Parent_RoleName = "Mail Recipients" # Name of the built-in role we are copying
$InboxRules_RoleName = "View-Only Inbox Rules" # Name of the new Inbox Rule role
$AcceptedDomains_RoleName = "View-Only Accepted Domains" # Name of the new Accepted Domains role

# Following commands will create a exact copy of the parent role
New-ManagementRole -Parent $Parent_RoleName -Name $InboxRules_RoleName
New-ManagementRole -Parent $Parent_RoleName -Name $AcceptedDomains_RoleName

# Filter, so a list of all the unwanted entries (cmdlets) are stored in variables
# First - the Inbox Rules role
$InboxRules_EntriesToRemove = Get-ManagementRoleEntry "$InboxRules_RoleName\*" | `
    Where-Object {$_.Name -ne "Get-InboxRule" }

# Second - the Accepted Domains
$AcceptedDomains_Entries = Get-ManagementRoleEntry "$AcceptedDomains_RoleName\*" | `
    Where-Object {$_.Name -ne "Get-AcceptedDomain" }

# Now, remove the unwanted entries from the custom roles
# First - the Inbox Rules role
Foreach ($Entry in $AcceptedDomains_Entries)
    $Role = $Entry.Identity
    $Name = $Entry.Name
    Remove-ManagementRoleEntry "$Role\$Name" -Confirm:$false

# Second - the Accepted Domains
Foreach ($Entry in $ManagementRoleEntries)
    $Role = $Entry.Identity
    $Name = $Entry.Name
    Remove-ManagementRoleEntry "$Role\$Name" -Confirm:$false

# Verification
Get-ManagementRoleEntry "$InboxRules_RoleName\*"
Get-ManagementRoleEntry "$AcceptedDomains_RoleName\*"

(Link to GitHub)

Add Custom Role to Role Group

Now go to Exchange Online Admin Center and open “Permissions”.

Open the existing custom admin role created in the previous article. Add the two new custom roles and click “Save”.

The permissions should be provisioned within half an hour.


Create Azure Automation Runbook

Please review the prerequisites further up in this article if you don’t have an Azure Automation account nor created an Azure Automation Credential.

In your automation account go to “Runbooks” (under Process Automation) and click “Add a runbook”. Then “Create a new runbook”.

Enter a name for the runbook, e.g. “ExO-MailboxAndInboxRuleForwardingReport”. Select “PowerShell” as “Runbook type” and hit “Create”.

Now copy-paste the content of the script ExO-MailboxAndInboxRuleForwardingReport into the blank PowerShell runbook.

The runbook should now look something like this:

You need to modify some SMTP variables in the declaration area of the script. Please review the documentation in the beginning of the script (green text in the runbook) and/or have a look at the ReadMe in the GitHub project.

Remember to click “Save”.


Test Runbook

When ready, click on “Test pane”.

Now fill the parameters:

  • AutomationPSCredentialName
  • ExcludeExternalDomain (Optional)
  • ExcludeExternalEmailAddress (Optional)
  • SendMailboxForwardingReport (Switch, Optional)
  • SendInboxRuleForwardingReport (Switch, Optional)

It could be something like this:

Parameter Value
AutomationPSCredentialName Exchange Online Service Account
ExcludeExternalDomain [‘lindevops.com’,’somecompany.com’]
ExcludeExternalEmailAddress [‘somoneusinggmail@gmail.com’]
SendMailboxForwardingReport true
SendInboxRuleForwardingReport true

If any doubts, please check the GitHub project ReadMe!

Now click “Start”, sit back, and relax.

In the example above, the runbook discovered two mailbox forwardings with external recipient and three external recipients in inbox rules. It then sent a report for each forwarding type.

Two e-mails received…

The attachment looks like this:


Deploy & Schedule Runbook

In the edit pane, click “Publish” and “Yes”.

Select “Schedules” under “Resources” and then click “Add a schedule”.

You can select the schedule created in the last article, or you can create a new one. It’s all up to you.

When a schedule is created/selected, fill the parameters of the schedule. Click OK.

If you want to test the published runbook, go to the overview of the runbook and click “Start” and enter the same parameters as in the schedule. Then review the “Output”.


Final thoughts

As with the script in last article, this script could be “modulized”. And yes, maybe I will do that – someday.

This script could also be “converted” into an Azure Function / Logic Apps workflow. Maybe some other day 😉



You should now receive an email notification whenever one or more mailboxes has mailbox forwarding and/or inbox rule forwarding.

And you have gained 5 extra Secure Score points!

Please note: if you have implemented the “Enable Client Rules Forwarding Block Advanced Action”, all client-side forwarding (such as inbox rule forwarding) should be blocked forcibly. However, this does not include mailbox forwarding.

Even though inbox rule forwardings already are blocked, the rule(s) could be an early stage of a larger and more serious exfiltration campaign. So, keep your eyes peeled!

In the next article, we will have a look at generating and sending “Global Admin, MFA Disabled Report” with an Azure Automation runbook.

Soren is an IT Professional based in Copenhagen, Denmark.

His primary work areas are system design, deployment, migration and administration of business-critical IT infrastructure.

Leave a Reply

Your email address will not be published. Required fields are marked *