Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Securing your API: a modern alternative to CSRF tokens (mixmax.com)
156 points by skeggse on June 13, 2017 | hide | past | favorite | 56 comments


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.


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.


Not to be That Guy 2.0; but doesn't all of this ad hoc hackery (CSRF tokens included, IMHO) around request authorisation and, if you'll permit me to extend the discussion a bit, also around request rate limiting, indicate that there might be something terribly broken at the heart of web requests?

In a same way that I feel the proliferation of front end frameworks indicates something is terribly broken at a cellular level about UI in HTML.

Might it not be time for a radical rethink? Can we not hope for a piercing, elegant solution?


You're wrong. If this is generated every time the page loads you get a new token each time. OR you have to transmit a god awful amount of data on the same page.

When I work for a company that accepts GBs of data over the same TLS connection on a single page, I'll worry about breach and only generating a CSRF token every time a page loads.


From the sample code provided I was assuming the value would not update on every request (since, if you were changing the expiration timestamp on every request you'd have a bit more work to do than you've shown here -- and either way the sample you've provided isn't a great way to generate the token).


I've generated this exact token at several companies and it's worked great.

An expiration of one day is not 86400 it's unix_time()+86400, to clarify. It's not stored in the users session and is completely stateless. It's great!

It has every property a csrf token needs

- per user

- not guessable or forgeable

- expires

Since it is stateless it doesn't require db lookups which can be an additional benefit. If you complain that it lacks the ability to be revoked I challenge you to find a single instance of people revoking csrf tokens.


You're wrong. If you MITM the connection you can block that response and the token will be user over and over again allowing these attacks


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.


I would like to note that not all frameworks are viable at-scale. We ran into that with Meteor, as it scaled extremely poorly. It didn't implement CSRF tokens, but also didn't use cookies to store auth data, so it didn't much matter.

We only target recent versions of modern browsers, as a consequence of our user base, so we're more able to move to an origin-based solution.


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.


CSRF tokens do not help you in any way against DDOS. CSRF tokens are handed out willingly on essentially any GET request.


Exactly -- I think that's one of the points of the article.


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.


So does using the client to hold information.


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/


And another one on slide 33: https://speakerdeck.com/filedescriptor/exploiting-the-unexpl...

There are plenty of middleware-like solutions that can add solutions automatically too. And if you fail closed, you will always notice in testing and can add tokens where they are missing.


