diff options
| author | Filippo Valsorda <filippo@golang.org> | 2025-08-26 16:52:39 -0400 |
|---|---|---|
| committer | Cherry Mui <cherryyz@google.com> | 2025-08-27 08:45:05 -0700 |
| commit | b1959cf6f7673eaffa89bbdb00e68b30cde3aa8a (patch) | |
| tree | 63750dc2d178f2438b32de70112a18cacb69d7df | |
| parent | cdd8cf4988c7c0f2bb8eb795f74c4f803c63a70d (diff) | |
| download | go-b1959cf6f7673eaffa89bbdb00e68b30cde3aa8a.tar.xz | |
[release-branch.go1.25] net/http: require exact match for CrossSiteProtection bypass patterns
Fixes #75160
Updates #75054
Fixes CVE-2025-47910
Change-Id: I6a6a696440c45c450d2cd681f418b01aa0422a60
Reviewed-on: https://go-review.googlesource.com/c/go/+/699276
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: Cherry Mui <cherryyz@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
| -rw-r--r-- | src/net/http/csrf.go | 20 | ||||
| -rw-r--r-- | src/net/http/csrf_test.go | 19 |
2 files changed, 31 insertions, 8 deletions
diff --git a/src/net/http/csrf.go b/src/net/http/csrf.go index 5e1b686fd1..d088b9b615 100644 --- a/src/net/http/csrf.go +++ b/src/net/http/csrf.go @@ -77,13 +77,21 @@ func (c *CrossOriginProtection) AddTrustedOrigin(origin string) error { return nil } -var noopHandler = HandlerFunc(func(w ResponseWriter, r *Request) {}) +type noopHandler struct{} + +func (noopHandler) ServeHTTP(ResponseWriter, *Request) {} + +var sentinelHandler Handler = &noopHandler{} // AddInsecureBypassPattern permits all requests that match the given pattern. -// The pattern syntax and precedence rules are the same as [ServeMux]. // -// AddInsecureBypassPattern can be called concurrently with other methods -// or request handling, and applies to future requests. +// The pattern syntax and precedence rules are the same as [ServeMux]. Only +// requests that match the pattern directly are permitted. Those that ServeMux +// would redirect to a pattern (e.g. after cleaning the path or adding a +// trailing slash) are not. +// +// AddInsecureBypassPattern can be called concurrently with other methods or +// request handling, and applies to future requests. func (c *CrossOriginProtection) AddInsecureBypassPattern(pattern string) { var bypass *ServeMux @@ -99,7 +107,7 @@ func (c *CrossOriginProtection) AddInsecureBypassPattern(pattern string) { } } - bypass.Handle(pattern, noopHandler) + bypass.Handle(pattern, sentinelHandler) } // SetDenyHandler sets a handler to invoke when a request is rejected. @@ -172,7 +180,7 @@ var ( // be deferred until the last moment. func (c *CrossOriginProtection) isRequestExempt(req *Request) bool { if bypass := c.bypass.Load(); bypass != nil { - if _, pattern := bypass.Handler(req); pattern != "" { + if h, _ := bypass.Handler(req); h == sentinelHandler { // The request matches a bypass pattern. return true } diff --git a/src/net/http/csrf_test.go b/src/net/http/csrf_test.go index 30986a43b9..a29e3ae16d 100644 --- a/src/net/http/csrf_test.go +++ b/src/net/http/csrf_test.go @@ -113,6 +113,11 @@ func TestCrossOriginProtectionPatternBypass(t *testing.T) { protection := http.NewCrossOriginProtection() protection.AddInsecureBypassPattern("/bypass/") protection.AddInsecureBypassPattern("/only/{foo}") + protection.AddInsecureBypassPattern("/no-trailing") + protection.AddInsecureBypassPattern("/yes-trailing/") + protection.AddInsecureBypassPattern("PUT /put-only/") + protection.AddInsecureBypassPattern("GET /get-only/") + protection.AddInsecureBypassPattern("POST /post-only/") handler := protection.Handler(okHandler) tests := []struct { @@ -126,13 +131,23 @@ func TestCrossOriginProtectionPatternBypass(t *testing.T) { {"non-bypass path without sec-fetch-site", "/api/", "", http.StatusForbidden}, {"non-bypass path with cross-site", "/api/", "cross-site", http.StatusForbidden}, - {"redirect to bypass path without ..", "/foo/../bypass/bar", "", http.StatusOK}, - {"redirect to bypass path with trailing slash", "/bypass", "", http.StatusOK}, + {"redirect to bypass path without ..", "/foo/../bypass/bar", "", http.StatusForbidden}, + {"redirect to bypass path with trailing slash", "/bypass", "", http.StatusForbidden}, {"redirect to non-bypass path with ..", "/foo/../api/bar", "", http.StatusForbidden}, {"redirect to non-bypass path with trailing slash", "/api", "", http.StatusForbidden}, {"wildcard bypass", "/only/123", "", http.StatusOK}, {"non-wildcard", "/only/123/foo", "", http.StatusForbidden}, + + // https://go.dev/issue/75054 + {"no trailing slash exact match", "/no-trailing", "", http.StatusOK}, + {"no trailing slash with slash", "/no-trailing/", "", http.StatusForbidden}, + {"yes trailing slash exact match", "/yes-trailing/", "", http.StatusOK}, + {"yes trailing slash without slash", "/yes-trailing", "", http.StatusForbidden}, + + {"method-specific hit", "/post-only/", "", http.StatusOK}, + {"method-specific miss (PUT)", "/put-only/", "", http.StatusForbidden}, + {"method-specific miss (GET)", "/get-only/", "", http.StatusForbidden}, } for _, tc := range tests { |
