top | item 14547765

Securing your API: a modern alternative to CSRF tokens

156 points| skeggse | 8 years ago |mixmax.com | reply

56 comments

order
[+] ejcx|8 years ago|reply
Why make an extremely complicated set up, with many edge cases, all to save yourself from a single token?

    - Privacy extensions often times block referrer headers.

    - POST requests are usually necessary.

    - Open redirects are common bugs, and getting your website to initiate one can be a problem.

    - Disabling CORS also relies on you killing crossdomain.xml, which you might overlook.
Instead you can just roll out CSRF tokens.

Having rolled them out myself many times. You probably want to use a library if you aren't a cryptographer or security person:

    expiration_time_of_1_day || hmac_sha256( secret_key, { expiration_time_of_1_day, user_id })
I've seen other people mention SameSite cookies, but we aren't near a time when browsers all support them. Don't get fancy preventing CSRF. It's a stupid bug.
[+] ubernostrum|8 years ago|reply
Not to be That Guy, but: your sample will reuse the same token over and over until it expires, which is how you get the BREACH attack.

In Django 1.10, we switched the CSRF token generation to use a consistent base value, but to combine it in a reversible way with a randomly-generated per-request nonce. CSRF verification then consists of recovering the base value and checking it; this lets you have a longer-lived "token" without sending the same value in every request/response cycle.

[+] jaza|8 years ago|reply
Agreed. The solution being proposed with CORS is fragile and complex. Maybe it will be viable in another 5 years' time, with better browser support. But I can't imagine that after reading the article, any dev in his/her right mind will be rushing off to ditch CSRF tokens on production sites.

CSRF tokens are taken care of by every web framework out there, anyway. They really should involve zero work to implement. For example, Flask-WTF handles them virtually transparently.

[+] jordanlev|8 years ago|reply
I believe the point of the article is that tokens don't get sent for HEAD or OPTIONS requests, and it's very possible that your API/server are still performing some sort of action on such requests. Or even if not performing a specific action, it still needs to be dealt with and hence is a DDOS vector.
[+] jadacyrus|8 years ago|reply
In addition to everything you've mentioned, the double submit cookie approach for csrf defense saves you from storing any state if you don't want to use a backend session.
[+] arkadiyt|8 years ago|reply
I used to prefer the origin/referer approach to blocking CSRF because it can be done upstream of the application server (or in a middleware), transparent to developers who often get these things wrong.

However there's been enough referer spoofing browser bugs lately that I'd rather have the extra safety (and complexity) of CSRF tokens. Just 3 months ago Edge had (another) referer spoofing bug: https://www.brokenbrowser.com/referer-spoofing-patch-bypass/

[+] minus7|8 years ago|reply
I can't shed the feeling that CORS is just piling more crap onto crap; this seems just way too complex. On one hand, I'm somewhat glad that I don't work on web things and don't have to deal with it, and on the other hand I'm scared of the many web developers are not (sufficiently) aware of the range of possible vulnerabilities and thus not protecting them.
[+] codedokode|8 years ago|reply
They tried to propose a simple modern solution but after reading the article it does not look simple and works much worse than traditional approach with tokens.

And of course it would be better if browsers would not make cross-origin requests unless permitted by server.

