This website uses cookies

To provide the highest level of service we use cookies on this site.
Your continued use of the site means that you agree to their use in accordance with our terms and conditions.

Pentest Chronicles

One-Time-Pwn: Unauthenticated account takeover via One-Time Password

Maksym Hensitskyi

January 16, 2026

Intro Security features are meant to strengthen authentication, but sometimes they do the opposite. Due to a logic flaw, the mechanism intended to enhance security instead functions as a backdoor. As a result, an attacker could compromise any registered account in the application simply by knowing its email address.

The web application implemented several security measures to protect its OTP-based authentication flow. Each OTP was a uniformly random 6‑digit code, and users were limited to three OTP verification attempts per code. After the third incorrect attempt, a new OTP had to be requested. Additionally, new OTP requests were throttled, enforcing a one‑minute cooldown before another code could be issued. In theory, this combination of rate limiting and cooldown logic was meant to prevent brute‑force attacks.

Issue was identified in the application’s OTP-based authentication flow, where weak logic allowed attackers to bypass rate limiting and repeatedly guess OTP codes.

By exploiting the application’s improper handling of the X-Forwarded-For header and its inconsistent rate-limiting logic (requests specified as originating from localhost were not subject to rate limiting.), an attacker was able to launch a high-volume OTP brute-force attack.

The application relied on the Cookie and User-Agent headers to track requests, tying rate-limit decisions to a client-controlled value instead of enforcing logic based on the targeted account.

The web application also enforced IP-based rate limiting, restricting the number of requests allowed from a single source address.

This design normally allows attackers to evade protections by rotating IPs, proxies, or VPNs—but in this case, evasion was even easier: simply removing the Cookie and User-Agent headers and manipulating the X-Forwarded-For header was enough to bypass rate limiting entirely and override source IP address protections.

The following example requests illustrate how the security mechanisms described above were bypassed. The first shows how OTP messages could be triggered without limitation, and the second demonstrates how unrestricted sign‑in attempts were possible.

Step 1 - send OTP: Step 2 - verify OTP: Combined, these weaknesses allowed the attacker to bypass protections and achieve full account takeover, exposing sensitive user data and administrative functionality. VULNERABILITY DISCOVERY Normal authentication workflow The web application is built on React and Next.js. At the time of our security assessment, the 10/10 React2Shell CVE‑2025‑55182 was yet to be discovered :) By design, the web application allows users to request a one-time password (OTP) consisting of a 6-digit numeric code. The operation is performed using the following request: Response from the server: During the normal authentication process, the following request is sent to log in using the code received via email: The application’s response, indicating a successful login, is shown below. The web application then issues a session token to represent the authenticated user’s identity. If the OTP is invalid, the server responds with a 400 Bad Request status and an INVALID_OTP message: After three failed OTP attempts, the server responds with a 403 Forbidden status and a TOO_MANY_ATTEMPTS message: An attack in action - bypassing rate limiting and brute-forcing the OTP code

An attack involves two simulation steps:

1. Constant mail sending - /api/auth/email-otp/send-verification-otp API endpoint.
2. Brute force of OTP access code - /api/auth/sign-in/email-otp API endpoint.

The attack is executed at a ratio of three OTP brute-force attempts per one OTP email request.

Each OTP is a uniformly random 6‑digit code. Under normal conditions, an attacker could legitimately request a new OTP and attempt three guesses per code.

Both actions are performed without effective rate limiting because it was found the web application accepts the X-Forwarded-For header.

By combining the attack cycle with removal of all Cookie headers and other unnecessary headers (such as User-Agent), and by supplying an X-Forwarded-For header with the value set to the application’s hostname or simply "localhost", rate limiting is completely bypassed.

It was also observed that after several minutes of brute force, OTP email requests occasionally returned 500 Internal Server Error responses while others succeeded, suggesting the presence of partial throttling mechanisms or that the application becomes unstable under high request volume. The Cookie and User-Agent headers are removed, and the X-Forwarded-For header is added with the value set to localhost. After a high volume of traffic was sent using a Burp Suite Intruder attack, the OTP-sending component of the application began generating a large number of error messages, although it still continued to send some emails. An attacker could log into the administrator account after 1 hour, 21 minutes, 39.9 seconds, following 266,935 failed OTP login attempts and 120,588 OTP email requests. Although only around 1,500 emails were actually received during the activity, this may indicate that the backend’s OTP logic rotates OTP codes before the user receives the corresponding email.

Successful account takeover: Recommendations and final notes To reduce the risk of similar attacks, it is important to:
- Ensure that OTP generation, rotation, and validation are robust and independent of headers or client-controlled data.
- Apply rate limiting and throttling in a way that reliably enforces per-account protections, rather than relying on cookies or IP addresses that can be spoofed.
- Use high-entropy, unpredictable OTP codes and keep their validity periods short to minimize the window for brute-force attacks.
- Consider additional security layers, such as multi-factor authentication, especially for administrative or high-privilege accounts.

This case highlights how security mechanisms can inadvertently create vulnerabilities when underlying logic is flawed or overly reliant on client-controlled input. OTP-based authentication, while intended to protect users, can be undermined if rate limiting, delivery logic, or code generation is weak. Proper separation between user identity, request source, and rate-limiting logic is critical.

Ultimately, security mechanisms should be carefully designed and tested to avoid unintentionally becoming attack vectors themselves. Even well-intentioned features, if not implemented correctly, can open the door to account takeovers and compromise sensitive data.




Next Pentest Chronicles

When Usernames Become Passwords: A Real-World Case Study of Weak Password Practices

Michał WNękowicz

9 June 2023

In today's world, ensuring the security of our accounts is more crucial than ever. Just as keys protect the doors to our homes, passwords serve as the first line of defense for our data and assets. It's easy to assume that technical individuals, such as developers and IT professionals, always use strong, unique passwords to keep ...

SOCMINT – or rather OSINT of social media

Tomasz Turba

October 15 2022

SOCMINT is the process of gathering and analyzing the information collected from various social networks, channels and communication groups in order to track down an object, gather as much partial data as possible, and potentially to understand its operation. All this in order to analyze the collected information and to achieve that goal by making …

PyScript – or rather Python in your browser + what can be done with it?

michał bentkowski

10 september 2022

PyScript – or rather Python in your browser + what can be done with it? A few days ago, the Anaconda project announced the PyScript framework, which allows Python code to be executed directly in the browser. Additionally, it also covers its integration with HTML and JS code. An execution of the Python code in …

Any questions?

Happy to get a call or email
and help!