Detection rules › Kusto
MFA Fatigue (OKTA)
MFA fatigue attack is a cybersecurity threat where attackers exploit user exhaustion from multi-factor authentication prompts to trick them into providing their MFA details thus compromising their own security. The query identifies MFA fatigue attempts in the Okta data. Ref: https://www.okta.com/blog/identity-security/mfa-fatigue-growing-security-concern/.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Credential Access | T1621 Multi-Factor Authentication Request Generation |
Event coverage
Rules detecting the same action
Other rules on this platform that filter on the same API call or operation.
- First Occurrence of Okta User Session Started via Proxy (Elastic)
- Okta AiTM Phishing Attempt Blocked by FastPass (Panther)
- Okta Fast Pass phishing Detection (Kusto)
- Okta FastPass Phishing Detection (Sigma)
- Okta FastPass Phishing Detection (Elastic)
- Okta MFA Bruteforce Attack (YARA-L)
- Okta MFA Exhaustion Hunt (Splunk)
- Okta Mismatch Between Source And Response For Verify Push Request (YARA-L)
Rule body kusto
id: c2697b81-7fe9-4f57-ba1d-de46c6f91f9c
name: MFA Fatigue (OKTA)
description: |
'MFA fatigue attack is a cybersecurity threat where attackers exploit user exhaustion from multi-factor authentication prompts to trick them into providing their MFA details thus compromising their own security. The query identifies MFA fatigue attempts in the Okta data.
Ref: https://www.okta.com/blog/identity-security/mfa-fatigue-growing-security-concern/.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: OktaSSO
dataTypes:
- Okta_CL
- connectorId: OktaSSOv2
dataTypes:
- OktaSSO
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1621
query: |
let PushThreshold = 10;
OktaSSO
| where ((eventType_s =="user.authentication.auth_via_mfa" and column_ifexists('debugContext_debugData_factor_s', '') == "OKTA_VERIFY_PUSH") or eventType_s == "system.push.send_factor_verify_push" or eventType_s == "user.mfa.okta_verify.deny_push")
| summarize IPAddress = make_set(client_ipAddress_s,100), City = make_set(client_geographicalContext_city_s,100),
successes = countif(eventType_s == "user.authentication.auth_via_mfa"),
denies = countif(eventType_s == "user.mfa.okta_verify.deny_push"),
pushes = countif(eventType_s == "system.push.send_factor_verify_push") by TimeGenerated, authenticationContext_externalSessionId_s, actor_alternateId_s,actor_displayName_s, outcome_result_s
| summarize lasttime = max(TimeGenerated), firsttime = min(TimeGenerated),
successes = sum(successes), failures = sum(denies), pushes = sum(pushes) by authenticationContext_externalSessionId_s, actor_alternateId_s,actor_displayName_s, outcome_result_s
| extend seconds = lasttime - firsttime
| where pushes > (PushThreshold)
| extend totalattempts = successes + failures
| extend finding = case(
failures == pushes and pushes > 1, "Authentication attempts not successful because multiple pushes denied",
totalattempts == 0, "Multiple pushes sent and ignored",
successes > 0 and pushes > 3, "Multiple pushes sent, eventual successful authentication!",
"Normal authentication pattern")
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: actor_alternateId_s
- identifier: DisplayName
columnName: actor_displayName_s
version: 1.1.2
kind: Scheduled
Stages and Predicates
Parameters
let PushThreshold = 10;
Stage 1: source
OktaSSO
Stage 2: where
| where ((eventType_s =="user.authentication.auth_via_mfa" and column_ifexists('debugContext_debugData_factor_s', '') == "OKTA_VERIFY_PUSH") or eventType_s == "system.push.send_factor_verify_push" or eventType_s == "user.mfa.okta_verify.deny_push")
Stage 3: summarize
| summarize IPAddress = make_set(client_ipAddress_s,100), City = make_set(client_geographicalContext_city_s,100),
successes = countif(eventType_s == "user.authentication.auth_via_mfa"),
denies = countif(eventType_s == "user.mfa.okta_verify.deny_push"),
pushes = countif(eventType_s == "system.push.send_factor_verify_push") by TimeGenerated, authenticationContext_externalSessionId_s, actor_alternateId_s,actor_displayName_s, outcome_result_s
Stage 4: summarize
| summarize lasttime = max(TimeGenerated), firsttime = min(TimeGenerated),
successes = sum(successes), failures = sum(denies), pushes = sum(pushes) by authenticationContext_externalSessionId_s, actor_alternateId_s,actor_displayName_s, outcome_result_s
Stage 5: extend
| extend seconds = lasttime - firsttime
Stage 6: where
| where pushes > (PushThreshold)
Stage 7: extend
| extend totalattempts = successes + failures
Stage 8: extend
| extend finding = case(
failures == pushes and pushes > 1, "Authentication attempts not successful because multiple pushes denied",
totalattempts == 0, "Multiple pushes sent and ignored",
successes > 0 and pushes > 3, "Multiple pushes sent, eventual successful authentication!",
"Normal authentication pattern")
finding =failures == "pushes" and pushes > 1"Authentication attempts not successful because multiple pushes denied"totalattempts == 0"Multiple pushes sent and ignored"successes > 0 and pushes > 3"Multiple pushes sent, eventual successful authentication!""Normal authentication pattern"Indicators
Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.
| Field | Kind | Values |
|---|---|---|
debugContext_debugData_factor_s | eq |
|
eventType_s | eq |
|
pushes | gt |
|
Output fields
Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.
| Field | Source |
|---|---|
actor_alternateId_s | summarize |
actor_displayName_s | summarize |
authenticationContext_externalSessionId_s | summarize |
failures | summarize |
firsttime | summarize |
lasttime | summarize |
outcome_result_s | summarize |
pushes | summarize |
successes | summarize |
seconds | extend |
totalattempts | extend |
finding | extend |