rs/cors

security issue: should not allow rejected requests to go forward

localvar opened this issue · 4 comments

The below example is a minor revised version of the one in the README:

package main

import (
    "net/http"

    "github.com/rs/cors"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        w.Write([]byte("{\"hello\": \"world\"}"))
    })

    // cors.Default() setup the middleware with default options being
    // all origins accepted with simple methods (GET, POST). See
    // documentation below for more options.
    handler := cors.New(cors.Options{AllowedOrigins: []string{"http://foo.com"}}).Handler(mux)
    http.ListenAndServe(":8080", handler)
}

And from the below CURL output, we can see that the response does not have the Access-Control-Allow-Origin header, this is correct. But the problem is the actual request handler was called (the response body is {"hello": "world"}), that is, though an attacker cannot get the result from the browser, he/she can execute a request successfully.

$ curl -X POST -H 'Origin:http://bar.com' http://localhost:8080/ -D -
HTTP/1.1 200 OK
Content-Type: application/json
Vary: Origin
Date: Thu, 28 Jul 2022 01:07:27 GMT
Content-Length: 18

{"hello": "world"}

I think this is working as intended.

One thing to note is that you did a POST request to the endpoint. Browsers will do an OPTIONS request first, to check if they're allowed to do a proper request. The handler shouldn't handle that OPTIONS request.

Since APIs can also serve clients that are not browsers (eg. mobile apps, desktop apps), CORS doesn't apply there, so a valid request shouldn't be blocked if it's missing valid CORS headers.

If you do an OPTIONS request above, it should not execute the handler (that's the important part). If it does, make sure your handler only handles the actual method that you want to handle (eg. POST or GET).

For a simple CORS request, the browser won't do an OPTIONS request, so I still think this is an issue.

You are right, I wasn't aware that Simple Requests exist.

@localvar The behaviour you're observing is normal. CORS is only a mechanism for instructing the browser to selectively relax some of the Same-Origin Policy's restrictions on network access from a set of Web origins. Neither the SOP nor CORS preflight are substitutes for server-side access control.

Besides, as you mentioned, requests that qualify as simple, regardless of any CORS configuration or the client's ability to read the response, will be handled by the server. This fact is well known to cross-origin attackers who want to trick an authenticated victim into performing some state-changing action ("CSRF"); for just one example, see https://jub0bs.com/posts/2022-02-08-cve-2022-21703-writeup/#bypassing-content-type-validation-and-avoiding-cors-preflight