135

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,
  • ...
Community
  • 1
  • 1
Chris Lercher
  • 36,734
  • 20
  • 97
  • 131

3 Answers3

51

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.

Community
  • 1
  • 1
SilverlightFox
  • 30,831
  • 11
  • 71
  • 140
  • The Flash example is a good one - and maybe other plugins may have a similar vulnerability. I can (unfortunately) only protect Alice from CSRF, if she uses a modern browser etc, that's clear. But it is not unreasonable, that even as a security-aware user, she might have installed 3rd party plugins - especially when they are from large (trustworthy) companies. Even though they may write safe plugins, I'm not 100% convinced, if they also think about CSRF! So unless browser sandboxes restrict the plugins from violating SOP (do they maybe?), I'd rather recommend to stick with the CSRF token. – Chris Lercher Jul 11 '14 at 12:14
  • @ChrisLercher: Yes modern day plugins appear to be a bit more robust. However, at any moment a new version could be released that allows an attacker to leverage it in such a way to bypass browser protections. The best way to handle it (e.g. token/header) will depend on the sensitivity of your data and whether such a risk is acceptable. Flash does obey a SOP, but the origin of a Flash plugin is the site it was loaded from (rather than the calling site like JavaScript). There is a `crossdomain.xml` that can enable cross-domain communication. – SilverlightFox Jul 11 '14 at 12:59
  • If the origin header is missing, a server can always reject the request, which mitigates the img src attribute type of attack. But agree, the trustless approach would encourage a csrf token. – GViz Feb 11 '22 at 21:05
30

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.

guest
  • 6,135
  • 27
  • 41
  • 5
    I've just tested Firefox 47 and it does not send an origin header for a cross-origin form post (a common way of attacking REST services that don't enable CORS for XHR), so I don't think an origin header check would be effective if the user is using Firefox. – Andy Jul 13 '16 at 13:41
  • 3
    For reference, the issue of Firefox not sending an "Origin" header is tracked at Bugzilla: https://bugzilla.mozilla.org/show_bug.cgi?id=446344 You could fallback to checking the "Referer" header in this case but in my experience some Firefox users block the "Referer" header because of privacy concerns (although IMHO it would be enough to strip the path and query). – Steffen Jan 05 '17 at 18:44
13

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).

Adam
  • 20,945
  • 18
  • 119
  • 202
  • Quote "...simple requests,... the request will be executed and if the request was not allowed, then a not-allowed error response will be returned." So you are saying the server will reject the request? Then why is CSRF token still necessary here? – Morris Mar 13 '22 at 20:51
  • 1
    @Morris server will execute and return an error message. So it will upda profile or w/e and respond with no access – Adam Mar 13 '22 at 22:17
  • What's preventing a evil domain from first scraping the HTML form string, obtaining token, then submitting evil requests with token? I've seen in above discussions, some said "Form HTML must require authentication", but we also said "When myApp is logged in, Evil domain can make a request to myApp domain, and browser will automatically send myApp cookies" - doesn't it mean Evil domain's request would still be considered "authenticated" ? – Morris Mar 14 '22 at 18:10
  • @Morris CSRF token is matched vs a key stored in a session. If you obtain token but wihtout the cookie of the user, meaning without session, then request is not executed and you get error message. – Adam Mar 14 '22 at 19:40
  • I don't get it: if the server checks the value of the `Origin` header and rejects requests with invalid origin, why do I still need CSRF token? – izogfif Apr 26 '22 at 15:36
  • @izogfif same question as from Morris. If you send a POST request to update your profile from invalid origin, then you receive an error message saying not allowed origin, but the action (the update of your profile) was still executed. – Adam Apr 26 '22 at 18:25
  • @Adam What kind of action are you talking about? The server checked the origin, it was invalid (did not match the allowed ones), and returned 403 (HTTP "forbidden") response. What action was executed? The profile was not updated because the request was rejected by the server. – izogfif Apr 26 '22 at 20:00
  • @izogfif no, same origin policy will not prevent the request from being executed on the server. The response returns forbidden but the request itself was executed. Thatis because there are no preflight requests for GET and POST. Maybe try to read https://stackoverflow.com/a/33324803/2311074 – Adam Apr 27 '22 at 06:11
  • @Adam Preflight requests have nothing to do with it. Let's say that the browser sends the request from attacker's domain without preflight request but with header `Origin` set to `https://attacker.org`. The server checks the value of the `Origin` (because there is a server-side check like `httpRequest.headers['Origin'] == 'https://validsite.net'`) and immediately sends `403: forbidden` reply, without even reading the body of the request and doesn't execute the command to update the profile of the user. So I still don't understand why you think that checking the `Origin` header is not enough. – izogfif Apr 27 '22 at 11:36
  • @izogfif did you read the reference? Especially *So, a POST with Content Type application/x-www-form-urlencoded will hit to the server (this means a CSRF vulnerability) but the browser will not make accessible the results from that request.* – Adam Apr 27 '22 at 17:26
  • @izogfif oh you mean if you prevent manually on the server to execute the event if Origin does not match? Of course you could do that. But thats not default behavior. See also https://security.stackexchange.com/a/197269/108639 – Adam Apr 27 '22 at 17:31
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/244262/discussion-between-izogfif-and-adam). – izogfif Apr 27 '22 at 17:52