Skip to content

M365 Unified Audit Log (UAL) Forensic Manual

PRIMARY CRIME SCENE EXCHANGE ONLINE & PURVIEW Data Retention: 180 Days (E5 / Audit Advanced: 1 Year)

In a 100% cloud-native organization, the Unified Audit Log (UAL) is the primary audit log for all user, administrative, and application actions across Exchange Online, SharePoint Online, OneDrive, Microsoft Teams, and Microsoft Entra ID.


🎯 Master Workload & RecordType Reference

Section titled “🎯 Master Workload & RecordType Reference”
WorkloadRecordTypeKey Operations to HuntForensic Value
Exchange OnlineExchangeItem (2)MailItemsAccessed, SendAs, SendOnBehalfProves adversary actually read or exfiltrated specific emails
Exchange AdminExchangeAdmin (1)New-InboxRule, Set-Mailbox, Add-MailboxPermissionMailbox delegation backdoors and forwarding rules
SharePoint / OneDriveSharePointFileOperation (6)FileDownloaded, FileAccessed, AnonymousLinkCreatedMass document staging, exfiltration, and external sharing
Entra IDAzureActiveDirectory (8)ConsentToApplication, Add service principal credentialsOAuth consent persistence and backdoored enterprise apps
Purview / SecuritySecurityComplianceCenterEOPFilter (38)New-ComplianceSearch, New-ComplianceSearchActionUncovering unauthorized searches or evidence tampering

🔬 Critical Forensic Operation: MailItemsAccessed (E5 / Purview Audit)

Section titled “🔬 Critical Forensic Operation: MailItemsAccessed (E5 / Purview Audit)”

Standard Exchange logs only prove that a mailbox was accessed. MailItemsAccessed proves which specific message IDs were read or downloaded via sync:

Terminal window
# Query MailItemsAccessed for a compromised user
$audit = Search-UnifiedAuditLog -StartDate (Get-Date).AddDays(-7) -EndDate (Get-Date) `
-RecordType ExchangeItem -Operations "MailItemsAccessed" `
-FreeText "victim.user@company.com" -ResultSize 5000
# Parse raw AuditData JSON to extract exact subject and item IDs
$audit | ForEach-Object {
$data = $_.AuditData | ConvertFrom-Json
[PSCustomObject]@{
CreationTime = $_.CreationDate
ClientIP = $data.ClientIPAddress
AppId = $data.AppId
LogonType = $data.LogonType
Folders = ($data.Item.ParentFolder.FolderPath -join "; ")
ItemCount = $data.Item.Count
}
} | Format-Table -AutoSize

⚡ Production High-Speed Extraction Script

Section titled “⚡ Production High-Speed Extraction Script”

The following production script handles UAL pagination, throttling (HTTP 429), and flattens nested AuditData into a structured timeline:

Terminal window
<#
.SYNOPSIS
Paginates and extracts UAL records to forensic CSV.
#>
param (
[Parameter(Mandatory=$true)]
[string]$TargetUser,
[int]$DaysBack = 14,
[string]$OutputFile = "C:\Forensics\UAL_Extracted.csv"
)
# Connect-ExchangeOnline -ShowBanner:$false
$startDate = (Get-Date).AddDays(-$DaysBack)
$endDate = Get-Date
Write-Host "[*] Extracting UAL records for: $TargetUser from $startDate to $endDate" -ForegroundColor Cyan
$sessionCommand = "Search-UnifiedAuditLog"
$allRecords = [System.Collections.Generic.List[PSObject]]::new()
$sessionIndex = 0
$pageSize = 2000
do {
$records = Search-UnifiedAuditLog -StartDate $startDate -EndDate $endDate `
-FreeText $TargetUser -ResultSize $pageSize -SessionIndex $sessionIndex
if ($records.Count -gt 0) {
foreach ($r in $records) {
$parsed = $r.AuditData | ConvertFrom-Json
$allRecords.Add([PSCustomObject]@{
CreationTime = $r.CreationDate
RecordType = $r.RecordType
Operation = $r.Operations
User = $r.UserIds
Workload = $parsed.Workload
ClientIP = $parsed.ClientIPAddress
UserAgent = $parsed.UserAgent
ResultStatus = $parsed.ResultStatus
RawAuditData = $r.AuditData
})
}
Write-Host " [+] Fetched $($records.Count) records (Total: $($allRecords.Count))" -ForegroundColor Green
$sessionIndex += $records.Count
Start-Sleep -Milliseconds 500 # Avoid throttling
}
} while ($records.Count -eq $pageSize)
$allRecords | Export-Csv -Path $OutputFile -NoTypeInformation
Write-Host "[✓] Extraction Complete! Saved $($allRecords.Count) events to $OutputFile" -ForegroundColor Yellow

🔍 KQL Correlation: Querying UAL in Microsoft Sentinel

Section titled “🔍 KQL Correlation: Querying UAL in Microsoft Sentinel”

If Microsoft 365 logs are streamed to Sentinel or Log Analytics, query the OfficeActivity table:

// Detect mass file downloads within a 15-minute window
OfficeActivity
| where TimeGenerated > ago(7d)
| where Operation == "FileDownloaded"
| summarize DownloadCount = count(), UniqueFiles = dcount(OfficeObjectId), TargetFiles = make_set(OfficeFileName, 15) by UserId, ClientIP, bin(TimeGenerated, 15m)
| where DownloadCount > 50
| order by DownloadCount desc