| Field | Detail |
|---|---|
| Platform | PortSwigger Web Security Academy |
| Type | Race Conditions |
| Difficulty | Practitioner |
| Objective | Exploit a race condition in the email change endpoint to associate [email protected] with our account, inherit admin privileges, and delete carlos |
Single-Endpoint Race Conditions¶
I logged in as wiener:peter and tested the email change flow:
I duplicated the request 20 times with different addresses (teto1@exploit through teto20@exploit) and sent the group sequentially first to establish a baseline:
Each address received exactly the confirmation email corresponding to its own change request — normal behavior:
Benchmarking sequential vs parallel is how you find single-endpoint race windows. Sequential sends produce correct, correlated results; parallel sends surface anomalies. Sending the same group in parallel:
An email sent to teto16@exploit contained a confirmation link for teto20@. Recipient and confirmed address didn't match. The race condition is inside the email send operation itself — the server writes the email body (including the target email in the confirmation link) and the TO field in two separate steps. Parallel requests interleave these writes: one request's body is paired with another request's recipient. Email-based operations are disproportionately vulnerable to this because emails are often sent in a background thread after the HTTP response, extending the race window beyond the normal request lifecycle.
Weaponizing this desync with two parallel requests:
- Request 1:
[email protected](our inbox — we receive it) - Request 2:
[email protected](the target address we want to claim)
Repeated until the race fired — the server wrote the [email protected] payload into the body but delivered it to our inbox:
To confirm your email change to [email protected], click the link below
The confirmation link remains valid across attempts, so each retry doesn't reset state. Clicking the link:
Account email now shows [email protected]. The pending admin invite activated. Navigating to /admin and deleting carlos:
Lab solved