The Link You Can Write Yourself: Deriving a Reset Token From Source

Web & API Security Level 3/4 ~5 min October 9, 2026

The challenge

Brightloom's password reset does everything the checklists ask for. It does not say whether an address is registered, the link expires in thirty minutes, and it goes to the address on file. It also never needs to be delivered to you, because the token it emails is calculated rather than generated. Three source files and a support export from the admin console are here. Work out the token the server would email for the operations account, [email protected], and write it exactly as the server would.

What you'll learn

  • Trace which generator a flow actually calls when several look alike
  • Derive a token value by executing a format string by hand
  • Explain why a reset token must be unpredictable, not merely unique
  • Identify the fields an attacker can already see as a token's entropy source
  • Recognise that confirming a token without a session removes the last barrier

Skills tested

Source code reviewAuthentication analysisDeriving values from code

Prerequisites

  • Reading Python
  • What strftime and the modulo operator do

How it works

A password reset link is a capability. Holding it is the same as proving you control the mailbox it was sent to, which is why the value has to be unpredictable to anybody who did not receive the email. Uniqueness is a database property and has nothing to do with it: sequential integers are unique too.

This generator draws both of its components from fields that are printed in a support console, on invoices and in the admin UI. The signup date and the account number are not secrets and were never meant to be. Feeding them into a format string produces a value that is unique per account and derivable by anyone who has ever seen a customer record, which is a much larger group than the people with access to the mailbox.

What makes it worse is the controls around it. No account enumeration, a thirty minute expiry, delivery to the registered address only: every one of them is correct, and every one of them assumes the token is the hard part. When the token can be computed, they protect the wrong thing, and the confirm endpoint looking a token up with no session attached is what turns the calculation into an account takeover.

Common mistakes

  • Using the id column. The operations row has id 7741 and account_no 40218. The generator reads account_no.
  • Taking the date from the billing row. It shares the 2024-03-11 signup date but has a different account number.
  • Taking the digits from Rosa Castellanos. Her account_no 30218 also ends in 0218, which is why a wrong row still looks plausible.
  • Using invite_token. It is right above the one that matters and uses day-of-year with three digits. The reset flow does not call it.
  • Dropping the leading zero. The format is %04d, so it is 0218 rather than 218.
  • Being reassured by secrets.token_urlsafe. It is in the same file, and it is used for sessions, not for resets.

How to defend against it

Generate the token, do not derive it.

  • Use a cryptographically secure random value of at least 128 bits, for example secrets.token_urlsafe(32), which this file already imports for sessions.
  • Store a hash of the token rather than the token, so a database read does not hand over live reset links.
  • Bind confirm to something more than the token: rate limit it hard, invalidate every other session on success, and email the account when a password changes.
  • Invalidate outstanding tokens when a new one is requested, and keep the expiry short. Both are cheap and both limit the window.
  • Treat BILL-1877 as the finding it is. A reset row that can be consumed by anyone who names it, with no rate limit, is the second half of this bug.

Full solution

Pro and Max members unlock the complete step-by-step walkthrough.

Go Pro

Community stats

146 completions
72% success rate
M2F14M3 First blood

Related Daily Hacks

31,000+ Hackers Real labs Free
Start Hacking Free or solve today's hack, no account needed