☁️Entra IDApplicationsPermissionsMonitoring

Entra App Registration Dangerous Graph API Permissions: Detection and Remediation

Directory.ReadWrite.All, Mail.ReadWrite, Files.ReadWrite.All, and RoleManagement.ReadWrite.Directory can turn a leaked app credential into full Global Administrator control. Here's how the escalation works, how to detect the grants, and how to remediate them.

Younes AZABARBy Younes AZABAR9 min read
Entra App Registration Dangerous Graph API Permissions: Detection and Remediation

Entra app registration dangerous Graph API permissions turn a single admin-consent click into a standing, tenant-wide credential. Directory.ReadWrite.All, Mail.ReadWrite, Files.ReadWrite.All, and RoleManagement.ReadWrite.Directory are application (app-only) permissions — grants that let a service principal act across an entire Microsoft 365 tenant with no signed-in user and no per-object scoping. This guide covers what each permission actually allows, a documented attack chain that chains them into full Global Administrator control, and how to detect and remediate the grants.

Entra App Registration Dangerous Graph API Permissions Explained

All four are application permissions, also called app-only permissions. Microsoft's own Graph documentation draws the line clearly: application permissions "work in the app-only access scenario, without a signed-in user present," and the app "can access any data that the permission is associated with." A delegated permission is bounded by what the signed-in user can already see; an application permission isn't bounded by anyone.

All four permissions require admin consent, and Microsoft restricts who can grant them to Privileged Role Administrator and Global Administrator. That single consent click — often made once, during onboarding, by whoever was in the room — is what turns an app registration into a standing, tenant-wide access grant that nobody revisits.

