Why Same-origin policy isn't enough to prevent CSRF attacks?

15,683

Solution 1

Summary

I had a misunderstood concepts about Same-origin policy and CORS that @Bergi, @Neil McGuigan and @SilverlightFox helped me to clarify.

First of all, what @Bergi says about

SOP does not prevent sending requests. It does prevent a page from accessing results of cross-domain requests.

is an important concept. I thought that a browser doesn't make the request to the cross domain accordingly to the SOP restriction but this is only true for what Monsur Hossain calls a "not-so-simple requests" in this excellent tutorial.

Cross-origin requests come in two flavors:

  • simple requests
  • "not-so-simple requests" (a term I just made up)

Simple requests are requests that meet the following criteria:

  • HTTP Method matches (case-sensitive) one of:
    • HEAD
    • GET
    • POST
  • HTTP Headers matches (case-insensitive):
    • Accept
    • Accept-Language
    • Content-Language
    • Last-Event-ID
    • Content-Type, but only if the value is one of:
      • application/x-www-form-urlencoded
      • multipart/form-data
      • text/plain

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. A POST with Content Type application/json is a "not-so-simple request" so the browser will make a prefligth request like this

OPTIONS /endpoint HTTP/1.1
Host: https://server.com
Connection: keep-alive
Access-Control-Request-Method: POST
Origin: https://evilsite.com
Access-Control-Request-Headers: content-type
Accept: */*
Accept-Encoding: gzip, deflate, sdch
Accept-Language: es-ES,es;q=0.8

If the server respond with for example:

Access-Control-Allow-Origin: http://trustedsite.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: content-type
Content-Type: text/html; charset=utf-8

the browser will not make the request at all, because

XMLHttpRequest cannot load http://server.com/endpoint. Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header contains the invalid value 'trustedsite.com'. Origin 'evilsite.com' is therefore not allowed access.

So I think that Neil was talking about this when he pointed out that:

the Same-origin Policy only applies to reading data and not writing it.

However, with the origin header explicit control that I proposed to Bergi I think is enough with respect to this issue.

With respect to my answer to Neil I didn't mean that that answer was the one to all my question but it remembered me another important issue about SOP and it was that the policy only applies to XMLHTTPRequest's.

In conclusion, I think that the equation

  • TLS + JWT in secure httpOnly cookie + Origin Header check + No XSS vulnerabilities.

is a good alternative if the API is in another domain like SilverlightFox says. If the client is in the same domain that the client I will have troubles with requests that doesn't include the Origin header. Again from the cors tutorial:

The presence of the Origin header does not necessarily mean that the request is a cross-origin request. While all cross-origin requests will contain an Origin header, some same-origin requests might have one as well. For example, Firefox doesn't include an Origin header on same-origin requests. But Chrome and Safari include an Origin header on same-origin POST/PUT/DELETE requests (same-origin GET requests will not have an Origin header).

Silverlight pointed this out to.

The only risk that remains is that a client can spoof the origin header to match the allowed origin, so the answer i was looking for was actually this

UPDATE: for those who watch this post, I have doubts about if the origin header is needed at all using JWT.

The equation would be:

  • TLS + JWT stored in secure cookie + JWT in request header + No XSS vulnerabilities.

Also, the previous equation has httpOnly cookie but this won't work if you got the client and the server in different domains (like many SPA application today) because the cookie wouldn't be sent with each request to the server. So you need access the JWT token stored in the cookie and send it in a header.

Solution 2

Why Same-origin policy isn't enough to prevent CSRF attacks?

Because the Same-origin Policy only applies to reading data and not writing it.

You want to avoid http://compromised.com from making a request like this (from the user's browser):

POST https://example.com/transfer-funds
fromAccountId:1
toAccountId:666

A legit request would look like this:

POST https://example.com/transfer-funds
fromAccountId: 1
toAccountId: 666
csrfToken: 249f3c20-649b-44de-9866-4ed72170d985

You do this by demanding a value (the CSRF token) that cannot be read by an external site, ie in an HTML form value or response header.

Regarding the Origin header, older browsers don't support it, and Flash had some vulnerabilities that let the client change it. Basically you'd be trusting Adobe not to screw anything up in the future...does that sound like a good idea?

ensure that you have CSRF protection on every HTTP request

You only need CSRF protection on requests with side-effects, such as changing state or sending a message

Solution 3

I just want to summarize the answers.

  1. As other mentioned SOP applies only to XmlHttpRequests. This means by specification browsers must send ORIGIN header along with requests that were made by means of XmlHttpRequests.
  2. If you check Chromium sends origin when you submit form as well. However this doesn't mean other browsers do. The image below illustrates two post requests made in Firefox. One is made by submitting a form and a second one using XHR. Both requests were made from http://hack:3002/changePassword to http://bank:3001/chanePassword.
  3. Browser is allowed to not send the origin header if request was made from the same domain. So server should check the origin policy only when origin header is set.

The conclusion is: if you use cookies and a request comes to server without origin header, you can't differentiate whether it was made by submitting a form from another domain or by XHR within the same domain. That's why you need additional check with CSRF.

enter image description here

Solution 4

TLDR:

As long as the request is sent(with cookie), there is a possibility of an csrf attack.

SOP(Same-origin-Policy) only don't allow cross-origin reads(except for embedded element such as <script> <img> etc), but allow cross-origin writes.

More specifically, browser use CORS mechanism to get Cross-Origin resource, there is two situations:

  1. Simple requests
    • Browser will add the Origin field, then send to the server.(csrf happen)
  2. Others (Other than Simple requests)
    • CORS preflight was triggered, the request may not be sent to the server.(csrf may happen)

Reference

Share:
15,683

Related videos on Youtube

gabrielgiussi
Author by

gabrielgiussi

Updated on June 11, 2022

Comments

  • gabrielgiussi
    gabrielgiussi about 2 years

    First of all, I assume a backend that control inputs to prevent XSS vulnerabilities.

    In this answer @Les Hazlewood explain how to protect the JWT in the client side.

    Assuming 100% TLS for all communication - both during and at all times after login - authenticating with username/password via basic authentication and receiving a JWT in exchange is a valid use case. This is almost exactly how one of OAuth 2's flows ('password grant') works. [...]

    You just set the Authorization header:

    Authorization: Bearer <JWT value here>
    

    But, that being said, if your REST client is 'untrusted' (e.g. JavaScript-enabled browser), I wouldn't even do that: any value in the HTTP response that is accessible via JavaScript - basically any header value or response body value - could be sniffed and intercepted via MITM XSS attacks.

    It's better to store the JWT value in a secure-only, http-only cookie (cookie config: setSecure(true), setHttpOnly(true)). This guarantees that the browser will:

    1. only ever transmit the cookie over a TLS connection and,
    2. never make the cookie value available to JavaScript code.

    This approach is almost everything you need to do for best-practices security. The last thing is to ensure that you have CSRF protection on every HTTP request to ensure that external domains initiating requests to your site cannot function.

    The easiest way to do this is to set a secure only (but NOT http only) cookie with a random value, e.g. a UUID.

    I don't understand why we need the cookie with the random value to ensure that external domains initiating requests to your site cannot function. This doesn't come free with Same-origin policy?

    From OWASP:

    Checking The Origin Header

    The Origin HTTP Header standard was introduced as a method of defending against CSRF and other Cross-Domain attacks. Unlike the referer, the origin will be present in HTTP request that originates from an HTTPS url.

    If the origin header is present, then it should be checked for consistency.

    I know that the general recommendation from OWASP itself is Synchronizer Token Pattern but I can't see what are the vulnerabilities that remains in:

    • TLS + JWT in secure httpOnly cookie + Same-origin policy + No XSS vulnerabilities.

    UPDATE 1: The same-origin policy only applies to XMLHTTPRequest, so a evil site can make a form POST request easily an this will break my security. An explicit origin header check is needed. The equation would be:

    • TLS + JWT in secure httpOnly cookie + Origin Header check + No XSS vulnerabilities.
    • Bergi
      Bergi over 8 years
      SOP does not prevent sending requests. It does prevent a page from accessing results of cross-domain requests.
    • gabrielgiussi
      gabrielgiussi over 8 years
      @Bergi What about including an explicit control in the backend that checks the origin header? If the check fails I will return an error status code immediately.
  • gabrielgiussi
    gabrielgiussi over 8 years
    It's not true that the Same-origin Policy only applies to reading data and not writing it but if it is that only xmlhttprequest is restricted by the Same-origin policy while a form POST is not.
  • SilverlightFox
    SilverlightFox over 8 years
    @gabrielgiussi: The answer you linked to is specific to the question being asked and shouldn't be taken as a general answer on the topic. The SOP only prevents reads, not writes. In the post you linked to, the cookies were probably not being set from the login POST because the SOP will have prevented the cookies from being read into the browser once set from the login. Check out this answer regarding using the Origin header. Short answer, it's fine if your API is on another domain.
  • Marlon Ou
    Marlon Ou about 6 years
    From your last paragraph, it sounds to me that we want "TLS + JWT stored in secure cookie + JWT in request header + No XSS vulnerabilities." because httpOnly flag is off, so that cookie is still sent cross domain. If we were to turn on httpOnly flag, then csrf wouldn't be a problem? Because as you mentioned cookies are not sent cross domain?
  • gaurav5430
    gaurav5430 almost 4 years
    Umm in your final suggested solution, the jwt in the cookie can be read from client side Javascript? Otherwise how would the app send it in request header?
  • gaurav5430
    gaurav5430 almost 4 years
    If yes, then wouldn't it be susceptible to xss?