Skip to content

Microsoft 365 Cloud Persistence Taxonomy

In on-premises operating system forensics, persistence mechanisms revolve around host-level anchors: registry Run keys, scheduled tasks, WMI event subscriptions, and daemon services. In the cloud, the paradigm shifts entirely. Microsoft 365 cloud persistence does not touch local operating systems; it operates within the distributed architectures of Microsoft Entra ID identity objects, SaaS application configurations, mail transport routing pipelines, and OAuth 2.0 delegated trust relationships.

A pervasive failure mode during cloud incident response is partial remediation: the incident responder terminates the victim’s active session and resets their password, believing the intrusion has been resolved. Hours or days later, the threat actor re-enters the tenant because secondary persistence hooksβ€”such as backdoored MFA phone numbers, illicit OAuth applications with offline_access, invisible inbox rules, or new application secretsβ€”remained undetected.

This guide establishes the comprehensive 5-Domain Cloud Persistence Taxonomy for Microsoft 365, details the credential survival matrix, maps logging surfaces, and outlines systematic tenant hunting methodologies.


Cloud persistence mechanisms in Microsoft 365 partition across five architectural planes:

graph TD
subgraph "M365 Cloud Persistence Architecture"
D1[Domain 1: Identity Plane<br/>MFA Backdoors, SSPR Methods, TAP Injection, Guest Accounts]
D2[Domain 2: Application & OAuth Plane<br/>Illicit Consent Grants, Secret/Cert Injection on Service Principals]
D3[Domain 3: Mailbox & Collaboration Plane<br/>Inbox Rules, Hidden MAPI Rules, Mailbox Delegates]
D4[Domain 4: Tenant Transport Plane<br/>Transport Rules, Auto-BCC, Inbound/Outbound Connectors]
D5[Domain 5: Administrative & Role Plane<br/>Permanent Directory Roles, PIM Tampering, Role-Assignable Groups]
end
COMPROMISE[Initial Compromise Achieved] --> D1
COMPROMISE --> D2
COMPROMISE --> D3
COMPROMISE --> D4
COMPROMISE --> D5
Persistence DomainPrimary Architectural AnchorTypical Adversary TechniqueLifespan / Survival Horizon
1. Identity PlaneEntra ID User ObjectAdding adversary-controlled phone/FIDO key to user authentication methods; generating a Temporary Access Pass (TAP).Survives user password resets; destroyed only by explicit MFA method revocation.
2. Application & OAuthService Principals & App RegistrationsAuthorizing multi-tenant apps (offline_access); adding client secrets to existing first-party or third-party service principals.Indefinite; survives password resets, MFA changes, and user offboarding.
3. Mailbox & CollaborationExchange Store Driver & MailboxInjecting server-side forwarding rules (ForwardTo), hidden MAPI rules, or granting FullAccess delegate permissions.Survives user password reset and session revocation; destroyed only by mailbox cleanup or deletion.
4. Tenant TransportExchange Online Transport PipelineConfiguring mail flow transport rules to auto-BCC sensitive financial emails or creating rogue outbound connectors.Tenant-wide; impacts all users; completely independent of individual account state.
5. Administrative & RoleEntra Role Assignment EngineAssigning permanent directory roles (Global Admin, Exchange Admin) or modifying PIM permanent eligibility settings.Permanent until administrative audit and role removal.

To design an airtight remediation plan, investigators must understand which persistence techniques survive standard incident response actions:

Persistence TechniqueSurvives Password Reset?Survives Revoke-MgUserSignSession?Survives MFA Re-Registration?Survives User License Deletion?
Adversary Mobile Number (MFA)YESYESNO (if fully purged)NO (account removed)
Temporary Access Pass (TAP)YESNOYESNO
OAuth Consent (offline_access)YESYESYESYES (if application grant)
App Secret on Service PrincipalYESYESYESYES (tenant-wide object)
Inbox Rule (ForwardTo)YESYESYESNO (mailbox inactive)
Exchange Transport Rule (BCC)YESYESYESYES (applies tenant-wide)
Mailbox Delegate (FullAccess)YESYESYESNO (mailbox unassigned)
Rogue Guest User AccountYESYESYESYES (separate identity)
Permanent Entra Role AssignmentYESYESYESYES (roles persist without license)

