azure-msgraph-app-readwrite-cred

A service principal holding Application.ReadWrite.All (or Directory.ReadWrite.All) can add a password or certificate credential to any app registration or service principal in the tenant and then authenticate as that identity, inheriting all its permissions - the identity-plane equivalent of sts:AssumeRole.

derived azure emits CanCreateCredentialFor

match

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

{'sp': None} HasRole {'approle': None} {'target': None} HasRole {'target_role': None}

where

node_type(?sp) in [MachineIdentity, ApplicationIdentity] ?approle is one of: Application.ReadWrite.All (id: 1bfefb4e-e0b5-418b-a88f-73c46d2cc8e9) OR Directory.ReadWrite.All (id: 19dbc75e-c2e2-444c-a770-ec69d8559fc7) on microsoft.graph SP ?target is any ApplicationIdentity or MachineIdentity in the same tenant (or another tenant if the SP has multi-tenant consent) node_type(?target) in [ApplicationIdentity, MachineIdentity] ?sp != ?target # self-grant via addPassword is an existing credential operation, not an escalation

emit

source typeIdentity
target typeIdentity
source?sp
target?target
permissionsApplication.ReadWrite.All Directory.ReadWrite.All
conditionsiam_permission service_state
state logicACTIVE when the appRoleAssignment for Application.ReadWrite.All or Directory.ReadWrite.All is granted and the SP holds a valid credential. CONDITIONAL(service_state) if the SP credential is expired/revoked. BLOCKED if the target app/SP is protected by an Azure Entra Privileged Identity Management policy that prevents credential modification (rare; not currently modeled in Graph API responses — flag UNKNOWN when PIM-for-Applications is in scope). Inherit weakest from the HasRole input edge. Do NOT emit for Directory.ReadWrite.All without confirming that addPassword/addKey is within its effective scope for the target (Directory.ReadWrite.All is broad but some per-object operations still require Application.ReadWrite.All specifically — flag CONDITIONAL for the Directory.ReadWrite.All variant).

Narrative

{sp.name} holds Application.ReadWrite.All and can add a client secret or certificate to {target.name} via POST /applications/{id}/addPassword (for app registrations) or POST /servicePrincipals/{id}/addPassword (for service principals), then authenticate as {target.name} and inherit all of its delegated permissions and role assignments - the identity-plane equivalent of assuming a role.

Raw rule rules/derived/azure/msgraph.yaml

id: azure-msgraph-app-readwrite-cred
emits: CanCreateCredentialFor
description: "A service principal holding Application.ReadWrite.All (or Directory.ReadWrite.All) can add\
  \ a password or certificate credential to any app registration or service principal in the tenant and\
  \ then authenticate as that identity, inheriting all its permissions \u2014 the identity-plane equivalent\
  \ of sts:AssumeRole."
match:
- - sp: null
  - HasRole
  - approle: null
- - target: null
  - HasRole
  - target_role: null
where:
- node_type(?sp) in [MachineIdentity, ApplicationIdentity]
- '?approle is one of: Application.ReadWrite.All (id: 1bfefb4e-e0b5-418b-a88f-73c46d2cc8e9) OR Directory.ReadWrite.All
  (id: 19dbc75e-c2e2-444c-a770-ec69d8559fc7) on microsoft.graph SP'
- ?target is any ApplicationIdentity or MachineIdentity in the same tenant (or another tenant if the SP
  has multi-tenant consent)
- node_type(?target) in [ApplicationIdentity, MachineIdentity]
- '?sp != ?target   # self-grant via addPassword is an existing credential operation, not an escalation'
emit:
  source_type: Identity
  target_type: Identity
  source: ?sp
  target: ?target
  permissions:
  - Application.ReadWrite.All
  - Directory.ReadWrite.All
  conditions:
  - iam_permission
  - service_state
  state_logic: "ACTIVE when the appRoleAssignment for Application.ReadWrite.All or Directory.ReadWrite.All\
    \ is granted and the SP holds a valid credential. CONDITIONAL(service_state) if the SP credential\
    \ is expired/revoked. BLOCKED if the target app/SP is protected by an Azure Entra Privileged Identity\
    \ Management policy that prevents credential modification (rare; not currently modeled in Graph API\
    \ responses \u2014 flag UNKNOWN when PIM-for-Applications is in scope). Inherit weakest from the HasRole\
    \ input edge. Do NOT emit for Directory.ReadWrite.All without confirming that addPassword/addKey is\
    \ within its effective scope for the target (Directory.ReadWrite.All is broad but some per-object\
    \ operations still require Application.ReadWrite.All specifically \u2014 flag CONDITIONAL for the\
    \ Directory.ReadWrite.All variant)."
  confidence: '0.95'
  derived_from:
  - <HasRole edge_id (appRoleAssignment Application.ReadWrite.All or Directory.ReadWrite.All on ?sp)>
  - "azure/identity-escalation.yaml#azure-sp-add-credential (SP OWNER path \u2014 parallel; this rule\
    \ covers the app-permission path)"
  - "azure/identity-escalation.yaml#azure-app-add-credential (app OWNER path \u2014 parallel)"
  false_positive_note: "Application.ReadWrite.All is a very broad permission; a holder can add credentials\
    \ to EVERY app and SP in the tenant, not just low-privilege ones. Model each (sp, target) pair as\
    \ a separate edge instance, noting that an attacker will choose the highest-privilege target. Do NOT\
    \ emit for targets that are protected by Entra's role-separation principle (Global Admin / Privileged\
    \ Role Admin apps may have additional protection in future Entra hardening \u2014 flag CONDITIONAL\
    \ in hardened tenants). Requires a valid SP credential to call Graph. Dedupe on (CanCreateCredentialFor,\
    \ ?sp, ?target) when both Application.ReadWrite.All AND Directory.ReadWrite.All are held \u2014 one\
    \ edge suffices. Directory.ReadWrite.All may not cover addPassword on all object types \u2014 prefer\
    \ the Application.ReadWrite.All appRoleId match for the highest-confidence emit. The rule targets\
    \ both ApplicationIdentity (via /applications/{id}/addPassword) and MachineIdentity (via /servicePrincipals/{id}/addPassword);\
    \ use Identity as target_type to express that both subtypes are valid."
  narrative: "{sp.name} holds Application.ReadWrite.All and can add a client secret or certificate to\
    \ {target.name} via POST /applications/{id}/addPassword (for app registrations) or POST /servicePrincipals/{id}/addPassword\
    \ (for service principals), then authenticate as {target.name} and inherit all of its delegated permissions\
    \ and role assignments \u2014 the identity-plane equivalent of assuming a role."
move · open · esc close