BECS - An Office 365 Incident Response Tool
Gosh golly, another blog post?! #
I seem to have found myself doing a lot of incident response (IR) engagements here lately. I mean, I’m not complaining; it’s bittersweet in that I have a lot of fun during these engagements, but also sad because they negatively impact businesses that have better things to do.
Several times now, I’ve needed to do some sort of DFIR in Office 365, where I just manually grabbed whatever data I needed at the time. But for the sake of automation (being lazy), tooling, and giving back to the community, I though gee, I should make a tool. With that, BECS was born, which is available on my GitHub:
---------------------------------------------
______ _____ _____ _____
| ___ \ | ___| / __ \ / ___|
| |_/ / | |__ | / \/ \ `--.
| ___ \ | __| | | `--. \
| |_/ / _ | |___ _ | \__/\ _ /\__/ /
\____/ (_) \____/ (_) \____/ (_) \____/
'Business Email Compromise Search'
An Office 365 Information Gathering Tool
---------------------------------------------
Let’s get what the tool does and how to run it out of the way first. The we’ll get into the juicy, technical goodness.
TL;DR: Run this tool to review any changes, new users, mailboxes, or questionable inbox rules in your Office 365 tenant
All right, what does it do? #
I’m so glad you asked! Like the name implies, BECS crawls an Office 365 tenant for information that would be pertinent to someone during an DFIR engagement. This includes:
- Basic information about the Office 365 tenant
- Company information
- Licenses
- Registered domains
- Tenant configuration
- Etc.
- Mailbox forward addresses
- Interesting Inbox Rules
- Forwards to outside emails with interesting keywords
- Deletes that try to hide non-deliverable notifications
- Recently changed Exchange Online mail flow rules
- E.G: Global mail flow rules
- Newly created mailboxes
- Recently added mobile devices
- List all MS Online role members
Note: #
Now, you may be asking if this is a suitable replacement to the Office 365 Security & Compliance Center features, or a traditional SIEM (Splunk, Graylog, ELK, etc), and by all means this does not replace either of those. This tool comes in handy if you’re in a pinch and don’t have either of those available, or if you’re an outside tech and need to quickly get familiar with an Office 365 tenant.
A traditional SIEM, or at a minimum the Security & Compliance center, is always recommended for properly monitoring an Office 365 tenant.
How do I use this thing? #
It’s pretty straight forward if you’ve ran a PowerShell script before:
- Review and download the tool from the BECS GitHub Repo
- Ensure you can connect to Exchange Online and Office 365 tenant via PowerShell
- Check the README for links that’ll assist you with this
- A user account in the Office 365 tenant you wish to run this against with the Global Administrator role assigned
- Your local execution policy set to RemoteSigned
Once you have all of the above, connect to Exchange Online and Office 365 and navigate to where you downloaded the tool to. When you run the tool, you will be prompted with several different options:
Depending on your selection, that will determine what data is retrieved:
- All will grab everything
- Exchange Online will grab tenant information, mailboxes, Exchange Online mail flow rules, etc.
- MSOnline will grab tenant information, administrative role members, MSOnline users, etc.
- InboxRules will grab tenant information and any interesting/questionable mailbox flow rule
- Since the cmdlets to retrieve inbox rules takes so long to run, the script will refresh its connection with Exchange Online every 150 mailboxes. This was put in place to prevent from being timed out.
All data that is fetched by BECS will be sent to a new folder on the users Desktop and the tool does not alter or create any new objects in either Office 365 or Exchange Online.
Let’s talk code #
BECS, a controller script, is comprised of 6 main functions, which have been named appropriately to hint at their roles.
- New-OutputDirectory
- Get-TenantInformation
- Get-ExchangeOnlineData
- Get-MSOnlineData
- Get-InterestingInboxRules
- Show-Menu
Let’s go through each function one by one:
New-OutputDirectory #
This function has the simple task of creating the folder structure on the user’s desktop to store all of the output data. The environment variable $env:USERPROFILE is used so it can be ran on any Windows users workstation.
Get-TenantInformation #
This and New-OutputDirectory are the only two functions that will be ran no matter what selection you make when you run the script.
This one runs the following cmdlets, converts the output to string data, and writes out to a text file.
- Get-MsolCompanyInformation
- Get-MsolDomain
- Get-MsolSubscription | ft -AutoSize
- Get-OrganizationConfig
It also does the additional check for auditing; if the organization config property AuditDisabled is set to $True
, it’ll warn the user running the tool:
I do plan on adding more checks in this function, but when I wrote this tool there was some sense of urgency since I needed it for an incident response engagement. It grabs everything you would need, but it would be nice to make some checks at run-time so you don’t have to sift through the text file.
Get-ExchangeOnlineData #
The purpose of this guy is to grab any relevant data in Exchange Online. To do so, several functions were nested inside this function:
- Get-MailboxData
- Get-RecentTransportRules
- Get-RecentMobileDevices
The Get-MailboxData function searches each mailbox in a tenant and records anything that has data in the ForwardingSMTPAddress property or if the WhenCreated date is within the last 90 days.
Get-RecentTransportRules, as the name implies, looks over the global Exchange rules and records any rule that has been altered within the last 90 days. Since there can be so much variety to a global rule, I voted to take the entire rule configuration and save it to a text file for review later.
Finally, Get-RecentMobileDevices records any recent, well.. mobile devices!
Get-MSOnlineData #
This one has two nested functions, Get-RecentMSOLUsers and Get-AlllMSOLRoleMemberships.
Get-RecentMSOLUsers simply records any MSOnline users that were created in the last 90 days.
And, as you may have deduced, Get-AllMSOLRoleMemberships records all users with administrative role in an Office 365 tenant (Global Admin, Exchange Admin, SharePoint Admin, etc.)
Get-InterestingInboxRules #
This guy was quite the booger, but also the main motivation behind the creation of this tool. With nearly every business email compromise I’ve handled, there was some sort of malicious inbox rule created by the attacker.
The meat of this function is Get-InboxRule
, which can give you a lot of information. Let’s look at all of the rules on Taylor Swift’s mailbox:
Now, she named these appropriately for this demonstration, but let’s just pretend that the rules ‘Bad - Delete’ and ‘Bad - Forward’ look interesting. Lets look at the delete first:
Now, I’m only showing the description since there are tons of properties that come with this command (see for yourself with Get-InboxRule -Mailbox FLast@Domain.com -Identity #### | gm
), but this should be a good indication that this rule is intended to delete anything that may be a NDR (non-deliverable), which would tip off a user that their mailbox is probably compromised. It’s a pretty common way for an attacker to cover their tracks.
Alright, let’s look at the forward rule:
Looking this one over, it should be easy to deduce what its intention is. Anything that has something to do with billing or invoices or, basically money, gets forwarded to an outside email.
It’s commonplace for attackers to intercept these, alter them a bit so they would be the recipient of the funds, then send that doctored invoice to someone internally in an attempt to get the funds sent to them.
Sneaky sneaky!
Using these rules as examples/baselines, the following checks were made in the Get-InterestingInboxRules function:
If ($InboxRule.DeleteMessage -eq $true -and $InboxRule.SubjectOrBodyContainsWords -like "*Mail Delivery*" -or $InboxRule.SubjectOrBodyContainsWords -like "*could not be delivered*")
{
Write-Warning "$ID was flagged for a questionable delete action"
IR-Logwrite " "
IR-Logwrite "---------------------------------------------------------------------------"
IR-Logwrite "The Inbox Rule from $ID deletes a message when processed:"
IR-Logwrite "---------------------------------------------------------------------------"
IR-Logwrite " "
$String = $InboxRule.Description | Format-List | Out-String
IR-Logwrite $String
}
elseIf ($InboxRule.DeleteMessage -eq $true -and $InboxRule.SubjectContainsWords -like "*Mail Delivery*" -or $InboxRule.SubjectContainsWords -like "*could not be delivered*")
{
Write-Warning "$ID was flagged for a questionable delete action"
IR-Logwrite " "
IR-Logwrite "---------------------------------------------------------------------------"
IR-Logwrite "The Inbox Rule from $ID deletes a message when processed:"
IR-Logwrite "---------------------------------------------------------------------------"
IR-Logwrite " "
$String = $InboxRule.Description | Format-List | Out-String
IR-Logwrite $String
}
# Forward to an outside email with questionable keywords
elseIf ($InboxRule.ForwardTo -like "*@*" -and $InboxRule.SubjectOrBodyContainsWords -like "*pay*" -or $InboxRule.SubjectOrBodyContainsWords -like "*invoice*" -or $InboxRule.SubjectOrBodyContainsWords -like "*wire*")
{
Write-Warning "$ID was flagged for a questionable forward action"
IR-Logwrite " "
IR-Logwrite "---------------------------------------------------------------------------"
IR-Logwrite "The Inbox Rule from $ID forwards a message when processed:"
IR-Logwrite "---------------------------------------------------------------------------"
IR-Logwrite " "
$String = $InboxRule.Description | Format-List | Out-String
IR-Logwrite $String
}
# Forward to an outside email with questionable keywords
elseIf ($InboxRule.ForwardTo -like "*@*" -and $InboxRule.SubjectContainsWords -like "*pay*" -or $InboxRule.SubjectContainsWords -like "*invoice*" -or $InboxRule.SubjectContainsWords -like "*wire*")
{
Write-Warning "$ID was flagged for a questionable forward action"
IR-Logwrite " "
IR-Logwrite "---------------------------------------------------------------------------"
IR-Logwrite "The Inbox Rule from $ID forwards a message when processed:"
IR-Logwrite "---------------------------------------------------------------------------"
IR-Logwrite " "
$String = $InboxRule.Description | Format-List | Out-String
IR-Logwrite $String
}
# Forward to an outside email with questionable keywords
elseIf ($InboxRule.ForwardAsAttachmentTo -like "*@*" -and $InboxRule.SubjectOrBodyContainsWords -like "*pay*" -or $InboxRule.SubjectOrBodyContainsWords -like "*invoice*" -or $InboxRule.SubjectOrBodyContainsWords -like "*wire*")
{
Write-Warning "$ID was flagged for a questionable forward action"
IR-Logwrite " "
IR-Logwrite "---------------------------------------------------------------------------"
IR-Logwrite "The Inbox Rule from $ID forwards a message as an attachment when processed:"
IR-Logwrite "---------------------------------------------------------------------------"
IR-Logwrite " "
$String = $InboxRule.Description | Format-List | Out-String
IR-Logwrite $String
}
# Forward to an outside email with questionable keywords
elseIf ($InboxRule.ForwardAsAttachmentTo -like "*@*" -and $InboxRule.SubjectContainsWords -like "*pay*" -or $InboxRule.SubjectContainsWords -like "*invoice*" -or $InboxRule.SubjectContainsWords -like "*wire*")
{
Write-Warning "$ID was flagged for a questionable forward action"
IR-Logwrite " "
IR-Logwrite "---------------------------------------------------------------------------"
IR-Logwrite "The Inbox Rule from $ID forwards a message as an attachment when processed:"
IR-Logwrite "---------------------------------------------------------------------------"
IR-Logwrite " "
$String = $InboxRule.Description | Format-List | Out-String
IR-Logwrite $String
}
else
{
$null
}
Basically, anything that looks questionable gets logged to a text file for further analysis.
One hurdle that I had to get over was how slow Get-InboxRule
takes to run. If you’re running too many commands within a period of time, you’ll get kicked out of your session by Exchange Online.
To get over this hurdle, I have the script function close its session and create a new one at every 150 mailboxes it checks:
Someday I’m going to go down the rabbit hole of Office 365 APIs in hopes to get around this bottleneck. Or hopefully the new Exchange Online Module, v2 will improve this cmdlet.
Show-Menu #
Finally, the Show-Menu function wraps everything together and gives the user running the script options to run. Rather than using Read-Host, I though it would be fun, and look cleaner, if .NET was leveraged:
Depending on what the user selects to run will determine what functions are called:
In Closing #
Overall, this was a lot of fun to make and I plan on adding more functionality in the future.
Got more blogs coming down the pipeline. Until next time <3