Did default SameSite:Lax put the nail in the coffin for CSRF? Mostly, but not always!
Most modern browsers have added the “SameSite: Lax” attribute to session cookies as default when not otherwise set. The driving factor for this change is to have a better default to defend against Cross-Site Request Forgery (CSRF). Did this end CSRF as we know it? Mostly, but there are some interesting gaps where it can still be a problem. This blog post will briefly cover what CSRF is, how the SameSite attribute affects it, and some interesting gaps discovered in the wild.
Cross-Site Request Forgery (CSRF)
Before jumping into the interesting bug discovered in the wild, let’s first establish what CSRF is. OWASP defines CSRF as the following:
Cross-Site Request Forgery (CSRF) is an attack that forces an end user to execute unwanted actions on a web application in which they’re currently authenticated. With a little help of social engineering (such as sending a link via email or chat), an attacker may trick the users of a web application into executing actions of the attacker’s choosing. If the victim is a normal user, a successful CSRF attack can force the user to perform state changing requests like transferring funds, changing their email address, and so forth. If the victim is an administrative account, CSRF can compromise the entire web application.
In less formal terms, CSRF is when an attacker takes advantage of a web browser being helpful. When a normal user browses to a website in which they are already authenticated (e.g., in another tab), the browser says, “Hey, you are already authenticated on this site, let me go ahead and add on those session cookies!”. Where things go wrong is the browser can’t tell the difference between a user themselves browsing to a given website versus an attacker sending them there. The former being a situation where adding cookies is great, that later not so much. An important thing to note is that the attacker, in most cases (improper CORs configurations can change stuff, but that’s a different topic), cannot see the response of where they sent the victim. So they are more or less blind. The impact of CSRF is typically more about performing sensitive actions than stealing sensitive information because of this. Here is a little visual to help:
Preventing CSRF was fairly tedious, and various strategies evolved, as seen in the OWASP Cross-Site Request Forgery Prevention Cheat Sheet. The most common mitigation revolved around the concept of “nonces” (sometimes called a token). See the cheat sheet for a full explanation, but the basic idea behind a “nonce” is to embed a piece of information on pages that you want to be protected against CSRF. This information is random, changes on every request, and is tied to a session. When an action is performed, the application should verify that piece of information is present, valid, and tied to the session in which the action is being performed. As mentioned above, a CSRF attacker cannot see responses and is blind. This prevents CSRF because the attacker has no way to collect this information prior to attempting the exploit. When correctly implemented, a site could protect itself against CSRF this way. But due to its tedious nature, there was a lot of room for mistakes, which created a lot of vulnerabilities. We developed a flow chart to help check all the various cases where an anti-CSRF implementation could go wrong! For several years there was widespread exploitation of CSRF. It enabled attackers to reach across domains and perform authenticated actions on behalf of victims. Because of this, a new and more simple approach was developed to fight CSRF.
The SameSite cookie attribute was first introduced in Google Chrome in version 51 (2016). The SameSite attribute aims to help websites define how browsers should handle their cookies when dealing with cross-site requests. That way, when ambiguous situations arrive, like explored above, a browser can act upon some instructions defined by a website. Something important to note here is that same-site and same-origin are different, as explained in the awesome blog post by jub0bs. I highly recommend reading the full post, but the short hand is same-site is more “loose” than same-origin. For example, “foo.hacker.com” and “bar.hacker.com” are considered same-site, while they are not considered same-origin. All that is required for same-site is for the eTLD+1 (“hacker.com” in the above example) to match. The three following values for the SameSite attribute can be set:
- Lax – Cookies are not sent on normal cross-site subrequests (for example, to load images or frames into a third party site), but are sent when a user is navigating to the origin site (i.e., when following a link).
- Strict – Cookies will only be sent in a first-party context and not be sent with requests initiated by third-party websites.
- None – Cookies will be sent in all contexts, i.e., in responses to both first-party and cross-site requests.
If a site uses the “Lax” or “Strict” values for the SameSite attribute when setting session cookies, they can get great CSRF protection with very little effort! Simply add the attribute to your session cookie and you’re are done. The browser will now only add the cookie to requests that originated on the same site! (there is a small caveat with Lax, but we will get into that later). Take a look at an updated visual:
Compared to adding “nonce” functionality to every page and performing the “nonce” verification properly, this was a huge improvement. But it required sites to opt-in by adding the attribute to their session cookie. The default behavior if the cookie attribute was missing was essentially the “none” option above and offered no CSRF protection. However, this changed with Chrome version 76 (2020):
Treat cookies as SameSite=Lax by default if no SameSite attribute is specified. Developers are still able to opt-in to the status quo of unrestricted use by explicitly asserting SameSite=None.
Now there was a strong default! Sites would have to opt-in to the insecure option. Other modern browsers followed suit. This was a massive change and brought massive improvements to sites everywhere. That’s it! Game over! CSRF is dead! Right?
For the most part, yes. And that’s great news!
But it’s not always the case. I recently found an interesting bug on an assessment that made me reconsider the state of CSRF…
Since the above change rolled out, CSRF was not at the forefront of my mind when performing assessments. However, I came across a bug recently that made me rethink things. While CSRF has dramatically changed, there are some bad situations that can arise still. So it’s important to keep an eye out for them. Without further delay, let’s get into the gaps!
GAP 1 – Pages without authentication, but with other vulnerabilities
As discussed above, CSRF is all about performing sensitive actions on a page using a victim’s authentication, graciously added by the browser. So what about unauthenticated pages? In theory, they could be reached via CSRF (one could debate if this should be called CSRF if there is no authentication, but it’s the most appropriate term). With “SameSite: Lax”, the browser won’t add session cookies, but because it’s an unauthenticated page, an attacker doesn’t need cookies to perform the cross-site request. But what’s the impact? If the page doesn’t require authentication, that likely means it is a benign page that can’t perform a sensitive action. And if it is a sensitive action, that’s more of an authorization issue than it is CSRF. Here’s the twist. What if the unauthenticated page has reflected XSS? This is what I found on a recent assessment. An attacker can use CSRF to send a victim to an unauthenticated page where the attacker XSS can now be executed against them. This doesn’t seem to have much impact for unauthenticated users, but what if they are authenticated? The page I found on my assessment was reachable by both unauthenticated and authenticated users. I set up a proof of concept. When an authenticated user is hit with CSRF, they are sent to the page with XSS via a request with no cookies due to “SameSite: Lax”. While the request to the page is unauthenticated, any additional requests made with the XSS using XHR or fetch are now running in the same site, and thus the browser will add session cookies! Bingo! We now can use CSRF chained with XSS to perform authenticated sensitive actions on behalf of a victim. Just like the good ole days! Here is an updated visual:
Interestingly, neither SameSite “Strict” or “Lax” cookie attributes would have stopped this because they revolve around authenticated sessions, which are not required on the page with XSS. Traditional anti-CSRF mechanisms could have stopped it, assuming they were operating with some sort of session management for guests (unauthenticated users). The extra bonus is that attackers can read responses, since the XSS is running in the same site. So not only could this be used to perform sensitive actions, but to also request and exfiltrate sensitive information. Because same-site is more “loose” than same-origin, as described above, the unauthenticated page that is vulnerable to XSS doesn’t necessary have to be in the same-origin. In fact, any subdomain would work!
Shorthand of the scenario where the gap can arise: XSS on a page that doesn’t require authentication, but is still reachable by authenticated users.
GAP 2 – Sensitive actions using GET
The second gap has to do with the caveat around the SameSite Lax. Let’s look at its definition again.
- Lax – Cookies are not sent on normal cross-site subrequests (for example to load images or frames into a third party site), but are sent when a user is navigating to the origin site (i.e., when following a link).
The interesting bit is the “but are sent when a user is navigating to the origin site (i.e., when following a link)” part. What does this mean? I set up a little proof of concept with SameSite Lax to better understand it. What I found is that cross-site GET requests from a different site will have session cookies sent along, while POST requests will not. So what they mean by “a user navigating” is that a user is sending a GET request. This means that if a site has an authenticated sensitive action that is performed via a GET request, it is CSRF-able! (again, this is debatable if it should be called CSRF, but it seems the most appropriate term). Here is an updated visual:
Short hand of the scenario where the gap can arise: authenticated sensitive actions via GET requests.
In summary, time marches on and vulnerabilities come and go. But sometimes it’s good to revisit things from the past, because you never know what gaps you might find! While SameSite cookie attributes stop a lot of CSRF, there are still a few gaps. I hope you found the post informative and happy hacking!