[Exploit the reset](https://portswigger.net/web-security/race-conditions/lab-race-conditions-exploiting-time-sensitive-vulnerabilities) flow so that **wiener** and **carlos** get the **same password-reset token**. Use the token you receive to reset **carlos**. Then log in as **carlos**, open **Admin**, delete **carlos**. --- ## Threat model (mental model in one minute) * Reset link: `/forgot-password` → email with `?username=<user>&token=<digest>`. * Token is a **fixed-length digest** derived from **time-dependent state** (e.g., timestamp) + possibly other static inputs. * Backend is **per-session locked** (e.g., PHP): one request at a time **per session**, but **not across sessions**. * If two **different sessions** trigger reset **at the same instant**, both users are assigned **identical token** (timestamp dominates digest; username likely excluded). * You see the email for **wiener**. You **swap `username=wiener` → `username=carlos`** in the URL and redeem the same token to reset **carlos**. ## Step-by-step ### 1) Establish baseline 1. Log in as `wiener:peter`. 2. Trigger **Forgot password** for `wiener`. 3. Observe email: `.../reset?username=wiener&token=<hex>` 4. Send `POST /forgot-password` to **Repeater**. Send it **several times** → tokens differ each time → digest has changing input (likely time). ### 2) Show per-session lock (requests serialized) 1. Duplicate the `POST /forgot-password` tab. Group the two tabs. 2. **Send in parallel** a few times (same session cookie). 3. Note responses still spaced out; tokens differ. ⇒ The app serializes **per session**. ### 3) Bypass session lock (use a new session) 1. Send `GET /forgot-password` **without** cookies in Repeater. 2. From the response, copy the **new session cookie** (and **CSRF** if present). 3. Replace cookie (+ CSRF) **in one** of your two `POST /forgot-password` tabs. * Result: you now control **two independent sessions**: * **Tab A**: Session A (original), body: `username=wiener`. * **Tab B**: Session B (new), body: `username=wiener`. 4. **Send in parallel** multiple times. Response times now often **match** or are very close. ### 4) Confirm timestamp-based collision 1. Watch the inbox for pairs when the two responses matched in time. 2. You should eventually see **two emails** with **the same `token`** (for `wiener`). * This proves **timestamp** (or equivalent) is the dominant entropy in the digest. ### 5) Execute the takeover 1. In **one** of the two tabs, change body to `username=carlos` (keep the two different sessions): * **Tab A** (Session A): `username=wiener` * **Tab B** (Session B): `username=carlos` 2. **Send in parallel** repeatedly until the pair lands in the same tick (you’ll only see **one** fresh email—inbox for wiener—because you don’t see carlos’ mailbox). * That single email implies **both users got the same token**. 3. Open the latest email you got for **wiener**. Copy the reset URL and **edit the query**: * Change `username=wiener` → `username=carlos` * Keep `token=<unchanged>` 4. Visit the edited URL. You should land on **Reset Password** for **carlos**. 5. Set a new password for **carlos**. 6. Log in as **carlos** → open **Admin** → delete **carlos**. Done. --- ## Repeater specifics (make it bulletproof) ### Request templates (adjust to your lab) **GET (to mint a fresh session & CSRF)** ```http GET /forgot-password HTTP/2 Host: LAB.HOST Accept: text/html ``` *(Response sets `Set-Cookie: session=...` and shows a CSRF token if used.)* **POST (trigger reset)** ```http POST /forgot-password HTTP/2 Host: LAB.HOST Cookie: session=SESSION_VALUE Content-Type: application/x-www-form-urlencoded Accept: */* csrf=CSRF_VALUE&username=wiener ``` * Duplicate to create **two tabs**. Replace cookie/CSRF **in ONE tab** with values from the **cookie-less GET** response so you truly have **two sessions**. * When attacking, switch one `username` to **carlos**. ### Parallel send tips * Use **Send group → Parallel**. * Run **several times**; the match is probabilistic (depends on server tick/clock granularity). * If you see two different emails each time, re-mint the second session cookie (new `GET /forgot-password` without cookies) and try again. --- ## Troubleshooting (fast) | Symptom | Likely cause | Fix | | ---------------------------------------- | -------------------------------------- | -------------------------------------------------------------------------------------- | | Two parallel posts still look serialized | You reused the same session | Ensure **different session cookies** (and CSRF) across the two tabs | | Always two different tokens | Requests not landing in same time tick | Fire parallel repeatedly; reduce local jitter; warm the connection once with a `GET /` | | CSRF errors | Mixed/expired tokens | Refresh both forms; pair **each session’s** CSRF with its cookie | | Edited URL gives “invalid token” | Typo or URL encoding | Copy full token exactly; change only `username=` | | Reset page still shows wiener | You didn’t edit the URL param | Ensure `username=carlos` in the query string |