azure-adls-listkeys-full-exfiltrate
Account key (from listKeys) grants full data-plane read and SAS generation - enabling complete data exfiltration.
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
?principal has EFFECTIVE Microsoft.Storage/storageAccounts/listKeys/action at ?account scope
node_type(?key) == AccessKey # the primary/secondary storage account key
emit
| source type | Identity |
|---|---|
| target type | Data |
| source | ?principal |
| target | ?account |
| permissions | Microsoft.Storage/storageAccounts/listKeys/action |
| conditions | iam_permission |
| state logic | if a deny assignment/deny Azure Policy covers listKeys/action: BLOCKED; else ACTIVE. The account key itself is valid from any network — no network_reachability gate applies once the key is obtained. |
Narrative
{principal.name} can list the account keys of {account.name}, then use those keys to read, copy, or delete every blob and file in the data lake and generate SAS tokens for external distribution - a full data exfiltration capability.
Raw rule rules/derived/azure/adls.yaml
id: azure-adls-listkeys-full-exfiltrate
emits: CanExfiltrate
description: "Account key (from listKeys) grants full data-plane read and SAS generation \u2014 enabling\
\ complete data exfiltration."
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
- ?principal has EFFECTIVE Microsoft.Storage/storageAccounts/listKeys/action at ?account scope
- 'node_type(?key) == AccessKey # the primary/secondary storage account key'
emit:
source_type: Identity
target_type: Data
source: ?principal
target: ?account
permissions:
- Microsoft.Storage/storageAccounts/listKeys/action
conditions:
- iam_permission
state_logic: "if a deny assignment/deny Azure Policy covers listKeys/action: BLOCKED; else ACTIVE. The\
\ account key itself is valid from any network \u2014 no network_reachability gate applies once the\
\ key is obtained."
confidence: '0.95'
derived_from:
- <HasPermission edge_id (listKeys/action)>
- "<ContainsCredential edge_id (account \u2192 AccessKey)>"
false_positive_note: "This rule is the two-step collapse of (a) list the key and (b) use it to read\
\ all data. The key is account-scoped and bypasses container-level RBAC, POSIX ACLs, and soft-delete\
\ protections (keys can even be used to permanently delete versioned blobs if soft-delete retention\
\ has expired). Separately, the key can generate SAS tokens allowing read/write by third parties with\
\ no Entra identity. Do NOT emit CanExfiltrate on the Storage Blob Data Reader role alone \u2014 that\
\ role cannot generate account-key-level SAS tokens and is ACL-constrained. credential-chains.yaml\
\ read-secret-yields-identity handles the pivot from ExposesCredential \u2192 CredentialsFor \u2192\
\ CanImpersonate when the key is the registered credential for a service principal (rare but documented\
\ pattern)."
narrative: "{principal.name} can list the account keys of {account.name}, then use those keys to read,\
\ copy, or delete every blob and file in the data lake and generate SAS tokens for external distribution\
\ \u2014 a full data exfiltration capability."