The Problem: MFA Does Not Stop AiTM

Adversary-in-the-Middle (AiTM) phishing is the technique that ends the conversation about “MFA stops phishing.” It doesn’t — not legacy MFA. When a user visits an Evilginx2, Modlishka, or Muraena proxy, the proxy relays their credentials and the legitimate service’s MFA challenge in real time. The user authenticates normally. The proxy captures both the authenticated session cookie and (in some implementations) the ESTS token. The attacker replays that session cookie from a different IP and OS without ever needing to enter a password or OTP.

Microsoft tracked AiTM phishing kits in large-scale campaigns throughout 2025-2026, including Storm-1167 campaigns targeting tens of thousands of Microsoft 365 users. The technique is mature, widely deployed by RaaS initial access brokers, and specifically designed to operate silently within normal authentication flows.

Attack Mechanics

  1. Target receives a phishing email linking to an attacker-controlled proxy (e.g., login.microsoft.attacker-domain.com)
  2. The proxy forwards all requests to the legitimate service (login.microsoft.com) in real time, acting as a transparent relay
  3. The user completes MFA. The proxy captures the resulting authenticated session cookie (typically the .ESTSAUTH cookie for Microsoft services)
  4. The proxy delivers the expected legitimate page to the user, who may suspect nothing
  5. The attacker replays the captured session cookie from a different IP, OS, and browser

The attack leaves evidence in sign-in logs: two successful authentications close together, the second from a different IP/device, with no corresponding MFA challenge.

Detection Layer 1: Impossible Travel

The first and most reliable detection is impossible travel — a successful authentication from Country A followed within minutes by a successful authentication from Country B.

KQL (Microsoft Sentinel — SigninLogs):

let lookback = 1h;
let max_travel_speed_kmh = 900; // Rough threshold to filter air travel
SigninLogs
| where TimeGenerated > ago(lookback)
| where ResultType == 0 // Successful sign-in
| project TimeGenerated, UserPrincipalName, IPAddress, Location, AppDisplayName
| sort by UserPrincipalName asc, TimeGenerated asc
| serialize
| extend PreviousTime = prev(TimeGenerated, 1),
         PreviousIP = prev(IPAddress, 1),
         PreviousLocation = prev(Location, 1),
         PreviousUser = prev(UserPrincipalName, 1)
| where UserPrincipalName == PreviousUser
| where IPAddress != PreviousIP
| where PreviousLocation != Location
| extend TimeDeltaHours = datetime_diff('minute', TimeGenerated, PreviousTime) / 60.0
| where TimeDeltaHours < 2 and TimeDeltaHours > 0
| where PreviousLocation !contains Location  // Different country
| project UserPrincipalName, TimeGenerated, IPAddress, Location, PreviousIP, PreviousLocation, TimeDeltaHours, AppDisplayName
| sort by TimeDeltaHours asc

When an attacker replays a stolen session cookie, the authentication succeeds without an MFA challenge appearing in the logs. This is detectable in AADNonInteractiveUserSignInLogs and SigninLogs.

KQL (Non-Interactive Sign-In with Token Replay Indicators):

SigninLogs
| where TimeGenerated > ago(24h)
| where ResultType == 0
| extend AuthDetails = todynamic(AuthenticationDetails)
| mv-expand AuthDetails
| where AuthDetails.authenticationMethod == "Previously satisfied" 
    or AuthDetails.succeeded == "true" and AuthDetails.authenticationStepRequirement == ""
| extend DeviceId = tostring(DeviceDetail.deviceId)
| extend OS = tostring(DeviceDetail.operatingSystem)
| join kind=leftouter (
    SigninLogs
    | where TimeGenerated > ago(7d)
    | where ResultType == 0
    | summarize KnownDevices = make_set(tostring(DeviceDetail.deviceId)) by UserPrincipalName
) on UserPrincipalName
| where isnotempty(DeviceId)
| where not(DeviceId in (KnownDevices))
| project TimeGenerated, UserPrincipalName, IPAddress, DeviceId, OS, AppDisplayName, AuthDetails

Detection Layer 3: Sigma Rule for Evilginx User-Agent Fingerprinting

