Merge commit from fork

Signed-off-by: Jan Larwig <jan@larwig.com>
This commit is contained in:
Jan Larwig 2025-11-08 12:42:45 +01:00 committed by GitHub
parent 87827435ce
commit 5993067505
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 55 additions and 4 deletions

View File

@ -12,6 +12,7 @@
- [#3244](https://github.com/oauth2-proxy/oauth2-proxy/pull/3244) chore(deps): upgrade to latest go1.25.3 (@tuunit) - [#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) - [#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) - [#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 # V7.12.0

View File

@ -25,10 +25,10 @@ func NewRequestHeaderInjector(headers []options.Header) (alice.Constructor, erro
} }
func newStripHeaders(headers []options.Header) alice.Constructor { func newStripHeaders(headers []options.Header) alice.Constructor {
headersToStrip := []string{} headersToStrip := []options.Header{}
for _, header := range headers { for _, header := range headers {
if !header.PreserveRequestValue { 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) { return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
for _, header := range headers { for _, header := range headers {
req.Header.Del(header) stripNormalizedHeader(req, header)
} }
next.ServeHTTP(rw, req) next.ServeHTTP(rw, req)
}) })
@ -113,3 +113,32 @@ func injectResponseHeaders(injector header.Injector, next http.Handler) http.Han
next.ServeHTTP(rw, req) 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)
}
}

View File

@ -205,6 +205,27 @@ var _ = Describe("Headers Suite", func() {
expectedHeaders: nil, 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", 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", DescribeTable("the response header injector",