Cookie's SameSite Attribute vs CSRF — Demystified - CyberSec Nerds

Cookie’s SameSite Attribute vs CSRF — Demystified

Ambient Authority in the browser

Whenever the browser sends off an HTTP request, it includes all the corresponding cookies for the request target. This model for using authority is called ambient because the requestor doesn’t explicitly specify what authority should be used. Instead, the execution environment automatically adds all possibly relevant authority to every request.

After finishing this article, you will understand the nitty-gritty of this feature including the side-effects.

Background

We have a legitimate bank named my-bank with its website at my-bank.com:8000. The login page for the banking service is as shown below.

Suppose alice logs into the bank using her credentials. She will get redirected to her dashboard which shows she has $500 in her account and the bank provides the service to transfer any amount (given that you have sufficient balance) to any person you desire.

Then the server provided alice with a cookie named sessionId for better session management. Lets focus on the cookie attributes.

  • sessionId=Wgg2yTD4ZqAwC11HvlwifQ%3D%3D represents name-value pair as set by the server
  • Domain=my-bank.com instructs the browser to attach this cookie to all subsequent requests sent to its subdomains too (Remember! specifying Domain is less restrictive than omitting it)
  • Path=/ means the cookie is valid all over the site regardless of any path requested
  • Expires attribute defines when should the cookie be removed from the browser
  • HttpOnly if set will prevent cookies to be access by the JavaScript
  • Secure restricts the cookie transfer to happen only over HTTPS
  • SameSite is the major attraction of this blog. We will talk about it the whole time.

Remember the SameSite attribute is set to None in this case.

SameSite: None

Back to my-bank dashboard. Alice just transferred 100$ to bob’s account. Here’s what the POST request to the server looks like.

POST /transfer HTTP/1.1
Host: my-bank.com:8000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 17
Origin: http://my-bank.com:8000
Connection: keep-alive
Referer: http://my-bank.com:8000/
Cookie: sessionId=Wgg2yTD4ZqAwC11HvlwifQ%3D%3D
Upgrade-Insecure-Requests: 1

to=bob&amount=100

Now, enters the attacker. By some means, the malicious attacker was able to send a legitimate-looking link cute-cats.com to send Miss alice . Let’s analyze the HTML of cute-cats.com.

<body>
<h1>Cute Cats</h1>
<img src="cat.png" width="500px">
<iframe src="malicious.html" frameborder="0" style="display: none;">
</body>

So basically the site is using an iframe with visibility:none to embed malicious html code inside it and to show its innocence, it is displaying a picture of a cat on the page. This is the HTML code of malicious.html.

<form method='POST', action='http://my-bank.com:8000/transfer/'>
<input type="text" name="to" value='bob'>
<input type="text" name='amount' value=100>
<input type="submit">
</form>
<script>
document.forms[0].submit();
</script>

What this is basically doing is sending a POST request to http://my-bank.com:8000/transfer/ endpoint with the post data to=bob&amount=100 that will effectively transfer 100$ from alice account to bob’s account.

alice clicks on link forwarded to her. I mean, why not? Who don’t love cats?

As soon as the page loads, the malicious code inside the malicious.html starts to show its colour. This is the POST request that will be sent by this innocent-looking page. Observe the Origin and Referer headers.

POST /transfer/ HTTP/1.1
Host: my-bank.com:8000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 17
Origin: http://cute-cats.com:8000
Connection: keep-alive
Referer: http://cute-cats.com:8000/malicious.html
Cookie: sessionId=Wgg2yTD4ZqAwC11HvlwifQ%3D%3D
Upgrade-Insecure-Requests: 1

to=bob&amount=100

And this is what alice will see. Awww! How innocent!

And this happened behind the scenes, successfully transferring 100$ to bob’s account.

Now i don’t think these cats are really cute anymore, right?

The main reason for this incident is the Ambient Authority mechanism of the browser which inadvertently add the session cookies with all the requests to my-bank.com disregarding where the request actually originates from. Neither had the cookie embedded, nor this attack would be successful. And all this credit goes to SameSite: None attribute .

SameSite: Lax

Cross-site POST request using iframe

This time we have set our SameSite attribute to Lax (other settings are the same). Lets see what it have got for us.

When alice goes to the cute-cats.com page this time, the request sent is different as shown.

POST /transfer/ HTTP/1.1
Host: my-bank.com:8000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 17
Origin: http://cute-cats.com:8000
Connection: keep-alive
Referer: http://cute-cats.com:8000/malicious.html
Upgrade-Insecure-Requests: 1

to=bob&amount=100

See how no cookies were attached to this POST request which means server could not authenticate the user and no balance transfer process was carried out effectively protecting us from the attack.

Cross-site GET request using iframe

Lets modify the cute-cats.com site’s HTML. We changed it to make GET request this time. And the iframe visibility is true i.e, we can see the page that will be embedded.

<body>
<h1>Cute Cats</h1>
<img src="cat.png" width="500px">
<iframe src="malicious.html" frameborder="0" width='100%'>
</body>
<form method='GET', action='http://my-bank.com:8000/'>
<input type="submit">
</form>
<script>
document.forms[0].submit();
</script>

Cookies were not sent with GET request this time too. The request below explains it all.

GET /login HTTP/1.1
Host: my-bank.com:8000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://cute-cats.com:8000/malicious.html
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0

And this is what alice will see. The bank’s login page embedded inside the cute-cats.com page. Weird!! But the real thing to notice here is we have evaded the attack.

Thus we can conclude that cookies are not sent by the browsers if the cross-site request is made by the page’s sub-sources ( anything that has to be fetched after the HTML has been parsed like scripts, stylesheets, images, iframes). But what about forms ?

Cross-site POST request using forms

<body>
<h1>Cute Cats</h1>
<img src="cat.png" width="500px">
<form method='POST', action='http://my-bank.com:8000/transfer/'>
<input type="text" name="to" value='bob'>
<input type="text" name='amount' value=100>
<input type="submit">
</form>
<script>
document.forms[0].submit();
</script></body>

Cookies were not sent in this case also. Eventually, server redirected us to the homepage. Since GET will be used to fetch the homepage, cookies will be attached and we are logged in, effectively saving us from the attack.

POST /transfer HTTP/1.1
Host: my-bank.com:8000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 17
Origin: http://cute-cats.com:8000
Connection: keep-alive
Referer: http://cute-cats.com:8000/
Upgrade-Insecure-Requests: 1

Cross-site GET request using forms

<body>
<h1>Cute Cats</h1>
<img src="cat.png" width="500px">
<form method='GET', action='http://my-bank.com:8000/'>
<input type="submit">
</form>
<script>
document.forms[0].submit();
</script></body>

This is only the condition when cookies are attached with the request in SameSite: Lax scenario. So we will be redirected to my-bank dashboard logged in.

We can conclude that SameSite: Lax prevents sending cookies to any cross-site requests that originate from sub-resources. And it also prevents cookies from being attached on any request which isn’t (supposed to be) read-only such as PUT, POST. That’s why cross-site GET requests will have the cookies attached as it is considered safe method.

SameSite: Strict

Cross-site GET request using forms

GET /? HTTP/1.1
Host: my-bank.com:8000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Referer: http://cute-cats.com:8000/
Upgrade-Insecure-Requests: 1

Cookies were not sent in this case. The server responded with 302 Found and redirected us to the login page. Since the browser won’t send cookies even with GET requests, we are not logged in in this case. The cool part is refreshing the page once again will log you in. Think why.

HTTP/1.1 302 Found 
X-Powered-By: Express 
Location: /login 
Vary: Accept 
Content-Type: text/html; charset=utf-8 
Content-Length: 56 
Date: Tue, 08 Jun 2021 11:20:52 GMT 
Connection: keep-alive 

Cross-site POST request using forms

Its not even allowing cookies with GET requests. So how dare you ask if it will be sent with POST request. Obviously NO.

Actually, to make it clear SameSite: Strict completely blocks sending cookies in the cross-site requests case. Even when clicking a top-level link on a third-party domain to your site, the browser will refuse to send the cookie. 

Is CSRF dead?

In old days, SameSite: None was the default case (allowing third-party requests by default). Instead of leaving the user’s cookies exposed to potential security vulnerabilities , the Chrome 80 update takes the power back and sets all cookies to SameSite=Lax by default. In other words, Chrome has decided to make all cookies limited to first-party context by default, and will require developers to mark a cookie as needing third-party visibility using SameSite=None explicitly.

So is CSRF dead? Or you have any other idea.

Kiran Dawadi

Founder of cybersecnerds.com. Electronics Engineer by profession, Security Engineer by passion. I am a Linux Enthusiast and highly interested in the offensive side of the CyberSec industry. You will find me reading InfoSec blogs most of the time.

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments