Summary
| Forestall ACL Alias | AZ_ROLE_SCOPED_TO |
| Entra ID (Azure AD) Alias | Scope |
| Affected Object Types | App Registrations, Service Principals |
| Exploitation Certainty | Certain |
| Graph Permission / Role | Application Administrator, Cloud Application Administrator, or custom Entra ID directory roles with scoped assignments |
Description
AZ_ROLE_SCOPED_TO represents the scoping relationship for Entra ID directory role assignments. Roles like Application Administrator and Cloud Application Administrator can be assigned either tenant-wide or scoped to specific objects:
- Tenant-scoped (
/): Control over all app registrations and service principals in the tenant. - Object-scoped (
/<objectId>): Control over only the specific app registration(s) or service principal(s) designated in the scope.
This distinction is critical for attack path analysis, as tenant-scoped assignments provide significantly broader control than object-scoped ones.
Identification
PowerShell
Enumerate Scoped Role Assignments
Import-Module Microsoft.Graph.Identity.DirectoryManagement
Connect-MgGraph -Scopes "RoleManagement.Read.Directory", "Directory.Read.All"
function Get-ScopedRoleAssignments { $allAssignments = [System.Collections.Generic.List[PSCustomObject]]::new()
# Build a role definition lookup (Graph only allows expanding one property per query) $roleDefinitions = @{} Get-MgRoleManagementDirectoryRoleDefinition -All | ForEach-Object { $roleDefinitions[$_.Id] = $_.DisplayName }
# Get all directory role assignments with principal expanded $roleAssignments = Get-MgRoleManagementDirectoryRoleAssignment -All -ExpandProperty "principal"
foreach ($ra in $roleAssignments) { $scope = $ra.DirectoryScopeId $isScoped = $scope -ne "/"
$principalName = $ra.Principal.AdditionalProperties.displayName $principalType = if ($ra.Principal.AdditionalProperties.'@odata.type') { $ra.Principal.AdditionalProperties.'@odata.type' -replace '^#microsoft\.graph\.', '' } else { 'Unknown' }
$roleName = $roleDefinitions[$ra.RoleDefinitionId]
$allAssignments.Add([PSCustomObject]@{ RoleName = $roleName RoleId = $ra.RoleDefinitionId PrincipalName = $principalName PrincipalId = $ra.PrincipalId PrincipalType = $principalType Scope = $scope IsScoped = $isScoped AssignmentId = $ra.Id }) }
$allAssignments}
$assignments = Get-ScopedRoleAssignments
# Show all scoped (non-tenant-wide) assignmentsWrite-Host "`n=== Object-Scoped Role Assignments ===" -ForegroundColor Yellow$assignments | Where-Object { $_.IsScoped -eq $true } | Sort-Object RoleName, PrincipalName | Format-Table RoleName, PrincipalName, PrincipalType, Scope -AutoSize
# Show tenant-wide assignments for scopeable roles$scopeableRoles = @("Application Administrator", "Cloud Application Administrator")Write-Host "`n=== Tenant-Wide Assignments for Scopeable Roles ===" -ForegroundColor Yellow$assignments | Where-Object { $_.IsScoped -eq $false -and $_.RoleName -in $scopeableRoles } | Sort-Object RoleName, PrincipalName | Format-Table RoleName, PrincipalName, PrincipalType -AutoSize
# Export all$assignments | Export-Csv -Path ".\ScopedRoleAssignments.csv" -NoTypeInformation -Encoding UTF8Write-Host "`nTotal assignments: $($assignments.Count)"Write-Host "Object-scoped assignments: $(($assignments | Where-Object IsScoped).Count)"Write-Host "Tenant-wide assignments: $(($assignments | Where-Object { -not $_.IsScoped }).Count)"Resolve Scoped Target Objects
# For each scoped assignment, resolve the target objectfunction Resolve-ScopedTarget { param([string]$DirectoryScopeId)
if ($DirectoryScopeId -eq "/") { return [PSCustomObject]@{ Type = "Tenant"; Name = "Entire Tenant"; Id = "/" } }
# Scoped assignments use format: /<objectId> or /administrativeUnits/<auId> $objectId = $DirectoryScopeId.TrimStart('/')
# Try as Application try { $app = Get-MgApplication -ApplicationId $objectId -ErrorAction Stop return [PSCustomObject]@{ Type = "Application"; Name = $app.DisplayName; Id = $app.Id } } catch {}
# Try as Service Principal try { $sp = Get-MgServicePrincipal -ServicePrincipalId $objectId -ErrorAction Stop return [PSCustomObject]@{ Type = "ServicePrincipal"; Name = $sp.DisplayName; Id = $sp.Id } } catch {}
# Try as Administrative Unit if ($DirectoryScopeId -match "/administrativeUnits/(.+)") { try { $au = Get-MgDirectoryAdministrativeUnit -AdministrativeUnitId $Matches[1] -ErrorAction Stop return [PSCustomObject]@{ Type = "AdministrativeUnit"; Name = $au.DisplayName; Id = $au.Id } } catch {} }
return [PSCustomObject]@{ Type = "Unknown"; Name = $DirectoryScopeId; Id = $DirectoryScopeId }}
# Example: Resolve all scoped targets$scopedAssignments = $assignments | Where-Object { $_.IsScoped }foreach ($sa in $scopedAssignments) { $target = Resolve-ScopedTarget -DirectoryScopeId $sa.Scope Write-Host "$($sa.RoleName) | $($sa.PrincipalName) -> $($target.Type): $($target.Name)" -ForegroundColor Cyan}Azure Portal
View Scoped Role Assignments
- Open Microsoft Entra admin center -> Roles and administrators.
- Select a role (e.g., Application Administrator).
- In the Assignments tab, observe the Scope column:
- Directory = tenant-wide assignment.
- A specific object name = scoped to that app registration or service principal.
Exploitation
Related Attack Paths:
- AZ_APP_ADMIN - Application Administrator role; if tenant-scoped, applies to all apps. If object-scoped, only applies to specific apps.
- AZ_CLOUD_APP_ADMIN - Cloud Application Administrator role; same scoping behavior as Application Administrator.
- AZ_ADD_SECRET - Principals with scoped Application/Cloud App Admin roles can add secrets to the scoped app.
- AZ_ADD_OWNER - Scoped role holders can add owners to the target app/SP.
The abuse potential depends entirely on whether the role is scoped to the tenant or to individual objects:
| Scope | Impact | Example |
|---|---|---|
Tenant-wide (/) |
Full control over all app registrations and service principals in the tenant | Application Administrator scoped to the directory can add credentials to any app, modify any SP, and escalate via high-privilege apps |
Object-scoped (/<objectId>) |
Control over only the specific app registration(s) or service principal(s) in scope | Application Administrator scoped to a single app can only add credentials/modify that one app |
Mitigation
This is a structural relationship (not directly abusable) indicating role assignment scope. Mitigation:
- Prefer object-scoped over tenant-wide assignments (least privilege principle).
- Audit tenant-wide assignments and narrow to specific objects where possible.
- Regularly review and remove unnecessary assignments.
Detection
Use the Microsoft Entra admin center:
- Open Microsoft Entra ID -> Audit logs.
- Filter Activity for:
- Add member to role
- Add eligible member to role
- Remove member from role
- Review Initiated by (actor), Target, Date, Status, and role scope details for unexpected scoped role changes.
- Open Roles & administrators and review the affected role assignment scope.
- Use Manage view -> Edit columns if any of those fields are hidden.
References
- https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/custom-assign-graph
- https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#application-administrator
- https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#cloud-application-administrator
- https://learn.microsoft.com/en-us/graph/api/rbacapplication-post-roleassignments
- https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/custom-create