azure-netapp-listcredentials-exposes-ad-cred
listCredentials/action permission on ANF account grants CanReadCredential on the AD join AccessKey.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
{'principal': None}
HasPermission {'account': None}
{'account': None} ContainsCredential {'cred': None}
where
node_class(?account) == Storage and node_type(?account) ==
FileStorage
?account.provider_type == 'Microsoft.NetApp/netAppAccounts'
node_type(?cred) == AccessKey # The credential is the AD machine account password
?principal has EFFECTIVE Microsoft.NetApp/netAppAccounts/listCredentials/action at ?account scope (Owner, Contributor, or custom role with listCredentials)
emit
| source type | Identity |
|---|---|
| target type | AccessKey |
| source | ?principal |
| target | ?cred |
| permissions | Microsoft.NetApp/netAppAccounts/listCredentials/action |
| conditions | iam_permission |
| state logic | if a deny assignment or Azure Policy deny effect covers listCredentials/action at the effective scope: BLOCKED; elif ?account.properties.activeDirectories is empty (no AD join, no credential): DO NOT EMIT (the ContainsCredential match will not fire); else ACTIVE. This is an ARM control-plane action — NOT gated by volume-level network access controls or delegated subnet firewall rules. |
Narrative
{principal.name} can invoke listCredentials on ANF account {account.name} (Microsoft.NetApp/netAppAccounts/listCredentials/action), retrieving the Active Directory join AccessKey credential. Combined with the credential-chains rule read-credential-yields-identity (pending linchpin extension), this enables lateral movement to the AD machine account within the joined domain.
Raw rule rules/derived/azure/netapp.yaml
id: azure-netapp-listcredentials-exposes-ad-cred
emits: CanReadCredential
description: listCredentials/action permission on ANF account grants CanReadCredential on the AD join
AccessKey.
match:
- - principal: null
- HasPermission
- account: null
- - account: null
- ContainsCredential
- cred: null
where:
- node_class(?account) == Storage and node_type(?account) == FileStorage
- ?account.provider_type == 'Microsoft.NetApp/netAppAccounts'
- 'node_type(?cred) == AccessKey # The credential is the AD machine account password'
- ?principal has EFFECTIVE Microsoft.NetApp/netAppAccounts/listCredentials/action at ?account scope (Owner,
Contributor, or custom role with listCredentials)
emit:
source_type: Identity
target_type: AccessKey
source: ?principal
target: ?cred
permissions:
- Microsoft.NetApp/netAppAccounts/listCredentials/action
conditions:
- iam_permission
state_logic: "if a deny assignment or Azure Policy deny effect covers listCredentials/action at the\
\ effective scope: BLOCKED; elif ?account.properties.activeDirectories is empty (no AD join, no credential):\
\ DO NOT EMIT (the ContainsCredential match will not fire); else ACTIVE. This is an ARM control-plane\
\ action \u2014 NOT gated by volume-level network access controls or delegated subnet firewall rules."
confidence: '0.93'
derived_from:
- ?principal HasPermission ?account
- ?account ContainsCredential ?cred
false_positive_note: Only emit when [?account, ContainsCredential, ?cred] matches (i.e., ANF account
is AD-joined). If the account is not AD-joined, the explicit rule does not emit ContainsCredential
and this derived rule never fires. listCredentials/action is an ARM control-plane call; it succeeds
from any network reaching management.azure.com regardless of volume-level network firewall rules.
Do NOT apply a network_reachability condition to the CanReadCredential emit itself. Emit BLOCKED only
when an Azure deny assignment explicitly covers listCredentials/action. The downstream CredentialsFor
link (AD join credential -> AD machine account identity) requires a dedicated linchpin rule in credential-chains.yaml
(read-credential-yields-identity, not read-secret-yields-identity, because AccessKey is NOT in CanReadSecret's
target set). The identity model confirms that the AD machine account (MachineIdentity node) is a valid
CanImpersonate target, so the credential chain fires automatically when both edges are present and
the linchpin rule is in place.
narrative: '{principal.name} can invoke listCredentials on ANF account {account.name} (Microsoft.NetApp/netAppAccounts/listCredentials/action),
retrieving the Active Directory join AccessKey credential. Combined with the credential-chains rule
read-credential-yields-identity (pending linchpin extension), this enables lateral movement to the
AD machine account within the joined domain.'