The Weak Link in Two-Factor Authentication: Exploiting Reusable OTPs

2 months ago 31
BOOK THIS SPACE FOR AD
ARTICLE AD

Tusharpuri

During a recent penetration test, I was tasked with evaluating the authentication security of an application. These tests are always exciting because, in my experience, authentication mechanisms often hide subtle yet severe vulnerabilities. However, it’s usually a process of trial and error, where persistence uncovers the flaws. In this case, I wasn’t expecting anything out of the ordinary — until I stumbled upon a reusable OTP flaw that opened the door to full account takeovers.

I began with the standard steps. First, I logged into the application using a test user account. The process was typical: enter your email and password, receive a One-Time Password (OTP) via email, and use that OTP to finalize the login. However, something caught my attention. The server’s response to the OTP verification included a JWT token and, surprisingly, a lot of sensitive user data — full name, email, phone number.

JWT shown here is truncated for confidentiality.

This immediately set off alarm bells. Why would a server expose so much personal information just during authentication? Although it’s not uncommon to see this in test environments, it should never make it to production. Something about this didn’t feel right. So, I decided to dig deeper.

First, I tried the obvious brute-force attack. I tested several common passwords and even tried a variety of email and password combinations, hoping to hit a weak point. But the application was resilient. Its password policy was solid, and I quickly ran into account lockouts after multiple failed attempts.

Then, I turned my attention to the OTP itself. I thought, “What if I could bypass the password and reuse the OTP directly?” The problem was that the GUI wouldn’t allow me to proceed to the OTP stage unless I entered the correct password first. Even if I tried, entering the password would generate a new OTP, making the previous one invalid.

Frustrated, I decided to abandon the GUI altogether and focus on the network traffic.

I opened up Caido (a web proxy tool) and intercepted the requests. The first request contained the usual — email and password. After verifying the password, the server generated an OTP. The second request, which confirmed the OTP, contained the email and OTP, and the server responded with the JWT token and all the user details.

But could this OTP be reused? Could I trick the server into thinking I was still in the middle of the login process even after the OTP had already been used?

I decided to replay the request using Caido, but this time, instead of generating a new OTP, I replayed the same one. To my surprise, the server responded with the same JWT token and user data again! The request had gone through without generating a new OTP or prompting an error.

At this point, I wondered if the server was verifying based on the same machine’s IP address or User-Agent. To confirm, I switched to a different machine and sent the same replayed request. The result? The server still accepted the OTP and responded with all the account details. The OTP was reusable.

Now that I had a reusable OTP, I wondered what else I could do. The next logical step was to bypass the dashboard authentication. After successfully verifying the OTP, users are redirected to their dashboard, where the JWT token is used to display account information.

I intercepted the request to the dashboard, added the JWT I had obtained from the reused OTP, and forwarded the request. Suddenly, the entire victim’s dashboard appeared on my screen — without me ever re-entering the password. I had bypassed the entire authentication process simply by replaying the same OTP request.

At this point, the potential of this vulnerability was clear. I could repeatedly replay OTPs to access different accounts. But how far could I push this? I needed to automate the process to show just how dangerous this flaw was.

So, I built a simple script to automate the attack:

Input any email address registered in the application. (how I found a list of valid email addresses will be part of an upcoming blog on account enumeration. For now, let’s just say I had a list of targets).Brute-force the OTP (a six-digit code with 1,000,000 possibilities).Once the correct OTP is found, use it to replay the request and extract all account details, including the JWT token.Login without the password and hijack the account.

The fact that the OTP remained valid until the next one was generated meant I had a window of opportunity to compromise any account.

Within minutes, I could target any user, brute-force the OTP, and access their account without needing their password. By reusing the OTP, I could take over the account, view the user’s personal information, and even maintain ongoing access through the JWT token.

This was more than a minor security issue — it was a critical vulnerability that exposed all user accounts on the platform. An attacker with minimal resources could wreak havoc by brute-forcing OTPs, retrieving sensitive data, and even locking legitimate users out of their own accounts.

This experience highlights the severe risks associated with improperly managed OTP mechanisms.

Some key takeaways from this vulnerability:

OTP Expiration: OTPs should expire immediately after use, preventing any possibility of reuse.Rate Limiting: Enforce strict rate limits on OTP entry to block brute-force attempts.Data Exposure: Personal information should never be sent in authentication responses unless absolutely necessary.Combining OTP and Password for Authentication: Another crucial improvement is ensuring that the OTP is always validated in combination with the user’s password during the final request. By sending both the OTP and password together, the application ensures that even if the OTP is compromised, it cannot be used independently.

In this case, what seemed like a minor flaw in OTP handling turned into a full-blown account takeover attack. Proper security measures could have prevented this, emphasizing the importance of robust authentication mechanisms in any web application.

Read Entire Article