Azure Blob Storage public access misconfigurations remain one of the most common causes of cloud data exposure. Despite Microsoft adding tenant-level controls in 2023 and making private-by-default the recommended configuration, incident response data consistently shows exposed storage accounts in Azure environments — often containing sensitive documents, application logs, database backups, and build artifacts.
This guide covers how public access works at the API level, how attackers enumerate exposed containers, and how to enforce a hardened configuration across your Azure estate.
How Azure Blob Public Access Works
Azure Blob Storage has two distinct access layers that are frequently confused:
Storage Account Level (allowBlobPublicAccess)
This property, set on the storage account resource itself, acts as a master switch. When set to false, it overrides all container-level settings and prevents anonymous access regardless of container configuration. When set to true (the legacy default), container-level settings take effect.
Container Level (publicAccess property)
Individual containers can be set to:
None— private, requires authenticationBlob— anonymous read access to individual blobs (requires knowing the URL)Container— anonymous read + list access (allows enumeration of all blob names)
The dangerous combination is allowBlobPublicAccess: true on the storage account AND publicAccess: Container on one or more containers. This makes blob contents publicly readable and their names enumerable — without any authentication.
How Attackers Enumerate Exposed Storage
Exposed Azure Blob containers are routinely discovered through automated scanning. The common enumeration path:
Step 1: Discover the storage account endpoint
Storage account URLs follow predictable patterns:
https://<account-name>.blob.core.windows.net/
Attackers use wordlists of common account naming patterns (companyname-backup, companyname-dev, companyname-logs) combined with tools like BlobHunter or simple curl loops.
Step 2: Enumerate containers
If publicAccess: Container is set, listing containers requires no authentication:
# Anonymous container enumeration via REST API
curl "https://mystorageaccount.blob.core.windows.net/?comp=list"
# Expected response for a misconfigured account
# Returns XML listing all public container names
Step 3: List and download blobs
# List blobs in an exposed container
curl "https://mystorageaccount.blob.core.windows.net/backups?restype=container&comp=list"
# Download a specific blob
curl -O "https://mystorageaccount.blob.core.windows.net/backups/database-backup-2026-05-30.sql.gz"
Using the Azure CLI, a misconfigured container can be dumped in seconds:
az storage blob list \
--account-name mystorageaccount \
--container-name backups \
--output table \
--no-auth-required
Auditing for Exposed Storage Accounts
Find accounts with public access enabled (Azure CLI)
# List all storage accounts in a subscription with public blob access allowed
az storage account list \
--query "[?allowBlobPublicAccess==true].{Name:name, RG:resourceGroup, Location:location}" \
--output table
# Check a specific storage account
az storage account show \
--name mystorageaccount \
--resource-group myresourcegroup \
--query "{AllowPublicAccess:allowBlobPublicAccess, DefaultAction:networkRuleSet.defaultAction}" \
--output json
Find containers with public access set
# For each storage account with public access enabled, check containers
az storage container list \
--account-name mystorageaccount \
--auth-mode login \
--query "[?properties.publicAccess != 'None'].{Name:name, Access:properties.publicAccess}" \
--output table
Azure Resource Graph query for tenant-wide visibility
Resources
| where type == "microsoft.storage/storageaccounts"
| where properties.allowBlobPublicAccess == true
| project name, resourceGroup, location, subscriptionId,
kind = properties.kind,
sku = sku.name
| order by subscriptionId, resourceGroup
Run this in Azure Resource Graph Explorer or via CLI:
az graph query -q "
Resources
| where type == 'microsoft.storage/storageaccounts'
| where properties.allowBlobPublicAccess == true
| project name, resourceGroup, location
"
Remediation
Disable public access on a storage account
az storage account update \
--name mystorageaccount \
--resource-group myresourcegroup \
--allow-blob-public-access false
Set all containers in an account to private
# Get all containers and set to private
for container in $(az storage container list \
--account-name mystorageaccount \
--auth-mode login \
--query "[].name" \
--output tsv); do
az storage container set-permission \
--name "$container" \
--account-name mystorageaccount \
--public-access off \
--auth-mode login
echo "Set $container to private"
done
Enforcing Policy at Scale with Azure Policy
Remediation alone is not enough — without preventive controls, new storage accounts can be created with public access enabled. Azure Policy provides guardrails.
Built-in policy: Storage account public access should be disallowed
Microsoft provides a built-in policy definition for this:
# Assign the built-in "Storage account public access should be disallowed" policy
az policy assignment create \
--name "deny-blob-public-access" \
--display-name "Deny public blob access on storage accounts" \
--policy "4fa4b6c0-31ca-4c0d-b10d-24b96f62a751" \
--scope "/subscriptions/<subscription-id>" \
--enforcement-mode Default
Custom policy for audit and deny
{
"mode": "All",
"policyRule": {
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.Storage/storageAccounts"
},
{
"field": "Microsoft.Storage/storageAccounts/allowBlobPublicAccess",
"equals": "true"
}
]
},
"then": {
"effect": "Deny"
}
}
}
Apply this as a deny policy at the management group level to cover all child subscriptions.
Additional Hardening Steps
Enable Microsoft Defender for Storage
Defender for Storage detects anomalous access patterns — including anonymous access to previously private containers and unusual geographic access. Enable per storage account or at the subscription level:
az security pricing create \
--name StorageAccounts \
--tier Standard
Require Azure AD authentication only
Disable shared key access to force all requests through Azure AD:
az storage account update \
--name mystorageaccount \
--resource-group myresourcegroup \
--allow-shared-key-access false
Network restrictions
Limit storage account access to specific virtual networks or IP ranges:
az storage account update \
--name mystorageaccount \
--resource-group myresourcegroup \
--default-action Deny \
--bypass AzureServices
Monitor with Azure Monitor and Defender for Cloud
Enable diagnostic logging to capture all read/write/delete operations. The StorageBlobLogs table in Log Analytics is searchable for anonymous access events:
StorageBlobLogs
| where AuthenticationType == "Anonymous"
| where TimeGenerated > ago(7d)
| summarize Count = count(), Bytes = sum(ResponseBodySize)
by bin(TimeGenerated, 1h), CallerIpAddress, Uri
| order by Count desc
Public access misconfigurations in Azure storage are preventable with a combination of tenant-level defaults, Azure Policy enforcement, and monitoring. The critical step is applying the deny policy at management group scope — remediation of individual accounts without preventive controls is a continuous manual burden.