Before I began to comprehend the complex science of information security, it seemed to me that Two-Factor Authentication is a guaranteed way to protect your account and no “these your hackers” can let’s say, withdraw my internal currency to buy clothes for characters in the game account. But over time, it has been proved empirically, — two-factor authentication system may have a large number of vulnerabilities.
In simple words, Two-Factor Authentication is a confirmation of the action by entering the generated code to increase security and throw sticks into the wheels of conditional hackers during the movement or before it starts.
In a good way, 2fa should be presented on cryptocurrency exchangers, banking systems, and absolutely on all sites whose users accounts are of a certain value to make it difficult to hack an account for gaining benefits/information.
The code verification system is quite widespread, it is used everywhere on various sites and can be connected for both primary and secondary logins. But the use-case is not limited to this — the developers attach a code confirmation to the functionality of password recovery, confirmation of registration/subscription, additional confirmation of financial transactions, password change, change of personal data. Also, sometimes, 2FA can be used as a wall after “timing” logout, and not a password or other way of confirmation.
In this article, I have collected ways to test 2FA for vulnerabilities, their exploitation, as well as possible options for circumventing existing protection against certain types of attacks. Let’s look at the list of checks for vulnerabilities that apply to 2FA:
1. Lack of rate limit.
The rate Limit algorithm is used to test whether a user session (or IP address) can be limited in attempts or speed, and under what circumstances this happens. If the user has performed too many requests within a certain period of time, the web application can respond with a 429 code (many requests) or apply a rate limit without showing errors. The absence of a rate limit implies that during normal enumeration there are no restrictions on the number of attempts and/or speed — it is allowed to iterate over codes any amount of times (at any speed) within the session / token validity period.
Quite often, I have to deal with a “silent” rate-limit, — if you saw that there are no errors and the HTTP body/code doesn’t change in subsequent requests, it’s early to rejoice, and firstly, you need to check the final result of the attack using valid code.
2. Rate limit exists, but it can be bypassed.
Cases that I had met before:
1) Limiting the flow rate with the absence of blocking after reaching a certain flow rate.
Often, security analysts try to pick up code using 2 or more threads to make an attack faster (in Burp Intruder, the default number of threads is 2 without delay). But sometimes a security system from brute-force or a regular Load Balancer can only respond to this single factor. If you are trying to brute-force with 2 threads, it is worth reducing the number to 1, and then to 1 with a delay of one second. Earlier, I was lucky to observe such behavior and precisely with the help of such manipulations the successful selection of code occurred, which led to Account Takeover. If the 2FA code doesn’t have a specific expiration date, then we have a lot of time to sort through. But if the validity period is present, then the success of the attack is reduced, but the potential danger of vulnerability is still presented since there is still a chance of getting into the right code.
2) The generated OTP code doesn’t change.
This doesn’t apply to dynamically change codes like in Google Authenticator, but only static ones that come by SMS, email or as a message in the messenger.
The essence of this bypass is that constantly or for some time, for example, 5 minutes, the same OTP code is sent in SMS, which is valid during all this time. It is also worthwhile to ensure that no silent rate-limit occurs.
Report example: https://hackerone.com/reports/420163
Let’s suppose the application generates a random code from 001 to 999 and sends it directly to the phone’s messages, within 10 minutes when the “send again” functionality is enabled, we get the same code. But the rate-limit is bound to the request, which limits the number of attempts per request token. We can constantly request a new code, generate a new request token, apply it to a subsequent request (using grep-match in a Burp Suite or using our own script) and perform brute force of the range of numbers from 001 to 999. Thus, constantly using a new request token we will successfully pick up the correct code since it doesn’t change and is static for a certain period of time. The limitations of this attack are a long number or mixing letters with numbers as a confirmation code.
This situation should not be encouraged, you should try to sort through at least part of our list and there is a possibility that the generated code will appear in this part of the list since it is generated randomly. When trying, you need to rely on random, but still, there is a chance of getting into the right combination, which proves a vulnerability that definitely needs to be fixed.
3) Rate-limit resetting when updating the code.
In code verification request, rate-limit is present, but after activating the functionality of code re-sending, it resets and allows you to continue the brute force of the code.
Examples of reports:
https://hackerone.com/reports/149598, — theory;
https://hackerone.com/reports/205000, — a practical exploit based on a previous report.
4) Bypassing the rate limit by changing the IP address.
Many locks are based on the restriction of accepting requests from IP, which has reached the threshold of a certain number of attempts when executing a request. If the IP address is changed, then there is an opportunity to bypass the restriction. In order to check this method, simply change your IP using the Proxy server/VPN and see if the blocking depends on the IP.
Ways to change IP:
— Proxies can be used in an attack using the IP Rotator extension for Burp Suite https://github.com/RhinoSecurityLabs/IPRotate_Burp_Extension. In my opinion, this is the best choice, because it gives us ~ unlimited brute force attempts and IP addresses that allow you to perform a brute-force attack without 42x errors and interruptions.
- A good option might be a python script with a proxy requests module, but first, you need to obtain a large number of valid proxies somewhere.
Since IP rotate tool sends requests using AWS IP addresses, all requests will be blocked if the web application is behind the Cloudflare firewall.
In this case, you need to additionally find the IP of the original web server or find a method that doesn’t concern AWS IP addresses.
5) On the site, support for X-Forwarded-For turned on.
The built-in header X-Forwarded-For can be used to change IP. If the application has built-in processing for this header, simply send X-Forwarded-For: desired_IP to replace the IP to bypass the restriction without the use of additional proxies. Every time a request is sent using X-Forwarded-For, the web server will think that our IP address matches the value transmitted through the header.
3. 2FA bypass by substituting part of the request from the session of another account.
If a parameter with a specific value is sent to verify the code in the request, try sending the value from the request of another account.
For example, when sending an OTP code, the form ID/user ID or cookie is checked, which is associated with sending the code. If we apply the data from the parameters of the account on which you want to bypass code verification (Account 1) to a session of a completely different account (Account 2), receive the code and enter it on the second account, then we can bypass the protection on the first account. After reloading the page, 2FA should disappear.
4. Bypass 2FA using the “memorization” functionality.
Many sites that support 2FA, have a “remember me” functionality. It is useful when the user doesn’t want to enter a 2FA code on subsequent login windows. And it is important to identify the way in which 2FA is “remembered”. This can be a cookie, a value in session/local storage, or simply attaching 2FA to an IP address.
1) If 2FA is attached using a cookie, the cookie value must be unguessable.
That is, if a cookie consists of a set of numbers that increased for each account, then it is quite possible to apply a brute-force attack to cookie value and bypass 2FA. Developers should supply the cookie (along with the key session cookie and CSRF token) with the HttpOnly attribute so that it cannot be stolen using XSS and used to bypass 2FA.
2) If 2FA is attached to an IP address, you can try to replace your IP address.
To identify this method, log in to your account with the 2FA “memorization” function, then switch to another browser or incognito mode of the current browser and try to log in again. If 2FA is not requested at all, then 2FA has been attached to the IP address.
To replace the IP address, you can use the X-Forwarded-For header at the stage of entering the login and password, if the web application supports it.
Using this header, you can also bypass the “IP address white-list” function, if one is present in the account settings. It can be used in conjunction with 2FA as additional account protection or may not even request 2FA if the IP address matches the white-list (with the consent of the user). Thus, even without attaching the 2FA to the IP address via “memorization” functionality, in some cases, 2FA can be bypassed with the help of bypassing the concomitant protection methods.
In general, attaching 2FA to an IP address is not a completely safe way of protection, since if you are present on the same network or connected to the same VPN/Internet service provider with a static IP address, 2FA can be bypassed.
5. Improper access control bug on the 2FA dialog page.
Sometimes a dialog page for entering 2FA is presented as a URL with parameters. Access to such a page with parameters in the URL with cookies that do not match those used to generate the page or without cookies at all is not safe. But if the developers decided to accept the risks, then you need to go through a few important points:
1) Does the link for 2FA dialog page expire;
2) Whether the link is indexed in search engines.
If the link has a long period of existence and/or the search engines contain working links for 2FA input/links can be indexed (there are no rules in robots.txt / meta tags), then there is a possibility of using a 2FA bypass mechanism on the 2FA input page, in which you can completely bypass entering login and password, and gain access to someone else’s account.
6. insufficient censorship of personal data on the 2FA page.
When sending code on a page, censorship is used to protect personal data such as email, phone number, nickname, etc. But this data can be fully disclosed in the API endpoints and other requests for which we have enough rights at the 2FA stage. If initially this data was not known, for example, we entered only a login without knowing the phone number, then this is considered “Information Disclosure” vulnerability. Knowledge of the phone number/email can be used for subsequent phishing/brute force attacks.
An example of exploiting a vulnerability using Credentials Stuffing. Let’s say there is a publicly accessible database with logins and passwords for site A. Attackers can use the data from this database on site B:
— Firstly they check whether the user exists in the database of site B using the “Accounts Enumeration” bug in registration/password recovery functionality. Typically, many sites do not consider this vulnerability and take risks. “Vulnerability” lies in the presence of an error that discloses a fact of user registration on the site. Ideally, a secure message on the password recovery page is as follows:
- After receiving the database of existing users, attackers apply passwords to these accounts.
— If they encounter 2FA, they are at a dead end. But in case of insufficient censorship of user data, they can supplement their database with users’ data if they were not in the original database and credentials for the original website don’t work.
7. Ignoring 2FA under certain circumstances.
When performing some actions that lead to automatic login to your account, 2FA may not be requested.
1) 2FA ignoring when recovering a password.
Many services perform automatic login into your account after completing the password recovery procedure. Since access to your account is provided instantly, when you log in to your account, 2FA can be skipped and completely ignored.
Impact of a similar report on HackerOne I sent recently:
If an attacker gains access to the victim’s email (he can hack the account using phishing, brute-force attacks, credentials stuffing, etc.), he can bypass 2FA, although in this case 2FA should protect the account. At the moment, for 2FA there is a check of the Google Authenticator code or the backup code, but not the code from the email, so this Bypass makes sense.
2) Ignoring 2FA when entering through a social network.
You can attach a social network to your account to log in quickly in the future, and at the same time set up 2FA. When you log in to your account via social networks, 2FA can be ignored. If the victim’s email is hacked, it is possible to recover the password to the social network account, if it allows you to do this and enter the desired application without entering 2FA.
Impact of one of the reports:
1) A bunch with other vulnerabilities, such as the previously sent OAuth misconfiguration #577468, to complete takeover of the account, overcoming 2FA.
2) If an attacker hacked the user’s email, he could try to gain access to the social network account and log into the account without additional verification.
3) If an attacker once hacked into a victim’s account, he could connect the social network to the account and log into the account in the future, completely ignoring 2FA and entering the login/password.
3) Ignoring 2FA in an older version of the application.
Developers often add staging versions of a web application to domains/subdomains to test certain functions. Interestingly, if you log in using your username and password, 2FA may not be requested. Perhaps the developers are using an older version of the application in which there is no protection for 2FA, 2FA itself is disabled or it was intentionally disabled for testing.
You can also check other vulnerabilities at the same time, — registering a new user on a staging server whose email exists in the production version database can give you a user’s personal data from production; the absence of a rate limit in important functionality, for example, password recovery. An example of such a vulnerability is a $ 15,000 facebook bug, which allowed hacking an account via the brute-force password recovery code at beta.facebook.com https://www.freecodecamp.org/news/responsible-disclosure-how-i-could-have- hacked-all-facebook-accounts-f47c0252ae4d/.
4) Ignoring 2FA in case of cross-platforming.
Implementations of 2FA in a mobile or desktop version may differ from the web version of the application. 2FA may be weaker than in the web version or completely absent.
7. When disabling 2FA, the current code or password is not requested.
If, when disabling 2FA, additional confirmation is not requested, such as current code from the google authenticator application, the code from email/phone, then this behavior has certain risks. With a clean request, there is a chance of a CSRF attack. If a bypass vector of CSRF protection is found (if any), then 2FA can be disabled. You can also use the clickjacking vulnerability — after a couple of clicks from an unsuspecting user, 2FA will be disabled. Validation of the previous code will add additional 2FA protection, considering potential CSRF / XSS / Clickjacking attacks, as well as CORS misconfigurations.
I’ll give you an example of the website hackerone.com, — when you turn off 2FA in the form, you need to enter two values at the same time, — the current code from google authenticator app and password. This is the best and recommended protection.
8. Previously created sessions remain valid after activation of 2FA.
When 2FA is enabled, it is desirable that parallel sessions on the same account end and that appearing 2FA dialog is required, the same with a password change. If the account was compromised and the first reaction of the victim will be the turn-on of 2fa, then the attacker’s session will be disabled and the next login and password will require 2FA. All in all, this is the best practice to be followed. I often notice how cryptocurrency exchanges add such protection. An example of the report on HackerOne, — https://hackerone.com/reports/534450.
9. Lack of Rate-limit in the user’s account.
2FA can be added to various functions of the user’s personal account in order to increase security. This can be a change of email address, password, confirmation of a code change for financial transactions, etc. The presence of rate-limit in your account may differ from the presence of rate-limit-a in the 2FA window upon entering your account. I have often come across similar cases when it was possible to freely brute-force 2FA code in my account, while at the entrance a “Strict” rate-limit was configured.
If the developers initially added protection against unauthorized data changes, then this protection must be supported and all possible bypasses should be fixed. If bypass is found, then this is considered as a security feature bypass vulnerability that was implemented by the developers, which is a vulnerability.
10. Manipulation of API’s versions.
If you see something like /v*/ in the application’s request, where * is a number, then it’s likely that you can switch to an older version of the API. In the old version of the API, there may be weak protection or it may be not presented at all. This is a fairly rare occurrence and occurs in the case if developers forgot to remove the old version of the API in the production/staging environment.
For example, /endpoint/api/v4/ login performs a login request, checking the username and password. If 2FA is tied to the account, this request should be followed by /endpoint/api/v4/2fa_check, no other options. If we replace the API version before 2FA, then, in some cases, we can avoid it. /endpoint/api/v3/login can lead to /endpoint/api/v3/login_successful?code=RANDOM, ignoring 2fa_check because in this version of the API, because 2FA was simply not implemented.
Another example, — in the request /endpoint/api/v4/2fa_check there is a rate-limit, while /endpoint/api/v3/2fa_check allows you to brute-force the codes without any restrictions.
11. Improper Access Control in the backup codes request.
Backup codes are being generated immediately after 2FA is enabled and are available on a single request. After each subsequent call to the request, the codes can be regenerated or remain unchanged (static codes).
If there are CORS misconfigurations/XSS vulnerabilities and other bugs that allow you to “pull” backup codes from the response’ request of the backup code endpoint, then the attacker could steal the codes and bypass 2FA if the username and password are known.
In general and whole, 2fa is one of the most reliable ways to protect your account, unless, of course, developers store the current 2FA codes of all users of the web application in the admin panel, — and this has happened in my practice. I would also like to note that developers of some companies create applications for generating their own 2FA codes (examples are Salesforce, Valve) and, due to the lack of security, this emphasis on independence from using other authentication applications gives attackers an additional entry point for 2FA bypassing. The range of application of this protection is quite large and gives those wishing to bypass 2FA protection more opportunities, space for creativity and increases the number of variations of 2FA bypasses.
I hope this information was useful for information security researchers, bug bounty hunters, as well as for developers to minimize the number of vulnerabilities in the service being developed. Stay safe!