123
votes

This question is about protecting against Cross Site Request Forgery attacks only.

It is specifically about: Is protection via the Origin header (CORS) as good as the protection via a CSRF token?

Example:

So:

  • If we don't check the Origin header (server-side), and no CSRF token, we have a CSRF security hole.
  • If we check a CSRF token, we're safe (but it's a bit tedious).
  • If we do check the Origin header, the request from evil.com's client side code should be blocked just as well as it would when using a CSRF token - except, if it is possible somehow for evil.com's code to set the Origin header.

I know, that this should not be possible with XHR (see e.g. Security for cross-origin resource sharing), at least not, if we trust the W3C spec to be implemented correctly in all modern browsers (can we?)

But what about other kinds of requests - e.g. form submit? Loading a script/img/... tag? Or any other way a page can use to (legally) create a request? Or maybe some known JS hack?

Note: I am not talking about

  • native applications,
  • manipulated browsers,
  • cross site scripting bugs in example.com's page,
  • ...
3
I believe many proxies strip the origin header.thefourtheye
And for form submit and img/script tags, we should rely on CSPs, not sure about the old browsers though.thefourtheye
@thefourtheye: Since the connection is initiated over TLS the user has a far more pressing issue than CSRF if a proxy can man-in-the-middle him/her.Perseids
@thefourtheye, why would they strip Origin? That would negate CORS protection.Paul Draper
I like this question and its answers because they are about something specific, but they also remind me of the difference between CSRF and CORS. (I admit those are not easily confusable concepts... But I still manage to confuse them.)The Red Pea

3 Answers

47
votes

know, that this should not be possible with XHR (see e.g. Security for cross-origin resource sharing), at least not, if we trust the W3C spec to be implemented correctly in all modern browsers (can we?)

At the end of the day you have to "trust" the client browser to safely store user's data and protect the client-side of the session. If you don't trust the client browser, then you should stop using the web at all for anything other than static content. Even with using CSRF tokens, you are trusting the client browser to correctly obey the Same Origin Policy.

While there have been previous browser vulnerabilities such as those in IE 5.5/6.0 where it has been possible for attackers to bypass the Same Origin Policy and execute attacks, you can typically expect these to be patched as soon as discovered and with most browsers automatically updating, this risk will be mostly mitigated.

But what about other kinds of requests - e.g. form submit? Loading a script/img/... tag? Or any other way a page can use to (legally) create a request? Or maybe some known JS hack?

The Origin header is normally only sent for XHR cross-domain requests. Image requests do not contain the header.

Note: I am not talking about

  • native applications,

  • manipulated browsers,

  • cross site scripting bugs in example.com's page,

I'm not sure whether this falls under manipulated browsers or not, but old versions of Flash allowed arbitrary headers to be set which would enable an attacker to send a request with a spoofed referer header from the victim's machine in order to execute an attack.

29
votes

Web content can't tamper with the Origin header. Furthermore, under the same origin policy, one origin can't even send custom headers to other origins. [1]

Thus, checking the Origin header is just as good at blocking attacks as using a CSRF token.

The main concern with relying on this is whether it it lets all legitimate requests work. The asker knows about this issue, and has set up the question to rule out the major cases (no old browsers, HTTPS only).

Browser vendors follow these rules, but what about plugins? They might not, but the question disregards "manipulated browsers." What about bugs in the browser that let an attacker forge the Origin header? There can be bugs that allow the CSRF token to leak across origins too, so it would take more work to argue that one is better than the other.

5
votes

Explaining the terms

I think the question should be same-origin policy vs CSRF token. Because CORS is a mechanism to allow two different domains to talk to each other (by relaxing same-origin policy), whereas same-origin policy and CSRF token restrict domains to talk to each other.

GET methods are never save

All browsers implement the same-origin policy. This policy avoids in general that a web application on domain A can make a HTTP request to an application on domain B. However, it does not restrict all requests. For example same-origin policy does not restrict embed tags like this:

<img src="https://dashboard.example.com/post-message/hello">

It’s irrelevant whether the response is a valid image — the request is still executed. This is why it’s important that state-changing endpoints on your web application cannot be invoked with the GET method.

Preflight check

You may use CORS to avoid same-origin policy and let the domain A make a request to domain B that would otherwise be forbidden. Before the actually request is send, a preflight request will be send to check if the server allows domain A to send this request type. If it does, domain A will send the original request.

For example, if no CORS are set, then a Javascript XMLHttpRequests would be restricted for domain A by a preflight, without executing the request on domain B.

Why you need CSRF token despite same-origin policy

If same-origin policy would work for all types of request then you would be right and there is no need to use CSRF token, because you would have full protection by the same-origin policy. However, this is not the case. There are a couple of HTTP requests that do not send a preflight request!

GET, HEAD and POST requests with specific headers and specific content-type do not send a preflight request. Such requests are called simple requests. This means the request will be executed and if the request was not allowed, then a not-allowed error response will be returned. But the problem is, that the simple request was executed on the server.

Unfortunately, a plain <form action="POST"> creates a simple requests!

And because of these simple requests, we have to protect the POST routes with CSRF tokens (GET routes don't need CSRF because they can be read anyway by embedded tags as shown above. Just make sure you don't have a state-changing get method).