Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some malicious/spoofed preflight requests cause prohibitive load #36

Open
jub0bs opened this issue Dec 23, 2024 · 2 comments
Open

Some malicious/spoofed preflight requests cause prohibitive load #36

jub0bs opened this issue Dec 23, 2024 · 2 comments

Comments

@jub0bs
Copy link

jub0bs commented Dec 23, 2024

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.

@pkieltyka
Copy link
Member

nice catch!

are you interested to submit a PR? otherwise we’ll take care of it, thanks again

@jub0bs
Copy link
Author

jub0bs commented Dec 23, 2024

@pkieltyka Thanks. Unless there's some bounty for fixing this in Chi, I don't intend to do it myself, since my time is limited and I've already got my own CORS library to maintain. But feel free to draw inspiration from my implementation, if you believe it suits the project.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants