Microsoft Purview Unified Audit Log (UAL) Deep Dive
The Unified Audit Log (UAL) in Microsoft Purview represents the forensic backbone of Microsoft 365 incident response. When adversaries operate inside an enterprise tenant—whether altering inbox forwarding rules in Exchange Online, exfiltrating intellectual property from SharePoint Online, manipulating channels in Microsoft Teams, or provisioning OAuth applications in Entra ID—the UAL is the single centralized store engineered to record their footprint.
However, treating the UAL as a standard relational database or flat syslog stream is the most common failure mode in cloud DFIR. The UAL is an asynchronous, multi-tenant aggregation engine that consumes events from more than 40 disparate microservices. Its schema is polymorphic, its ingestion latency varies by workload from minutes to full days, and its query interfaces impose severe pagination and rate-limiting boundaries.
1. Architectural Topology & Ingestion Pipeline
Section titled “1. Architectural Topology & Ingestion Pipeline”The UAL does not generate telemetry directly. Instead, individual Microsoft 365 workloads record transactions locally within their respective service boundaries and publish them onto a distributed internal event hub:
graph TD subgraph "Workload Producers" EXO[Exchange Online<br/>Mailbox & Admin Ops] SPO[SharePoint Online<br/>File & Sharing Events] ODB[OneDrive for Business<br/>File Sync & Downloads] TEAMS[Microsoft Teams<br/>Chats, Channels, Bots] ENTRA[Entra ID<br/>Directory Changes] PUR[Purview Services<br/>DLP & eDiscovery] end
subgraph "Purview Ingestion Fabric" HUB[Internal Event Bus / Service Bus] NORM[Normalization & Enrichment Engine<br/>Client IP, User Identity, Session Binding] ES_STORE[Purview Search & Compliance Store<br/>Partitioned by Tenant & Timestamp] end
subgraph "Forensic Ingestion Interfaces" PS[Exchange Online PowerShell<br/>Search-UnifiedAuditLog] MGMT_API[Office 365 Management Activity API<br/>Webhook Subscriptions & Polling] GRAPH[Microsoft Graph API<br/>/security/auditLog/queries] PORTAL[Purview Compliance Portal<br/>GUI Asynchronous Search Jobs] end
EXO -->|15m - 60m| HUB SPO -->|15m - 30m| HUB ODB -->|15m - 30m| HUB TEAMS -->|1h - 24h| HUB ENTRA -->|15m - 60m| HUB PUR -->|30m - 4h| HUB
HUB --> NORM NORM --> ES_STORE
ES_STORE --> PS ES_STORE --> MGMT_API ES_STORE --> GRAPH ES_STORE --> PORTALIngestion Latencies per Workload
Section titled “Ingestion Latencies per Workload”A major forensic pitfall during live incident containment is assuming that the absence of a record in the UAL implies the absence of adversary activity. Ingestion latency (the duration between event occurrence and indexing in the UAL) is workload-dependent:
| Workload | Typical Ingestion Latency | Maximum SLA / Edge Latency | Forensic Caveat |
|---|---|---|---|
| Exchange Online | 15 – 45 minutes | Up to 4 hours | High-volume mailbox events (MailItemsAccessed) may experience batch buffering. |
| SharePoint & OneDrive | 15 – 30 minutes | Up to 2 hours | Batch upload/download operations are consolidated and flushed periodically. |
| Microsoft Entra ID | 15 – 60 minutes | Up to 2 hours | Entra audit logs appear faster in the Entra portal (~2-5 min) than in the Purview UAL. |
| Microsoft Teams | 2 – 6 hours | Up to 24 hours | Teams compliance logs have the highest variance; real-time response cannot rely on UAL alone. |
| Power Platform / Apps | 1 – 4 hours | Up to 24 hours | Low-priority telemetry pipeline. |
2. UAL Record Schema Anatomy
Section titled “2. UAL Record Schema Anatomy”Every entry in the UAL consists of an outer envelope containing standardized platform metadata, and an inner polymorphic JSON payload stored as a string within the AuditData attribute.
Outer Envelope Fields
Section titled “Outer Envelope Fields”| Field | Type | Description | Forensic Relevance |
|---|---|---|---|
CreationTime | UTC DateTime | Exact timestamp when the action occurred on the source workload. | Crucial: this is the workload execution time, not the UAL ingestion time. |
RecordType | Integer / Enum | Numeric identifier mapping to the workload or event category. | Allows fast server-side filtering (e.g., 15 for Entra, 2 for ExchangeItem). |
Operation | String | Specific action performed (e.g., FileAccessed, New-InboxRule). | Primary pivot for behavioral clustering and attacker technique hunting. |
OrganizationId | GUID | Unique directory tenant identifier. | Validates multi-tenant isolation and prevents cross-tenant contamination. |
UserType | Integer / Enum | Actor classification (Regular, Admin, System, ServicePrincipal). | Detects automated vs interactive actions; flags unauthorized admin elevations. |
UserKey | String | Canonical subject identifier or immutable user PUID. | Tracks actors across UPN renaming or account alias manipulation. |
Workload | String | Target service (Exchange, SharePoint, AzureActiveDirectory). | Isolates scope to specific application planes. |
ResultStatus | String | Outcome (Succeeded, Failed, PartiallySucceeded). | Identifies brute-force failures vs successful exploit executions. |
AuditData | String (JSON) | Workload-specific schema containing deep contextual parameters. | Must be unpacked to extract IPs, file paths, parameters, and session tokens. |
Key RecordType Enumeration
Section titled “Key RecordType Enumeration”Filtering queries by RecordType significantly accelerates server-side search performance and prevents hitting query result ceilings:
RecordType 1 : ExchangeAdmin (Admin PowerShell cmdlets & configuration changes)RecordType 2 : ExchangeItem (Mailbox item-level access: MailItemsAccessed, SoftDelete)RecordType 4 : General (Shared platform operations, eDiscovery searches)RecordType 6 : SharePointFileOperation (File downloads, views, modifications, deletions)RecordType 8 : ComplianceDLPExchange (Data loss prevention rule matches on mail flow)RecordType 15 : AzureActiveDirectory (User management, role assignments, app consents)RecordType 25 : MicrosoftTeams (Team creation, channel settings, guest additions)RecordType 28 : ThreatIntelligence (Automated attack simulation and SecOps hunting)RecordType 49 : SecurityComplianceAlerts (Purview and Defender alert generation events)3. Dissecting the Polymorphic AuditData Payload
Section titled “3. Dissecting the Polymorphic AuditData Payload”Because AuditData stores varying properties depending on the RecordType, incident responders must adapt their parsing logic to each workload.
{ "CreationTime": "2026-09-15T08:14:22", "Id": "a4f89d32-28e1-4c12-b891-883109aef102", "Operation": "MailItemsAccessed", "OrganizationId": "72f988bf-86f1-41af-91ab-2d7cd011db47", "RecordType": 2, "ResultStatus": "Succeeded", "UserKey": "1003200189ABCDEF@tenant.onmicrosoft.com", "UserType": 0, "Version": 1, "Workload": "Exchange", "ClientIPAddress": "198.51.100.45", "ClientInfoString": "Client=REST;Client=OutlookService;Version=16.0.17830.20138;", "ClientRequestId": "9b12a843-ef45-42a1-9a14-8712a45930b1", "ExternalAccess": false, "InternalLogonType": 0, "MailboxOwnerUPN": "cfo@defense-corp.org", "MailboxOwnerSid": "S-1-5-21-3829104821-291823901-192831092-1004", "MailboxGuid": "4d12f890-341a-4f89-91a1-987120adfe01", "LogonType": 0, "OperationProperties": [ { "Name": "MailAccessType", "Value": "Bind" }, { "Name": "IsThrottled", "Value": "False" } ], "Folder": { "FolderItems": [ { "InternetMessageId": "<BL3PR02MB10293847@BL3PR02.EURPRD02.PROD.OUTLOOK.COM>", "Subject": "CONFIDENTIAL: Q3 Financial Restructuring Strategy", "ItemSize": 45812 } ], "Id": "LgAAAAD4k1...AQ==" }}{ "CreationTime": "2026-09-15T08:32:11", "Id": "e12a4b89-1234-4567-890a-bcdef0123456", "Operation": "FileDownloaded", "OrganizationId": "72f988bf-86f1-41af-91ab-2d7cd011db47", "RecordType": 6, "ResultStatus": "Succeeded", "UserKey": "i:0#.f|membership|attacker_recon@external.org", "UserType": 0, "Workload": "SharePoint", "ClientIP": "203.0.113.195", "UserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:130.0) Gecko/20100101 Firefox/130.0", "ObjectId": "https://defensecorp.sharepoint.com/sites/RAndD/Shared Documents/Project_Hermes_Blueprint.pdf", "SiteUrl": "https://defensecorp.sharepoint.com/sites/RAndD", "SourceFileName": "Project_Hermes_Blueprint.pdf", "SourceFileExtension": "pdf", "HighPriorityMedia": false, "EventSource": "SharePoint", "ItemType": "File"}{ "CreationTime": "2026-09-15T09:02:44", "Id": "f98234ab-5678-4321-9abc-0123456789de", "Operation": "Add member to role.", "OrganizationId": "72f988bf-86f1-41af-91ab-2d7cd011db47", "RecordType": 15, "ResultStatus": "Success", "Workload": "AzureActiveDirectory", "Actor": [ { "ID": "admin_compromised@defense-corp.org", "Type": 5 } ], "Target": [ { "ID": "Global Administrator", "Type": 2 }, { "ID": "User_Backdoor_Guest#EXT#@defense-corp.onmicrosoft.com", "Type": 1 } ], "ModifiedProperties": [ { "Name": "Role.DisplayName", "NewValue": ""Global Administrator"", "OldValue": "" } ]}4. Querying & Extracting the UAL: Operational Tooling
Section titled “4. Querying & Extracting the UAL: Operational Tooling”Investigating the UAL via the web compliance portal is unsuitable for incident response due to a hard ceiling of 50,000 exportable items and asynchronous browser latency. DFIR operations require programmatic extraction.
Exchange Online PowerShell: Mastering Search-UnifiedAuditLog
Section titled “Exchange Online PowerShell: Mastering Search-UnifiedAuditLog”The Search-UnifiedAuditLog cmdlet is the primary interactive tool. However, naive executions often crash or return incomplete data sets due to pagination limits.
# Authenticate with least-privilege Compliance/View-Only Audit Log permissionsConnect-ExchangeOnline -UserPrincipalName dfir-analyst@defense-corp.org
# Critical parameters: Date window slicing and Session Command pagination$startDate = (Get-Date "2026-09-14T00:00:00Z").ToUniversalTime()$endDate = (Get-Date "2026-09-15T23:59:59Z").ToUniversalTime()
# Execute sliced extraction$auditEvents = Search-UnifiedAuditLog ` -StartDate $startDate ` -EndDate $endDate ` -RecordType ExchangeItem ` -Operations "MailItemsAccessed", "SendAs", "New-InboxRule" ` -ResultSize 5000 ` -FormattedThe 50,000 Record Limit & Date-Slicing Pagination Pattern
Section titled “The 50,000 Record Limit & Date-Slicing Pagination Pattern”Search-UnifiedAuditLog caps any single query at 50,000 records. If an attacker performs a high-volume activity (or during normal business hours in large tenants), a 24-hour query might exceed 50,000 events. The cmdlet will silently stop returning results beyond 50,000 without raising an exception.
To guarantee complete evidence recovery, analysts must implement adaptive date-time window slicing:
function Get-CompleteUnifiedAuditLog { param( [Parameter(Mandatory=$true)][DateTime]$StartUtc, [Parameter(Mandatory=$true)][DateTime]$EndUtc, [Parameter(Mandatory=$false)][string]$RecordType, [Parameter(Mandatory=$false)][string]$Operations )
$windowStart = $StartUtc $intervalHours = 4 $allRecords = [System.Collections.Generic.List[PSObject]]::new()
while ($windowStart -lt $EndUtc) { $windowEnd = $windowStart.AddHours($intervalHours) if ($windowEnd -gt $EndUtc) { $windowEnd = $EndUtc }
Write-Host "[-] Slicing UAL Window: $windowStart -> $windowEnd" -ForegroundColor Cyan
$session = "DFIR_Session_" + [Guid]::NewGuid().ToString() $page = 1 $totalWindowCount = 0
do { $params = @{ StartDate = $windowStart EndDate = $windowEnd ResultSize = 5000 SessionCommand = "ReturnLargeSet" SessionId = $session } if ($RecordType) { $params["RecordType"] = $RecordType } if ($Operations) { $params["Operations"] = $Operations }
$batch = Search-UnifiedAuditLog @params if ($batch) { $batchCount = $batch.Count $totalWindowCount += $batchCount $allRecords.AddRange($batch) Write-Host " [+] Page $page : Retrieved $batchCount events (Total Window: $totalWindowCount)" -ForegroundColor Gray $page++ } else { break } } while ($batchCount -eq 5000)
# If a single window hits near 50,000, reduce the interval and re-run if ($totalWindowCount -ge 49000) { Write-Warning "[!] Window reached 50k ceiling. Halving window interval..." $intervalHours = [Math]::Max(1, [Math]::Floor($intervalHours / 2)) continue }
$windowStart = $windowEnd }
return $allRecords}5. Automated Telemetry Export: Office 365 Management Activity API
Section titled “5. Automated Telemetry Export: Office 365 Management Activity API”For ongoing incident monitoring or enterprise SIEM ingestion, the Office 365 Management Activity API is the architectural standard. It replaces interactive polling with an asynchronous notification model:
sequenceDiagram autonumber participant App as SIEM / DFIR Collector (Azure AD App) participant API as Office 365 Management API participant Blob as Azure Storage Content Blob
App->>API: POST /subscriptions/start?contentType=Audit.Exchange API-->>App: 200 OK (Subscription Active) Note over API: Workloads continuously aggregate and publish logs API->>App: Webhook Callback (Or App polls /subscriptions/content) App->>API: GET /subscriptions/content?contentType=Audit.Exchange API-->>App: 200 OK (List of Content URIs) loop For each Content URI App->>Blob: GET https://manage.office.com/.../contentBlob Blob-->>App: 200 OK (GZipped Array of UAL Records) endThe Management API separates content streams into five independent content types:
Audit.AzureActiveDirectoryAudit.ExchangeAudit.SharePointAudit.General(Includes Teams, OneDrive, Purview, PowerBI)DLP.All(Data Loss Prevention events across all services)
6. Advanced KQL Hunting in Defender XDR (CloudAppEvents)
Section titled “6. Advanced KQL Hunting in Defender XDR (CloudAppEvents)”When a tenant licenses Microsoft Defender for Cloud Apps or Microsoft 365 Defender, UAL events are ingested into Defender’s Advanced Hunting fabric and stored in the CloudAppEvents table. This enables sub-second KQL querying across 30 days of raw telemetry:
// Hunting for anomalous mass mail forwarding rule creation and external accessCloudAppEvents| where TimeGenerated > ago(7d)| where ActionType in ("New-InboxRule", "Set-InboxRule", "MailItemsAccessed")| extend AuditData = parse_json(RawEventData)| extend ClientIP = tostring(AuditData.ClientIPAddress), Mailbox = tostring(AuditData.MailboxOwnerUPN), RuleName = tostring(AuditData.Parameters[0].Value)| summarize MailItemsAccessedCount = countif(ActionType == "MailItemsAccessed"), RuleCreations = make_set_if(RuleName, ActionType has "InboxRule"), IPList = make_set(ClientIP), CountryList = make_set(Location) by Mailbox, bin(TimeGenerated, 1h)| where array_length(RuleCreations) > 0| order by TimeGenerated desc7. Forensic Gotchas & Investigative Pitfalls
Section titled “7. Forensic Gotchas & Investigative Pitfalls”8. Cross-Reference & Operational Mesh
Section titled “8. Cross-Reference & Operational Mesh”- 08. Logging Architecture — High-level map of the four logging planes in M365.
- 14. Audit Log vs Unified Audit Log vs Entra Audit Log — Comparative matrix of scope, latency, and overlaps.
- 17. Mailbox Auditing & MailItemsAccessed Deep Dive — Granular extraction of email access evidence.
- 18. Microsoft 365 Audit Retention & Licensing Realities — Retention boundaries (180d vs 1y vs 10y) and licensing prerequisites.
- 45. Reconstructing a Microsoft 365 Attack Timeline — End-to-end multi-source correlation methodology.\n