| ▲ | CSRF protection without tokens or hidden form fields(blog.miguelgrinberg.com) |
| 148 points by adevilinyc 3 days ago | 43 comments |
| |
|
| ▲ | owenthejumper 8 hours ago | parent | next [-] |
| Right now the problem is what the author already mentions - the use of Sec-Fetch-Site (FYI, HTTP headers are case insensitive :) - is considered defense in depth in OWASP right now, not a primary protection. Unfortunately OWASP rules the world. Not because it's the best way to protect your apps, but because the corporate overloads in infosec teams need to check the box with "Complies with OWASP Top 10" |
| |
| ▲ | miguelgrinberg 7 hours ago | parent | next [-] | | Hi, author here. This was actually a mistake. If you look at the OWASP cheat sheet today you will see that Fetch Metadata is a top-level alternative to the traditional token-based protection. I'm not sure I understand why, but the cheat sheet page was modified twice. First it entered the page with a top-level mention. Then someone slipped a revision that downgraded it to defense in depth without anyone noticing. It has now been reverted back to the original version. Some details on what happened are in this other discussion from a couple of days ago: https://news.ycombinator.com/item?id=46347280. | |
| ▲ | 8n4vidtmkvmk 2 hours ago | parent | prev | next [-] | | Since when are they case sensitive? https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/... says otherwise. It's possible for a server to treat them as case sensitive, but that seems like a bad idea. | | |
| ▲ | thomascountz 25 minutes ago | parent [-] | | +1 HTTP/2, headers are not unique if they only differ by casing, but they must be encoded as lowercase. Just as in HTTP/1.x, header field names are strings of ASCII characters that are compared in a case-insensitive fashion. However, header field names MUST be converted to lowercase prior to their encoding in HTTP/2. A request or response containing uppercase header field names MUST be treated as malformed (Section 8.1.2.6).[1]
HTTP/1.X, headers are insensitive to casing for reasons of comparison and encoding. Each header field consists of a name followed by a colon (":") and the field value. Field names are case-insensitive.[2]
So, if Sec-Fetch-Site is sensitive at all, it would be sec-fetch-site when sending via HTTP/2 and you're responsive for encoding/decoding.[1]: https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2 [2]: https://datatracker.ietf.org/doc/html/rfc2616#section-4.2 |
| |
| ▲ | nchmy 6 hours ago | parent | prev | next [-] | | Can you share links to better guidance than OWASP? | |
| ▲ | tptacek 2 hours ago | parent | prev [-] | | The OWASP Top 10 is a list of vulnerabilities, not a checklist of things you have to actually "do". |
|
|
| ▲ | tmsbrg 7 hours ago | parent | prev | next [-] |
| I'm surprised there's no mention of the SameSite cookie attribute, I'd consider that to be the modern CSRF protection and it's easy, just a cookie flag: https://scotthelme.co.uk/csrf-is-dead/ But I didn't know about the Sec-Fetch-Site header, good to know. |
| |
| ▲ | tordrt 3 hours ago | parent | next [-] | | Yep SameSite lax, and just make sure you never perform any actions using Get requests, which you shouldn’t anyway. | | | |
| ▲ | nhumrich 3 hours ago | parent | prev | next [-] | | This is "not allowing cross site at all" so, technically it's not "request forgery" protection.
Yes, this is very semantic, but, CSRF is a vulnerability introduced by enabling CS and CORS.
So, technically, same-site cookies are not "protection" against CSRF. | | |
| ▲ | nchmy 2 minutes ago | parent | next [-] | | Cs and cors have nothing to do with csrf... Though, yes, neither does same-site | |
| ▲ | hn_throwaway_99 an hour ago | parent | prev [-] | | I don't understand your distinction at all. I may not quite grok your meaning here, but CORS is usually discussed in the context of allowing cross-origin AJAX calls. But cross origin form posts are and have always been permitted, and are the main route by which CSRF vulnerabilities arise. Nothing on the client or server needs to be enabled to allow these form posts. Furthermore, the approach detailed in the article simply has the server block requests if they are cross site/origin requests, so I'm not sure what the semantic difference is. | | |
| ▲ | true_religion 17 minutes ago | parent [-] | | Yeah, CORS is not a safety mechanism. It’s a procedure of loosening the default safety mechanism of not sharing any response data from a cross site request with client side JavaScript. |
|
| |
| ▲ | hatefulheart an hour ago | parent | prev | next [-] | | I’m confused, how does this prevent a CSRF attack? SameSite or not is inconsequential to the check a backend does for a CSRF token in the POST. | | |
| ▲ | hn_throwaway_99 12 minutes ago | parent | next [-] | | The only reason CSRF is even possible is because the browser sends (or, well, used to send) cookies for a particular request even if that request initiated from a different site. If the browser never did that (and most people would argue that's a design flaw from the get go) CSRF attacks wouldn't even be possible. The SameSite attribute makes it so that cookies will only be sent if the request that originated them is the same origin as the origin that originally wrote the cookie. | | |
| ▲ | hatefulheart 3 minutes ago | parent [-] | | I think I understand now, the Cookie just is not present in the POST if a user clicked on, for example, a maliciously crafted post from a different origin? |
| |
| ▲ | tptacek an hour ago | parent | prev [-] | | No? The whole point of SameSite=(!none) is to prevent requests from unexpectedly carrying cookies, which is how CSRF attacks work. | | |
| ▲ | hatefulheart 5 minutes ago | parent [-] | | What does this even mean? I’m not being rude, what does it mean to unexpectedly carry cookies? That’s not what I understand the risk of CSRF is. My understanding is that we want to ensure a POST came from our website and we do so with a double signed HMAC token that is present in the form AND the cookie, which is also tied to the session. What on earth is unexpectedly carrying cookies? |
|
| |
| ▲ | miguelgrinberg 7 hours ago | parent | prev [-] | | The OWASP CSRF prevention cheat sheet page does mention SameSite cookies, but they consider it defense in depth: https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Re.... | | |
| ▲ | hn_throwaway_99 an hour ago | parent | next [-] | | I don't understand the potential vulnerabilities listed at the linked section here: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc... They give 2 reasons why SameSite cookies are only considered defense in depth: ---- > Lax enforcement provides reasonable defense in depth against CSRF attacks that rely on unsafe HTTP methods (like "POST"), but does not offer a robust defense against CSRF as a general category of attack: > 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. > 2. Features like "<link rel='prerender'>" [prerendering] can be exploited to create "same-site" requests without the risk of user detection. > When possible, developers should use a session management mechanism such as that described in Section 8.8.2 to mitigate the risk of CSRF more completely. ---- But that doesn't make any sense to me. I think "the robust solution" should be to just be sure that you're only performing potential sensitive actions on POST or other mutable method requests, and always setting the SameSite attribute. If that is true, there is absolutely no vulnerability if the user is using a browser from the past seven years or so. The 2 points noted in the above section would only lead to a vulnerability if you're performing a sensitive state-changing action on a GET. So rather than tell developers to implement a complicated "session management mechanism", it seems like it would make a lot more sense to just say don't perform sensitive state changes on a GET. Am I missing something here? Do I not understand the potential attack vectors laid out in the 2 bullet points? | |
| ▲ | tptacek 2 hours ago | parent | prev [-] | | Because of clientside Javascript CSRF, which is not a common condition. |
|
|
|
| ▲ | rvnx 6 hours ago | parent | prev | next [-] |
| If you want, “SameSite=Strict” may also be helpful and is supported on “all” browsers so it is reasonable to use it (but like you did, adding server validation is always a +). https://caniuse.com/mdn-http_headers_set-cookie_samesite_str... This checks Scheme, Port and Origin to decide whether the request should be allowed or not. |
| |
| ▲ | simonw 6 hours ago | parent | next [-] | | I find that cookie setting really confusing. It means that cookies will only be respected on requests that originated on the site that set them... but that includes when you click links from one site to another. So if you follow a link (e.g. from a Google search) to a site that uses SameSite=Strict cookies you will be treated as logged out on the first page that you see! You won't see your logged in state until you refresh that page. I guess maybe it's for sites that are so SPA-pilled that even the login state isn't displayed until a fetch() request has fired somewhere? | | |
| ▲ | ctidd 5 hours ago | parent | next [-] | | You want lax for the intuitive behavior on navigation requests from other origins. Because there’s no assumption navigation get requests are safe, strict is available as the assumption-free secure option. | |
| ▲ | macNchz 2 hours ago | parent | prev [-] | | SameSite=Strict is belt-and-suspenders protection in the case where you could have GET requests that have some kind of impact on state, and the extra safety is worth the UX impact (like with an online banking portal). Discussions about this often wind up with a lot of people saying "GET requests aren't supposed to change state!!!", which is true, but just because they're not supposed to doesn't mean there aren't some floating around in large applications, or that there aren't clever ways to abuse seemingly innocuous side effects from otherwise-stateless GET requests (maybe just visiting /posts/1337/?shared_by_user=12345 exposes some tiny detail about your account to user #12345, who can then use that as part of a multi-step attack). Setting the strict flag just closes the door on all of those possibilities in one go. |
| |
| ▲ | Macha 5 hours ago | parent | prev [-] | | Note SameSite=Strict also counts against referrals too, which means your first request will appear unauthenticated. If this request just loads your SPA skeleton, that might be fine, but if you're doing SSR of any sort, that might not be what you want. |
|
|
| ▲ | est 4 hours ago | parent | prev | next [-] |
| reminds me of something similar https://news.ycombinator.com/item?id=46321651 e.g. serve .svg only when "Sec-Fetch-Dest: image" header is present. This will stop scripts |
| |
| ▲ | amluto 2 hours ago | parent [-] | | Or sending Content-Security-Policy: script-src 'none' for everything that isn’t intended to be a document. Or both. IMO it’s too bad that suborigins never landed. It would be nice if Discord’s mintlify route could set something like Suborigin: mintlify, thus limiting the blast radius to the mintlify section. | | |
|
|
| ▲ | louiskottmann an hour ago | parent | prev | next [-] |
| This is a massive change for cache in webapp templates as it makes their rendering more stable and thus more cacheable. A key component here is that we are trusting the user's browser to not be tampered with, as it is the browser that sets the Sec-Fetch-Site header and guarantees it has not been tampered with. I wonder if that's a new thing ? Do we already rely on browsers being correct in their implementation for something equally fundamental ? |
| |
| ▲ | tptacek an hour ago | parent [-] | | The entire web security model assumes we can trust browsers to implement web security policies! | | |
| ▲ | louiskottmann 29 minutes ago | parent [-] | | I appreciate that, but in the case of TLS or CSRF tokens the server is not blindly trusting the browser in the way Sec-Fetch-Site makes it. | | |
| ▲ | tptacek 25 minutes ago | parent [-] | | Sure it is. The same-origin rule that holds the whole web security model together is entirely a property of browser behavior. | | |
|
|
|
|
| ▲ | shermantanktop 7 hours ago | parent | prev | next [-] |
| Am I missing something? The suggested protection helps with XSS flavors of CSRF but not crafted payloads that come from scripts which have freedom to fake all headers. At that point you also need an oauth/jwt type cookie passed over a private channel (TLS) to trust the input. Which is true for any sane web app, but still… |
| |
| ▲ | varenc 6 hours ago | parent | next [-] | | If an attacker has a user's private authentication token, usually stored in a __Host prefixed cookie, then it's game over anyway. CSRF is about protecting other sites forcing a user to make a request to a site they're authenticated to, when the malicious site doesn't actually have the cookie/token. CSRF is when you don't have the authentication token, but can force a user to make a request of your choosing that includes it. In this context you're using HTML/JS and are limited by the browser in terms of what headers you can control. The classic CSRF attack is just a <form> on a random site that posts to "victim.com/some_action". If we were to re-write browser standards today, cross-domain POST requests probably just wouldn't be permitted. | | |
| ▲ | naasking 4 hours ago | parent [-] | | > If we were to re-write browser standards today, cross-domain POST requests probably just wouldn't be permitted. That would be a terrible idea IMO. The insecurity was fundamentally introduced by cookies, which were always a hack. Those should be omitted, and then authorization methods should be designed to learn the lessons from the 70s and 80s, as CSRF is just the latest incarnation of the Confused Deputy: https://en.wikipedia.org/wiki/Confused_deputy_problem | | |
| ▲ | varenc 2 hours ago | parent [-] | | Ah, so true. That's what i mean! Cross domain requests that pass along the target domain's cookies. As in, probably every cookie would default to current __Host-* behavior. (and then some other way to allow a cookie if you want. Also some way of expressing desired cookie behavior without a silly prefix on its name...) |
|
| |
| ▲ | ctidd 5 hours ago | parent | prev | next [-] | | CSRF exists as a consequence of insecure-by-default browser handling of cookies, whereby the browser sends the host’s cookies on requests initiated by a third-party script to the vulnerable host. If a script can fake all headers, it’s not running in a browser, and so was never exposed to the insecure browser cookie handling to be able to leverage it as a vector. If no prerequisite vector, then no vulnerability to mitigate. | |
| ▲ | t-writescode 5 hours ago | parent | prev [-] | | As I understand it, the moment you’re dealing with custom scripts, you’ve left the realm of a csrf attack. They’re dependent upon session tokens in cookies |
|
|
| ▲ | altmind 6 hours ago | parent | prev [-] |
| Are there any approaches to csrf tokens that don't require storing issued tokens on server-side? |
| |
| ▲ | maxbond 3 hours ago | parent | next [-] | | The alternative to storing tokens is to use an AEAD encryption scheme like AES-GCM to protect tokens from forgery or tampering. You will still have to worry about reuse, so you will probably want to restrict use of this token to the user it was generated for and to a lifetime (say, 24 hours). That is a very high level description, there are details (like nonce generation) that must be done correctly for the system to be secure. | |
| ▲ | t-writescode 5 hours ago | parent | prev [-] | | Most of them. You can send in a cookie and a field and compare. CSRF is about arbitrary clicks in emails and such that automagic your logged-in-session cookies to the server. If you require an extra field and compare it, you’re fine |
|