Why this matters now
Multi-factor authentication stopped being a guarantee the day attackers learned to simply ask again. In an MFA fatigue attack — also called push bombing — the attacker already holds a valid username and password, harvested by phishing or bought from an infostealer log. They sign in, triggering a push approval prompt on the victim's phone. The victim declines. The attacker signs in again. And again. Ten prompts, twenty, sometimes more than a hundred, often late at night, until a tired, confused, or annoyed user taps Approve just to make the notifications stop. That single tap hands the attacker a fully authenticated session.
This is not a theoretical technique. The Lapsus$ group used MFA fatigue in its 2022 intrusions, and the September 2022 Uber breach began the same way: an attacker spammed an employee's phone with push prompts until one was approved, then moved through the network from that foothold. Microsoft has documented the pattern in its own threat guidance, and Entra ID now ships number matching specifically because blind Approve/Deny prompts proved too easy to wear down. If your tenant still allows plain push approvals, every denial in your sign-in logs is a free early warning — but only if someone is hunting for the burst.
The technique, step by step
MFA fatigue has a short, repeatable kill chain, which is exactly what makes it huntable:
- Credential acquisition. The attacker gets a valid password first — phishing, credential stuffing, or a stealer log. Without it, no prompt is ever generated, which is why this attack pairs with password hygiene failures.
- Prompt spam. The attacker submits sign-in after sign-in, each one generating a push notification on the victim's authenticator app. The prompts may arrive in a tight burst over minutes or be spread across an hour to look less mechanical.
- The fatigued approval. Eventually the user approves — accidentally while dismissing notifications, deliberately out of frustration, or because a social-engineering message ("IT needs you to approve this prompt") arrives alongside the spam. One approval is enough.
- Session abuse. The attacker is in with a legitimate session. From there they register their own MFA method, set up mailbox rules, or pivot — the same post-access moves covered in the playbook on investigating unexpected MFA method changes.
What the defender sees in Entra sign-in logs is distinctive: a cluster of sign-ins for one user where the authentication step shows "Mobile app notification" and the result is denied, followed by one success from the same source. The denied prompts are the signal — most users deny a push once in a blue moon, never a dozen times in ten minutes.
Hunt 1: KQL — the denial burst
Start in SigninLogs. The first query surfaces users with an unusual volume of denied MFA prompts in a short window. Error code 500121 marks authentication that failed during MFA — the code you see when a user declines a push prompt — and the authentication details record both the method and the step result.
SigninLogs
| where TimeGenerated > ago(7d)
| where ResultType == 500121
| extend AuthMethod = tostring(AuthenticationDetails[0].authenticationMethod),
StepResult = tostring(AuthenticationDetails[0].authenticationStepResultDetail)
| where AuthMethod == "Mobile app notification"
| summarize DeniedPrompts = count(),
FirstSeen = min(TimeGenerated),
LastSeen = max(TimeGenerated),
Sources = make_set(IPAddress),
Locations = make_set(LocationDetails.countryOrRegion)
by UserPrincipalName
| where DeniedPrompts >= 5
| extend BurstMinutes = datetime_diff("minute", LastSeen, FirstSeen)
| where BurstMinutes <= 60
| sort by DeniedPrompts desc
Tune the thresholds to your tenant: five denials in an hour is a conservative starting point. Legitimate explanations exist — a user repeatedly tapping Deny while their own sign-in retries — but genuine users almost never stack five denials from one source in an hour. Triage the top of this list first, and treat any burst that coincides with off-hours as urgent.
Hunt 2: KQL — the denial burst that ended in success
A burst alone is a warning; a burst followed by a success is an incident. This query joins denied prompts to a successful interactive sign-in for the same user within the following two hours, and flags whether the success came from a new source IP or country:
let Denials = SigninLogs
| where TimeGenerated > ago(7d)
| where ResultType == 500121
| extend AuthMethod = tostring(AuthenticationDetails[0].authenticationMethod)
| where AuthMethod == "Mobile app notification"
| summarize DenialCount = count(), LastDenial = max(TimeGenerated) by UserPrincipalName;
let Successes = SigninLogs
| where TimeGenerated > ago(7d)
| where ResultType == 0
| summarize FirstSuccess = min(TimeGenerated),
SuccessIP = take_any(IPAddress),
SuccessCountry = take_any(LocationDetails.countryOrRegion)
by UserPrincipalName;
Denials
| join kind=inner Successes on UserPrincipalName
| where FirstSuccess between (LastDenial .. (LastDenial + 2h))
| where DenialCount >= 5
| project UserPrincipalName, DenialCount, LastDenial, FirstSuccess, SuccessIP, SuccessCountry
| sort by DenialCount desc
Every row here deserves an immediate check: contact the user, confirm whether they approved the prompt, and revoke sessions if they did not. The same burst-then-success shape appears in brute-force hunting — see the playbook on cloud console brute-force — but here the failed attempts are MFA denials, not bad passwords, which is what separates fatigue from guessing. For the password-first half of the same attack chain, the Entra password-spraying playbook covers the sign-in failure telemetry that precedes the prompt spam.
Hunt 3: The same behaviour in Splunk (SPL)
For tenants forwarding Entra sign-in logs to Splunk, the equivalent hunt groups denied MFA steps by user and hour, then looks for a success on the same account shortly after:
index=azure sourcetype="ms:aad:signin" errorCode=500121 authenticationMethod="Mobile app notification"
| bin _time span=1h
| stats count as denied_prompts, values(src_ip) as sources, values(country) as countries by user, _time
| where denied_prompts >= 5
| sort - denied_prompts
Follow any hit with a lookup for the same user with errorCode=0 in the two hours after the burst window. In both KQL and SPL, the detection logic is identical: count denied push prompts per user per hour, alert on the burst, escalate on burst-then-success.
Triage: five questions before you call it
- Did the user approve anything? Ask them directly. A burst with no approval is an attempted attack; a burst with an approval is a compromise until proven otherwise.
- Where did the prompts come from? One source IP and one country suggests a single attacker. Multiple sources may indicate a distributed attempt or a user travelling — check against their known locations.
- What happened after the success? Look for MFA method registrations, Conditional Access exclusions, mailbox rule creation, or sign-ins to sensitive apps in the hour after approval.
- Was the password itself the weak link? Check for a recent phishing report, a password-spray pattern against the account, or the credential appearing in a stealer log.
- Is it happening to anyone else? One fatigued user may be one of several targeted accounts. Run Hunt 1 tenant-wide, not just for the reported user.
Remediation and hardening
Hunting catches the attack; configuration kills the technique. In order of impact:
- Enable number matching for push notifications. The user must type the number shown on the sign-in screen into the authenticator app. A blind Approve tap no longer works, and an attacker cannot relay the number they cannot see. This is the single most effective control against fatigue.
- Move to phishing-resistant MFA (FIDO2 keys or certificate-based authentication) for privileged and high-risk users, and enforce it with authentication-strength Conditional Access policies.
- Show additional context in prompts — application name and geographic location — so a 3 a.m. prompt for an unfamiliar app is obviously wrong to the user.
- Alert on denial bursts, not just successes. Wire Hunt 1 into your SIEM as a scheduled alert with a threshold tuned to your tenant, and page on burst-then-success rather than waiting for a user report.
- Train the response, not just the awareness. Users should know that an unexpected MFA prompt is a reportable security event, not an annoyance — and that tapping Approve to silence it is exactly what the attacker is counting on.
- After a confirmed approval: revoke the user's sessions and refresh tokens, force a password reset, remove any attacker-registered authentication methods, and review the post-access activity from Hunt 2's window before closing the incident.
MFA fatigue works because it attacks the human in the loop, not the cryptography. The prompts are legitimate, the session is legitimate, and the only anomaly is volume — a dozen denials where a user normally has none. Hunt the volume and the technique loses its cover.