Interesting. But in both these cases (yours and @arkadiyt's) these vulnerabilities only affect GET requests right? In which case—though we would love to lock down GET requests, to prevent DOS attacks, and because GET routes _might_ in some cases modify state—the impact is pretty limited.

I (one of the co-authors of the post) would also characterize our approach as "skating to where the puck will be". I'm sure that browsers will patch these bugs, the Edge one was fixed quickly. Our product only nominally supports the latest - 1 versions of Chrome and Safari. This is of course a luxury not available to all developers.


Almost no one ever gets hacked through web app bugs at all, let alone CSRF, so in the end this doesn't really matter.

I still wouldn't recommend this as a solution though, since it's been broken repeatedly.


Man oh man I used to think the same but I took infosec training.

That taught me little. I held onto the belief hacking a target by website is only for really dumb victims.

Then I started reading Bug Bounty reports on HackerOne and BugCrowd and was terrified at people doing account takeovers with CSRF attacks on oft overlooked functionality in no name sites like Twitter or FB.

That humbled me real quick.

As an aside, FB has to put a stupid interactive prompt to not open the browser JS console unless you type a key code. In an era where people will misguidedly copy paste into that, it is real brazen to believe token stealing and other CSRF vectors are not footholds and do not really happen.

I presume I just misunderstood you, but I wanted to go on the record for those on HN secretly believing this position and call them nuts.


Are you FB or Twitter or on a similar scale?

Also, bug bounties are not representative of real attacks.

I've written my own share of complicated exploits, but from an actual defense perspective... that's not how people are getting hacked IRL. It's all word macros and sqlmap.


Having worked in IR in various capacities in the past, I'd like to point out that many intrusions are not shared publicly. There are definitely targeted intrusions that begin with XSS or CSRF, you just don't hear about them.

As for the majority of hacks being something else I full on agree. I think phishing for credentials and malware installs, and leaked credentials in recent years, makes up the majority of intrusions. Many of those are opportunistic though and not necessarily targeted


So, I acknowledge there are breaches which started with XSS (Atlassian, etc), but even when you look at breaches that started with "client-side web bugs", how many of those are CSRF rather than XSS? Probably only a fraction, since they're shittier bugs.

And then, from the pool of "client-side web bugs", how many involve browser bugs?

This is just such a tail risk that it's hard to make myself care.


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.


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.


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.


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.


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?


I don't really like the word secure next to CORS; you're relying on the browser to be secure... which it is not.


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.


If you could find a way of compromising that

CVE-2011-0696 (the Django version of a bug that did affect several major things) is what happens when you find a way of getting the browser to make a cross-domain request with custom headers.

(the underlying issue there was a combination of a bug in Flash, and the semantics of the HTTP 307 status code)


You are totally correct. CORS also falls apart if the client isn't a browser.


But, CSRF is inherently a browser vulnerability.


Yes, but then you get people that fail to understand that CSRF only applies to a browser, and CORS only affects a user agent that implements a Same Origin Policy. And they fail to protect their resources otherwise.

They add CORS support to their server resources and get the false assumption that their resources can only ever be retrieved from a web page under their origin or an origin they have trusted (via CORS). They believe that this also protects them from malicious users making requests from outside of a browser (eg. via curl).

CORS is not security, it is loosening of security (namely SOP).

The number of times that I have had someone ask "why can someone access the endpoint via curl? I thought this was protected by CORS" makes me sad.


CSRF doesn't save you here... I can send up a CSRF token using curl just as easily...


I wasn't saying a CSRF token saves you here.

All I'm saying is that security is a complicated topic. People already misunderstand things like CSRF, CORS, resource protection, etc. And they already get them wrong.

CORS is designed to loosen the security protections added by a same origin policy. It is not designed to increase security.

Piling on CSRF protections is just blurring the use case for CORS. All this is going to do is confuse people more, and more people are going to get it wrong.


On the same topic of CSRF prevention, we also recently wrote about the dangers of using Content Types for security -- https://medium.com/@longtermsec/chrome-just-hardened-the-nav...


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


Well, it's a bug. It absolutely should send the origin header, but it doesn't ¯\_(ツ)_/¯


I see that it's unclear, but my second set of questions was related to how chrome and safari don't send it for requests caused by img tags.


The author admits that this solution won't work for "older" browsers. Which browser versions specifically?


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


For our specific solution, we also rely on (partial) support for Referrer-Policy: https://caniuse.com/#feat=referrer-policy


What a well-written, insightful article! You have outdone yourself good sir


What stops a user from writing a simple HTTP client to spoof the header?


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.


Isn't JWT a modern alternative to CSRF tokens?


It's not. If you think it is you probably store JWT unsafely instead of in an httpOnly secure cookie.


Why do you think storing JWT in secure cookie is only secure solution?


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.


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?


Worst because it is neither proven as tokens which are used for decades already nor as convenient as simply whitelisted Origin and you even offer an extra dependency. Are those scenarios without Origin important? Like ff for form request - you can use xhr there and drop referer support entirely.


You're right, support for cross-origin form POSTs isn't important for us. What is important, however, is same-origin requests, and Firefox doesn't send the Origin header with those. For us, the Referer serves its most important role by indicating where those requests originate from.

EDIT: when Firefox _does_ start sending the Origin header, we may drop the Referer - we'll see.


Just checked, indeed no origin on same site. It sucks, this makes origin barely reliable.


Thoughtful article!


Thus said the "Co-founder & CTO @Mixmax" ;)


:-D




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: