Skip to content

Some malicious/spoofed preflight requests cause prohibitive load #36

@jub0bs

Description

@jub0bs

Problem

This package (v1.2.1 and possibly earlier versions) suffers from a similar (though less severe) form of rs/cors#170. Processing a preflight request with a maliciously long Access-Control-Request-Headers header indeed requires a relatively long time and causes a lot of undue heap allocations. For example, processing a single malicious preflight request whose ACRH header comprises 1MiB's worth of commas takes about 5ms and allocates about 17 MiB:

var testHandler = http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
	_, _ = io.WriteString(w, "bar")
})

func BenchmarkPreflight(b *testing.B) {
	req := httptest.NewRequest(http.MethodOptions, "https://example.org/admin", nil)
	req.Header.Add("Origin", "https://example.com")
	req.Header.Add("Access-Control-Request-Method", http.MethodGet)
	req.Header.Add("Access-Control-Request-Headers", "Authorization")
	handler := Handler(Options{})(testHandler)

	b.ReportAllocs()
	b.ResetTimer()
	for range b.N {
		res := httptest.NewRecorder()
		handler.ServeHTTP(res, req)
	}
}

func BenchmarkPreflightAdversarialACRH(b *testing.B) {
	req := httptest.NewRequest(http.MethodOptions, "https://example.org/admin", nil)
	req.Header.Add("Origin", "https://example.com")
	req.Header.Add("Access-Control-Request-Method", http.MethodGet)
	req.Header.Add("Access-Control-Request-Headers", strings.Repeat(",", http.DefaultMaxHeaderBytes))
	handler := Handler(Options{})(testHandler)

	b.ReportAllocs()
	b.ResetTimer()
	for range b.N {
		res := httptest.NewRecorder()
		handler.ServeHTTP(res, req)
	}
}
go test -run '^$' -bench . -count 8 > bench.out 
benchstat bench.out 
goos: darwin
goarch: amd64
pkg: whatever
cpu: Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz
                           │  bench.out  │
                           │   sec/op    │
Preflight-8                  1.477µ ± 2%
PreflightAdversarialACRH-8   4.890m ± 4%
geomean                      84.97µ

                           │  bench.out   │
                           │     B/op     │
Preflight-8                  1.133Ki ± 0%
PreflightAdversarialACRH-8   17.00Mi ± 0%
geomean                      140.4Ki

                           │ bench.out  │
                           │ allocs/op  │
Preflight-8                  15.00 ± 0%
PreflightAdversarialACRH-8   15.00 ± 0%
geomean 

Impact

This behaviour could be abused by attackers to produce undue load on the middleware/server as an attempt to cause a denial of service. Moreover, because CORS middleware occurs before authentication, attackers wouldn't even need to be authenticated. Sure, most WAFs would likely drop those malicious preflight requests, but not all servers sit behind a WAF.

Solution

See https://github.com/jub0bs/cors/blob/b23eccc884c252107c2c8bd15de090706b6c2759/internal/headers/sortedset.go#L52 for inspiration.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions