CheatSheetSeries
CheatSheetSeries copied to clipboard
Update: CSRF prevention cheat sheet to offer more detail on SameSite cookie limitations
What is missing or needs to be updated?
With regards to SameSite cookies, the cheat sheet says this:
It is important to note that this attribute should be implemented as an additional layer defense in depth concept. This attribute protects the user through the browsers supporting it, and it contains as well 2 ways to bypass it as mentioned in the following section. This attribute should not replace having a CSRF Token. Instead, it should co-exist with that token in order to protect the user in a more robust way.
The 2 ways to bypass described in the linked document are:
- Attackers can still pop up new windows or trigger top-level navigations in order to create a "same-site" request (as described in section 2.1), which is only a speedbump along the road to exploitation.
- Features like
<link rel='prerender'>
[prerendering] can be exploited to create "same-site" requests without the risk of user detection.
However, other commentary I've found (e.g. this SO post) suggests that those bypass approaches might be dated and/or limited in scope.
How should this be resolved?
It would be nice if the cheat sheet addressed these limitations in more detail. Can SameSite be a first-class mitigation mechanism under certain scenarios (e.g. no domain sharing, no mutating GET requests)? What attacks are still possible?
To my knowledge:
SameSite=Lax
Lax
provides a reasonable balance between security and usability for websites that want to maintain a user's logged-in session after the user arrives from an external link. Lax
will send the cookie in cross-site requests, but only if both of the following conditions are met:
- The request uses a "safe" HTTP method (
GET
,HEAD
,OPTIONS
andTRACE
) (1) - The request resulted from a top-level navigation by the user. You can think of this as equivalent to when the URL shown in the URL bar changes, e.g. a user clicking on a link to go to another site. 1
Sjoerd Langkemper, Web application Hacker at Qbit Cyber Security, created the following table showing which SameSite
values send cookies during a cross-site request
Feature | Code Example | Cookies sent when SameSite value is |
---|---|---|
link (with user action) | <a href="…"> |
None , Lax |
prerender (with user action) | <link rel="prerender" href="…"> |
None , Lax |
form GET (with user action) |
<form method="get" action="…"> |
None , Lax |
form POST (with user action) |
<form method="post" action="…"> |
None |
iframe (initialized with page load) | <iframe src="…"> |
None |
XHR (initialized with page load) | fetch('…') |
None |
image (initialized with page load) | <img src="…"> |
None |
Therefore, SameSite=Lax
by itself provides insufficient security against CSRF attacks.
SameSite=Strict
According to a Google paper by Artur Janc (page 6), using SameSite=Strict
prevents many CSRF attacks, since the cookie (session cookie with credentials) will only be sent on same-site requests [1. This is great as an additional defence mechanism for websites that require high security, such as banks.
However, this approach requires the browser to recognize and correctly implement the attribute (1), which at this day of writing (4/2023) is approximately 95% of all active browsers.
Entirely bypassing SameSite
The SameSite
cookie attribute can create a false sense of security. While it offers some CSRF protection with Lax
and Strict
, the attribute itself can be entirely bypassed(1 | 2) with:
- The website (
a.com
) is vulnerable to XSS or HTML Injection. - A website's sub-/sibling domain is vulnerable to XSS or HTML Injection. For example:
- Subdomain:
a.com
andvulnerable.a.com
- Sibling:
a.a.com
andvulnerable.a.com
- Subdomain:
- A subdomain takeover (SDTO) attack
- A Man-in-the-middle attack, gaining full control of the cookie(known as Session Hijacking.)
Thanks for your detailed answer @advename ! I still have two questions, though:
Therefore, SameSite=Lax by itself provides insufficient security against CSRF attacks.
(1) I'm not following how you arrived at your conclusion. Are you worried about CSRF attacks against GET endpoints? Isn't the correct mitigation for such a vulnerability to change side-effect endpoints to POST/PUT/etc instead of GET?
The website (a.com) is vulnerable to XSS or HTML Injection. A Man-in-the-middle attack, gaining full control of the cookie(known as Session Hijacking.)
(2) My understanding was that if you have these vulnerabilities in your site then an attacker can circumvent any CSRF control, so this consideration should be out of scope. Is that not true?
Regardless, given your answer I think the cheat sheet should be updated to reflect your information. Notably:
- SameSite does not protect you if there are sibling- or sub-domains of your site which you don't control (those other domains create a vulnerability if they themselves are vulnerable or malicious). Because of this, relying on SameSite makes SDTO attacks more effective against your site.
- SameSite=Lax requires only protects "unsafe" HTTP methods. This is all that should be needed, but if your site violates the expectations of the safe methods like GET then you will not be fully protected
Do you agree?
(1) I'm not following how you arrived at your conclusion. Are you worried about CSRF attacks against GET endpoints? Isn't the correct mitigation for such a vulnerability to change side-effect endpoints to POST/PUT/etc instead of GET?
Exactly. On paper, the correct mitigation is to not allow any state-changing actions with GET
requests. However, in reality, some developers still use GET
requests for state changes.
Ask yourself: can you say with 100% confidence that all GET
endpoints in your application have no exceptions? What if a junior developer or even a senior developer, who is unaware, has implemented a GET
request that has some state changes, which went under the radar during a review.
(2) My understanding was that if you have these vulnerabilities in your site then an attacker can circumvent any CSRF control, so this consideration should be out of scope. Is that not true?
Anyone correct me if I'm wrong, but I don't think so. In Session Hijacking, the attacker can force connections to http://www.a.com
instead of https://www.a.com
and set or overwrite any cookies, even with the Secure
cookie attribute in place. Secure
only prevents the attacker from reading the cookie value. Hence, with a session bound & signed CSRF Token, the attacker can't set or overwrite the CSRF cookie with a new token, since it doesn't match with your session.
Do you agree? Something along those lines, yes.
As one of the cheatsheet leads I am following this comversation with great interest and tend to agree with madelson so far.
- Manicode
Ask yourself: can you say with 100% confidence that all GET endpoints in your application have no exceptions? What if a junior developer or even a senior developer, who is unaware, has implemented a GET request that has some state changes, which went under the radar during a review.
This is true, although in my experience implementing a mitigation like the recommended Synchronizer Token Pattern is also generally opt-in (a developer must take some action to add the token to their AJAX request, for example), so unless you take the hardline stance of disallowing any GET request without an antiforgery token it will still be reliant on junior devs knowing that they should do this when it matters.
Furthermore, I'll note that, at least on the platform I'm using, the built-in "validate by default" approach already omits GET requests, so if you're set up this way devs still have to know not to create vulnerable GETs.
But really, this comment touches on my original motivation for looking into these techniques and asking about SameSite limitations. The appeal of the SameSite=Lax + Referer/Origin validation controls is that I can centralize them in one small part of the system and developers don't even have to be aware/opt in. In contrast, the recommended primary mitigations require more setup, overhead, and developer diligence to implement correctly.
Hence, with a session bound & signed CSRF Token, the attacker can't set or overwrite the CSRF cookie with a new token, since it doesn't match with your session.
I think I don't fully understand this scenario. It makes sense that an attacker can't create their own valid CSRF token, but can't they generate a valid one simply by using the session cookie to issue a request that will generate one (e.g. using the mechanism suggested for AJAX antiforgery here)?
Also, does this attack rely on your site accepting HTTP traffic?
This is true, although in my experience implementing a mitigation like the recommended Synchronizer Token Pattern is also generally opt-in (a developer must take some action to add the token to their AJAX request, for example) ...
Not entirely. Client side XMLHttpRequest libraries such as axios allow you to manage these things automatically with interceptors. With interceptors, you can narrow it down to a set of HTTP methods that attach the CSRF token.
I see this in two ways:
- If you build something for the public (open source, API's...) then I'd stick to the consensus to omit the CSRF for safe HTTP methods, just to not confuse anyone.
- If you build something from start-to-end or internally, then I'd think twice and maybe even require the CSRF for safe HTTP methods, then document it and present it during new employee introduction days
The appeal of the SameSite=Lax + Referer/Origin validation controls is that I can centralize them in one small part of the system and developers don't even have to be aware/opt in.
Again, this entirely depends on the CSRF token implementation. Some techniques require you to remember it every time, like with the Synchronizer pattern where you have to include it in a hidden <form>
input field.
Moreover, as nice as your approach sound, I don't think the technology is mature enough for it (yet).
From [OWASP CSRF Origin/Referer]
- Internet Explorer 11 does not add the Origin header on a CORS request across sites of a trusted zone. The Referer header will remain the only indication of the UI origin. See the following references in Stack Overflow here and here.
- In an instance following a 302 redirect cross-origin, Origin is not included in the redirected request because that may be considered sensitive information that should not be sent to the other origin.
- There are some privacy contexts where Origin is set to "null" For example, see the following here.
- Origin header is included for all cross origin requests but for same origin requests, in most browsers it is only included in POST/DELETE/PUT Note: Although it is not ideal, many developers use GET requests to do state changing operations.
- Referer header is no exception. There are multiple use cases where referrer header is omitted as well (1, 2, 3, 4 and 5). Load balancers, proxies and embedded network devices are also well known to strip the referrer header due to privacy reasons in logging them.
I think I don't fully understand this scenario. It makes sense that an attacker can't create their own valid CSRF token, but can't they generate a valid one simply by using the session cookie to issue a request that will generate one (e.g. using the mechanism suggested for AJAX antiforgery here)?
First, the attacker can't simply change the HttpOnly
session cookie with their own session value and thereby generate a new valid CSRF token. The session-bound & signed CSRF token is created when the session is started and not dependent on what is stored in the session cookie.
Now, to your question, as long as the CSRF Token is bound to the session, then no. They might generate a valid CSRF token, but not for the victims' session. I think it's important to highlight that the Double Submit Pattern creates the CSRF Token by hashing the session id (You can read more about it in this issue #1110 that I opened yesterday). The Synchronizer pattern, on the other hand, commonly uses a random value.
I have no experience with ASP.NET, but from your link it seems that Razor renders the CSRF in the response body (in the HTML) as a hidden <form/>
input field, effectively using the Synchronizer Pattern.
I also have to correct something. Gaining access to the session value, e.g. from the session cookie, is called "Session Hijacking". Setting a predefined session value, e.g. setting or overwriting the session cookie, is called "Session Fixation". My bad. Source: https://stackoverflow.com/a/43761092/3673659
Client side XMLHttpRequest libraries such as axios allow you to manage these things automatically
Agreed this seems like the way to go. I still can't force devs to use axios over alternatives like fetch, but if everyone gets onboard with axios it is easier to centralize this sort of behavior.
Again, this entirely depends on the CSRF token implementation. Some techniques require you to remember it every time, like with the Synchronizer pattern where you have to include it in a hidden
What are techniques that don't require remembering it every time? The two mentioned in the cheat sheet (STP and double submit) both seem to require a request parameter which (axios aside) I think you do need to remember.
Referer header is no exception. There are multiple use cases where referrer header is omitted as well
Seems like the only applicable cases for non-GET requests are load balancers / proxies / anti-virus / etc which are intentionally stripping this header. If you were to require either a valid Referer header or a valid Origin header, I suppose you lose out on some percentage of users with these setups. However, I wonder if there is still a security hole in that case?
setting or overwriting the session cookie, is called "Session Fixation"
Ah ok I was imagining a stolen session cookie or XSS attack where you could simply ask the server for a valid token. Session Fixation feels related (equivalent?) to login CSRF, which I agree that a SameSite cookie cannot protect from since you don't have the session cookie yet.
Agreed this seems like the way to go. I still can't force devs to use axios over alternatives like fetch, but if everyone gets onboard with axios it is easier to centralize this sort of behavior.
Axios is far from perfect. But the interceptors allow centralizing a bunch of things.
What are techniques that don't require remembering it every time? The two mentioned in the cheat sheet (STP and double submit) both seem to require a request parameter which (axios aside) I think you do need to remember.
My bad, this was a badly formulated answer. Synchronizer token is stateful, e.g. you store the token server side and send in some request to the client in the body making it vulnerable to BREACH attacks. With stateless patterns, like the Double Submit, you send the token in a cookie, which is part of the HTTP Header, making it not vulnerable to the BREACH attack.
Seems like the only applicable cases for non-GET requests are load balancers / proxies / anti-virus / etc which are intentionally stripping this header. If you were to require either a valid Referer header or a valid Origin header, I suppose you lose out on some percentage of users with these setups. However, I wonder if there is still a security hole in that case?
Load balancers, proxies, WAF,... many web hosting services use these by default, without your knowledge. Moving from one to another may open for a new attack vector.
With stateless patterns, like the Double Submit, you send the token in a cookie, which is part of the HTTP Header, making it not vulnerable to the BREACH attack.
Very interesting; I hadn't heard of this type of attack against a CSRF token.
However, from reading the article and from your comment it seems like the vulnerability is more related to putting the token value in the form vs. a request header, right? My understanding was that both double submit and STP do this, e.g. per the cheat sheet "The site then requires that every transaction request includes this pseudorandom value as a hidden form value (or as a request parameter/header)". What am I missing?
Load balancers, proxies, WAF,... many web hosting services use these by default, without your knowledge. Moving from one to another may open for a new attack vector.
I my site requires either an origin or a referer header and rejects requests missing both, then what is the "new attack vector"? I would think that the downside of this approach is that some users would be unable to access the site because of their configurations, or that my site might reject all users if something in my own environment is doing this. But neither of those would be a vulnerability.
However, from reading the article and from your comment it seems like the vulnerability is more related to putting the token value in the form vs. a request header, right? My understanding was that both double submit and STP do this, e.g. per the cheat sheet "The site then requires that every transaction request includes this pseudorandom value as a hidden form value (or as a request parameter/header)". What am I missing?
The BREACH attack exists on the HTTP body (<form>
AJAX payload,...), not HTTP header (Cookie)
I my site requires either an origin or a referer header and rejects requests missing both
Well, the client (e.g. a web browser) is solely responsible for setting the Origin header. There are cases, where the browser sends null
as a value for privacy-sensitive context, and the Origin header has only a 50% coverage on caniuse in 04/2023. In other words
I have not enough experience with checking Referer/Origin headers. It seems like the OWASP doc sees Referrer/Origin headers check as a mitigation technique, but still lists them under "Defense in depth" (≠ full mitigation), maybe because it's not widely supported yet? (Need help)
Also:
If neither of these headers are present, you can either accept or block the request. We recommend blocking. Alternatively, you might want to log all such instances, monitor their use cases/behavior, and then start blocking requests only after you get enough confidence.
This sounds like bad UX, blocking genuine users because we don't know better? (Need help)
@jmanico or somebody else, can anybody come with some information to those questions marked with (Need help)
?
@madelson, I believe this may also be of interest https://github.com/data-govt-nz/ckanext-security/issues/23#issuecomment-478862937
@madelson, some pages also have an interest in disabling the Referer header completely: https://www.sjoerdlangkemper.nl/2017/06/21/bypass-csrf-check-using-referrer-policy/
@advename @madelson @jmanico - can we use this discussion to improve the cheatsheet? Explain it better in the cheatsheet and give some resources to dive deeper for others?
Works for me! However, I'd suggest that others join the discussion. Currently, it's mostly been ping-ponging between me and @madelson.
Alright - I've been digging now through several Origin/Referer resources + specifications and can confidentially say this:
1.
My previous statement was erroneous:
.... the Origin header has only a 50% coverage on caniuse in 04/2023.
This seems to be incorrect. The 50% coverage exists as there's a lot of unknown data to confidently determine the coverage. However, CORS which is built on the Origin
header has a coverage of ≈98%
2.
Referer
validation against CSRF
Lenient Referer
validation
With and educated guess of 0.5% of all requests not sending the Referer
header, resulting in 5,000 out of 1,000,000 users, some businesses do not want to lose even this small percentage of mostly legitimate traffic.
This issue has therefore been addressed with lenient referer validations for CSRF protection, meaning that the server validates the Referer
header or allows the request to pass if no referer is available. Disqus, a popular commenting service, has used this approach for several years since at least 2012 to avoid losing this minor percentage of traffic.
Strict & Strong Referer
validation (Recommended)
Lenient referer validations expose vulnerabilities to hackers, who can bypass CSRF Referer
validations by setting no-referrer
on their malicious pages.
Referer
checks are only reliable with strict & strong Referer
validation, meaning:
- blocking all requests with missing
Referer
header, with the side effect of losing 0.5% legitimate users - with a strong
Referer
validation method. For example with the JavaScriptURL()
constructor which propperly parses an URL:
const { origin } = new URL(request.headers["Referer"]); // https://a.com.b.com
if (origin !== "https://a.com") {
// CSRF Attack, block
return response.status("403");
}
Django, a Python web framework with built-in CSRF protection, has implemented strict Referer
validation as a supplementary measure to CSRF tokens since its beginning. The OWASP Cheat Sheet Series regards strict Referer
validation as a Defense-in-Depth (DiD) technique against CSRF, but not a complete CSRF mitigation solution due to the side effects.
However, strict Referer
validation can fill the gap in CSRF prevention where a "weak", session-independent CSRF Token implementation might fail (e.g. not using HMAC CSRF Token that are bound to a session). This can occur during a MITM attack or when dealing with vulnerable subdomain/sibling domain attacks. This has always been the CSRF mitigation strategy for Django, as they've never used session-bound CSRF tokens.
In other words, both of the following provide the same level of CSRF prevention:
- Session-bound CSRF Tokens
- Session-independent CSRF Tokens + strict
Referer
validation
3.
Verifying the origin of a request using a combination of strict and strong Origin
and Referer
checks is a widely-used CSRF (Cross-Site Request Forgery) defense in depth (DiD) technique. However, it is not considered a complete mitigation method, like CSRF Tokens because it cannot be guaranteed that both headers will be available during all requests.
The Origin
header is mainly omitted in GET
requests, which ideally should not have any state-changing actions in the first place. On the other hand, the Referer
header can be entirely removed at the client's discretion. As a result, conducting strict and strong checks on both headers may cause some parts of an application to break or leave GET
requests vulnerable to CSRF attacks.
I recently found an additional method to bypass the SameSite
cookie attribute or Referer
validation when a website has an "Open Redirect", sometimes referred to "Unvalidated Redirects and Forwards", vulnerability. CSRF Token would prevent this aswell.
Sources:
- https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html (Explains the issue at hand)
- https://blog.detectify.com/2019/05/16/the-real-impact-of-an-open-redirect/ (Explains the issue at hand + mentions that it bypass
Referer
validation) - https://stackoverflow.com/a/70165503 (Explains that the issue bypasses the
Referer
validation)
I recently found an additional method to bypass the SameSite cookie attribute or Referer validation when a website has an "Open Redirect"
@advename this still relies on sensitive, state-changing GET
requests, right?
can we use this discussion to improve the cheatsheet?
@mackowski I think there's lots to improve/clarify here. As a starting point:
- I think the current content wrt the limitations of
SameSite
should be replaced with some of @advename 's more up-to-date findings and tradeoffs. - It feels like a lot of the edge-cases where simpler solutions break down is attacks against
GET
endpoints. Currently the cheat sheet says not to do this, but it is the last bullet in the list and it isn't clear whether discussions of various methods are assuming you've followed this guidance or not.
this still relies on sensitive, state-changing GET requests, right?
@madelson correct
It feels like a lot of the edge-cases where simpler solutions break down is attacks against GET endpoints. Currently the cheat sheet says not to do this, but it is the last bullet
I agree. It's easy to overlook or ignore the GET
fact. Personally, I've always grasped resources better when there is a "Why?" section.
I.e. "Why should we not perform state changing actions with a GET
request?"
"Because some browser built-in features like SOP & SameSite don't protect you in 95% of CSRF attacks when using GET
" (or something along those lines).
Agree on that we should improve that. But keep in mind that this is cheatsheet and CSRF is already longer that we want. Anyway SameSite needs clarification. I like how you summarised this discussion in the two last comments: https://github.com/OWASP/CheatSheetSeries/issues/1101#issuecomment-1566246581 and https://github.com/OWASP/CheatSheetSeries/issues/1101#issuecomment-1567904987.
@advename you are already doing changes to CSRF cheatsheet do you want to improve this part as well?
But keep in mind that this is cheatsheet and CSRF is already longer that we want. @mackowski
I definitely agree. Personally, you can feel that the CSRF cheat sheet has been "Dumped" with a lot of information and it lacks a more clear, global, structure. CSRF by itself is a big topic, but the current CSRF cheat sheet is overwhelming to read and pick out the information that matters. I definitely could improve this part as well, but as you can see from the comments, there's too much information that needs to be applied.
Excuse me for my French. But maybe it's time to archive the current CSRF cheat sheet and create a new one.
I have been editing the CSRF cheatsheet for years and am fond of it as is. Can I ask if any information it incorrect?
-- Jim Manico @Manicode Calendly.com/manicode Secure Coding Education
Hi @jmanico glad that you asked!
There is no incorrect information. The CSRF Cheat Sheet is a great and extensive resource. But hear me out. Let's look at a common DX:
- A developer wants to research how they can protect their application against the most common vulnerabilities
- Developer Googles. After lots of reading, they frequently land on OWASP CSRF Cheat Sheet
- There's a lot of great information to consume. But wait:
- Tokens?
- Synchronizer what?
- Double Submit the heck?
- Encrypted Cookie qué?
- Referer and Origin? God bless you
- ...
- Tokens?
The Cheat Sheet became the main linked resource regarding CSRF and highlights all the important CSRF cases. However, a novice has a hard time understanding the whys, while, the Cheat Sheet has to stay short because, it is, a Cheat sheet.
"Archiving the current CSRF cheat sheet and create a new one" doesn't seem to be the right choice.
After some thoughts, if the Cheat Sheet website supports this, how about using Collapsible Sections? This way, we can still keep the Cheat Sheet short, while providing direct access to further information and explaining whys? I've used them in a previous company internal coding guidelines and they worked perfectly.
I am totally ok with collapsible sections, major edits and clarifications. I just do not want to start over. Too much work has gone into it so far.
Can you consider a few PR's and edits? I'll respond quickly.
- Jim
I am totally ok with collapsible sections, major edits and clarifications. I just do not want to start over. Too much work has gone into it so far. @jmanico
I agree. It was a late night hasty thought, my bad!
I have little time to spare due to a fulltime job and am already looking into #1143. I might be able to do so at a later time. Meanwhile, can you confirm that collapsibles, with Markdown code, work? I've seen limited support when it comes to Markdown code inside HTML collapsibles.
All good, we want your help!
And here you go, this should help!
https://gist.github.com/pierrejoubert73/902cc94d79424356a8d20be2b382e1ab
@jmanico I linked to the same gist in my previous comment:
After some thoughts, if the Cheat Sheet website supports this, how about using Collapsible Sections?
The question is, does the website support this?