azure-storage-public-blob-container
Storage account with public blob access enabled and a public container exposes blob data to unauthenticated internet access.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
where
node_class(?account) == Storage and node_type(?account) ==
ObjectStorage
node_type(?account) matched provider_type == 'Microsoft.Storage/storageAccounts'
?account.properties.allowBlobPublicAccess == true
node_type(?container) == ObjectStorage
node_type(?container) matched provider_type == 'Microsoft.Storage/storageAccounts/blobServices/containers'
?container.properties.publicAccess in ['Blob', 'Container']
emit
| source type | ObjectStorage |
|---|---|
| target type | AnonymousIdentity |
| source | ?container |
| target | <AnonymousIdentity> |
| state logic | if ?account.properties.publicNetworkAccess == Disabled: DO NOT EMIT (storage account explicitly blocks all public network access regardless of blob public access settings). Elif ?account.properties.allowBlobPublicAccess == false: DO NOT EMIT (container publicAccess is ignored by the storage service when account flag is false). Elif networkAcls.defaultAction == Deny AND public internet IP not in IP allowlist: CONDITIONAL(network_reachability) — the container property allows anonymous access but the network firewall blocks public internet traffic to the storage endpoint. Elif publicNetworkAccess == Enabled AND networkAcls.defaultAction == Allow: ACTIVE (no network_reachability condition — the endpoint is publicly accessible by design). For publicAccess=Container: ACTIVE (anonymous list-and-read); for publicAccess=Blob: ACTIVE (anonymous blob read, requires knowing blob URL). |
Narrative
{container.name} in {account.name} is publicly readable from the internet without authentication: the account has allowBlobPublicAccess=true and the container publicAccess={container.properties.publicAccess}. Any internet user can read{if publicAccess=='Container': ' and list'} blobs in this container without any credential.
Raw rule rules/derived/azure/storage.yaml
id: azure-storage-public-blob-container
emits: ExposedToInternet
description: Storage account with public blob access enabled and a public container exposes blob data
to unauthenticated internet access.
match:
- - account: null
- Contains
- container: null
where:
- node_class(?account) == Storage and node_type(?account) == ObjectStorage
- node_type(?account) matched provider_type == 'Microsoft.Storage/storageAccounts'
- ?account.properties.allowBlobPublicAccess == true
- node_type(?container) == ObjectStorage
- node_type(?container) matched provider_type == 'Microsoft.Storage/storageAccounts/blobServices/containers'
- ?container.properties.publicAccess in ['Blob', 'Container']
emit:
source_type: ObjectStorage
target_type: AnonymousIdentity
source: ?container
target: <AnonymousIdentity>
permissions: []
conditions: []
state_logic: "if ?account.properties.publicNetworkAccess == Disabled: DO NOT EMIT (storage account explicitly\
\ blocks all public network access regardless of blob public access settings). Elif ?account.properties.allowBlobPublicAccess\
\ == false: DO NOT EMIT (container publicAccess is ignored by the storage service when account flag\
\ is false). Elif networkAcls.defaultAction == Deny AND public internet IP not in IP allowlist: CONDITIONAL(network_reachability)\
\ \u2014 the container property allows anonymous access but the network firewall blocks public internet\
\ traffic to the storage endpoint. Elif publicNetworkAccess == Enabled AND networkAcls.defaultAction\
\ == Allow: ACTIVE (no network_reachability condition \u2014 the endpoint is publicly accessible by\
\ design). For publicAccess=Container: ACTIVE (anonymous list-and-read); for publicAccess=Blob: ACTIVE\
\ (anonymous blob read, requires knowing blob URL)."
confidence: '0.93'
derived_from:
- rule_id: azure-storage-account-contains-container
binding: ?account Contains ?container
- rule_id: azure-storage-container-public-access-property
binding: ?container HasPolicy <publicAccess>
- rule_id: azure-storage-allow-public-access-property
binding: ?account HasPolicy <allowBlobPublicAccess>
false_positive_note: "CRITICAL: ?account.properties.allowBlobPublicAccess == false OVERRIDES any container-level\
\ publicAccess \u2014 the storage service returns 409 for anonymous requests on such accounts. Do\
\ NOT emit ExposedToInternet when the account flag is false even if the container property says Blob\
\ or Container. publicNetworkAccess=Disabled completely blocks public access regardless of container\
\ publicAccess \u2192 do not emit. A network firewall (defaultAction=Deny with no public IP rule)\
\ blocks the public endpoint \u2192 emit CONDITIONAL(network_reachability). CRITICAL FIX: do NOT include\
\ network_reachability in conditions[] when the account is intentionally public (publicNetworkAccess=Enabled,\
\ networkAcls.defaultAction=Allow). The endpoint is publicly reachable BY DESIGN; emit ACTIVE with\
\ no conditions. publicAccess=Container allows directory listing (broader than Blob-only) \u2014 note\
\ in narrative. This is anonymous data exposure; target AnonymousIdentity, not a tenant or subscription\
\ boundary node. Azure Storage Accounts created after November 2023 default to allowBlobPublicAccess=false\
\ \u2014 verify the actual property."
narrative: '{container.name} in {account.name} is publicly readable from the internet without authentication:
the account has allowBlobPublicAccess=true and the container publicAccess={container.properties.publicAccess}.
Any internet user can read{if publicAccess==''Container'': '' and list''} blobs in this container
without any credential.'