From 5993067505cac4c8e80192787ccd1f4cba05d994 Mon Sep 17 00:00:00 2001 From: Jan Larwig Date: Sat, 8 Nov 2025 12:42:45 +0100 Subject: [PATCH] Merge commit from fork Signed-off-by: Jan Larwig --- CHANGELOG.md | 1 + pkg/middleware/headers.go | 37 ++++++++++++++++++++++++++++++---- pkg/middleware/headers_test.go | 21 +++++++++++++++++++ 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbb0974c..20a8f67f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - [#3244](https://github.com/oauth2-proxy/oauth2-proxy/pull/3244) chore(deps): upgrade to latest go1.25.3 (@tuunit) - [#3238](https://github.com/oauth2-proxy/oauth2-proxy/pull/3238) chore: Replace pkg/clock with narrowly targeted stub clocks (@dsymonds) - [#3237](https://github.com/oauth2-proxy/oauth2-proxy/pull/3237) - feat: add option to use organization id for preferred username in Google Provider (@pixeldrew) +- [GHSA-vjrc-mh2v-45x6](https://github.com/oauth2-proxy/oauth2-proxy/security/advisories/GHSA-vjrc-mh2v-45x6) fix: request header smuggling by stripping all normalized header variants (@tuunit) # V7.12.0 diff --git a/pkg/middleware/headers.go b/pkg/middleware/headers.go index 8d2f8e3e..f833848f 100644 --- a/pkg/middleware/headers.go +++ b/pkg/middleware/headers.go @@ -25,10 +25,10 @@ func NewRequestHeaderInjector(headers []options.Header) (alice.Constructor, erro } func newStripHeaders(headers []options.Header) alice.Constructor { - headersToStrip := []string{} + headersToStrip := []options.Header{} for _, header := range headers { if !header.PreserveRequestValue { - headersToStrip = append(headersToStrip, header.Name) + headersToStrip = append(headersToStrip, header) } } @@ -50,10 +50,10 @@ func flattenHeaders(headers http.Header) { } } -func stripHeaders(headers []string, next http.Handler) http.Handler { +func stripHeaders(headers []options.Header, next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { for _, header := range headers { - req.Header.Del(header) + stripNormalizedHeader(req, header) } next.ServeHTTP(rw, req) }) @@ -113,3 +113,32 @@ func injectResponseHeaders(injector header.Injector, next http.Handler) http.Han next.ServeHTTP(rw, req) }) } + +// normalizeHeaderName normalizes the header name by lowercasing it +// and replacing underscores with hyphens. +func normalizeHeaderName(headerName string) string { + headerName = strings.ToLower(headerName) + headerName = strings.ReplaceAll(headerName, "_", "-") + return headerName +} + +// stripNormalizedHeader removes any headers from the request that match +// the normalized version of the provided header's name. +func stripNormalizedHeader(req *http.Request, header options.Header) { + normalizedName := normalizeHeaderName(header.Name) + + toBeDeleted := []string{} + for h := range req.Header { + if normalizeHeaderName(h) == normalizedName { + // necessary to avoid modifying the map while iterating + toBeDeleted = append(toBeDeleted, h) + } + } + + for _, h := range toBeDeleted { + // necessary because req.Header.Del accesses the map via + // the header's canonicalized name. We need to delete by + // the original name. + delete(req.Header, h) + } +} diff --git a/pkg/middleware/headers_test.go b/pkg/middleware/headers_test.go index 06440eea..14937955 100644 --- a/pkg/middleware/headers_test.go +++ b/pkg/middleware/headers_test.go @@ -205,6 +205,27 @@ var _ = Describe("Headers Suite", func() { expectedHeaders: nil, expectedErr: "error building request header injector: error building request injector: error building injector for header \"X-Auth-Request-Authorization\": error loading basicAuthPassword: secret source is invalid: exactly one entry required, specify either value, fromEnv or fromFile", }), + Entry("strips normalized variants before injecting (no preservation)", headersTableInput{ + headers: []options.Header{ + { + Name: "X-Auth-Request-User", + Values: []options.HeaderValue{ + { + ClaimSource: &options.ClaimSource{Claim: "user"}, + }, + }, + }, + }, + initialHeaders: http.Header{ + "X-Auth-Request-User": []string{"old"}, + "X-Auth_Request_User": []string{"evil"}, + }, + session: &sessionsapi.SessionState{User: "user-123"}, + expectedHeaders: http.Header{ + "X-Auth-Request-User": []string{"user-123"}, + }, + expectedErr: "", + }), ) DescribeTable("the response header injector",