CVE-2017-20146: Origin Validation Error (CWE-346)
value: Usage of the CORS handler may apply improper CORS headers, allowing the requester to explicitly control the value of the Access-Control-Allow-Origin header, which bypasses the expected behavior of the Same Origin Policy.
CWE description: The product does not properly verify that the source of data or communication is valid.
CVSS v3 base score: 9.8
Published: 2022-12-27T22:15Z
Repository: https://github.com/gorilla/handlers
Language: Go
Files changed: cors.go, cors_test.go
Commit date: 2017-11-01 10:43:35 -0700
Commit message: [bugfix] Don't return the origin header when configured to * (#116)

There's no reason to allow for a server to reflect all origin headers.
This has caused numerous security problems in the past.
 - https://github.com/cyu/rack-cors/issues/126
 - https://nodesecurity.io/advisories/148
 - https://github.com/captncraig/cors/commit/cc1cf75f5e3c06124602f2aa7893c8f3b1eef0b7

Some helpful blog posts on the topic:
 - https://ejj.io/misconfigured-cors/
 - http://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html
Diff: diff --git a/cors.go b/cors.go
index 1f92d1a..1cf7581 100644
--- a/cors.go
+++ b/cors.go
@@ -108,11 +108,21 @@ func (ch *cors) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
 	if len(ch.allowedOrigins) > 1 {
 		w.Header().Set(corsVaryHeader, corsOriginHeader)
 	}
 
-	w.Header().Set(corsAllowOriginHeader, origin)
+	returnOrigin := origin
+	for _, o := range ch.allowedOrigins {
+		// A configuration of * is different than explicitly setting an allowed
+		// origin. Returning arbitrary origin headers an an access control allow
+		// origin header is unsafe and is not required by any use case.
+		if o == corsOriginMatchAll {
+			returnOrigin = "*"
+			break
+		}
+	}
+	w.Header().Set(corsAllowOriginHeader, returnOrigin)
 
 	if r.Method == corsOptionMethod {
 		return
 	}
 	ch.h.ServeHTTP(w, r)
diff --git a/cors_test.go b/cors_test.go
index c63913e..61eb18f 100644
--- a/cors_test.go
+++ b/cors_test.go
@@ -325,12 +325,47 @@ func TestCORSHandlerWithCustomValidator(t *testing.T) {
 			return true
 		}
 		return false
 	}
 
-	CORS(AllowedOriginValidator(originValidator))(testHandler).ServeHTTP(rr, r)
+	// Specially craft a CORS object.
+	handleFunc := func(h http.Handler) http.Handler {
+		c := &cors{
+			allowedMethods: defaultCorsMethods,
+			allowedHeaders: defaultCorsHeaders,
+			allowedOrigins: []string{"http://a.example.com"},
+			h:              h,
+		}
+		AllowedOriginValidator(originValidator)(c)
+		return c
+	}
+
+	handleFunc(testHandler).ServeHTTP(rr, r)
 	header := rr.HeaderMap.Get(corsAllowOriginHeader)
 	if header != r.URL.String() {
 		t.Fatalf("bad header: expected %s to be %s, got %s.", corsAllowOriginHeader, r.URL.String(), header)
 	}
 
 }
+
+func TestCORSAllowStar(t *testing.T) {
+	r := newRequest("GET", "http://a.example.com")
+	r.Header.Set("Origin", r.URL.String())
+	rr := httptest.NewRecorder()
+
+	testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+	originValidator := func(origin string) bool {
+		if strings.HasSuffix(origin, ".example.com") {
+			return true
+		}
+		return false
+	}
+
+	CORS(AllowedOriginValidator(originValidator))(testHandler).ServeHTTP(rr, r)
+	header := rr.HeaderMap.Get(corsAllowOriginHeader)
+	// Because * is the default CORS policy (which is safe), we should be
+	// expect a * returned here as the Access Control Allow Origin header
+	if header != "*" {
+		t.Fatalf("bad header: expected %s to be %s, got %s.", corsAllowOriginHeader, r.URL.String(), header)
+	}
+
+}

Vulnerable code: 	w.Header().Set(corsAllowOriginHeader, origin)
	CORS(AllowedOriginValidator(originValidator))(testHandler).ServeHTTP(rr, r)
Fixed code: 	returnOrigin := origin
	for _, o := range ch.allowedOrigins {
		// A configuration of * is different than explicitly setting an allowed
		// origin. Returning arbitrary origin headers an an access control allow
		// origin header is unsafe and is not required by any use case.
		if o == corsOriginMatchAll {
			returnOrigin = "*"
			break
		}
	}
	w.Header().Set(corsAllowOriginHeader, returnOrigin)
	// Specially craft a CORS object.
	handleFunc := func(h http.Handler) http.Handler {
		c := &cors{
			allowedMethods: defaultCorsMethods,
			allowedHeaders: defaultCorsHeaders,
			allowedOrigins: []string{"http://a.example.com"},
			h:              h,
		}
		AllowedOriginValidator(originValidator)(c)
		return c
	}

	handleFunc(testHandler).ServeHTTP(rr, r)

func TestCORSAllowStar(t *testing.T) {
	r := newRequest("GET", "http://a.example.com")
	r.Header.Set("Origin", r.URL.String())
	rr := httptest.NewRecorder()

	testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
	originValidator := func(origin string) bool {
		if strings.HasSuffix(origin, ".example.com") {
			return true
		}
		return false
	}

	CORS(AllowedOriginValidator(originValidator))(testHandler).ServeHTTP(rr, r)
	header := rr.HeaderMap.Get(corsAllowOriginHeader)
	// Because * is the default CORS policy (which is safe), we should be
	// expect a * returned here as the Access Control Allow Origin header
	if header != "*" {
		t.Fatalf("bad header: expected %s to be %s, got %s.", corsAllowOriginHeader, r.URL.String(), header)
	}

}