Access control and authentication
Log in Join

Lesson 6 · Reading · 30 min

Breaking password reset

An account can have a strong password, a rate-limited login, even two-factor, and still fall in seconds if its reset flow is broken. Password recovery is the back door every account has by design, and it is one of the most reliably vulnerable features on the web because it is fiddly to build correctly. This lesson takes it apart.

The reset chain: request, token, link, set, with the weak link at each step

The flow, and where each step breaks

A typical reset has four steps, and each has its own classic bug.

1. Request a reset. You give an email or username and the app sends a link. The bugs here are enumeration (different responses for real vs fake accounts, from the last lesson) and, occasionally, being able to send the link somewhere you control by tampering with the request.

2. A token is emailed. The link contains a secret token. Everything depends on that token being unguessable, single-use, and short-lived. The failures:

  • Predictable tokens. If the token is a small number, a timestamp, a sequential id, or an MD5 of the email, you can generate it yourself without ever seeing the email. Look at a token from your own reset: is it long and random, or does it have structure you can reproduce?
  • Host header poisoning. Many apps build the link from the incoming request's Host header: https://{Host}/reset?token=.... If the server trusts a Host (or X-Forwarded-Host) header you set, you can make the victim's reset email point the token at your domain. When they click, their token lands in your logs. This is why a mature platform builds every email link from a fixed configured URL, never from the request host.
  • Token in the URL, leaking. Tokens in query strings end up in server logs, browser history, and the Referer header sent to third-party scripts on the reset page. A token that leaks to an analytics script is a token an attacker can replay.

3. Open the link. The token should work exactly once and then die. The failures: a token that is reusable (works again after the password is set), one that never expires, or one that is not bound to the account so a token issued for you can reset someone else.

4. Set the new password. The subtle, dangerous one: does completing a reset log the attacker straight in and skip a second factor the account had? A reset is meant to be the first factor, proof you can read the email, not the whole door. If an account has TOTP, a correct flow still demands the code after the emailed link. A flow that logs you in on the link alone lets a stolen link bypass two-factor entirely.

How to test it

Use two accounts you own and walk the flow deliberately:

  • Trigger a reset for your own account and study the token: length, characters, structure, whether two resets produce related tokens. Reproducible structure is the whole game.
  • Send the reset, then try the link twice: does the token still work after use? Wait, then try it, does it ever expire?
  • Intercept the reset request and change the Host (and X-Forwarded-Host) header to a domain you control; check whether the link in the email uses your host. If it does, that is host-header poisoning.
  • If the app supports 2FA, enrol it on your test account, run a reset, and see whether finishing the reset skips the code.
  • Try using account A's token with account B's identity in the final request, is the token bound to the account it was issued for?

The identifier subtlety

On platforms where you can log in by email or username, the reset request often takes either too. That opens a linkage question you should be aware of: the throttling and the responses must not let an attacker confirm that a given handle and a given email belong to the same person. A well-built flow keys its limits on the value as typed and never reveals which address a handle maps to. You will not usually break this, but noticing whether a reset flow leaks the association between a public handle and a private email is exactly the kind of subtle authentication finding that stands out in a report.

The honesty gate

The finding is taking over an account you should not control, and you prove it on your own two accounts: reset account A using a flaw, from account B's position, without A's cooperation. Never run a reset against a real stranger's account "to see if it works", issuing password resets for other people's real accounts is both noisy and harmful. One clean self-owned takeover demonstrates the flaw.

Your lab

QuickCourt reset-code takeover is a realistic reset flow with a flaw in how the reset code is generated or checked. Trigger a reset, study the code, and use the weakness to take over an account you were not given the code for. It is account takeover through recovery, the highest-impact outcome in this whole path, reached without ever knowing the original password.

Where this goes next

You have broken the ways in. The next lesson is about what happens after login: the session, the value that keeps you authenticated, and the ways it can be stolen, fixed in advance, or left alive long after it should have died.

This lesson has 2 labs to try what you just read.