Evilginx2 and some related proxies leave characteristic patterns in web proxy or WAF logs when the phishing page is visited. Additionally, when attackers replay cookies, they frequently use automation/headless browser user-agents inconsistent with the session’s original browser.

Sigma Rule — Suspicious User-Agent Inconsistency After Authentication:

title: Session Token Replay - User-Agent Mismatch After Successful Auth
id: b3f9a1e2-5c74-4d8a-9f06-7e3b2a8c4d91
status: experimental
description: >
  Detects a successful authentication followed within 30 minutes by
  a resource access with a significantly different User-Agent string,
  indicating potential session token theft and replay.
author: SOC Analyst Hub
date: 2026-06-06
references:
  - https://attack.mitre.org/techniques/T1539/
tags:
  - attack.credential_access
  - attack.t1539
  - attack.t1557
logsource:
  category: proxy
  product: azure
detection:
  selection_auth:
    EventID: "Sign-in activity"
    ResultType: "0"
    AuthenticationRequirement: "multiFactorAuthentication"
  selection_replay:
    EventID: "Sign-in activity"
    ResultType: "0"
    AuthenticationRequirement: "singleFactorAuthentication"
    AuthMethod: "Previously satisfied"
  condition: selection_auth and selection_replay
fields:
  - UserPrincipalName
  - IPAddress
  - UserAgent
  - Location
  - AppDisplayName
falsepositives:
  - Mobile device switching between Wi-Fi and cellular
  - VPN split tunneling configuration changes
  - Legitimate travel with multiple auth sessions
level: high

Detection Layer 4: Entra ID Sign-In Risk Signals

Microsoft Entra ID’s Identity Protection generates unfamiliarFeatures and anonymizedIPAddress risk signals that fire on AiTM-like behaviour. These are visible in the riskEventTypes field of SigninLogs.

SigninLogs
| where TimeGenerated > ago(24h)
| where ResultType == 0
| extend RiskDetail = tostring(RiskDetail)
| extend RiskEventTypes = todynamic(RiskEventTypes)
| where RiskEventTypes has_any ("unfamiliarFeatures", "anonymizedIPAddress", "maliciousIPAddress", "suspiciousInboxManipulationRules")
| project TimeGenerated, UserPrincipalName, IPAddress, RiskDetail, RiskEventTypes, AppDisplayName, Location
| sort by TimeGenerated desc

Post-Compromise: What to Hunt For

When AiTM succeeds, attackers typically move quickly. Within the same session window, look for:

  • Inbox rules created — filtering forwarding rules that copy email to external addresses (Set-InboxRule in OfficeActivity logs)
  • MFA method registered — attacker adds their own phone number or authenticator app to lock out the victim
  • OAuth app consent granted — high-privilege app registrations that persist access beyond session expiry
  • SharePoint/OneDrive access — bulk download of files, particularly from HR, finance, or legal folders
// Hunt: Inbox forwarding rules created within 2 hours of a high-risk sign-in
OfficeActivity
| where TimeGenerated > ago(24h)
| where Operation in ("Set-InboxRule", "New-InboxRule")
| where Parameters has "ForwardTo" or Parameters has "RedirectTo"
| join kind=inner (
    SigninLogs
    | where RiskLevelDuringSignIn in ("high", "medium")
    | where ResultType == 0
    | project RiskUser = UserPrincipalName, RiskTime = TimeGenerated
) on $left.UserId == $right.RiskUser
| where abs(datetime_diff('minute', TimeGenerated, RiskTime)) < 120
| project TimeGenerated, UserId, Operation, Parameters, RiskTime

When AiTM session theft is confirmed or suspected:

  1. Revoke all active sessionsRevoke-MgUserSignInSession in Microsoft Graph, or via Entra ID portal. This invalidates the stolen cookie immediately.
  2. Require re-authentication — conditional access policies can force re-auth after session revocation.
  3. Disable legacy authentication — AiTM kits often rely on basic auth fallback; CA policies blocking legacy auth reduce the attack surface.
  4. Enforce phishing-resistant MFA — FIDO2/passkeys and Windows Hello for Business are resistant to AiTM proxying. SMS and TOTP OTP codes are not.
  5. Review OAuth consents — use Get-MgOAuth2PermissionGrant to audit delegated app permissions granted by the compromised account.