Test case 07 / 19FAIL

OTP Bruteforce Protection Bypass - Authentication - Password Reset

Lab environment — not a production system

Screenshots and raw traffic are not translated; text within them may be in another language.

crAPI/Evidence/[FAIL] OTP Bruteforce Protection Bypass - Authentication - Password Reset

01 — Notes

Test notes

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).
View source note (English)
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.

POC:
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).

02 — Screenshots

Screenshots (3)

01 / Normal: request a reset OTP

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

02 / Normal: one OTP attempt

Normal flow: one incorrect OTP attempt returns Invalid OTP.

03 / 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.

03 — Requests and responses

HTTP captures

Some credential values are masked in this public HTML view. This does not mean every sensitive value or linked file has been redacted.

CAPTURE 01 / 1Normal: request a reset OTP
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 flow: the forgot-password flow sends a 4-digit OTP to the victim's email.

CAPTURE 02 / 2Normal: one OTP attempt
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 flow: one incorrect OTP attempt returns Invalid OTP.

CAPTURE 03 / 3Impact: forget-password resets the lockout
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 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.