When hunting for established persistence hooks across a tenant, investigators must interrogate both Microsoft Entra ID Audit Logs and the Purview Unified Audit Log (UAL):

graph TD
HUNT[Persistence Triage & Hunting] --> L_ENTRA[Entra ID AuditLogs Table]
HUNT --> L_UAL[Purview Unified Audit Log]
L_ENTRA --> E1[Category: UserManagement<br/>Operations: User registered security info, User changed default authentication method]
L_ENTRA --> E2[Category: ApplicationManagement<br/>Operations: Add service principal, Consent to application, Update application - Certificates and secrets management]
L_ENTRA --> E3[Category: RoleManagement<br/>Operations: Add member to role, Add eligible member to role]
L_UAL --> U1[RecordType: ExchangeItem / ExchangeAdmin<br/>Operations: New-InboxRule, Set-InboxRule, Add-MailboxPermission]
L_UAL --> U2[RecordType: ExchangeAdmin<br/>Operations: New-TransportRule, Set-TransportRule, New-OutboundConnector]
Persistence VectorTelemetry TablePrimary Operation / Activity NameCritical Fields to Inspect
Rogue MFA MethodAuditLogsUser registered security infoTargetResources[0].modifiedProperties (Phone number, Key ID).
TAP CreationAuditLogsGenerate temporary access passInitiatedBy, TargetResources[0].userPrincipalName.
OAuth ConsentAuditLogsConsent to applicationTargetResources[0].displayName, Permissions (offline_access).
App Secret AdditionAuditLogsUpdate application - Certificates and secrets managementTargetResources[0].displayName, Key credential expiration date.
Mailbox Inbox RuleCloudAppEvents / UALNew-InboxRule, Set-InboxRuleParameters.ForwardTo, Parameters.DeleteMessage, Parameters.MoveToFolder.
Mailbox DelegateCloudAppEvents / UALAdd-MailboxPermissionParameters.User, Parameters.AccessRights (FullAccess).
Transport RuleCloudAppEvents / UALNew-TransportRule, Set-TransportRuleParameters.BlindCopyTo, Parameters.RedirectMessageTo.
Role EscalationAuditLogsAdd member to roleTargetResources[0].displayName (Global Administrator), InitiatedBy.

4.1 Comprehensive Tenant Persistence Sweep (Last 30 Days)

Section titled β€œ4.1 Comprehensive Tenant Persistence Sweep (Last 30 Days)”

Execute this unified hunting query in Microsoft Sentinel or Defender Advanced Hunting to detect all major persistence modifications in a single execution:

let TargetOperations = dynamic([
"User registered security info",
"User changed default authentication method",
"Consent to application",
"Add service principal",
"Update application - Certificates and secrets management",
"Add member to role",
"Add eligible member to role",
"Add-MailboxPermission",
"New-InboxRule",
"Set-InboxRule",
"New-TransportRule",
"Set-TransportRule"
]);
union
(
AuditLogs
| where TimeGenerated >= ago(30d)
| where OperationName in (TargetOperations)
| project TimeGenerated, Source="EntraAudit", OperationName,
Initiator=tostring(InitiatedBy.user.userPrincipalName),
IPAddress=tostring(InitiatedBy.user.ipAddress),
Target=tostring(TargetResources[0].displayName),
Details=tostring(TargetResources[0].modifiedProperties)
),
(
CloudAppEvents
| where TimeGenerated >= ago(30d)
| where ActionType in (TargetOperations)
| project TimeGenerated, Source="PurviewUAL", OperationName=ActionType,
Initiator=AccountDisplayName, IPAddress,
Target=ObjectName, Details=tostring(RawEventData)
)
| sort by TimeGenerated desc

