- Published on
SameSite Cookies
- Authors
- Name
- Teo Selenius
- Follow @TeoSelenius
In this article you will learn how SameSite cookies work and how they can protect against CSRF, XSS, XS-Leaks, and other vulnerabilities.
What are SameSite cookies?
SameSite is a cookie security attribute introduced in 2016. Its purpose is to prevent cookies from getting included in cross-site requests in order to mitigate different client-side attacks such as CSRF, XS-Leaks and XSS.
What is a cross-site request?
It's a request from another website.
For example, when evil.com loads an image from example.com, it's a cross-site request. It's also a cross-site request when evil.com frames example.com or navigates to it.
So what's a site? To be specific, the site
of an URL is calculated from the host
part by taking the effective TLD (I'll get to the effective thing in a minute) and the part immediately before it. This combination is called eTLD+1
.
So the host
part of "https://www.appsecmonkey.com" is www.appsecmonkey.com.
And the effective TLD is com. And the part immediately before the TLD is appsecmonkey. So the site is appsecmonkey.com.
Using the same formula, the site for http://a.b.c.appsecmonkey.com:8080/ is also appsecmonkey.com
.
Note that unlike cross-origin requests, the scheme
(http) and port
(8080) are not relevant in cross-site-requests.
Finally, I promised to clarify the effective TLD thing. The TLD of https://www.appsecmonkey.co.uk is .uk. But the effective TLD, or eTLD, is .co.uk. The list of these "eTLD suffixes" such as .co.uk is facilitated by the public suffix list.
To summarize, the site is eTLD+1, and any interaction where the source and destination have a different site is called cross-site.
Lax vs. Strict vs. None
SameSite cookies have three modes: Lax
, Strict
and None
.
Generally, Lax
is suitable for all applications, while Strict
tends to be a better fit for security-critical applications. None
is just for opting out.
Lax
SameSite=Lax
will protect the cookie from cross-site interactions in a third-party context. These include:
- evil.com loading a resource (image, style, script, etc.) from example.com.
- evil.com loading example.com in an iframe.
- evil.com submitting a form to example.com.
- evil.com sending a fetch request to example.com.
Lax cookies, however, will be sent when navigating. For example:
- evil.com has a link to example.com (which the user clicks).
- evil.com redirects the user to example.com.
- evil.com calls window.open('example.com').
You can use it like so.
Set-Cookie: foo=bar; SameSite=Lax; ...
Strict
SameSite=Strict
has all the protections of the lax mode, with the addition that it also protects the cookies when navigating.
Browsers include SameSite=Strict
cookies only in first-party context, which is to say when the user types something into the URL bar and presses enter (or uses a bookmark).
While the strict mode is the most secure, it has drawbacks such as breaking links to the application, so it's not always suitable.
Set-Cookie: foo=bar; SameSite=Strict; ...
None
SameSite=None
opts out of the protection when you explicitly want to send the cookie in cross-site interactions. It is necessary because browsers have started to enable SameSite=Lax
as the default (which is awesome).
In order to use SameSite=None
, you are required to specify the Secure
flag as well. The secure flag will prevent the cookie from leaking over an unencrypted connection. I don't fully understand the reasoning behind requiring it, but apparently, it has something to do with pervasive monitoring.
Set-Cookie: foo=bar; SameSite=None; Secure; ...
At any rate, it's always a good practice to use the other cookie security features as well: Secure, HttpOnly and __Host-prefix.
Set-Cookie: __Host-foo=bar; SameSite=Lax; Secure; HttpOnly; Path=/
SameSite vs. CSRF
One of the best features of SameSite cookies is their capability to prevent CSRF (Cross-Site Request Forgery) attacks.
Here is how a CSRF attack might work. Let's pretend that our user logs in to appsecmonkey.com, which sets the user's session cookie like so.
Set-Cookie: SessionID=ABC123; Secure; HttpOnly
And a malicious website, evil.com, hosts the following HTML.
<form method="POST" action="https://www.appsecmonkey.com/user/update-email/">
<input type="hidden" name="new_email" value="attacker@evil.com" />
</form>
<script type="text/javascript">
document.badform.submit()
</script>
When the unwitting user browses to the URL, the following cross-site POST
request to appsecmonkey.com gets sent from the user's browser:
POST /user/update-email/ HTTP/1.1
Host: www.appsecmonkey.com
Cookie: SessionId=ABC123
...
new_email=attacker@evil.com
And the user's email address gets changed to attacker@evil.com
.
However, if appsecmonkey.com sets the cookie as SameSite, and the same attack happens, the resulting POST request will not contain the cookie.
Set-Cookie: SessionID=ABC123; Secure; HttpOnly; SameSite=Lax
POST /user/update-email/ HTTP/1.1
Host: www.appsecmonkey.com
...
new_email=attacker@evil.com
And the user's email remains intact.
Note
CSRF synchronizer tokens are the primary defense against CSRF attacks. SameSite is merely an extra layer of security, hardening that should be used in addition to the actual security control (CSRF tokens) to achieve defense-in-depth.
SameSite vs. HTTP Verb Misuse CSRF
Imagine the same scenario, but this time appsecmonkey has blundered even worse than forgetting CSRF tokens. This time whe web application uses HTTP GET
for changing stuff. Namely, the user's email address.
This means that just getting the target user to browse to the URL www.appsecmonkey.com/user/update-email?new_email=attacker@evil.com
would be enough to change the email.
This time SameSite=Lax
won't help. But if the web application sets the cookie in strict mode, the attack will fail again.
Set-Cookie: SessionID=ABC123; Secure; HttpOnly; SameSite=Strict
SameSite vs. Reflected XSS
XSS (Cross-Site Scripting) vulnerabilities arise when untrusted data gets interpreted as code in a web context. They usually result from:
- Generating HTML unsafely (parameterizing without encoding correctly).
- Allowing users to edit HTML directly (WYSIWYG editors, for example).
- Allowing users to upload HTML/SVG files and serving those back unsafely.
- Using JavaScript unsafely (passing untrusted data into executable functions/properties).
- Using outdated and vulnerable JavaScript frameworks.
SameSite doesn't prevent all XSS attacks. But it can serve as a pretty good extra layer of security against reflected XSS, especially in Strict
mode.
What are reflected XSS attacks?
XSS vulnerabilities are reflected if malicious links or websites can exploit them. For example, look at search.php
here.
echo "<p>Search results for: " . $_GET('search') . "</p>"
The URL parameter search
gets reflected as part of the returned document's HTML structure. An attacker could create a link like so, rendering malicious JavaScript on the page when someone opens the link.
https://www.example.com/?search=
<script>
alert('XSS')
</script>
The returned HTML looks like this.
<p>
Search results for:
<script>
alert('XSS')
</script>
</p>
How are XSS vulnerabilities exploited?
An attacker can exploit XSS vulnerabilities by injecting JavaScript code that performs unwanted actions in the logged-in user's session.
How does SameSite help?
In the above example, SameSite in Lax
mode wouldn't help because it doesn't protect from navigating to links. If the XSS were in a POST request, then it would help.
However, SameSite in Strict
mode would have prevented the attack.
SameSite vs. XS-Leaks
XS-Leaks (or Cross-Site Leaks) are a set of browser side-channel attacks. They enable malicious websites to infer data from the users of other web applications.
For example, browsers make it easy to time cross-domain requests.
var start = performance.now()
fetch('https://example.com', {
mode: 'no-cors',
credentials: 'include',
}).then(() => {
var time = performance.now() - start
console.log('The request took %d ms.', time)
})
The request took 129 ms.
Cross-site timing makes it possible for a malicious website to differentiate between responses. Suppose that there is a search API for patients to find their records. If the patient has diabetes and searches for "diabetes", the server returns data.
GET /api/v1/records/search?query=diabetes
{ 'records': [{ 'id': 1, ... }] }
And if the patient doesn't have diabetes, the API returns an empty JSON.
GET /api/v1/records/search?query=diabetes
{ 'records': [] }
Generally, the former request would take a longer time. An attacker could then create a malicious website that clocks requests to the "diabetes" URL and determines whether or not the user has diabetes.
You can expand the attack and search for a... b... c... d... yes. da.. db... di... yes. This sort of attack is known as XS-search.
How does SameSite help?
In this case, SameSite=Lax
is enough to prevent the attack, because the fetch
requests happen in a third party context. It also helps prevent other xs-leaks such as monitoring error events, frame counting, monitoring navigations and monitoring blur events.
Summary
Using SameSite cookies will significantly improve your application's client-side security, protecting against XSS, CSRF, and XS-Leak attacks.
The strict mode has drawbacks and might not be the best fit for most applications, but luckily the lax mode covers most attacks.
The lax mode is becoming the default as I write, so make sure you are ready for the change.