Here's the trap that catches most teams: any storage account created before 2023 shipped
with the anonymous-access gate wide open, and almost nobody went back to close it. When
Microsoft flipped the default, existing accounts were left exactly as they were. So the
account that's been quietly serving traffic since 2021 is very likely still set to allow
anonymous reads. If a container under it has its public access level set to blob
or container, anyone holding the storage account hostname can pull files down with no
Azure account, no key, nothing.
How Azure Blob containers become publicly readable
Azure Blob anonymous access is controlled by two layered settings that must both be permissive for exposure to occur.
The account-level toggle. Every storage account has a setting called "Allow Blob
anonymous access" (surfaced in the Azure portal under Settings → Configuration). When
this is set to Disabled, anonymous access is blocked at the authorization layer
regardless of what any individual container specifies (the request still reaches the
service, it just gets rejected). Microsoft made Disabled the default for
newly created storage accounts starting in 2023, but storage accounts created before
that change were not retroactively updated. They still default to Enabled, meaning
the account-level gate is open.
The container-level public access level. Even when the account allows anonymous access, each container has its own public access level:
Private: no anonymous access; authentication required for all requests.Blob: individual blobs can be read anonymously if the caller knows the exact URL, but the container's contents cannot be enumerated.Container: anonymous callers can enumerate the container (list all blobs) and download any blob without authentication.
The Container access level is the more dangerous of the two public options. It
combines directory listing with full object read, so an attacker who only knows the
storage account hostname and container name has full access to every file inside.
The common path to exposure: a developer sets a container to Container access during
a prototype or data-migration phase so a colleague can access files without going
through Azure AD. The project ships, the container access level is never changed back,
and the storage account's account-level toggle was never set to Disabled because the
account predates the 2023 default change.
How to detect anonymous access
Unauthenticated container enumeration. If a container's public access level is set
to Container, the following request returns an XML manifest of all blobs inside it
with no credentials or Authorization header required:
GET https://<account>.blob.core.windows.net/<container>?restype=container&comp=list
A 200 response with <EnumerationResults> XML confirms the container is publicly
listable. The error responses are easy to mix up:
- 409
PublicAccessNotPermitted: the account-level toggle isDisabled. The container's own setting is irrelevant at this point; the account gate shut you out. - 404
ContainerNotFound(a variant ofResourceNotFound): the container is either private or simply doesn't exist. Azure deliberately won't tell you which, so a 404 here is not a signal that you've found a real-but-locked container. - 403
AuthorizationFailure: a distinct case, returned when the request carries credentials or conditions that don't authorize the operation. Don't conflate it with the 404 above; they mean different things.
Azure CLI account-level check. If you own or have access to the storage account:
az storage account show \
--name <account-name> \
--resource-group <rg-name> \
--query "allowBlobPublicAccess"
A true result means the account-level gate is open. Check individual container
access levels:
az storage container list \
--account-name <account-name> \
--query "[*].{name:name, publicAccess:properties.publicAccess}"
Any container showing container or blob under publicAccess should be reviewed.
Check a container here. Paste the blob URL or hostname into the
open-bucket viewer and the tool will run the ?restype=container&comp=list
probe and report whether an anonymous caller can enumerate the contents.
What's at risk
Container-level access (listable + readable) is effectively equivalent to an open
S3 bucket. A caller who issues the ?restype=container&comp=list request gets an XML
document containing every blob name, its size, content type, and last-modified
timestamp. They can then download each blob individually with a direct GET request.
Common findings include:
- Database backup files (
*.bak,*.sql,*.dump) containing customer records, credentials, and schema details. - Application configuration exports with connection strings, API keys, and secrets that were stored outside a key vault.
- Log files recording user activity, internal IP addresses, and session identifiers.
- Document libraries and file uploads from line-of-business applications.
Blob-level access (readable but not enumerable) still bites. The attacker can't
list the container, but predictable naming often makes that unnecessary. Names like
backup-2024-01-15.zip or export-users.csv are directly fetchable with a single GET,
and knowing one date-stamped backup name reveals the format for every other day.
Certificate transparency logs can widen the exposure: they leak custom CDN subdomains
tied to an account (the hostnames, not the blob paths; paths never show up in CT),
handing an attacker the front door to start guessing against.
Remediation
Disable anonymous access at the account level. Start here. One setting shuts the
gate for every container under the account, so you don't have to chase each container's
own access level. In the Azure portal, navigate to the storage account → Settings →
Configuration and set "Allow Blob anonymous access" to Disabled. Via the Azure CLI:
az storage account update \
--name <account-name> \
--resource-group <rg-name> \
--allow-blob-public-access false
Disabling this at the account level overrides any container-level blob or container
setting, immediately blocking anonymous access to all containers in that account.
Set container access to Private. For any container that should not be public,
change its public access level to Private:
az storage container set-permission \
--account-name <account-name> \
--name <container-name> \
--public-access off
Replace permanent anonymous access with SAS tokens. When you need to share individual blobs or containers with external users for a limited time, use Shared Access Signatures (SAS) instead of anonymous public access. A SAS token grants narrowly scoped permission (read-only, specific blob, specific time window) and does not expose surrounding container contents:
az storage blob generate-sas \
--account-name <account-name> \
--container-name <container-name> \
--name <blob-name> \
--permissions r \
--expiry 2026-06-16T00:00:00Z
This produces a time-limited token that grants read access to one blob only. When the token expires, access is revoked automatically.
One caveat on SAS type: prefer a user-delegation SAS (signed with an Azure AD key) or a stored-access-policy SAS over an account-key SAS. The account-key variety can't be revoked before expiry without rotating the storage account key, which kills every other token signed with that key too. A user-delegation SAS dies when you revoke the delegation key, and a stored-access-policy SAS dies when you delete the policy, so both give you a real off switch.
Audit with Azure Policy. Azure provides built-in policy definitions that can be assigned at the subscription or management-group level to flag or deny storage accounts where anonymous access is enabled. Assigning a deny-effect policy prevents new storage accounts from being created with the toggle enabled, and a remediation task can apply the fix to existing accounts at scale.
Rotate credentials found in previously-public containers. If a container was publicly accessible for any period, treat every secret or key it contained as compromised and rotate it: connection strings, API keys, and anything else that lived in blob contents or blob names. Public exposure has no audit trail you can trust to scope the blast radius, so assume the worst and rotate.
For a full index of publicly accessible Azure Blob containers found by this service, see the Azure Blob exposure index. To probe a specific container URL right now, use the open-bucket viewer.