Query Details

10 SIGNIN MFA Fatigue

Query

id: f0a1b2c3-d4e5-4f6a-7b8c-9d0e1f2a3b4c
name: "SigninLogs — MFA Fatigue Attack (Push Bombardment)"
version: 1.0.0
kind: Scheduled
description: |
  Detects MFA fatigue attacks where an attacker repeatedly triggers MFA push notifications hoping the user approves one out of frustration. Identified by 5 or more MFA result codes (50074, 50076, 50079, 50158) in a 1-hour window for a single user. MITRE ATT&CK: T1621 (MFA Request Generation).
severity: Medium
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - SigninLogs
queryFrequency: PT1H
queryPeriod: PT1H
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
relevantTechniques:
  - T1621
query: |

    // ---- Network Allowlist (exclude trusted IPs / CIDR / ranges) --------------
    let _allow = materialize(union isfuzzy=true (print R="" | take 0), (_GetWatchlist('NetworkAllowlist') | project R = tostring(IPOrRange)) | where isnotempty(R));
    let _allowCIDR  = toscalar(_allow | where not(R matches regex @'^\d+\.\d+\.\d+\.\d+-\d+\.\d+\.\d+\.\d+$') | extend R = iff(R has '/', R, strcat(R, '/32')) | summarize make_list(R));
    let _allowRange = toscalar(_allow | where R matches regex @'^\d+\.\d+\.\d+\.\d+-\d+\.\d+\.\d+\.\d+$' | summarize make_list(R));
    let _ExcludeAllowlistedIPs = (T:(IPAddress:string)) {
        T
        | extend IPAddress = tostring(IPAddress)
        | where array_length(_allowCIDR) == 0 or isnull(ipv4_is_in_any_range(IPAddress, _allowCIDR)) or not(ipv4_is_in_any_range(IPAddress, _allowCIDR))
        | mv-apply _r = _allowRange to typeof(string) on (
            extend _lo = tostring(split(_r,'-')[0]), _hi = tostring(split(_r,'-')[1])
            | extend _inRange = ipv4_compare(IPAddress, _lo) >= 0 and ipv4_compare(IPAddress, _hi) <= 0
            | summarize _anyInRange = max(toint(_inRange)))
        | where isnull(_anyInRange) or _anyInRange == 0
        | project-away _anyInRange
    };
    // ---------------------------------------------------------------------------
    let mfaBombardThreshold = 5;
    SigninLogs
    | invoke _ExcludeAllowlistedIPs()
    | where TimeGenerated > ago(1h)
    | where ResultType in ("50074", "50076", "50079", "50158")
    | summarize
        MFAInterruptCount = count(),
        FirstInterrupt    = min(TimeGenerated),
        LastInterrupt     = max(TimeGenerated),
        SourceIPs         = make_set(IPAddress, 10),
        Locations         = make_set(Location, 5),
        Apps              = make_set(AppDisplayName, 5)
      by UserPrincipalName
    | where MFAInterruptCount >= mfaBombardThreshold
    | extend
        InterruptDurationMin = datetime_diff("minute", LastInterrupt, FirstInterrupt)
    | project
        TimeGenerated         = FirstInterrupt,
        UserPrincipalName,
        MFAInterruptCount,
        InterruptDurationMin,
        SourceIPs, Locations, Apps,
        LastInterrupt
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserPrincipalName
customDetails:
  MFAInterruptCount: MFAInterruptCount
  InterruptDurationMin: InterruptDurationMin
alertDetailsOverride:
  alertDisplayNameFormat: "MFA Fatigue Attack — {{UserPrincipalName}} ({{MFAInterruptCount}} pushes)"
  alertDescriptionFormat: "{{MFAInterruptCount}} MFA push notifications in 1 hour for {{UserPrincipalName}} — possible MFA bombardment."
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: PT1H
    matchingMethod: AllEntities
    groupByEntities:
  - Account



Explanation

This query is designed to detect potential Multi-Factor Authentication (MFA) fatigue attacks, also known as push bombardment attacks. In such attacks, an attacker repeatedly sends MFA push notifications to a user, hoping the user will approve one out of frustration.

Here's a simple breakdown of what the query does:

  1. Network Allowlist: It first defines a list of trusted IP addresses or ranges (allowlist) to exclude from the analysis. This helps in focusing only on suspicious activities.

  2. Threshold Definition: It sets a threshold of 5 or more MFA push notifications within a 1-hour period for a single user as suspicious.

  3. Data Source: The query uses data from Azure Active Directory's SigninLogs.

  4. Filtering: It filters out sign-in attempts that come from allowlisted IPs and only considers those with specific MFA result codes (50074, 50076, 50079, 50158) within the last hour.

  5. Aggregation: It counts the number of MFA interruptions (push notifications) for each user and gathers information about the source IPs, locations, and apps involved.

  6. Detection: If a user receives 5 or more MFA push notifications in an hour, it flags this as a potential attack.

  7. Output: The query outputs details such as the user's name, the number of MFA interruptions, the duration of these interruptions, and information about the source IPs, locations, and apps.

  8. Alert Configuration: If the conditions are met, it creates an alert with details about the potential attack, including the user's name and the number of push notifications received.

  9. Incident Management: It is configured to create an incident if such an attack is detected, with options for grouping related incidents.

Overall, this query helps in identifying and alerting on potential MFA fatigue attacks to enhance security by preventing unauthorized access through compromised credentials.

Details

David Alonso profile picture

David Alonso

Released: April 20, 2026

Tables

SigninLogs

Keywords

SigninLogsAzureActiveDirectoryIPAddressUserPrincipalNameLocationAppDisplayNameAccount

Operators

letmaterializeunionprinttakeprojectwhereisnotemptytoscalarmatches regexextendiffstrcatsummarizemake_listnotisnullipv4_is_in_any_rangemv-applyto typeofsplitipv4_comparemaxtointproject-awayinvokeagoincountminmaxmake_setbydatetime_diffproject

Actions