Microsoft Entra Audit Logs Analysis
While sign-in logs record who passed through the perimeter, Microsoft Entra ID Directory Audit Logs record how the directory itself was altered. In cloud intrusions, threat actors rarely remain content with stolen credentials; they modify directory objects to establish persistence, elevate privileges, evade conditional access, and extract high-value assets.
Entra ID audit logs capture every administrative, automated, and lifecycle change executed across tenant objects: user creations, group memberships, directory role assignments, Privileged Identity Management (PIM) elevations, OAuth enterprise application consent, service principal credential additions, and security policy modifications.
This guide provides an exhaustive forensic breakdown of Entra directory audit logs, their JSON schema, critical high-risk event types, and production-grade detection queries.
Concept
Section titled “Concept”Entra ID Directory Audit Logs operate as an immutable, append-only chronological ledger tracking state transitions across all objects governed by the tenant’s control plane:
graph TD subgraph "Entra ID Directory Mutation Engine" ADMIN[Admin Action / Script / PIM] APP[Automated App / Service Principal]
ADMIN -->|Execute Change| CORE[Entra Directory Core Store] APP -->|Execute Change| CORE
CORE -->|Generate Audit Record| PIPELINE[Audit Event Processing Pipeline]
PIPELINE --> AUDIT_LOG["Directory Audit Logs<br/>(AuditLogs in Sentinel / Graph API)"]
AUDIT_LOG --> CAT1[User & Group Lifecycle] AUDIT_LOG --> CAT2[Role Management & PIM Elevations] AUDIT_LOG --> CAT3[App Registrations & Credential Injection] AUDIT_LOG --> CAT4[OAuth Consent Grants] AUDIT_LOG --> CAT5[Conditional Access & Security Policies] endCore Logging Categories
Section titled “Core Logging Categories”- UserManagement & GroupManagement: Tracks account creations, password resets, account enable/disable states, and membership changes in security or role-assignable groups.
- RoleManagement: Records active and eligible directory role assignments (e.g., granting
Global Administrator), role revocations, and PIM activation approvals. - ApplicationManagement: Captures application registrations, creation of service principals, and the addition of secondary client secrets or certificates (a primary persistence technique).
- ConsentManagement: Logs user and administrator consent grants for delegated and application permissions (
OAuth2PermissionGrant). - Policy: Records modifications, disabling, or deletions of Conditional Access policies, Named Locations, and authentication methods.
Why It Matters in DFIR
Section titled “Why It Matters in DFIR”Directory audit logs provide the definitive evidence required to reconstruct an adversary’s post-compromise actions:
- Detecting Rogue Application Backdoors: Adversaries frequently inject new client secrets or certificates into existing, legitimate enterprise applications to achieve persistent, silent API access that survives user password resets.
- Exposing Privilege Escalation Chains: Tracking the exact moment an attacker added a compromised account to a privileged group (e.g.,
Helpdesk Admins) or activated a PIM role with bogus ticketing justification. - Auditing Malicious OAuth Consent (Illicit Consent Grant): Identifying when an attacker tricked a user or administrator into granting broad API permissions (
Mail.ReadWrite,Files.ReadWrite.All) to an external multi-tenant application. - Uncovering Defense Evasion (Security Policy Tampering): Proving that an attacker altered a Conditional Access policy to exclude their own IP range or disabled legacy authentication blocking rules.
- Tracking Rogue Authentication Methods: Detecting when an attacker added a secondary FIDO2 security key or authenticator app to a victim’s account to secure persistence.
JSON Schema Deep Dive: The Critical Forensic Fields
Section titled “JSON Schema Deep Dive: The Critical Forensic Fields”When querying directory audit logs via Microsoft Graph API (/auditLogs/directoryAudits) or Azure Log Analytics (AuditLogs), the following fields form the core evidentiary structure:
| Field Name (Graph API) | Field Name (Log Analytics) | Forensic Significance & Investigative Interpretation |
|---|---|---|
id | CorrelationId / Id | Unique event GUID. Correlate with admin sign-in logs to identify the interactive session that initiated the change. |
activityDateTime | TimeGenerated | Precise UTC timestamp when the directory modification was committed to the store. |
activityDisplayName | OperationName | Human-readable action name (e.g., “Add member to role”, “Update application - Certificates and secrets management”). |
category | Category | High-level domain: RoleManagement, ApplicationManagement, UserManagement, Policy. |
loggedByService | LoggedByService | Underlying subsystem that authored the event: Core Directory, PIM, Self-service Password Reset. |
initiatedBy | InitiatedBy | Actor identity. Can be a User (userPrincipalName, ipAddress, id) or an Application (appId, displayName, servicePrincipalId). |
targetResources | TargetResources | Array of modified objects. Contains id, displayName, type (“User”, “Role”, “Application”), and critically: modifiedProperties. |
modifiedProperties | Dynamic JSON array inside TargetResources | The forensic crown jewel. Displays displayName, oldValue, and newValue for every modified attribute. |
additionalDetails | AdditionalDetails | Supplemental metadata: User-Agent, Session ID, PIM ticket justification, or request details. |
result | Result | Outcome: success or failure. |
resultReason | ResultReason | Reason for failure (e.g., “Insufficient privileges”, “PIM approval rejected”). |
Critical Directory Audit Operations for DFIR
Section titled “Critical Directory Audit Operations for DFIR”Responders must monitor and hunt for the following high-risk activityDisplayName operations:
| Activity Display Name | Category | Threat Actor Technique / Context |
|---|---|---|
Add member to role | RoleManagement | Direct assignment of a privileged directory role (e.g., Global Admin, Privileged Auth Admin). |
Add eligible member to role | RoleManagement | PIM eligibility assignment, allowing an attacker to elevate on demand. |
Add member to group | GroupManagement | Adding a user to a group that synchronizes to on-premises AD or has administrative roles assigned. |
Update application - Certificates and secrets management | ApplicationManagement | CRITICAL BACKDOOR: A new client secret or certificate was added to an application. |
Add service principal credentials | ApplicationManagement | Injecting credentials into an existing Service Principal for automated Graph access. |
Consent to application | ConsentManagement | User or admin granted OAuth permissions to an application (e.g., reading emails or files). |
Update policy / Delete policy | Policy | Modifying or removing a Conditional Access policy or tenant-wide authentication policy. |
Admin registered security info | UserManagement | An administrator added an MFA phone number, FIDO2 key, or software token to an account. |
User registered security info | UserManagement | Target user registered a new MFA method (attacker securing persistence post-compromise). |
Hard delete user | UserManagement | Covering tracks: permanent destruction of a rogue account or evidence object. |
What Is Possible vs What Is Not Possible
Section titled “What Is Possible vs What Is Not Possible”What Is Possible
Section titled “What Is Possible”- Exact State Reconstruction: Comparing
oldValueandnewValuewithinmodifiedPropertiesto see exactly what an attacker changed (e.g., which IP was added to a Named Location, or which role was assigned). - Actor Identification: Pinpointing whether an action was performed by a human administrator (with their source IP and UPN) or by an automated service principal via API.
- PIM Audit Trail: Uncovering fraudulent PIM elevation requests, including the timestamp, the approver, and the submitted ticket number.
- Detecting Dormant Backdoors: Identifying newly registered enterprise applications that have received high-privilege application permissions (
RoleAssignmentSchedule.ReadWrite.Directory).
What Is Not Possible
Section titled “What Is Not Possible”- Tracking Data Plane Reads: Audit logs record that an app was granted
Mail.ReadWrite; they do not record which emails were actually read by that app (this requires Purview UAL or Graph Activity logs). - Retroactive Recovery on Expired Tenants: Audit records older than 7 days on Entra Free, or 30 days on P1/P2, are permanently deleted if not exported to Log Analytics.
- Viewing Raw Secret Values: When an attacker adds a client secret, the audit log records the secret’s name, key ID, and expiry date. It does not log the secret text itself.
- Detecting Read-Only Reconnaissance: An administrator or attacker browsing directory objects, listing users, or enumerating groups generates zero directory audit events (reads are not audited in the directory audit log).
Investigation Methodology: Forensic Extraction and KQL Playbooks
Section titled “Investigation Methodology: Forensic Extraction and KQL Playbooks”# ==============================================================================# Hermes Codex - Entra ID Directory Audit Extractor (Microsoft.Graph)# Exports directory changes, role assignments, and application modifications# ==============================================================================
Import-Module Microsoft.Graph.Authentication, Microsoft.Graph.Reports -ErrorAction StopConnect-MgGraph -Scopes "AuditLog.Read.All", "Directory.Read.All" -NoWelcome
$DaysBack = 30$StartDate = (Get-Date).AddDays(-$DaysBack).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")$OutDir = "./Entra_DirectoryAudits_$(Get-Date -Format 'yyyyMMdd')"New-Item -ItemType Directory -Path $OutDir -Force | Out-Null
Write-Host "[*] Extracting Entra Directory Audits since $StartDate..." -ForegroundColor Cyan
$filter = "activityDateTime ge $StartDate"$auditUri = "https://graph.microsoft.com/v1.0/auditLogs/directoryAudits?`$filter=$filter&`$top=500"
$allAudits = [System.Collections.Generic.List[PSObject]]::new()do { $resp = Invoke-MgGraphRequest -Method GET -Uri $auditUri if ($resp.value) { $allAudits.AddRange($resp.value) } $auditUri = $resp.'@odata.nextLink' Write-Host " [+] Collected $($allAudits.Count) audit records..." -ForegroundColor Gray Start-Sleep -Milliseconds 150} while ($null -ne $auditUri)
$outputFile = "$OutDir/DirectoryAudits_Last30Days.jsonl"$allAudits | ForEach-Object { $_ | ConvertTo-Json -Compress -Depth 10 } | Set-Content -Path $outputFile
$hash = Get-FileHash -Path $outputFile -Algorithm SHA256Write-Host "[✓] Extraction Complete. Total: $($allAudits.Count) events. SHA-256: $($hash.Hash)" -ForegroundColor Green// ==============================================================================// Detect Backdoor Secrets / Certificates Added to Applications// MITRE ATT&CK: T1098.001 - Account Manipulation: Additional Cloud Credentials// ==============================================================================AuditLogs| where TimeGenerated >= ago(30d)| where OperationName in ( "Update application - Certificates and secrets management", "Add service principal credentials", "Add service principal", "Update application")| where Result =~ "success"| mvexpand TargetResources| extend TargetAppName = tostring(TargetResources.displayName), TargetAppId = tostring(TargetResources.id), TargetType = tostring(TargetResources.type)| mvexpand TargetResources.modifiedProperties| extend PropName = tostring(TargetResources_modifiedProperties.displayName), OldVal = tostring(TargetResources_modifiedProperties.oldValue), NewVal = tostring(TargetResources_modifiedProperties.newValue)| where PropName has_any ("KeyDescription", "CredentialData", "Certificates", "ClientSecret")| project TimeGenerated, OperationName, InitiatedBy = tostring(InitiatedBy.user.userPrincipalName), InitiatedIP = tostring(InitiatedBy.user.ipAddress), TargetAppName, TargetAppId, PropName, NewVal, CorrelationId| order by TimeGenerated desc// ==============================================================================// Detect Assignment of Tier-0 Entra ID Directory Roles// ==============================================================================let Tier0Roles = dynamic([ "Global Administrator", "Privileged Role Administrator", "Security Administrator", "Conditional Access Administrator", "User Administrator", "Exchange Administrator", "Application Administrator", "Cloud Application Administrator"]);
AuditLogs| where TimeGenerated >= ago(30d)| where OperationName in ("Add member to role", "Add eligible member to role")| where Result =~ "success"| mvexpand TargetResources| extend TargetRoleName = tostring(TargetResources.displayName)| mvexpand TargetResources.modifiedProperties| extend PropName = tostring(TargetResources_modifiedProperties.displayName), TargetUser = tostring(TargetResources_modifiedProperties.newValue)| where PropName == "Role.DisplayName" and TargetRoleName in (Tier0Roles)| project TimeGenerated, OperationName, RoleAssigned = TargetRoleName, AssignedTo = TargetUser, AssignedBy = tostring(InitiatedBy.user.userPrincipalName), AssignedByIP = tostring(InitiatedBy.user.ipAddress), CorrelationId| order by TimeGenerated desc// ==============================================================================// Detect Changes to Authentication Methods (Attacker Adding Persistence MFA)// ==============================================================================AuditLogs| where TimeGenerated >= ago(14d)| where Category == "UserManagement"| where OperationName in ( "User registered security info", "Admin registered security info", "User registered all required security info", "Admin updated security info")| mvexpand TargetResources| extend TargetUser = tostring(TargetResources.userPrincipalName)| mvexpand TargetResources.modifiedProperties| extend PropName = tostring(TargetResources_modifiedProperties.displayName), MethodDetails = tostring(TargetResources_modifiedProperties.newValue)| project TimeGenerated, OperationName, TargetUser, InitiatedBy = tostring(InitiatedBy.user.userPrincipalName), InitiatedIP = tostring(InitiatedBy.user.ipAddress), PropName, MethodDetails, CorrelationId| order by TimeGenerated descInvestigation Scenario: The Service Principal Stealth Backdoor
Section titled “Investigation Scenario: The Service Principal Stealth Backdoor”Incident Context
Section titled “Incident Context”An attacker compromises the credentials of an IT administrator who holds the Cloud Application Administrator role.
Step-by-Step Reconstruction via Directory Audits
Section titled “Step-by-Step Reconstruction via Directory Audits”- Initial Access: At 02:14 UTC, the attacker authenticates from a foreign IP address (recorded in
m365-09). - Reconnaissance: The attacker enumerates existing Enterprise Applications via PowerShell. (Zero audit events generated—read-only).
- The Backdoor Action (02:22:15 UTC):
OperationName:Update application - Certificates and secrets management.InitiatedBy.user.userPrincipalName:compromised_it_admin@corp.com.TargetResources[0].displayName:Corp-Reporting-Automation(a legitimate, trusted business application).TargetResources[0].modifiedProperties:displayName:KeyDescription.oldValue:[].newValue:[{"KeyIdentifier":"3a8f...","DisplayName":"BackupKey2026","EndDateTime":"2028-09-16T00:00:00Z"}].
- Privilege Exploitation:
- The application
Corp-Reporting-Automationalready held pre-existing application permissions forExchange.ManageAsAppandUser.ReadWrite.All. - By adding a secret to an existing application rather than creating a new one, the attacker avoided triggering “New Application Created” alerts.
- The attacker immediately switched to authenticating as the Service Principal, bypassing all user-targeted Conditional Access policies and password resets.
- The application
Without inspecting the modifiedProperties in Entra Directory Audits, the security team would have reset the administrator’s password, believed the incident was contained, while the attacker retained permanent, privileged programmatic access.
The Transversal Doctrine: Audit Telemetry vs Malicious Proof
Section titled “The Transversal Doctrine: Audit Telemetry vs Malicious Proof”Directory modifications must be evaluated against the Hermes Codex certainty framework:
+-------------------------------------------------------------------------------+| THE 7 LEVELS OF FORENSIC CERTAINTY || || 1. Possible -> Platform supports the administrative operation. || 2. Configured -> Audit pipeline active, Diagnostic Settings streaming. || 3. Authorized -> Initiating identity held the required RBAC role. || 4. Accessible -> Identity was able to reach the Graph API / Admin Portal. || 5. Utilized -> Directory mutation executed and accepted by store. || 6. Observed -> Record appears in DirectoryAudits with modifiedProperties.|| 7. Proven -> Reconstructed correlation proves unauthorized backdooring.|+-------------------------------------------------------------------------------+Forensic Distinctions in Audit Logs
Section titled “Forensic Distinctions in Audit Logs”- Authorized != Utilized: An administrator held
Privileged Role Administrator(authorized to assign Global Admin). That does not prove they elevated anyone unless anAdd member to roleentry is observed inAuditLogs. - Utilized != Malicious: An
Add service principal credentialsevent is a standard operational task performed during CI/CD maintenance. Proving that an event was malicious requires demonstrating that:- The initiating user session was compromised (e.g., unfamiliar IP, AiTM session token).
- No authorized change request or DevOps ticket correlates with the modification timestamp.
- Downstream service principal sign-ins occurred from adversary-controlled infrastructure.
- Observed != Proven: Observing a record with
Result = "success"confirms that a change was committed. Proving an intrusion requires linking that change to the broader kill chain (e.g., initial phishing -> sign-in -> audit mutation -> data exfiltration in UAL).
Common Pitfalls and Traps
Section titled “Common Pitfalls and Traps”| Trap | Technical Root Cause | Investigative Impact | Corrective Action |
|---|---|---|---|
| Looking for Mailbox Rules in Directory Audits | User inbox rules are Exchange Online data-plane objects, not Entra directory objects. | Searching Entra audit logs for malicious forwarding rules yields 0 results. | Query Purview UAL (m365-13) for New-InboxRule or Set-Mailbox. |
| Overlooking Service Principal Initiators | Filtering exclusively for InitiatedBy.user and ignoring changes made by automated apps (InitiatedBy.app). | Misses automated lateral movement and privilege escalation executed via Graph API scripts. | Always query both InitiatedBy.user and InitiatedBy.app branches. |
| Stopping at User Password Reset | Failing to audit for secondary credentials added to enterprise apps during the compromise. | Threat actor retains backdoor API access indefinitely despite password change. | Always audit Update application - Certificates and secrets management for the victim’s timeline. |
| Ignoring Nested ModifiedProperties | modifiedProperties is ingested as a JSON array of objects inside TargetResources. | Basic CSV exports or flat queries truncate old and new values. | Use mvexpand in KQL or parse modifiedProperties as JSON objects in PowerShell. |
| Assuming Read Operations are Audited | Directory Audits only log mutations (Create, Update, Delete). | Believing that lack of audit logs proves an attacker did not perform directory reconnaissance. | Acknowledge that read operations are invisible in Directory Audits; inspect Graph Activity Logs if enabled. |
2026 Feature State: Entra Directory Audit Logs
Section titled “2026 Feature State: Entra Directory Audit Logs”Recent Changes
Section titled “Recent Changes”- PIM 2.0 Telemetry Modernization: Role eligibility and activation events now include comprehensive approval metadata, ticket system links, and step-up authentication claims natively in the audit record.
- Application Credential Expiration Guardrails: Entra ID enforces stricter maximum lifetimes on client secrets, generating explicit warning audit events prior to certificate expiration.
- Cross-Tenant Synchronization Auditing: Dedicated audit categories track changes to B2B direct connect, cross-tenant access policies, and inbound/outbound trust configurations.
Deprecated Features
Section titled “Deprecated Features”- Azure AD Graph API (
graph.windows.net): Permanently deprecated and deactivated. All automated audit log collection pipelines must target Microsoft Graph (graph.microsoft.com/v1.0/auditLogs/directoryAudits). - Legacy MSOnline PowerShell (
Get-MsolAuditLog): Completely non-functional. Scripts must useMicrosoft.Graph.Reports.
Current Limitations
Section titled “Current Limitations”- No Native Read Auditing: Graph API read queries (
GET /users,GET /groups) are not captured in Directory Audit logs. Capturing read reconnaissance requires streaming Microsoft Graph Activity Logs to an Azure Log Analytics workspace. - Secret Text Omission: For security reasons, Microsoft never logs the plain-text secret string generated during an
Add service principal credentialsevent; responders can only identify the key identifier, description, and expiration.
Key Takeaways
Section titled “Key Takeaways”- Directory Audits record persistence and privilege escalation: Whenever an admin account is compromised, prioritize auditing
ApplicationManagementandRoleManagement. - The
modifiedPropertiesfield is the core evidence: Always parse old and new values to determine the exact configuration delta. - Beware of application credential injection: Attackers bypass password resets by adding certificates and secrets to existing, legitimate enterprise applications.
- Audit MFA method changes: Verify whether secondary phone numbers or FIDO2 keys were added to the compromised account during the intrusion.
- Remember that reads are not audited here: Lack of audit logs does not mean lack of reconnaissance; use Graph Activity Logs to investigate directory enumeration.