PermissionTypeWhat it actually allows (Microsoft's description)
Directory.ReadWrite.AllApplicationRead and write directory data — users, groups, and more — without a signed-in user. Microsoft ranks this permission second in overall privilege among Entra ID permissions, just behind the highest-privileged delegated permission.
Mail.ReadWriteApplicationCreate, read, update, and delete email in every mailbox in the tenant, without a signed-in user.
Files.ReadWrite.AllApplicationRead, create, update, and delete every file across all site collections, without a signed-in user.
RoleManagement.ReadWrite.DirectoryApplicationRead and write all role management data in the directory — including activating eligible role assignments and managing Entra role assignments — without a signed-in user.

Source: Microsoft Graph permissions reference.

⚠️

⚠️ Warning: These aren't obscure permissions an attacker has to discover — they're standard entries in the Graph permissions picker, granted through the same admin-consent dialog as everything else. The danger is entirely in the blast radius, not the mechanism.

How It Works

An application permission attaches to a service principal, the tenant-local identity for an app registration. Once granted, the service principal authenticates with its own credential — a client secret or certificate — using the OAuth 2.0 client credentials flow. The resulting access token carries the permission in its roles claim, and Microsoft Graph enforces access based on that claim alone. There's no user context to fall back on, and no per-resource consent boundary: an app with Files.ReadWrite.All can touch a file it has never been shared, in a site it has no other relationship to.

RoleManagement.ReadWrite.Directory sits in a smaller, more dangerous category still. Microsoft's own permissions documentation flags a class of "permissions that allow granting authorization" — permissions that let an application hand out additional privileges to itself, to other applications, or to any user — as needing special caution (Microsoft Graph permissions reference). RoleManagement.ReadWrite.Directory is exactly that kind of permission for Entra directory roles: an app holding it can assign any built-in Entra role, including Global Administrator, to any security principal — including itself.

The Attack Chain: From a Leaked Credential to Global Administrator

This isn't a theoretical composition of two dangerous permissions — it's a documented technique. Semperis research (published as "EntraGoat Scenario 2") walks through exactly this escalation path end to end.

Step 1 — Credential Exposure

An attacker recovers a base64-encoded certificate and password left in CI/CD pipeline artifacts.

Step 2 — Attribution

The certificate thumbprint is matched against a service principal's keyCredentials.customKeyIdentifier metadata, identifying which app it belongs to.

Step 3 — App-Only Authentication

The attacker authenticates as the service principal via the OAuth 2.0 client credentials flow — no MFA prompt, no Conditional Access sign-in policy to satisfy, because there's no interactive sign-in at all.

Step 4 — Recon

The attacker inspects the token's roles claim and finds the service principal already holds AppRoleAssignment.ReadWrite.All — a permission that lets it assign Graph app roles to any service principal, including itself.

Step 5 — Self-Escalation

Using that permission, the attacker assigns RoleManagement.ReadWrite.Directory to the same service principal.

Step 6 — Role Assignment

With a fresh token carrying the new permission, the attacker adds the service principal to the Global Administrator directory role — a call as simple as New-MgDirectoryRoleMemberByRef against the well-known Global Administrator role template ID (62e90394-69f5-4237-9190-012177145e10).

Step 7 — Takeover

Because Entra directory roles are evaluated dynamically at runtime rather than baked into the token, the service principal is a fully privileged Global Administrator on its very next call — from which point the attacker can reset any admin's password.

AppRoleAssignment.ReadWrite.All (already held)
        |
        v
  assign RoleManagement.ReadWrite.Directory to self
        |
        v
  add service principal to Global Administrator role
        |
        v
  reset target admin password -> full tenant takeover

🚨 Danger: Step 4 is the part defenders miss most often. The dangerous outcome here isn't one over-privileged permission — it's the combination. AppRoleAssignment.ReadWrite.All alone lets an app grant Graph permissions to service principals; RoleManagement.ReadWrite.Directory alone lets an app manage directory roles. Neither review catches the pair unless someone is looking for permission-granting permissions specifically.

Detection

Focus detection on the moment a dangerous permission is granted and the moment it's used to touch directory roles — both are logged by default in the Microsoft Entra audit log, no premium add-on required.

IndicatorAudit log activityWhat to filter onSource
App granted a dangerous application permissionAdd app role assignment to service principalTarget(s) = Microsoft Graph, AppRole.Value in Directory.ReadWrite.All, Mail.ReadWrite, Files.ReadWrite.All, RoleManagement.ReadWrite.DirectoryEntra audit log (Category: ApplicationManagement)
Service principal added to a privileged directory roleAdd member to role, Add eligible member to role, or Add scoped member to rolePrincipal type = service principal, role = Global Administrator or another privileged roleEntra audit log
Admin approved the consentConsent to applicationConsentContext.IsAdminConsent = trueEntra audit log
End-user consent to a risky scopeConsent to applicationConsentContext.IsAdminConsent = false, app requests a high-risk scopeEntra audit log

This is Microsoft's own documented guidance — see Microsoft Entra security operations for applications, which also ships ready-made Microsoft Sentinel detections for exactly this "app role with sensitive access" pattern and for ServicePrincipalAssignedPrivilegedRole.

Baseline the Existing Grants

To baseline what's already granted, query existing app role assignments directly instead of waiting for the next audit event:

# Enumerate service principals holding any of the four dangerous permissions
$dangerousRoleIds = @(
    "19dbc75e-c2e2-444c-a770-ec69d8559fc7", # Directory.ReadWrite.All
    "e2a3a72e-5f79-4c64-b1b1-878b674786c9", # Mail.ReadWrite
    "75359482-378d-4052-8f01-80520e7db3cd", # Files.ReadWrite.All
    "9e3f62cf-ca93-4989-b6ce-bf83c28f9fe8"  # RoleManagement.ReadWrite.Directory
)
$graphSp = Get-MgServicePrincipal -Filter "appId eq '00000003-0000-0000-c000-000000000000'"
Get-MgServicePrincipalAppRoleAssignedTo -ServicePrincipalId $graphSp.Id -All |
    Where-Object { $_.AppRoleId -in $dangerousRoleIds }
ℹ️

ℹ️ Note: AppRoleAssignment.ReadWrite.All — the permission that made step 5 of the attack chain possible — is worth auditing on its own, separately from the four permissions above. Any service principal holding it can grant itself anything else in this table.

Also watch Microsoft Graph activity logs for a service principal that suddenly starts calling /roleManagement/directory/roleAssignments or /servicePrincipals/{id}/appRoleAssignedTo — endpoints a normal integration (mail sync, file sync, directory sync) has no reason to touch.

Remediation

💡

💡 Quick Win: Run the PowerShell query above today. Any service principal holding RoleManagement.ReadWrite.Directory that isn't a documented, actively-used PIM or governance integration should be revoked immediately — this permission has essentially no legitimate use outside identity-governance tooling.

  1. Classify the four permissions as high-impact. In Entra ID > Enterprise applications > Consent and permissions > Permission classifications, mark Directory.ReadWrite.All, Mail.ReadWrite, Files.ReadWrite.All, and RoleManagement.ReadWrite.Directory as high impact so ordinary users can never consent to them, even if user consent is otherwise enabled (Configure how users consent to applications).
  2. Route everything else through the admin consent workflow. Enable the admin consent workflow so a blocked user can request review instead of the request silently disappearing — but pair it with step 1, since the workflow only matters if users can't just self-consent around it.
  3. Prefer narrower permissions. Mail.ReadWrite and Files.ReadWrite.All are frequently over-scoped for what the integration actually does — an application access policy can restrict Mail.ReadWrite to specific mailboxes instead of every mailbox in the tenant, and resource-specific consent can narrow file access below Files.ReadWrite.All.
  4. Treat the credential like a Tier 0 secret. Every one of these permissions is only as safe as the certificate or client secret protecting it — see Entra app registration credential rotation for what "stale" and "over-shared" secrets look like in practice, and never let one sit in a CI/CD artifact the way the Semperis research describes.
  5. Constrain where the service principal can even authenticate. Conditional Access for workload identities enforces location and sign-in risk policies on service principal authentications to Microsoft Graph, and continuous access evaluation for workload identities revokes a token in near-real time if the risk signal changes mid-session.
  6. Re-run the app consent grant report on a schedule, not just at onboarding — a consent grant audit surfaces permissions nobody remembers approving, and a permission that was justified two reorgs ago rarely still is.
  7. Bring service-principal role assignments into your privileged-access review, alongside human PIM assignments — a service principal in Global Administrator is exactly as dangerous as a standing human Global Admin, and CVE-2025-55241 is a reminder of how much reach that one role carries.

How EtcSec Detects This

EtcSec's Entra ID audit checks for exactly this exposure: APP_DANGEROUS_PERMISSION_DIR, APP_DANGEROUS_PERMISSION_MAIL, APP_DANGEROUS_PERMISSION_FILES, and APP_DANGEROUS_PERMISSION_ROLE each flag a service principal holding one of the four permissions above, and APP_CONSENT_GRANTED_TENANT_WIDE flags consent grants scoped to every user in the tenant rather than a bounded group.

ℹ️

ℹ️ Note: EtcSec automatically checks for this vulnerability during every Azure/Entra audit. Run a free audit to see which of your app registrations already hold one of these permissions.

Explore the identity security pages that support this topic