Authentication method changes and OAuth consent hunting

Three hunts for post-access persistence via identity controls: new authenticator registrations outside normal patterns, mailbox forwarding rules set by suspicious sessions, and OAuth consent grants to high-privilege applications. All three represent attacker persistence that survives a password reset if not explicitly revoked.

Data sources: Microsoft Entra ID audit log via the Microsoft Graph PowerShell module (Connect-MgGraph -Scopes "AuditLog.Read.All,Application.Read.All"); Exchange Online PowerShell (Connect-ExchangeOnline); Microsoft 365 Unified Audit Log via Search-UnifiedAuditLog.

New authenticator registrations and TAP issuance

Hypothesis: an attacker with account access has registered a new authentication method, or a Temporary Access Pass has been issued to facilitate bypassing existing authentication. The attacker’s registered device persists after the compromised session is invalidated.

$startTime = (Get-Date).AddDays(-14).ToUniversalTime().ToString('o')

# new authenticator registrations
Get-MgAuditLogDirectoryAudit -Filter (
    "activityDisplayName eq 'User registered security info' and " +
    "activityDateTime ge $startTime"
) |
    Select-Object ActivityDateTime,
        @{N='User';        E={$_.TargetResources[0].UserPrincipalName}},
        @{N='Method';      E={($_.AdditionalDetails | Where-Object Key -eq 'methodsRegistered').Value}},  # key name may vary; verify against real data
        @{N='InitiatedBy'; E={if ($_.InitiatedBy.User.UserPrincipalName) { $_.InitiatedBy.User.UserPrincipalName } else { $_.InitiatedBy.App.DisplayName }}},
        @{N='IP';          E={$_.InitiatedBy.User.IPAddress}} |
    Sort-Object ActivityDateTime
# TAP issuance events
$startTime = (Get-Date).AddDays(-14).ToUniversalTime().ToString('o')
Get-MgAuditLogDirectoryAudit -Filter (
    "activityDisplayName eq 'Create Temporary Access Pass method for user' and " +
    "activityDateTime ge $startTime"
) |
    Select-Object ActivityDateTime,
        @{N='TargetUser'; E={$_.TargetResources[0].UserPrincipalName}},
        @{N='IssuedBy';   E={if ($_.InitiatedBy.User.UserPrincipalName) { $_.InitiatedBy.User.UserPrincipalName } else { $_.InitiatedBy.App.DisplayName }}} |
    Sort-Object ActivityDateTime

A new authenticator registered from an IP not previously associated with the user, outside business hours, or immediately following a self-service password reset is the two-step recovery bypass: email access to complete SSPR, then a new authenticator registered on the attacker’s device. A TAP issued for a privileged account, or issued by an account outside the Authentication Administrator role, is worth treating as a priority alert.

The MGM Resorts 2023 incident followed this path: social engineering of the IT helpdesk reset credentials, which allowed the attacker to register their own authentication method before the legitimate user noticed anything was wrong.

Mailbox forwarding rules

Hypothesis: an attacker with mailbox access has created a forwarding rule to exfiltrate mail continuously without maintaining active access. The rule survives a password reset and may predate the current investigation window.

# mailbox-level forwarding to external addresses
Get-Mailbox -ResultSize Unlimited |
    Where-Object { $_.ForwardingSmtpAddress -ne $null -or $_.DeliverToMailboxAndForward } |
    Select-Object UserPrincipalName, ForwardingSmtpAddress, DeliverToMailboxAndForward |
    Sort-Object UserPrincipalName
# inbox rules forwarding or redirecting to external addresses
Get-Mailbox -ResultSize Unlimited | ForEach-Object {
    Get-InboxRule -Mailbox $_.UserPrincipalName -ErrorAction SilentlyContinue |
        Where-Object { $_.ForwardTo -or $_.RedirectTo -or $_.ForwardAsAttachmentTo } |
        Select-Object @{N='Mailbox'; E={$_.MailboxOwnerID}},
            Name, ForwardTo, RedirectTo, ForwardAsAttachmentTo
} | Where-Object Mailbox | Sort-Object Mailbox
# UAL events for forwarding rule creation in the past 30 days
$start = (Get-Date).AddDays(-30)
Search-UnifiedAuditLog -StartDate $start -EndDate (Get-Date) `
    -Operations 'Set-InboxRule','New-InboxRule','Set-Mailbox' `
    -ResultSize 1000 |
    ForEach-Object {
        $a = $_.AuditData | ConvertFrom-Json
        [PSCustomObject]@{
            Time      = $_.CreationDate
            User      = $a.UserId
            Operation = $a.Operation
            ClientIP  = $a.ClientIP
            Params    = ($a.Parameters | ConvertTo-Json -Compress)
        }
    } | Sort-Object Time

A forwarding rule set from an IP or user agent inconsistent with the mailbox owner’s normal access pattern, or created in the same session as a sign-in from a known phishing IP, links the persistence mechanism to the initial access event. The UAL query provides the creation event and its source IP; Get-InboxRule shows the current state. Both are worth running: the UAL query catches rules that were created and later deleted to obscure the activity, while Get-InboxRule catches rules that are still active.

Hypothesis: a user was socially engineered into granting a malicious OAuth application delegated access to their mailbox or files. The application continues to access data as the user, without requiring further authentication, for as long as the grant persists.

$startTime = (Get-Date).AddDays(-14).ToUniversalTime().ToString('o')

# consent events in the audit log
Get-MgAuditLogDirectoryAudit -Filter (
    "activityDisplayName eq 'Consent to application' and " +
    "activityDateTime ge $startTime"
) |
    Select-Object ActivityDateTime,
        @{N='User';   E={$_.InitiatedBy.User.UserPrincipalName}},
        @{N='App';    E={($_.TargetResources | Where-Object Type -eq 'ServicePrincipal').DisplayName}},
        @{N='Scopes'; E={($_.AdditionalDetails | Where-Object Key -eq 'Oauth2PermissionScopeConsentGranted').Value}},  # key name may vary; verify against real data
        @{N='IP';     E={$_.InitiatedBy.User.IPAddress}} |
    Sort-Object ActivityDateTime
# existing delegated permission grants: surface high-privilege scopes in one paginated call
# ClientId is the application's service principal object ID
Get-MgOauth2PermissionGrant -All |
    Where-Object {
        $_.Scope -match 'Mail\.(Read|ReadWrite|Send)|Files\.ReadWrite\.All|offline_access'
    } |
    Select-Object ClientId, PrincipalId, Scope, ConsentType |
    Sort-Object ClientId

An application with Mail.Read and offline_access consented to by a user whose account appears in a recent phishing report is the consent phishing pattern. The offline_access scope means the token refreshes indefinitely without re-authentication. Files.ReadWrite.All with offline_access consented in the same window as a suspicious sign-in event is worth treating as a confirmed persistence mechanism pending further investigation.

Existing grants from the second query may include legitimate applications with broad scopes: the filter catches everything matching those patterns, not only malicious grants. ClientId is a service principal object ID; resolve names with Get-MgServicePrincipal -ServicePrincipalId <ClientId> | Select-Object DisplayName for the entries worth investigating. Correlating the PrincipalId against the sign-in log and the application’s registration date distinguishes a long-standing productivity tool from a recently registered application consented to during an active compromise.