On July 12, Microsoft's threat intelligence team published a writeup most of our clients never read, describing a phishing campaign that had by then hit more than 10,000 organizations since September 2021. The technique wasn't a new exploit or an unpatched zero-day. It was a reverse proxy sitting between the user and the real Microsoft 365 sign-in page, harvesting the password and the post-MFA session cookie in the same transaction, then replaying that cookie to walk straight past multifactor authentication. Microsoft tied the campaign to Evilginx2, an open-source reverse-proxy phishing framework that's circulated in red-team and criminal toolkits since 2018. We read the write-up in July, nodded, and filed it under "things to mention at the next steering meeting."
In September, we stopped nodding. A controller at a South Florida title insurer we support clicked a DocuSign-styled link, typed her password into a page that looked exactly like the real thing, and approved the Authenticator push that followed — because the prompt was real, relayed from the actual identity platform through the attacker's proxy in real time. Nine minutes later, sign-in activity showed the same session token used from an ASN in Eastern Europe to open web mail and set up an inbox rule quietly filing anything containing "wire" or "ACH" into an RSS subscription folder nobody checks. We caught it because Azure AD Identity Protection flagged the session with an anomalous token risk detection, and someone was actually watching the queue that morning. Plenty of estates would not have caught it at all.
This note is what that incident, and three shorter conversations with other clients since, taught us about the gap between "we require MFA" and "we can't be phished into handing over an already-authenticated session." Those are not the same claim, and the difference is now the most consequential thing we discuss in identity engagements.
How a reverse proxy beats a one-time code
AiTM phishing kits are not credential-harvesting pages in the old sense. A static fake login page can only ever capture what a victim types into it — mostly useless against MFA, because the attacker still has to get past the second factor live, in real time, before the victim notices. An AiTM kit instead stands up a reverse proxy: the victim's browser talks to the attacker's domain, the attacker's server talks to the real identity provider, and the attacker relays every request and response between them, including the MFA challenge itself. The victim is genuinely authenticating to Microsoft. The attacker is just sitting in the wire, and at the end of that exchange Azure AD hands back a session cookie and access token that the proxy captures before forwarding the response along to the browser.
From that point, MFA is irrelevant to the attacker. They import the stolen cookie into a browser session under their own control, and they are the user — with a live, MFA-satisfied session — until that token expires or gets revoked. What changed in 2022 wasn't the technique; it's the scale and the packaging. Phishing-as-a-service operators now rent out AiTM infrastructure the way ransomware affiliates rent encryptors, and the lure quality has gone up with it.
The tell, if you know to look for it, is the URL bar: the phishing domain is never the real sign-in domain, even though everything rendered on the page is a faithful copy. That's a thin thread to hang enterprise security on when a controller is processing forty emails before a 9 a.m. meeting.
Why "we require MFA" stopped being reassuring
For several years, "do you have MFA everywhere" was close enough to a complete answer for a board or an auditor. It no longer is, and we've started pushing back in scoping calls when it's offered as one. MFA answers "did the right human enter the right credentials." It says nothing about whether the resulting session is bound to anything — the device, the browser, the network — or whether it can be lifted wholesale and replayed from anywhere on the internet for as long as the token stays valid.
Push-based MFA has a second, related weakness worth separating from AiTM cleanly, because clients keep conflating them: MFA fatigue, where an attacker who already has a stolen password just spams push approvals until an annoyed user taps "approve" to make the notifications stop. Number matching addresses that specific problem — Microsoft has it in preview for Authenticator — but it's opt-in, not the default in most tenants we look at, and it wouldn't have changed anything in our September incident anyway. The controller approved a completely genuine prompt, because it was one. Number matching stops fatigue. It does not stop AiTM. Treating them as the same fix is how a program ends up defended against the attack that didn't happen.
What phishing-resistant authentication actually rules out
FIDO2 security keys and certificate-based authentication — Azure AD CBA reached general availability earlier this year — share a property that push notifications and even one-time codes don't: the cryptographic exchange is bound to the origin the browser is actually talking to. A FIDO2 key checks the relying party identifier against the domain in the address bar before it will sign anything. Point a victim at a lookalike domain proxying the real login page, and the key simply won't respond, because the phishing site isn't the origin the credential was registered against. Windows Hello for Business behaves the same way through the platform authenticator.
That's the actual meaning of "phishing-resistant," and it's worth being precise about it with clients who hear the term and assume it just means "stronger MFA." It doesn't defeat every downstream risk. A compromised or malware-infected endpoint with an active, authenticated session is still a problem phishing-resistant sign-in doesn't solve, and a token already exfiltrated from a compromised device doesn't care what your sign-in method was. But for the specific mechanism Microsoft described in July — a proxy harvesting credentials and cookies at the login moment — a hardware-bound or certificate-bound sign-in removes the attack surface at the one point where a human is in the loop. We're now scoping FIDO2 pilots for privileged roles first in every identity engagement on our books, not as a someday line item.
Shrinking the token's shelf life
Even with phishing-resistant auth on every account — a multi-quarter project for anyone past a few hundred seats — a stolen device, a malicious browser extension, or a token exfiltrated some other way still leaves a live session in someone else's hands. That's where session lifetime and continuous access evaluation earn their keep.
Continuous access evaluation (CAE) lets Azure AD revoke a token near-instantly on specific triggers — user disabled, password changed, account deleted, or a sharp change in assessed risk — instead of waiting for the token's stated expiry. Microsoft has been extending CAE's coverage across Exchange Online, SharePoint Online, and Teams over the past year, and in our tenants we're seeing it fire correctly on the scenarios it's built for. It is not, by itself, a detection tool, and it does nothing about a token stolen and used within the same window before any triggering event occurs — exactly the case in a fast-moving AiTM-to-BEC pivot like the one we saw in September.
That's what Conditional Access session controls are for. Sign-in frequency forces reauthentication on a schedule we choose rather than the platform default; we're now setting it to a small number of hours for privileged roles and anything touching finance systems, instead of leaving it at the long default most tenants never touch. Disabling persistent browser sessions on unmanaged devices closes the easiest path to a stolen cookie surviving a browser restart. None of this is glamorous. All of it shrinks the window an attacker gets to use a token before something forces a fresh, ideally phishing-resistant, authentication.
Microsoft has also signaled that binding a token to the device that requested it — sometimes discussed as token protection — is coming to Conditional Access. As of this month it isn't something we can turn on broadly across a tenant, so we're not building client roadmaps around it yet. We're watching the rollout, not deploying it.
Where each control stops the attack, and where it doesn't
| Control | Stops | Doesn't stop alone |
|---|---|---|
| FIDO2 / certificate-based auth | Credential and cookie theft at the login moment | Token theft from an already-compromised device |
| Number matching | Push-approval / MFA fatigue | AiTM replay of a genuine prompt |
| Short sign-in frequency, no persistent session | The usable lifetime of a stolen token | Detection during that lifetime |
| Continuous access evaluation | Access after a disable, password change, or risk trigger | A token used before any trigger fires |
| Sign-in risk / anomalous token detection | Discovery after the fact | Anything, if nobody reviews the queue |
Every row needs an owner who actually checks it, not a checkbox that says it's enabled. Our September save worked because a real person looked at a risk alert within the hour. A tenant with identical licensing and identical policies, but nobody reading the Identity Protection queue, has the same wire fraud attempt succeed instead.
Reading sign-in logs for a token that shouldn't exist
Assume some tokens will be stolen regardless of what you deploy, and build the habit of looking for one. Azure AD Identity Protection's anomalous token detection is the most direct signal available today — it looks specifically for session and refresh tokens with unusual lifetime or replay characteristics from unfamiliar locations:
Connect-MgGraph -Scopes "IdentityRiskEvent.Read.All"
Get-MgRiskDetection -Filter "riskEventType eq 'anomalousToken'" |
Select-Object userDisplayName, riskEventType, riskLevel, detectedDateTime, ipAddress, location |
Sort-Object detectedDateTime -Descending
That requires Azure AD Premium P2 licensing, which not every client has everywhere. Where it isn't available, the sign-in logs themselves still show the pattern if someone is actually looking: the same user authenticating from geographically implausible locations within an implausible time window, or a sign-in against Exchange or SharePoint from an IP address that never shows up for that user otherwise. A rough version of that query against a Log Analytics workspace:
SigninLogs
| where TimeGenerated > ago(1d)
| where ResultType == 0
| summarize IPCount = dcount(IPAddress), IPs = make_set(IPAddress) by UserPrincipalName, AppDisplayName, bin(TimeGenerated, 1h)
| where IPCount > 1
It's noisy — road warriors and split-tunnel VPN users will trip it — but noisy and reviewed beats silent and unreviewed. We're setting this up as a standing alert for every identity client this quarter, tuned per tenant, because "we'll notice" was not a plan before September, and it certainly isn't one now.
If you're facing this
If your MFA rollout predates this conversation, the honest next step isn't a new tool purchase — it's an inventory of which accounts still authenticate with something a reverse proxy can relay, starting with anyone who can touch finance systems or sits in Global Admin. We help Microsoft-centric estates work through phishing-resistant rollouts, Conditional Access session hardening, and the detection work that catches what gets through anyway. Bring us your Conditional Access baseline and we'll tell you honestly where the gaps are.