[+] wearhere|8 years ago|reply
Hi @codedokode, I'm one of the authors of the post. There's a lot of background material at the top but if you skip to the use of our new module (direct link: https://github.com/mixmaxhq/cors-gate/#usage) I think you'll find it simpler in both code and infrastructure than a typical CSRF setup. https://github.com/expressjs/csurf, for instance, requires you to lock down every API both server-side and client-side, and by default requires session middleware; whereas with cors-gate you can register it once, server-side, before any API routes.
[+] lol768|8 years ago|reply
I guess the modern part here is the use of the Origin header, but in all honestly this feels really flaky in comparison to using a CSRF token. I'd argue that SameSite cookies are a better 'modern' alternative than this (for a majority of use-cases), especially because of all the edge cases that have to be dealt with for this approach (Origin not being sent, falling back to Referers, policies for those...). It feels like a giant hack.

Also, I really don't get the relevance of the refutation of "Use only JSON APIs" - it's a fixed bug that seemingly only impacted Chrome anyway? The second point is that preflights are expensive, which might have some weight - but e.g. Twitch seem to cope okay with these preflights and I think a lot of browsers do cache them for a short period of time now anyway.

I'll be sticking to CSRF tokens plus SameSite cookies (for where the browser supports them). The only issue I can see with SameSite is that JS can still send requests (with no credentials), but I'm not convinced this is a credible DoS vector.

[+] rossy|8 years ago|reply
I do this in one of our business' apps. It does strict checking of the Origin and Referer headers when it receives a POST request. We only support modern browsers, and doing this instead of using CSRF tokens feels modern and clean.

I'm paranoid though, so at the last minute, before shipping the first public version of the app, I added a traditional CSRF token check in addition to the Origin/Referer check. I guess it's a layered defense?

[+] alexchamberlain|8 years ago|reply
I don't really like the word secure next to CORS; you're relying on the browser to be secure... which it is not.
[+] wearhere|8 years ago|reply
You can trust the browser itself (not application code, but the native code) to issue the appropriate headers. If you could find a way of compromising that, it would be a vulnerability wayyy beyond the scope of our protection.

(Caveat: browsers may not implement the specs completely/bug-free yet, as we cover in our post. But we fully expect they will, and in the meantime our module supports fallbacks. This approach is "skating to where the puck will be".)

Non-browser clients can spoof these headers, but the risk then is DOS, not clients leveraging the user's credentials—which is the primary focus of CSRF protection. It's nice that our method can prevent browser-based DOS attacks, but that's by no means complete DOS protection.

[+] bhhaskin|8 years ago|reply
You are totally correct. CORS also falls apart if the client isn't a browser.
[+] hamandcheese|8 years ago|reply
> Firefox will not send the Origin header with any same-origin requests (bug)

Am I the only one very surprised to hear this? In 2017?

Is there any reason browsers shouldn't just send origin headers along with all requests? Why the exceptions?

[+] skeggse|8 years ago|reply
Well, it's a bug. It absolutely should send the origin header, but it doesn't ¯\_(ツ)_/¯
[+] Retr0spectrum|8 years ago|reply
The author admits that this solution won't work for "older" browsers. Which browser versions specifically?
[+] nevir|8 years ago|reply
https://caniuse.com/#feat=cors and hit "show all"

TL;DR - evergreen browsers, mobile browsers (except for images), IE 11+, and IE 8+ partially supports it

[+] ziwikiwi|8 years ago|reply
What a well-written, insightful article! You have outdone yourself good sir
[+] makkesk8|8 years ago|reply
What stops a user from writing a simple HTTP client to spoof the header?
[+] treve|8 years ago|reply
Then that user can only 'hack themselves'. CSRF is a security vulnerability is because you can do a HTTP request on behalf of someone else, on someone else's browser, in someone else's session/security context.
[+] mrmagooey|8 years ago|reply
Isn't JWT a modern alternative to CSRF tokens?
[+] vmasto|8 years ago|reply
It's not. If you think it is you probably store JWT unsafely instead of in an httpOnly secure cookie.
[+] homakov|8 years ago|reply
If I were to drop CSRF tokens (which work reliably) I would go directly to Origin verification. No Referer. Something in the middle is worst of both worlds.
[+] skeggse|8 years ago|reply
I'm curious about your comment. We (attempted) to address cases where we needed to infer the Origin from the Referer due to incomplete browser support. What about using both makes this necessarily worse, when the use of the Referer is really only a temporary bandage for said incomplete support?
[+] bradavogel|8 years ago|reply
Thoughtful article!
[+] janwillemb|8 years ago|reply
Thus said the "Co-founder & CTO @Mixmax" ;)