simonw / public-notes

Public notes as issue threads
25 stars 0 forks source link

Figuring out the state of the art in CSRF protection, late-2022 edition #2

Open simonw opened 1 year ago

simonw commented 1 year ago

Started thinking about this here:

Short version: I want to write some JavaScript that does a POST to a JSON API endpoint, and I'd like to not have to bother with extracting a CSRF token from a cookie and sending it with that POST.

And more generally, I'd like to update my mental model of CSRF protection for what works best circa 2022.

Fundamental questions to answer:

simonw commented 1 year ago

Started a Twitter conversation here:

https://twitter.com/simonw/status/1578953514973134848

Do I still need to check CSRF tokens for POST requests where the client has specified Content-Type: application/json ?

Internet search results seem slightly uncertain on this issue (and are mostly dated 5-10 years ago)

(I'm still one of those paranoid types that doesn't 100% trust SameSite=Lax cookies to protect against CSRF because of the risk that someone will CNAME helpdesk.mydomain.com over to a third-party vendor who end up with an XSS hole of their own)

Maybe I should trust the incoming Origin: header instead as a sign that CSRF checks can be skipped? https://twitter.com/jaffathecake/status/1238080408216047617

Urgh, lots of complex notes about Origin: here: https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#verifying-origin-with-standard-headers

What does this mean? "Internet Explorer 11 does not add the Origin header on a CORS request across sites of a trusted zone."

OWASP do seem to be in favour of custom request headers as a way to skip CSRF checks for fetch() calls though: https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#use-of-custom-request-headers

I remember there used to be CSRF attacks that took advantage of features in Flash that could make cross-domain cookied requests that set custom HTTP request headers

Presumably the chance someone has a vulnerable version of the Flash player installed is effectively zero now?

Like did Adobe consider that a bug in Flash and roll out a fix for that 15 years ago?

Another question: what's the absolute cutting edge state of the art in CSRF protection these days?

Any new web frameworks that are doing something thoroughly robust based on modern browser characteristics?

Is Django still the gold standard here?

(My hunch is that modern frameworks might just be leaving this entirely up to SameSite cookies, which I'm not sold on as a 100% solid approach)

simonw commented 1 year ago

I'm specifically looking for a solution which protects against the insecure subdomain scenario - the scenario where the code I want to protect runs on mysite.com, but it's possible someone may CNAME support.mysite.com off to a vendor who themselves have a XSS hole in their code.

So I want malicious code on support.mysite.com to be unable to perform successful CSRF attacks against mysite.com.

(Or maybe just against www.mysite.com if it turns out protecting the www. variant is easier than protecting the naked root domain.)

simonw commented 1 year ago

Also relevant: https://simonwillison.net/2021/Aug/3/samesite/ when I explored SameSite cookies last year.

simonw commented 1 year ago

https://twitter.com/MaltheJorgensen/status/1579229773498580992 says:

I decided 5 years ago that: yes, when the Content-Type is application/json then it is safe from CSRF.

The landscape is definitely muddy. It seems that until Chrome 59 (June 2017) you could use navigator.sendBeacon to POST JSON cross-origin: https://stackoverflow.com/questions/11008469/are-json-web-services-vulnerable-to-csrf-attacks?noredirect=1&lq=1#comment77025657_11024387

I feel like the Flask or Werkzeug docs used to contain some nice thoughts on this subject, but I can't find it -- even looking at fairly old versions, so that's probably just a false memory on my part.

Given that old navigator.sendBeacon() vuln. I'm wondering if there are newer similar browser holes, since the browsers surface is generally being expanded quite fast.

Closing thoughts: I like your idea of adding a custom header from the client side. Even if a browser bug allows Content-Type: application/json cross-origin, having a custom header on top narrows the exposure.

simonw commented 1 year ago

https://twitter.com/samuel_colvin/status/1579215646415388672 says:

https://github.com/samuelcolvin/foxglove/blob/8cf9165526829a3ee23c43560947e0bd6a7c2432/foxglove/middleware.py#L246

Is what I use, has passed multiple pen testing inspections (for what that's worth), and hasn't caused any problems for users in a long time.

Would love your opinion.

I replied:

Since it uses SameSite cookies, I wonder if it's vulnerable to CSRF attacks from an XSS attack against a subdomain of the primary domain it's hosted on?

That's the one very obscure edge case I'm most worried about here

The origin and referrer header checks might protect against that?

Samuel said:

That's the idea.

simonw commented 1 year ago

Bought a new domain, csrf.club, to host some demos to help explore this more (like I did in https://simonwillison.net/2021/Aug/3/samesite/ for SameSite cookies).

simonw commented 1 year ago

Great comment here: https://twitter.com/mountain_ghosts/status/1579244599360425986

your app accepting JSON doesn't prevent a page on another origin sending you an un-pre-flighted POST request, to which the browser might attach cookies

some frameworks abstract over different request body encodings so your app might accept normal form data without you realising

I tend to default to whatever's safest in general. yes you don't need CSRF tokens if you have nothing else hosted on the same site, and if your app definitely rejects content-types that don't require CORS permissions

but these assumptions are easy to forget and break

like it's very easy years down the line for someone to stick a third party app under your domain and not realise it means they need to change how your session and other security stuff works

simonw commented 1 year ago

Some demos I'd like to see:

jspraul commented 1 year ago

👋 https://news.ycombinator.com/item?id=33162854#33163900

simonw commented 1 year ago

https://twitter.com/jub0bs/status/1580905051840602113

The __Host- cookie name prefix is very useful if you rely on the so-called "double submit cookie" defence against CSRF. Otherwise, a malicious subdomain could create a homonymous cookie scoped at a common parent domain but with an arbitrary value...

... which would defeat the "double submit cookie" defence.

https://www.youtube.com/watch?v=tl6JCLAj5os

simonw commented 1 year ago

This presentation is amazingly useful: https://speakerdeck.com/filedescriptor/the-cookie-monster-in-your-browsers

It references this post by GitHub where they talk about why they moved GitHub pages content to *.github.io: https://github.blog/2013-04-09-yummy-cookies-across-domains/