CSRF, CORS, and HTTP Security Headers Demystified
blog.vnaik.com
blog.vnaik.com
As well as a system you can use to evaluate your site: https://observatory.mozilla.org/
Be careful with assuming SameSite fully protects from CSRF attacks. I thought it does, but then I read what "site" actually refers to in the context of same site (eTLD+1).
If the eTLD+1 (i.e. company.com) is not listed on the Public Suffix List, even SameSite=strict cookies for a.company.com will still be sent for requests initiated from b.company.com
1. cookies can prevent js access (httpOnly flag)
2. cookies can enforce https only (Secure flag)
I suppose the other thing you can do with cookies is use cookie prefixes. __Host probably makes no sense in the context of localStorage/sessionStorage anyway though, since they're all tied to the exact domain.
Having HttpOnly set only buys you so much, too. Sure, you can't steal the session from an XSS vector but your code can still do AJAX queries as the victim, potentially set up a JavaScript shell that works whilst the tab is open...
localStorage and IndexedDB on the other hand are most useful for frontend only stuff that the server doesn't need to ever see, and for large chunks of data that you do not want to send with every request, and app-domain specific caches that would be awkward to implement using regular browser caches or ServiceWorkers.
[0] For example, cloudflare implements their "browser checks" anti-DDOS-protections by setting some token in a cookie so your browser isn't hit with that check page on every navigation (at least in theory, TOR users and a lot of VPN users have different experiences). Since the browser will automatically manage and maintain such a cookie, the actual websites behind cloudflare do not need any changes whatsoever to their code.
c.f. https://tools.ietf.org/html/draft-west-first-party-cookies-0... and https://tools.ietf.org/html/draft-west-first-party-cookies-0...
Excerpts (draft 2):
> If "document" is a first-party context, and "request"'s URI's origin is the same as the origin of the URI of the active document in the top-level browsing context of "document", then return "First-Party".
vs. (draft 3)
> A document is considered a "first-party context" if and only if the registerable domain of the origin of its URI is the same as the registerable domain of the first-party origin, and if each of the active documents in its ancestors' browsing contexts' is a first-party context.
It's basically an origin, but disregarding subdomains.
I think (and this might just be an old wive's tale) it was related to the browser connection limits being per domain, and so subdomains + looser cookie origins was a band aid for it.
"As an added bonus, many of the mitigations on this page can be applied at the proxy server (CSP, HSTS, HPKP) or network level (better server proxying to remove the need for CORS), and only the CSRF and XSS protections really need to be added to the application."
If I add a line to the localhost-bound forward proxy that the aplication uses so that "SameSite" is added to every cookie, then it appears the second statement is misleading.
As a user, I rely on a (forward) proxy. Much easier for me to focus on the proxy than trying to make sure every application^1 is doing the right things.
Both parties to an HTTP transaction can use proxies to execute mitigations. And as the author states, the ones he is mentioning are only some of the possibilities.
1. Especially ones that we do not compile from source and are distributed by "tech" companies that rely on online advertising as their main source of revenue. We users are not their customers, we are the guinea pigs.
I really wish the author included an explanation for this. What are "default headers"? What special header(s) needs to be on the request in order for a preflight request to be made?
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
For your specific question, this is the relevant section of the above link
----
Apart from the headers automatically set by the user agent (for example, Connection, User-Agent, or the other headers defined in the Fetch spec as a “forbidden header name”), the only headers which are allowed to be manually set are those which the Fetch spec defines as a “CORS-safelisted request-header”, which are:
Accept
Accept-Language
Content-Language
Content-Type (but note the additional requirements below)
I nearly tried to use it once to try and see if a react app was on an intranet or not, but the client decided they didn't want it.
Instead developers take the shortcut of creating middleware that captures the Origin header in the request and mirrors it into the response, effectively creating the same insecure ruleset.
For example, Firefox has both first-party isolation mode and now Total Cookie Protection, which isolates cookies and would thus likely prevent CSRF. However, I think first-party isolation causes CORS issues like when trying to pay with Paypal on another retail site.
Generate URLs with a time limited token as query param. No cookies needed.
CSRF is often done via redirecting you or submitting a form, both of which obviously completely bypass FPI and dFPI (i.e. the cookie part of Total Cookie Protection).
> I'd love to see a write up how Firefox, Chrome, Brave and other browsers can be set up to prevent some of this.
I only use firefox, you'll have to find information elsewhere for other browsers.
CSRF, XSS, Set-Cookie
Need to be fixed server-side, there is little to nothing you can do as the client. CSRF and XSS represent straight-up vulnerabilities in the website. Report to the developer and/or stop using the vulnerable website.
CORS
No additional work needed for security benefits, to reduce its ability to track you: https://addons.mozilla.org/en-US/firefox/addon/privacy-orien...
CSP, X-Frame-Options
You can achieve the same effect of whitelisting 3rd parties by using an extension such as uBlock Origin or uMatrix (warning: no longer in development) in default-deny mode.
HSTS
https://support.mozilla.org/en-US/kb/https-only-prefs
HPKP
Nobody uses this nowadays. Only semi-related, but you can turn on mandatory revocation checking (security.OCSP.require).
Referrer-Policy
network.http.referer.XOriginPolicy
0=always (default), 1=only if base domains match, 2=only if hosts match network.http.referer.XOriginTrimmingPolicy
0=send full URI (default), 1=scheme+host+port+path, 2=scheme+host+portThese apply only to cross-origin requests but that's probably where you care about the referer. Note that the website's Referrer-Policy might override these, I haven't tested that.
All the difficulty seems to be people trying to do crazy, esoteric things there's no good reason to be doing in the first place.
What are you saying?
CORS is defending against a particular class of attack, which is indistinguishable from the scenario you outlined: evilexample.com wants to get access to your private repos on GitHub (which can be reached purely through GET requests).
> Why do you need to run that on the client?
Because it's a good idea (less wasteful) to do that on the client. Rather than wasting bandwidth rerouting it via my own server.
What?! For responding to OPTIONS requests and setting the right header on responses from your backend?
I don't really see the problems you're citing to be a cause of "complexity in CORS" either, but more not having a proper development setup or similar. CORS is specifically about domains. As long as you set the frontend domain as accepted origin in your responses from the backend (and respond to OPTIONS), you're good to go.
Is it really a list? AFAIK, and according to MDN: "Only a single origin can be specified."
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Ac...
Non-browser clients (and browser for non-CORS and/or "simple" requests) will not usually send any CORS headers and preflight requests, so you should account for that when building a web API. Non-browser clients can of course just fake any browser header and request they want, so the Access-Control headers are NOT a substitute for real access control/authentication.
What are these "default" headers. I have seen access-control-allow- response headers when making HTTP requests. I do not send unnecessary headers. Perhaps some of the ones I do not send are considered "default".
"Thus CORS is a way of selectively loosening security not of tightening it."
Proxy config I use scrubs all CORS headers. As the author states, CORS is irrelevant outside the browser. I make most HTTP requests outside the ("modern") browser anyway.
"Overall, as the web grows in terms of features and complexity, the attack surface also grows correspondingly large."
Job security for some people, I guess.
Apparently there is no sufficient incentive to simplify things (by subtraction not addition).