How to detect a breach in Microsoft 365

How to detect a breach in Microsoft 365

How To Detect A Breach In Microsoft 365 With PowerShell

When an attacker gains access to your environment, there’s no incentive for them to reveal their presence. Microsoft 365 data breaches will often go unnoticed for months, with attackers collecting information and waiting for opportunities to make money.

The advice from Microsoft, and many others, is to assume a breach is occurring and act accordingly.

The good news is that you can spot Microsoft 365 breaches relatively quickly – if you know where to look.

Not technical? Let us help.

The instructions below are aimed at a technical audience but don’t worry if you’re not. If you need help detecting and investigating suspicious activities, get in touch with us today.

How to detect suspicious activity in Microsoft 365

Enable the Unified Audit Log

The unified audit log is your primary tool for detecting suspicious activity. If you’re administering Microsoft 365 for a company with a relatively small number of users (under 500), you can generally work out whether something fishy is going on pretty quickly.

The unified audit log generates a considerable amount of data on user and app activities in Microsoft 365. Due to this overhead, Microsoft doesn’t enable it by default. You’ll need to switch it on in the Security and Compliance Center at https://protection.office.com under Search – Audit Log Search.

Detecting, Stopping and Cleaning up after a breach

While this article demonstrates how to use the Unified Audit Log and a locator API to detect suspicious activities in Microsoft 365, it doesn’t help too much if the events occurred long ago, or before the logs started recording activities.

Many hackers will leave behind similar clues as they attempt to access and exfiltrate your data. To help spot these, we recommend downloading our guide on How to Find, Stop and Prevent Office 365 Security Breaches. It’s a piece of marketing material we put together, but it contains a list of things we check for during and after a breach is discovered.

If you do have access to the unified audit logs, and the activity occurred in the last 90 days, read on.

Investigating a Microsoft 365 Breach with the Unified Audit Log

The unified audit log has its user interface in the Security and Compliance Center. However, it’s relatively useless unless you already know what has happened. It also only displays pages of 150 logs, up to a maximum of 5000. Most small businesses would generate records than that in a couple of days. Exporting logs from this interface takes time and creates a CSV document with most of the valuable data hidden in JSON format under the AuditData column.

These aren’t criticisms of Microsoft; they have done a great job making a vast amount of data from several different services available in a single interface. We just need to use the right tool to collect and filter this data.

The best way to investigate breaches with the Unified Audit Log is via PowerShell. With PowerShell, we can collect the relevant logs, scan it’s attached IP address with an IP locator API, group the activities by country, and export each type of operation to a separate for later analysis.

Investigating a Microsoft 365 Breach with PowerShell

We can investigate activity on user accounts by connecting to Exchange Online via PowerShell.

In this example, we’ll connect to Exchange Online via the new module. You can install this module by opening PowerShell as an administrator and running:

Install-Module ExchangeOnlineManagement

Accept any package or module installation prompts, and then run:


To start investigating a user, we can use the Search-UnifiedAuditLog cmdlet. Let’s try retrieving the last ten days of logs for someone – we’ll use my account as an example.

$logs = Search-UnifiedAuditLog -ResultSize 5000 -StartDate (Get-Date).AddDays(-10) -EndDate (Get-Date) -UserIds elliot@gcits.com

Get an overview of what the user has been doing by grouping the logs by the operation.

$logs | group-object operations

Search Unified Audit Log PowerShell

Get access to the AuditData values to get more relevant info on the activities.

$auditdata = $logs.auditdata | ConvertFrom-Json

Most operations in the audit log have a value called ClientIP which usually refers to the IP address of the user who performed the activity. In our experience, this is a great way to identify suspicious behaviours. I’ve written a couple of functions that take this ClientIP value, run it through an IP locator API and attach the location information to the logs for filtering and sorting.

You can copy the functions below into your PowerShell window or save them into your PowerShell Profile.

Functions to run Microsoft 365 Logs against an IP Locator API

