Missing rate limit on /rest/user/change-password enables credential brute-force and pivot
The password-change endpoint has no rate limiting, allowing unlimited attempts at brute-forcing the current password or hammering the endpoint with stolen tokens.
routes/changePassword.ts is wired up in server.ts:588 as:
app.get('/rest/user/change-password', utils.asyncHandler(changePassword()))
There is no rateLimit(...) middleware applied to this path. The rate-limiting block in server.ts:338-344 only covers /rest/user/reset-password, and the 2FA endpoints get their own limiters (lines 451-465). The handler reads query.current (current password) and query.new (new password) and gates the change on security.hash(currentPassword) !== loggedInUser.data.password:
if (currentPassword && security.hash(currentPassword) !== loggedInUser.data.password) {
res.status(401).send(res.__('Current password is not correct.'))
return
}
With no per-IP / per-token throttle, an attacker who has obtained a valid Bearer token (e.g., via XSS or log leak) can run unlimited current-password guesses against this endpoint — and, more importantly, the endpoint has a known logic flaw where supplying no current parameter skips the check entirely, so an attacker with a token can immediately change the victim's password. Either way, the absence of rate limiting amplifies the blast radius: credential-stuffing, token brute-forcing, and large-scale account takeover are not throttled.
The endpoint matches the high-risk pattern "password change" and the file/import chain contains no rateLimit, throttle, consume, or HOF that adds one.
- Obtain or guess any valid JWT (e.g. via XSS, log leak, or replayed cookie).
- Loop:
for i in $(seq 1 100000); do
curl -s 'https://target/rest/user/change-password?new=newpass&repeat=newpass' \
-H 'Authorization: Bearer=<token>'
done
Nothing throttles the requests. Combined with the missing-current-password bypass, the very first request rewrites the password to newpass; even without that bug, brute-forcing current= is unbounded.
Unlimited password-change attempts against any authenticated session token. Enables credential brute-force, mass account takeover via stolen tokens, and amplification of related token-leakage bugs. Authentication is required (Bearer token) but no rate limit exists.
The handler in changePassword.ts is registered with only utils.asyncHandler — no rateLimit/throttle middleware appears in the file or its imports. The if (currentPassword && security.hash(currentPassword) !== loggedInUser.data.password) short-circuit also confirms the secondary bypass the detector cited. Combined with token-based auth (headers.authorization), an attacker with any valid token can hammer the endpoint without throttling, and scope rules require treating Juice Shop as production. Finding is technically accurate and exploitable.
The /rest/user/change-password route is registered in server.ts:588 with no rateLimit middleware (the limiter block at 338-344 only protects /rest/user/reset-password), and is reachable over HTTP — AV:N, AC:L. The handler requires a Bearer token resolvable via security.authenticatedUsers.get(token), so any authenticated user (or an attacker holding a stolen/leaked token) qualifies — PR:L, UI:N. Because if (currentPassword && security.hash(currentPassword) !== loggedInUser.data.password) only runs when currentPassword is truthy, omitting query.current skips the check entirely and the unthrottled endpoint immediately overwrites user.password, yielding full account takeover of the token's owner (C:H/I:H) and locking the legitimate user out (A:L). Scope stays Unchanged since the impact is confined to the Juice Shop user account authority.
- CWE-307
- OWASP API4:2023 Unrestricted Resource Consumption