Skip to content

[BUG] Sometimes missing Vary: Origin header may lead to Web cache poisoning #248

Open
@jub0bs

Description

@jub0bs

Is there an existing issue for this?

  • I have searched the existing issues

Current Behavior

The CORS middleware fails to add a Vary: Origin header in responses in some cases where it's needed.

Multiple origins allowed vs CORS request from a disallowed origin

If multiple origins are allowed (but not via the wildcard) and a CORS request comes from a disallowed origin, the response lacks a Vary: Origin header. Here's a failing test case that illustrates the problem:

func TestMultipleOriginsAllowedVersusCorsRequestFromDisallowedOrigin(t *testing.T) {
	cors := CORS(
		AllowedOrigins([]string{
			"https://example.com",
			"https://example.org",
		}),
	)
	r := newRequest(http.MethodGet, "https://backend.com")
	r.Header.Set("Origin", "https://disallowed.com")
	rr := httptest.NewRecorder()

	testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})

	cors(testHandler).ServeHTTP(rr, r)
	resp := rr.Result()
	if resp.Header.Get("Vary") != "Origin" {
		t.Fatal("Origin not listed in Vary header")
	}
}

Why is it problematic? Because caching intermediaries may cache such a response, which would subsequently get served to clients that do issue requests from allowed origins. In essence, this would lead to a limited form of denial of service for as long as that "bad" response remains in the cache.

Multiple origins allowed vs non-CORS request

A similar issue exists when the request lacks an Origin header. Here's a failing test case that illustrates the problem:

func TestMultipleOriginsAllowedVersusNonCorsRequest(t *testing.T) {
	cors := CORS(
		AllowedOrigins([]string{
			"https://example.com",
			"https://example.org",
		}),
	)
	r := newRequest(http.MethodGet, "https://backend.com")
	rr := httptest.NewRecorder()

	testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})

	cors(testHandler).ServeHTTP(rr, r)
	resp := rr.Result()
	if resp.Header.Get("Vary") != "Origin" {
		t.Fatal("Origin not listed in Vary header")
	}
}

Multiple origins allowed via AllowedOriginValidator

Similar issue for users who opt for AllowedOriginValidator to allow multiple origins:

func TestMultipleOriginsAllowedViaCallback(t *testing.T) {
	cors := CORS(
		AllowedOriginValidator(func(origin string) bool {
			switch origin {
			case "https://example.com", "https://example.org":
				return true
			default:
				return false
			}
		}),
	)
	r := newRequest(http.MethodGet, "https://backend.com")
	r.Header.Set("Origin", "https://example.com")
	rr := httptest.NewRecorder()

	testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})

	cors(testHandler).ServeHTTP(rr, r)
	resp := rr.Result()
	if resp.Header.Get("Vary") != "Origin" {
		t.Fatal("Origin not listed in Vary header")
	}
}

Edit: this specific problem was already reported in #244

Expected Behavior

In both cases, the middleware should list Origin in a Vary response header.

Steps To Reproduce

Reproducible in v1.5.1.

Anything else?

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    Status

    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions