Everything is Yes: Detecting and Preventing MFA Fatigue Attacks
UPDATED 22-04-12: We have added a Splunk query in the “How would we detect these attacks” section that is optimized for Okta Classic
I’m the proud parent of 13-year-old fraternal twins. Most of the time they’re wonderful smaller humans, but sometimes they drive me bonkers with endless streams of rapid-fire, closed-ended questions.
Here’s an example of a recent, pre-dinnertime question barrage:
“Are you making pizza for dinner with your special dough? Did you get the shredded cheese or did you shred it yourself? Is it gross fake cheese? Will we have salad with the pizza? Do I have to eat the salad?”
And on, and on, and on, until the kids have converted me into a bitter, broken shell of a man. Eventually, I fatigue, and in my final throes of exasperation exclaim: “yes, yes, yes! Everything is yes. EVERYTHING. IS. YES. YES.”
This mollifies them, and the barrage of questions stops. Until breakfast.
What does this have to do with MFA?
Feast your eyes on this recent Mandiant writeup about the ongoing activity of UNC2452 (a Russian-state-sponsored APT group that Microsoft calls Nobelium and Crowdstrike attributes to a campaign they call StellarParticle.) The report discusses a straightforward method that the adversary has used to successfully bypass Multi Factor Authentication (MFA) to compromise victims and gain access to accounts. Some researchers have dubbed this technique an “MFA fatigue attack.” The attack isn’t especially novel, but when some of the most infamous hackers from the Russian intelligence services get caught using it, people start paying attention!
The gist of this attack is:
- Adversary has already stolen primary username/password credentials by some other means;
- Adversary uses them to log into an account protected by push MFA and does this multiple times in succession;
- Victim gets valid push notifications (normally to a mobile app of some sort) over and over;
- Eventually, the victim tires of this flood of MFA notifications and taps “yes, it’s me” instead of “no, it’s not me.”
In other words, the victim relents to this MFA “spam”, and out of exasperation just taps “yup.“ Simple!
Just like me saying “EVERYTHING IS YES” to my twins.
And all it takes is one absent-minded click on “yes, it’s me” and the attacker has their foothold. Recorded Future, Malwarebytes, and GoSecure have all researched this attack in recent weeks. At the time of this writing, this technique could be technically be used against many different kinds of applications and services.
So let's talk about Okta features that detect this attack, in the first instance, and prevent it in the longer term.
How would we detect these attacks?
First of all, don’t panic. If you’re protecting accounts using Multi-Factor Authentication, those accounts are (on average) targeted and compromised less often given the additional work you've created for an attacker. Given that only 22% of Microsoft's cloud customers are using MFA at all, you're a step ahead already. Further, these "MFA Fatigue" attacks tend by their very nature to be noisy events, creating ripe opportunities for detection.
We've started to see other organizations explore how to find evidence of this attack in (non-Okta) authentication logs. So how would you go about finding them using Okta? One way is to identify attacks “at a glance” would be using the native System Log function in your Okta Admin console. Run a query like:
outcome.result eq "FAILURE" and eventType eq "user.authentication.auth_via_mfa"
Here, you can see that one of my users is experiencing quite a lot of MFA failures compared to other users. You can click on that user and see exactly what’s going on. However, that’s a bit manual, and it doesn’t fire off any alerts.
To get more advanced analysis and detection, you can also send Okta logs to the security analytics or log management destination of your choice, over REST API or via Event Hooks. In the next example, we’ll leverage the Okta Identity Cloud Add-On for Splunk. Assuming you've ingested Okta logs, consider something like the following Splunk query:
index=main source="Okta:im2" eventType=system.push.send_factor_verify_push OR ((legacyEventType=core.user.factor.attempt_success) AND (debugContext.debugData.factor=OKTA_VERIFY_PUSH)) OR ((legacyEventType=core.user.factor.attempt_fail) AND (debugContext.debugData.factor=OKTA_VERIFY_PUSH)) | stats count(eval(legacyEventType="core.user.factor.attempt_success")) as successes count(eval(legacyEventType="core.user.factor.attempt_fail")) as failures count(eval(eventType="system.push.send_factor_verify_push")) as pushes by authenticationContext.externalSessionId,user,_time | stats latest(_time) as lasttime earliest(_time) as firsttime sum(successes) as successes sum(failures) as failures sum(pushes) as pushes by authenticationContext.externalSessionId,user | eval seconds=lasttime-firsttime | eval lasttime=strftime(lasttime, "%c") | search (pushes>1) | eval totalattempts=successes+failures | eval finding="Normal authentication pattern" | eval finding=if(failures==pushes AND pushes>1,"Authentication attempts not successful because multiple pushes denied",finding) | eval finding=if(totalattempts==0,"Multiple pushes sent and ignored",finding) | eval finding=if(successes>0 AND pushes>3,"Multiple pushes sent, eventual successful authentication!",finding) | where seconds<600
An important caveat: The query above relies on data being ingested from a standard Okta Identity Engine (OIE) organization, and being ingested in Splunk via the Okta Identity Cloud Add On for Splunk, with no modifications. If you are running Okta Classic, scroll further for an alternative query. As always, you’ll have to modify either of these queries for your organization to eliminate false positives. But they should be a good starting point!
Also, while this isn’t the time or place for a crash course in Splunk query language, here is what the above query looks for: First, it pulls in MFA push notification events and their matching success or failure events, per unique session ID and user. It then calculates the time elapsed during the login period (limited to 10 minutes), and calculates the number of push notifications sent, along with the number of push notifications responded to affirmatively and negatively. Then it makes simple decisions based on the combinations of results returned. If more than three pushes are seen, and a single successful notification is seen, then this could be something worth more investigation.
Here’s a similar query that will work in an Okta Classic environment. It uses slightly different search logic and techniques:
index=main ((eventType="user.authentication.auth_via_mfa" AND debugContext.debugData.factor="OKTA_VERIFY_PUSH") OR eventType="system.push.send_factor_verify_push" OR eventType="user.mfa.okta_verify.deny_push") | eval user=coalesce('actor.alternateId',user), actual_time = _time | bin _time span=5m | stats count(eval(eventType="user.authentication.auth_via_mfa")) as successes count(eval(eventType="user.mfa.okta_verify.deny_push")) as denies count(eval(eventType="system.push.send_factor_verify_push")) as pushes min(actual_time) as earliest max(actual_time) as latest by user,_time | transaction user maxspan=10m | streamstats window=1 sum(denies) as denies sum(pushes) as pushes sum(successes) as successes min(earliest) as earliest max(latest) as latest by user | eval duration = latest - earliest | search (pushes>=1) | eval firsttime=strftime(earliest, "%c"),lasttime=strftime(latest, "%c") | eval totalattempts=successes+denies | eval suspicious_ratio=pushes/successes | eval finding="Probable normal authentication pattern" | eval finding=if(denies==pushes AND pushes>1,"Authentication attempts not successful because multiple pushes denied",finding) | eval finding=if(totalattempts==0 AND pushes>1,"Multiple pushes sent and never responded to",finding) | eval finding=if(successes>0 AND suspicious_ratio>2,"INVESTIGATE: Multiple pushes sent, eventual successful authentication!",finding) | table user,lasttime,firsttime,suspicious_ratio,successes,denies,pushes,finding,duration,totalattempts
Again, no Splunk certification will be earned from reading this blog, but this one pulls in similar MFA push notification events and success or failure events. It uses the clever transaction and streamstats commands to catch events that occur within a 10 minute rolling window, so we don’t get tripped up by hard 10-minute event boundaries. Then it looks for similar outcomes to the above OIE search, but also calculates the ratio of pushes sent to successful MFA responses, and suggests investigation if the number of pushes sent is more than double the amount of accepts. By the way - either of these queries could be further refined with the inclusion of other MFA metadata captured by Okta, for example the presence of a new IP address, or a new device running Okta Verify.
With Splunk or other security analytics platforms, you can write advanced queries to compare MFA failures against a baseline-per-user, and of course you can format the results of a query like the one above to alert your SOC proactively that your users may be under an MFA Fatigue attack. Or, we could even kick off an Okta Workflow from the results of analysis, and take some form of corrective action, such as automatically stepping-up the users MFA (to require another authenticator type) or alerting a security admin!
I’m not sure there’s a clean mapping between this kind of adversary behavior and MITRE ATT&CK techniques, if that’s your jam, but I’d call this a variety of Brute Force (T1110) and it certainly maps to Valid Accounts (T1078).
How can we help prevent these attacks with Okta?
Okta provides a few different methods with which to mitigate this type of attack.
The standard Push Notification within Okta Verify is geared toward minimal user friction, offering the possibility for a user to quickly click “yes, it’s me” absent-mindedly. Ideally, access to sensitive apps and data should be protected by higher assurance authenticators, such as those that require a biometric factor via WebAuthn (Face ID, Touch ID, fingerprint, Yubikey, etc.) Okta supports and encourages the use of these technologies.
A less disruptive course of action is to use the Push Notification with Number Challenge option within Okta Verify. When you configure this in your Okta organization, Okta presents a screen similar to the below when the adversary attempts to log in with valid credentials:
In order for the adversary to access the organization, the victim isn’t presented with a simple yes or no - they’d have to also click the correct, corresponding number for the login attempt:
Another very effective way of mitigating against MFA Fatigue attacks is Okta’s Risk-Based Authentication (part of Adaptive MFA), which can be configured by admins via the Behavior Detection dialogs.
When these detections are active, admins can write sign-in policies that “step-up” the required authentication factor for any given login attempt.
So for example, if the adversary is trying to authenticate using known-good credentials from a device never before seen, a new geographic location, or a new IP address,they will need to meet an additional MFA challenge to gain access. Since this additional MFA will use methods outside of the normal “MFA Spam” that the adversary will be creating, it is possible that the victim will identify that they are under attack, and it certainly adds additional detection opportunities.
With AMFA enabled, System Log entries will include the details of these behavior categories when a push notification is sent, so admins can better analyze patterns of MFA behavior across their organization, as you can see below in this screenshot from Splunk:
Finally, if you’re running Okta Identity Engine, you also have the option of passwordless authentication using Okta FastPass. This enables admins to set standard push notifications as an optional authenticator method. Okta FastPass, delivered via Okta Verify on Windows, MacOS, iOS, and Android, can also leverage the native biometric authenticators built-in to your device. All devices running Okta Verify can default to passwordless, trusted authentication with optional biometric support - with no push verification required, as long as the device is registered with your Okta organization.
Thanks for making it this far! Hopefully you aren’t too…fatigued from reading my first Okta blog and now have a new appreciation for how to detect and prevent MFA Fatigue attacks using Okta. Ultimately - the best approach to solving this issue is a mix of prevention (configuring strong authenticators), detection (alerting and responding to anomalous push challenges) and user awareness (teaching users to identify suspicious push requests).
We’ll be providing some followup material on some of the topics above in an upcoming blog, so watch this space!
Now if you’ll excuse me, I have to go make some pizza for my kids…