azure-adls-listkeys-exposes-credential

listKeys/action returns ADLS Gen2 account keys, bypassing all RBAC and ACLs.

derived azure emits CanReadCredential

match

A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.

{'principal': None} HasPermission {'account': None} {'account': None} ContainsCredential {'key': None}

where

node_class(?account) == Data and node_type(?account) == DataLake ?account.properties.isHnsEnabled == true # ADLS Gen2 only ?principal has EFFECTIVE Microsoft.Storage/storageAccounts/listKeys/action at ?account scope (Storage Account Key Operator Service Role 81a9662b-bebf-436f-a333-f67b29880f12, Contributor (b24988ac-6180-42a0-ab88-20f7382dd24c — NOT Storage Account Contributor which lacks listKeys), Owner, or custom role with listKeys) node_type(?key) == AccessKey # the primary/secondary storage account keys

emit

source typeIdentity
target typeAccessKey
source?principal
target?key
permissionsMicrosoft.Storage/storageAccounts/listKeys/action
conditionsiam_permission
state logicif a deny assignment or deny Azure Policy covers listKeys/action at the effective scope: BLOCKED; else ACTIVE. Network firewall (networkAcls) gates the DATA plane, NOT the ARM control-plane listKeys call — listKeys succeeds over the public ARM endpoint regardless of storage firewall settings.

Narrative

{principal.name} can call listKeys on {account.name} (Microsoft.Storage/storageAccounts/listKeys/action), retrieving the primary and secondary account keys. These keys bypass all Azure RBAC role assignments and POSIX ACLs, authenticate as the storage service itself, and allow generating arbitrary SAS tokens - a complete credential exposure for this data lake.

Raw rule rules/derived/azure/adls.yaml

id: azure-adls-listkeys-exposes-credential
emits: CanReadCredential
description: listKeys/action returns ADLS Gen2 account keys, bypassing all RBAC and ACLs.
match:
- - principal: null
  - HasPermission
  - account: null
- - account: null
  - ContainsCredential
  - key: null
where:
- node_class(?account) == Data and node_type(?account) == DataLake
- '?account.properties.isHnsEnabled == true  # ADLS Gen2 only'
- "?principal has EFFECTIVE Microsoft.Storage/storageAccounts/listKeys/action at ?account scope (Storage\
  \ Account Key Operator Service Role 81a9662b-bebf-436f-a333-f67b29880f12, Contributor (b24988ac-6180-42a0-ab88-20f7382dd24c\
  \ \u2014 NOT Storage Account Contributor which lacks listKeys), Owner, or custom role with listKeys)"
- 'node_type(?key) == AccessKey  # the primary/secondary storage account keys'
emit:
  source_type: Identity
  target_type: AccessKey
  source: ?principal
  target: ?key
  permissions:
  - Microsoft.Storage/storageAccounts/listKeys/action
  conditions:
  - iam_permission
  state_logic: "if a deny assignment or deny Azure Policy covers listKeys/action at the effective scope:\
    \ BLOCKED; else ACTIVE. Network firewall (networkAcls) gates the DATA plane, NOT the ARM control-plane\
    \ listKeys call \u2014 listKeys succeeds over the public ARM endpoint regardless of storage firewall\
    \ settings."
  confidence: '0.97'
  derived_from:
  - <HasPermission edge_id (listKeys/action)>
  - "<ContainsCredential edge_id (account \u2192 AccessKey)>"
  false_positive_note: "This rule models the principal's capability to read/retrieve the account's AccessKey\
    \ via listKeys/action. The credential-chains linchpin then derives CanImpersonate if the key CredentialsFor\
    \ another identity, or attacker uses it directly for CanExfiltrate (see azure-adls-listkeys-full-exfiltrate).\
    \ listKeys is a CONTROL-PLANE ARM action and is NOT gated by the storage account firewall (networkAcls)\
    \ \u2014 it is callable from any network that can reach ARM (management.azure.com). Do NOT apply a\
    \ network_reachability condition to CanReadCredential for the ARM action itself. Emit BLOCKED only\
    \ when an Azure deny assignment or Azure Policy deny effect explicitly covers listKeys/action."
  narrative: "{principal.name} can call listKeys on {account.name} (Microsoft.Storage/storageAccounts/listKeys/action),\
    \ retrieving the primary and secondary account keys. These keys bypass all Azure RBAC role assignments\
    \ and POSIX ACLs, authenticate as the storage service itself, and allow generating arbitrary SAS tokens\
    \ \u2014 a complete credential exposure for this data lake."
move · open · esc close