Securing your API: a modern alternative to CSRF tokens
mixmax.com
mixmax.com
- 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.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.
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.
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.
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?
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.
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.
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/
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.
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.
I still wouldn't recommend this as a solution though, since it's been broken repeatedly.
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.
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.
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
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.
And of course it would be better if browsers would not make cross-origin requests unless permitted by server.
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'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?
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.
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.
(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.
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)
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?
TL;DR - evergreen browsers, mobile browsers (except for images), IE 11+, and IE 8+ partially supports it
EDIT: when Firefox _does_ start sending the Origin header, we may drop the Referer - we'll see.