More and more organizations are enrolling users in Multi-Factor Authentication (hereafter referred to as MFA), wherein a secondary form of authentication occurs following a user inputting their credentials into a service to ensure a user is who they say they are. It’s an added layer of security and authentication that can help prevent compromise. But this isn’t bulletproof.
Recently a few blog posts and papers have begun to come out detailing a bypass technique known as "MFA bombing", "MFA Fatigue", "Push Notification Spamming", and many other terms describing high-profile threat actors such as LAPSUS$ who have abused the technique to gain access to otherwise protected areas. We at Lares (and other red teams!) have successfully used this method. We know it as Push Fatigue.
What is Push Fatigue?
Simply put, Push Fatigue is when a user is consistently spammed with push notifications from an MFA service requesting confirmation that a login attempt was made with their credentials. The objective of the technique is to annoy or "fatigue" the user into accepting one of the pushes to make the notifications stop. In this stage, an attacker has successfully guessed or otherwise compromised a valid set of credentials for a service and is now attempting to authenticate to the service and continue their attack path.
Wouldn’t this annoy the user?
This was my initial thought. Wouldn’t a user inundated with these messages notice that something is off? Wouldn’t they report this to their IT team so they can investigate the issue?
Unfortunately, it’s not that simple. There are a lot of variables that come into play when this technique is performed, such as:
- The time of day the user is prompted
- The frequency of the prompts
- The type of prompt (Phone call or authenticator push)
- The security awareness training of the user
- The organization policy on reporting incidents
- The controls put in place by the organization
- How do all of these play a part?
Let’s go through each one quickly.
Time of day: An attacker will try and time the attack for when it most inconveniences users. Whether that be dinner time, the weekends at 9 AM, 1 AM on a weekday, or a holiday — the more inconvenient the time, the more likely a user will accept the prompt to make the flood stop.
Frequency of the prompts: One tip Will Bonk gave me (in addition to introducing me to the technique — you the man Will!) was to try and time prompts to occur at a fixed time increment, such as every 5 minutes or every 10 minutes. The idea here is to try and convince a user that an automated program is authenticating on the user’s behalf and is failing, so it keeps retrying! Of course, sending a new push as soon as the first one expires or gets rejected is also valid.
The type of prompt: Phone calls are harder to ignore than push notifications. Spamming phone calls is more likely to elicit a reaction from the user, hoping to answer and follow the phone prompts to accept the notification and make the calls stop. This is especially true since users are typically conditioned with push notifications only to receive them when they log into a service. If a user starts receiving push notifications when they did not log in to anything, it may raise more suspicion.
Security awareness training: Push Fatigue isn’t always talked about in Security Awareness training programs. Users often aren’t even aware that this is something they SHOULD be mindful of or something that can happen to them.
Organization Incident Policy: A user may not always understand that multiple MFA prompts, even when they reject them, indicate that their account credentials have been compromised. In my personal experience performing this attack, even when a user rejected the prompts and/or reported the attempts, the password wasn’t permanently changed. This can sometimes be attributed to an organizations incident reporting policy as to what qualifies as an incident or not as well as actions to take as a response.
Organization Controls: Nowadays, different types of multi-factor authentication controls can be used to detect or even prevent this type of attack. As an example, Microsoft is currently previewing a “number matching” feature with Office 365/Azure MFA that will display a number that the end user has to enter into the prompt. Other third-party authentication solutions will send a four to six-digit PIN code to the user’s mobile device that must be entered. These of course all have their ups and downs, but each one plays a unique role in helping prevent Push Fatigue.
There’s some immediate steps that can be taken to help prevent push fatigue. The first and most immediate? Do not use push-based notifications or calls. Some multiple other options and solutions are available that do not rely on a user tapping "yes" or "no" to allow an authentication attempt through. As previously mentioned, requiring a four-to-six digit PIN code is an option for some MFA solutions, and Microsoft themselves is previewing a number matching feature for Office 365 MFA. For more information, please refer to the following documentation for Microsoft number matching: https://docs.microsoft.com/en-us/azure/active-directory/authentication/how-to-mfa-number-match
Another immediate step that can be taken is in regard to detection. The MITRE ATT&CK mapping and relevant KQL for a possible scenario are below:
The above was generated using the Mitre ATT&CK Navigator.
//Detect when a user denies MFA several times within a single sign in attempt and then completes MFA. //This could be a sign of someone trying to spam your users with MFA prompts until they accept. //Select your threshold of how many times a user denies MFA before accepting let threshold=2; SigninLogs | project TimeGenerated, AuthenticationRequirement, AuthenticationDetails, UserPrincipalName, CorrelationId //Expand and count all the denied challenges and then return CorrelationId's where the MFA denied count is greater or equal to your threshold | where ['Result Types'] has_any ("MFA denied; user declined the authentication","MFA denied; user did not respond to mobile app notification") | summarize ['Denied MFA Count']=count()by ['Result Types'], CorrelationId, UserPrincipalName | where ['Denied MFA Count'] >= threshold
If you’d like to learn more about optimising your mitigating controls, testing them for deficiencies, or simply stress testing them in a real-world environment, please contact us today.