4.2 Detecting Application Secret Additions to Existing High-Privilege Apps

Section titled β€œ4.2 Detecting Application Secret Additions to Existing High-Privilege Apps”

Identify when an administrator or service principal adds a new client secret or certificate to an existing application:

AuditLogs
| where TimeGenerated >= ago(30d)
| where OperationName has "Certificates and secrets management"
| extend InitiatedByUser = tostring(InitiatedBy.user.userPrincipalName),
InitiatedByApp = tostring(InitiatedBy.app.displayName),
TargetApp = tostring(TargetResources[0].displayName),
AppId = tostring(TargetResources[0].id)
| project TimeGenerated, OperationName, InitiatedByUser, InitiatedByApp, TargetApp, AppId, TargetResources
| sort by TimeGenerated desc

Run this script during incident response to automatically audit and list all persistence mechanisms across the tenant:

Terminal window
# Prerequisites: Microsoft.Graph, ExchangeOnlineManagement
# Connect-MgGraph -Scopes "Directory.Read.All","Application.Read.All","UserAuthenticationMethod.Read.All"
# Connect-ExchangeOnline
Write-Host "=================================================" -ForegroundColor Cyan
Write-Host " MICROSOFT 365 TENANT PERSISTENCE AUDIT DISCOVERY " -ForegroundColor Cyan
Write-Host "=================================================" -ForegroundColor Cyan
# 1. Audit Transport Rules for Auto-Forwarding or BCC
Write-Host "`n[*] Auditing Exchange Transport Rules..." -ForegroundColor Yellow
$transportRules = Get-TransportRule | Where-Object { $_.BlindCopyTo -or $_.RedirectMessageTo -or $_.ForwardTo }
if ($transportRules) {
Write-Warning "[!] Suspicious Transport Rules Found:"
$transportRules | Select-Object Name, BlindCopyTo, RedirectMessageTo | Format-Table
} else {
Write-Host "[+] No suspicious redirect/BCC transport rules." -ForegroundColor Green
}
# 2. Audit Mailbox Forwarding
Write-Host "`n[*] Auditing External Mailbox Forwarding Addresses..." -ForegroundColor Yellow
$forwardedMailboxes = Get-Mailbox -ResultSize Unlimited | Where-Object { $_.ForwardingSmtpAddress -or $_.ForwardingAddress }
if ($forwardedMailboxes) {
Write-Warning "[!] Mailboxes with external forwarding configured:"
$forwardedMailboxes | Select-Object DisplayName, UserPrincipalName, ForwardingSmtpAddress, ForwardingAddress | Format-Table
} else {
Write-Host "[+] No external mailbox forwarding configured." -ForegroundColor Green
}
# 3. Audit OAuth Delegated Permission Grants
Write-Host "`n[*] Auditing OAuth2 Permission Grants (offline_access & Mail)..." -ForegroundColor Yellow
$grants = Get-MgOauth2PermissionGrant
$riskyGrants = $grants | Where-Object { $_.Scope -match "offline_access" -or $_.Scope -match "Mail\." }
Write-Host "[!] Found $($riskyGrants.Count) OAuth grants with persistent or mailbox permissions." -ForegroundColor Yellow
$riskyGrants | Select-Object ClientId, ConsentType, Scope | Format-Table -AutoSize
# 4. Audit Recently Created Client Secrets on Service Principals
Write-Host "`n[*] Auditing Service Principal Password Credentials (Client Secrets)..." -ForegroundColor Yellow
$recentThreshold = (Get-Date).AddDays(-30)
$apps = Get-MgApplication -All
foreach ($app in $apps) {
$recentSecrets = $app.PasswordCredentials | Where-Object { $_.StartDateTime -ge $recentThreshold }
if ($recentSecrets) {
Write-Warning "[!] Application '$($app.DisplayName)' has $($recentSecrets.Count) secret(s) created in the last 30 days!"
}
}
Write-Host "`n[+] Persistence audit discovery complete." -ForegroundColor Green