Microsoft 365 Unified Audit Log Forensics: How to Extract Attacker Activity from UAL Records During Incident Response

Retool's new app builder is where AI-generated code ships safely
Building apps with AI is easy. Getting them to production safely is another story.
Business email compromise investigations in M365 environments follow a predictable forensic sequence: identify the compromised account, trace what actions the attacker took after gaining access, find any persistence mechanisms they created, and determine what data they accessed. The Unified Audit Log contains all of this if the queries are structured correctly. The challenge is that Search-UnifiedAuditLog returns JSON-encoded audit data in the AuditData field that requires parsing to surface the meaningful fields.
Enabling UAL and Verifying Audit Coverage
The UAL is enabled by default in most M365 tenants, but should be verified:
# Connect to Security & Compliance PowerShell
Connect-IPPSSession -UserPrincipalName admin@domain.com
# Check if audit logging is enabled for the tenant
Get-AdminAuditLogConfig | Select-Object UnifiedAuditLogIngestionEnabled
# Enable if disabled
Set-AdminAuditLogConfig -UnifiedAuditLogIngestionEnabled $true
# Check if mailbox audit logging is enabled for a specific user
Get-Mailbox -Identity user@domain.com | Select-Object AuditEnabled, AuditAdmin, AuditDelegate, AuditOwner
Mailbox-level audit logging captures owner actions (the user logging in to their own mailbox), delegate actions (someone with Send As or Full Access permissions), and admin actions. By default, owner actions are not all audited. Check the AuditOwner property -- if it does not include MailItemsAccessed, MoveToDeletedItems, SoftDelete, and HardDelete, update it:
Set-Mailbox -Identity user@domain.com -AuditOwner @{Add="MailItemsAccessed","MoveToDeletedItems","SoftDelete","HardDelete"}
Note: MailItemsAccessed requires a Microsoft 365 E5 or Purview Audit Premium license. Without this license, forensic coverage of which specific emails were read is not available.
Querying Inbox Rule Creation: The Top BEC Persistence Pattern
After compromising an M365 account, attackers almost always create inbox rules to hide replies, forward emails, or delete detection alerts:
# Find inbox rule creation events for a specific user over the last 90 days
$rules = Search-UnifiedAuditLog -StartDate (Get-Date).AddDays(-90) -EndDate (Get-Date) `
-Operations "New-InboxRule","Set-InboxRule","UpdateInboxRules" `
-UserIds "compromised.user@domain.com" `
-ResultSize 5000
# Parse the AuditData JSON for each result
$rules | ForEach-Object {
$data = $_.AuditData | ConvertFrom-Json
[PSCustomObject]@{
Time = $_.CreationDate
User = $_.UserIds
Operation = $_.Operations
RuleName = $data.Parameters | Where-Object {$_.Name -eq "Name"} | Select-Object -ExpandProperty Value
ForwardTo = $data.Parameters | Where-Object {$_.Name -eq "ForwardTo"} | Select-Object -ExpandProperty Value
DeleteMsg = $data.Parameters | Where-Object {$_.Name -eq "DeleteMessage"} | Select-Object -ExpandProperty Value
MoveToFolder = $data.Parameters | Where-Object {$_.Name -eq "MoveToFolder"} | Select-Object -ExpandProperty Value
SubjectFilter = $data.Parameters | Where-Object {$_.Name -eq "SubjectContainsWords"} | Select-Object -ExpandProperty Value
ClientIP = $data.ClientIP
}
} | Format-Table -AutoSize
High-priority findings:
- ForwardTo set to an external address: active email exfiltration
- SubjectContainsWords matching 'invoice', 'payment', 'wire', 'bank': BEC targeting filter
- DeleteMessage True with SubjectContainsWords matching security alert terms: detection evasion
Briefings like this, every morning before 9am.
Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.
OAuth Application Consent Events: The Consent Phishing Investigation Path
Consent phishing attacks trick users into granting a malicious OAuth application full access to their mailbox. The UAL captures both the consent event and subsequent application access:
# Find all OAuth application consent events in the last 90 days
$consent = Search-UnifiedAuditLog -StartDate (Get-Date).AddDays(-90) -EndDate (Get-Date) `
-Operations "Add app role assignment grant to user","Consent to application","Add delegated permission grant" `
-ResultSize 5000
$consent | ForEach-Object {
$data = $_.AuditData | ConvertFrom-Json
[PSCustomObject]@{
Time = $_.CreationDate
User = $_.UserIds
AppName = $data.Target | Where-Object {$_.Type -eq 2} | Select-Object -ExpandProperty ID
Permissions = $data.ModifiedProperties | Where-Object {$_.Name -eq "Included Updated Scopes"} | Select-Object -ExpandProperty NewValue
ClientIP = $data.ActorIpAddress
}
} | Format-Table -AutoSize
# Once you have a suspicious app name, find all API access it performed
$appAccess = Search-UnifiedAuditLog -StartDate (Get-Date).AddDays(-90) -EndDate (Get-Date) `
-Operations "MailItemsAccessed" `
-FreeText "suspicious-app-display-name" `
-ResultSize 5000
Cross-reference consent events with Graph API sign-in logs to determine the access scope the application actually used after consent was granted.
MFA and Authentication Method Changes
Attackers who gain account access often add a secondary MFA method to maintain persistence after the victim's password is changed:
# Find authentication method additions and removals (Entra ID operations in UAL)
$mfaChanges = Search-UnifiedAuditLog -StartDate (Get-Date).AddDays(-90) -EndDate (Get-Date) `
-Operations "User registered security info","User deleted security info","User registered all required security info","Admin updated security info","Admin deleted security info" `
-ResultSize 5000
$mfaChanges | ForEach-Object {
$data = $_.AuditData | ConvertFrom-Json
[PSCustomObject]@{
Time = $_.CreationDate
TargetUser = ($data.ObjectId)
Actor = $_.UserIds
Operation = $_.Operations
Method = $data.AdditionalDetails | Where-Object {$_.key -eq "Type"} | Select-Object -ExpandProperty value
ClientIP = $data.ActorIpAddress
}
} | Format-Table -AutoSize
For each MFA registration event:
- Correlate the actor IP with the user's normal login IP pattern (from Entra ID sign-in logs)
- If the actor is the same as the target user (self-registration), check whether the IP is consistent with the user's normal locations
- If the actor is not the target user (admin registration), verify the action was authorized
- Registration of a new authenticator app from an unfamiliar IP immediately after an account compromise is a strong persistence indicator
SharePoint Mass Download and Teams Export Detection
Data staging and exfiltration through SharePoint and Teams produces distinct audit event patterns:
# Detect mass file downloads from SharePoint (more than 50 files in one hour)
$spDownloads = Search-UnifiedAuditLog -StartDate (Get-Date).AddDays(-7) -EndDate (Get-Date) `
-RecordType SharePointFileOperation `
-Operations "FileDownloaded" `
-ResultSize 5000
$spDownloads | ForEach-Object {
$data = $_.AuditData | ConvertFrom-Json
[PSCustomObject]@{
Time = $_.CreationDate
User = $_.UserIds
File = $data.SourceFileName
SiteURL = $data.SiteUrl
ClientIP = $data.ClientIP
Hour = [datetime]::Parse($_.CreationDate).ToString("yyyy-MM-dd HH")
}
} | Group-Object User, Hour | Where-Object {$_.Count -gt 50} |
Select-Object @{N='User';E={$_.Name.Split(',')[0]}},
@{N='Hour';E={$_.Name.Split(',')[1]}},
@{N='FileCount';E={$_.Count}} |
Sort-Object FileCount -Descending
# Teams channel message exports (used to stage conversation data)
$teamsExport = Search-UnifiedAuditLog -StartDate (Get-Date).AddDays(-7) -EndDate (Get-Date) `
-Operations "MessagesExported","ChatRetrieved" `
-ResultSize 1000
For SharePoint mass download incidents, also check whether the downloads used a browser (identifiable by user agent in ClientIP metadata) or a scripted tool -- scripted downloads typically show consistent per-second request rates that differ from human browsing patterns.
The bottom line
The M365 Unified Audit Log is the foundation of every M365 incident investigation. The critical UAL queries are: inbox rule creation (BEC persistence), OAuth app consent (consent phishing), MFA method addition (authentication persistence), and SharePoint mass download (data staging). Parse the AuditData JSON field to extract meaningful details from each event type. Extend default mailbox audit logging to include MailItemsAccessed (requires E5 or Purview Premium) for complete forensic coverage of mailbox access by attackers.
Frequently asked questions
How far back can I search the UAL during an incident response?
The default UAL retention is 90 days for most M365 licenses. Microsoft 365 E5, Office 365 E5, and Microsoft Purview Audit Premium licenses extend retention to 1 year for all event types and up to 10 years for specific compliance-critical events. During IR, check the license level immediately to understand the investigation horizon. If an incident was ongoing for more than 90 days before detection and the tenant has standard licensing, the earliest attacker activity logs may be gone.
What is the difference between UAL and mailbox audit logging?
The UAL captures tenant-level activity across all M365 services including admin operations, SharePoint, Teams, and Entra ID. Mailbox audit logging captures per-mailbox access events at the individual email item level (which emails were read, moved, deleted, or sent). MailItemsAccessed events in mailbox audit logging allow forensic reconstruction of exactly which email items an attacker read. The UAL is the first-stop investigation source; mailbox audit logging provides deeper email-specific forensic detail.
How do I export large volumes of UAL data when 5,000 results are not enough?
Paginate using date ranges. Search-UnifiedAuditLog returns a maximum of 5,000 results per call. For high-volume events, split the time window into smaller intervals (1-hour or 6-hour windows) and run separate queries for each interval. Alternatively, use the Purview compliance portal's audit log export feature, which bypasses the 5,000 row limit and exports up to 50,000 records per export job to a CSV file. For bulk forensic exports, the Microsoft Graph API audit log endpoint also supports paging without the 5,000-record cap.
Can I tell from the UAL which IP address was used for an attacker's login?
Yes, but the UAL is not the best source for this. The Entra ID sign-in logs (Entra ID > Monitoring > Sign-in logs) are the authoritative source for login IP addresses, user agent strings, and authentication methods used. The UAL captures the ClientIP for actions taken after login (inbox rule creation, file downloads, etc.) but some UAL records have empty or internal IP addresses for server-side operations. For IP-based attacker tracing, start with Entra ID sign-in logs filtered to the compromised user and the incident time window.
How long does Microsoft retain Unified Audit Log data and what are the licensing differences?
Retention depends on the license assigned to the user whose actions generated the log entries. Microsoft 365 E3 and below: 90 days. Microsoft 365 E5 or users with the Microsoft Purview Audit (Premium) add-on: 1 year. With Microsoft Purview Audit (Premium), you can extend retention to 10 years by creating audit retention policies in the Purview compliance portal. Note that the license applies to the user performing the action, not the admin investigating -- if an E3 user's mailbox is compromised, their UAL entries are retained for only 90 days regardless of the investigating admin's license level. For incident response on E3 tenants, export and preserve audit logs immediately.
What is the fastest way to search the Unified Audit Log for a specific user's actions during an incident?
Use Search-UnifiedAuditLog with the -UserIds parameter to scope to one account: Search-UnifiedAuditLog -StartDate '2026-06-01' -EndDate '2026-06-30' -UserIds user@domain.com -ResultSize 5000 | Export-Csv user_audit.csv. For incident response where you need results immediately, use the Microsoft Purview compliance portal's Audit search with the User filter set to the compromised account and date range set to the suspected compromise window. The portal supports export to CSV and shows results faster than the PowerShell cmdlet for small result sets. For large result sets (more than 5,000 events), use the Office 365 Management Activity API with PowerShell pagination, as Search-UnifiedAuditLog caps at 5,000 results per call and requires iterating with a session ID to retrieve all results.
Sources & references
Free resources
Critical CVE Reference Card 2025–2026
25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.
Ransomware Incident Response Playbook
Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.
Get threat intel before your inbox does.
50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, daily, no spam.
Unsubscribe anytime. We never sell your data.

Founder & Cybersecurity Evangelist, Decryption Digest
Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals every morning.
Win a $2,495 Black Hat pass.
Full-access to Black Hat USA 2026 in Las Vegas. Subscribe free to enter.
