Password reset accepts a previously used token
Critical- Component
- Account recovery endpoint
- Prerequisite
- One expired reset link. No session, no credentials.
- Impact
- Anyone able to read one historic reset email can take that account indefinitely.
Reproduction
POST /account/reset HTTP/1.1
Content-Type: application/json
{"token":"<token from a reset email
sent six weeks ago>",
"password":"<new password>"}
HTTP/1.1 200 OK
{"status":"password_updated"}Change that closes it
- if (token.isValid())
+ if (token.isValid() && !token.consumedAt)Mark the token consumed inside the same transaction that writes the new password, so two concurrent requests cannot both succeed.
