Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update: CSRF prevention cheat sheet to offer more detail on SameSite cookie limitations #1101

Open
madelson opened this issue Mar 8, 2023 · 27 comments
Labels
ACK_OBTAINED Issue acknowledged from core team so work can be done to fix it. HELP_WANTED Issue for which help is wanted to do the job. UPDATE_CS Issue about the update/refactoring of a existing cheat sheet.

Comments

@madelson
Copy link

madelson commented Mar 8, 2023

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:

  1. 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.
  1. 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?

@madelson madelson added ACK_WAITING Issue waiting acknowledgement from core team before to start the work to fix it. HELP_WANTED Issue for which help is wanted to do the job. UPDATE_CS Issue about the update/refactoring of a existing cheat sheet. labels Mar 8, 2023
@advename
Copy link
Contributor

advename commented Apr 2, 2023

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 and TRACE) (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 and vulnerable.a.com
    • Sibling: a.a.com and vulnerable.a.com
  • A subdomain takeover (SDTO) attack
  • A Man-in-the-middle attack, gaining full control of the cookie(known as Session Hijacking.)

@madelson
Copy link
Author

madelson commented Apr 2, 2023

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?

@advename
Copy link
Contributor

advename commented Apr 2, 2023

(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.

@jmanico
Copy link
Member

jmanico commented Apr 2, 2023 via email

@madelson
Copy link
Author

madelson commented Apr 3, 2023

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?

@advename
Copy link
Contributor

advename commented Apr 3, 2023

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.

@advename
Copy link
Contributor

advename commented Apr 4, 2023

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

@madelson
Copy link
Author

madelson commented Apr 5, 2023

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

input field.

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.

@advename
Copy link
Contributor

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.

@madelson
Copy link
Author

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.

@advename
Copy link
Contributor

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)?

@advename
Copy link
Contributor

@madelson, I believe this may also be of interest data-govt-nz/ckanext-security#23 (comment)

@advename
Copy link
Contributor

@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/

@mackowski
Copy link
Collaborator

@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?

@advename
Copy link
Contributor

advename commented May 4, 2023

Works for me! However, I'd suggest that others join the discussion. Currently, it's mostly been ping-ponging between me and @madelson.

@advename
Copy link
Contributor

advename commented May 5, 2023

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 JavaScript URL() 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.

@advename
Copy link
Contributor

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:

@madelson
Copy link
Author

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.

@advename
Copy link
Contributor

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).

@mackowski
Copy link
Collaborator

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: #1101 (comment) and #1101 (comment).

@advename you are already doing changes to CSRF cheatsheet do you want to improve this part as well?

@mackowski mackowski added ACK_OBTAINED Issue acknowledged from core team so work can be done to fix it. and removed ACK_WAITING Issue waiting acknowledgement from core team before to start the work to fix it. labels Jun 13, 2023
@advename
Copy link
Contributor

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.

@jmanico
Copy link
Member

jmanico commented Jun 14, 2023 via email

@advename
Copy link
Contributor

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:

  1. A developer wants to research how they can protect their application against the most common vulnerabilities
  2. Developer Googles. After lots of reading, they frequently land on OWASP CSRF Cheat Sheet
  3. 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
    • ...

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.

@jmanico
Copy link
Member

jmanico commented Jun 14, 2023 via email

@advename
Copy link
Contributor

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.

@jmanico
Copy link
Member

jmanico commented Jun 14, 2023 via email

@advename
Copy link
Contributor

@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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ACK_OBTAINED Issue acknowledged from core team so work can be done to fix it. HELP_WANTED Issue for which help is wanted to do the job. UPDATE_CS Issue about the update/refactoring of a existing cheat sheet.
Projects
None yet
Development

No branches or pull requests

5 participants
@jmanico @madelson @advename @mackowski and others