In this example, I use the ip-api.com API. If you plan on using this in a commercial environment, you’ll need to sign up for a pro key from here, and then update the function below with your key. I’ve included the free endpoint in the function so you can confirm it works when testing.

function Get-IPAPIInfo {
        [Parameter(Mandatory = $true)][string]$Ip
    $originalQuery = $ip
    if ($ip) {
        if ($ip -match "\." -and $ip -match ":") {
            $ip = ($ip -split ":")[0]
        if ($ip -match "\]:") {
            $ip = ($ip -split "\]:")[0]
            $ip = $ip -replace "\[", ""
    if ($ip -match "\]") {
        $ip = $ip -replace "\]", ""
        $ip = $ip -replace "\[", ""
    $ipResult = Invoke-restmethod -method get -uri "http://ip-api.com/json/$($ip)"
    Start-Sleep -s 1
    # Edit this with your key and remove the 2 lines above if you're using the Pro version of IP-API.com (recommended).
    #$ipResult = Invoke-restmethod -method get -uri "http://pro.ip-api.com/json/$($ip)?key=YOURIP-APIKEYHERE"
    $ipResult | Add-Member originalQuery $originalQuery -force

function Merge-IPAPIInfo {
        [Parameter(Mandatory = $true)][string]$IpProperty,
        [Parameter(Mandatory = $true)][object]$Item

    $ips = $item | Sort-Object $IpProperty -Unique
    $ipResultCollection = @()
    foreach($ip in $ips){
        $ipResultCollection += Get-IPAPIInfo -Ip $ip.$($IpProperty)

    foreach ($object in $item) {
        $ipresult = $ipResultCollection | Where-Object {$_.originalQuery -eq $object.$($IpProperty)}
        foreach ($property in $ipresult.psobject.properties.name) {
            $object | add-member $property $ipresult.$($property) -force


Once you have the functions in memory, you can get the location info for your logs by running:

$auditdata = Merge-IPAPIInfo -Item $auditdata -IpProperty ClientIp

You may get a few errors if any audit logs don’t have a value stored under ClientIp; however, you should now be able to run:

$auditdata | Group-Object Country

Group Unified Audit Log Activities By Country

In this example, I’ve switched to display logs for a user with several failed logins attempts.

You can filter out any failed logins to show successful activities by country using:

$auditdata | where operation -notmatch failed | group country

With this filter on, the results are less concerning.

Successful Audit Log Operations

You can export these logs for archive and later analysis in a couple of ways. I like to export the original logs using something like:

$logs | ConvertTo-Json -Depth 99 | Out-File C:\temp\logs.json

You can export more readable versions using the following script, it will export a separate spreadsheet for each type of operation, along with the IP address and location information for it.

$groups = $auditdata | Group-Object operation
foreach($group in $groups){ $group.group | Export-Csv C:\temp\$($group.name).csv -NoTypeInformation }

Export Microsoft 365 Activities By Operation To CSV

Why do we export Microsoft 365 audit data to CSVs?

While we are proficient in PowerShell, many are not. Exporting the relevant data to CSV makes it easier for non-technical people to analyse and identify suspicious behaviours using Excel.

We use PowerShell to convert and export the AuditData value because that contains the most specific information about a particular action, including the IP address. The AuditData value is a JSON object with most operations having a unique schema. The unique schema requires each to have a separate CSV.

Properties And Country Info For UserLoginFailed

Need to investigate many users and analyse months of logs?

In this example, I demonstrated how to run this operation for one user with under 5000 records – but what if you need to export many more? Most tenants have 90 days worth of activity, with E5 tenants storing up to a year’s worth of audit logs. Collecting this amount of data for analysis and investigation can be quite the task.

We’ve built a few tools for this and will happily share them if there’s enough interest. We’re also available for Microsoft 365 breach investigations and security audits.

Get in touch with us today for more information.

  • This field is for validation purposes and should be left unchanged.
Was this article helpful?

Related Articles

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

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