| Field | Detail |
|---|---|
| Platform | PortSwigger Web Security Academy |
| Type | Authentication — Password Reset Broken Logic |
| Difficulty | Apprentice |
| Objective | Reset Carlos's password, then log in and access his "My account" page |
| Note | Own credentials wiener:peter, victim username carlos |
Password Reset Broken Logic¶
The lab starts with a "Forgot password" link on the login page. Clicking it redirects to /forgot-password, which prompts for a username or email.
Submitting wiener returned "Please check your email for a reset link." The email delivered a link with a token:
web-security-academy.net/forgot-password?temp-forgot-password-token=dvo8c3biirideeutzmutaf5xk85s256j
A quick sanity check: tampering with the token value directly returned "invalid token," so the token itself is validated server-side.
The interesting part was intercepting the actual POST /forgot-password request after submitting the new password form:
POST /forgot-password?temp-forgot-password-token=17yj7clt07cf9q4ai5azf42v7di8qslh HTTP/2
Host: 0afd001e03bc2136860f7f0800db00bf.web-security-academy.net
temp-forgot-password-token=17yj7clt07cf9q4ai5azf42v7di8qslh&username=wiener&new-password-1=teto&new-password-2=teto
The username parameter was being sent as a separate field in the request body alongside the token — not derived server-side from the token itself. The token proved "this is a valid, in-progress reset flow," but never got bound to a specific user. That meant whoever controls the request controls which account gets its password changed.
Swapping username=wiener for username=carlos while keeping the valid token intact:
POST /forgot-password?temp-forgot-password-token=17yj7clt07cf9q4ai5azf42v7di8qslh HTTP/2
Host: 0afd001e03bc2136860f7f0800db00bf.web-security-academy.net
temp-forgot-password-token=17yj7clt07cf9q4ai5azf42v7di8qslh&username=carlos&new-password-1=teto&new-password-2=teto
The server returned a 302.
Following the redirect gave a 200 OK on /.
The root issue is that anywhere a token is meant to authorize an action against a specific resource, the resource identifier needs to come from the token itself server-side — never from an attacker-controlled parameter in the same request. Worth noting: the token also didn't appear to get invalidated after first use, which would have been a separate finding if the main attack had failed.
Logging in as carlos:teto confirmed the password change went through.
Lab solved