In March 2026, a mid-sized law firm lost access to 47 Microsoft 365 mailboxes in under six hours. The attacker never cracked a password. They stole session tokens using an adversary-in-the-middle phishing kit — the same technique used in the 2023 Storm-0558 campaign against government tenants. If you manage M365, this is what actually happened and how to detect it.
How Adversary-in-the-Middle Phishing Steals M365 Sessions
Tools like Evilginx and Modlishka act as reverse proxies between the victim and the real Microsoft login page. The victim authenticates normally — including MFA — but the proxy captures the authenticated session cookie. MFA is completely bypassed because the token is stolen after authentication completes.
A real phishing lure looks indistinguishable from a legitimate SharePoint notification. The link resolves to a domain like sharepoint-docreview[.]com running on 192.0.2.47, proxying login.microsoftonline.com in real time.
Once the attacker has the session token, they replay it directly. Here is what that looks like using a stolen cookie in a raw request:
# Replaying a captured M365 session token with curl
curl -s -X GET "https://graph.microsoft.com/v1.0/me/messages?$top=10" \
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...TRUNCATED" \
-H "Content-Type: application/json"
# Partial response:
{
"value": [
{
"id": "AAMkADk2N...",
"subject": "Q3 Merger Terms - CONFIDENTIAL",
"from": { "emailAddress": { "address": "cfo@lawfirm-example.com" } },
"receivedDateTime": "2026-08-28T14:22:11Z"
}
]
}
That bearer token grants full mailbox read access — no username, no password, no MFA prompt. The attacker is now reading email as the CFO. Next move: enumerate inbox rules, forward sensitive threads, and pivot to other users via internal phishing from a trusted address.
Detecting Impossible Travel and Token Replay in Unified Audit Logs
Microsoft 365 logs every sign-in and API call. The problem is volume — a busy tenant generates millions of events. You need to hunt for specific anomalies: impossible travel, unfamiliar client IDs, and UserAuthenticationMethod mismatches.
Pull sign-in logs using the Microsoft Graph PowerShell module and filter for suspicious conditions:
# Connect and query risky sign-ins from the last 24 hours
Connect-MgGraph -Scopes "AuditLog.Read.All"
$signIns = Get-MgAuditLogSignIn -Filter "
createdDateTime ge 2026-08-28T00:00:00Z and
riskLevelDuringSignIn eq 'high'
" -Top 50 | Select-Object UserPrincipalName, IPAddress,
Location, ClientAppUsed, RiskLevelDuringSignIn,
AuthenticationRequirement, CreatedDateTime
$signIns | Format-Table -AutoSize
# Sample output:
UserPrincipalName IPAddress Location ClientAppUsed Risk AuthReq
------------------------- ------------- -------------- ---------------- ------ --------
jmartin@lawfirm-ex.com 192.0.2.201 Kyiv, UA Browser high singleFA
cfo@lawfirm-ex.com 192.0.2.47 Amsterdam, NL Python-requests high singleFA
admin@lawfirm-ex.com 192.0.2.88 Chicago, US Mobile Apps none mfa
Two things jump out immediately. First, cfo@lawfirm-ex.com authenticated from Amsterdam using Python-requests — that is a scripted API client, not a human browser. Second, AuthenticationRequirement shows singleFA, meaning MFA was not enforced for that session. That is the token replay signature: the attacker used a pre-authenticated token that satisfied Conditional Access without triggering a new MFA challenge.
From a defender’s position, these two accounts need immediate session revocation. Run Revoke-MgUserSignInSession -UserId cfo@lawfirm-ex.com to invalidate all active tokens. Then check inbox rules — attackers almost always set a forwarding rule within minutes of gaining access.
OAuth App Consent Phishing: The Persistent Backdoor
Token replay gives temporary access. OAuth consent phishing gives permanent access. The attacker registers a malicious Azure AD app, crafts a consent URL, and tricks an admin into granting it Mail.Read or Files.ReadWrite.All permissions. The app then has delegated access that survives password resets and MFA changes.
A malicious consent URL looks exactly like a legitimate Microsoft OAuth flow:
https://login.microsoftonline.com/common/oauth2/v2.0/authorize?
client_id=a1b2c3d4-e5f6-7890-abcd-ef1234567890
&response_type=code
&redirect_uri=https://192.0.2.47/callback
&scope=Mail.Read+Files.ReadWrite.All+offline_access
&state=randomstate123
The victim sees a real Microsoft consent dialog listing the requested permissions. One click and the attacker’s app has OAuth tokens that refresh indefinitely. Audit your tenant’s consented apps now — most organizations have dozens they have never reviewed.
Use this Graph query to list all apps with Mail permissions:
Get-MgServicePrincipal -All | Where-Object {
$_.Oauth2PermissionScopes.Value -match "Mail"
} | Select DisplayName, AppId, CreatedDateTime
Any app you do not recognize with mail or files permissions is a red flag requiring immediate investigation and revocation.
What To Do Now
Run the OAuth app audit command above against your tenant right now. Pipe the output to a CSV, open it, and sort by CreatedDateTime. Focus on apps created in the last 90 days that you cannot attribute to a known deployment. Revoke consent for anything suspicious using Remove-MgServicePrincipal -ServicePrincipalId <AppId>. Do this before you do anything else today — a forgotten OAuth app is often the access vector that survives an entire incident response cycle.
