Skip to content

Commit b1959cf

Browse files
FiloSottilecherrymui
authored andcommitted
[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 <[email protected]> Reviewed-by: Cherry Mui <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent cdd8cf4 commit b1959cf

File tree

2 files changed

+31
-8
lines changed

2 files changed

+31
-8
lines changed

src/net/http/csrf.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,21 @@ func (c *CrossOriginProtection) AddTrustedOrigin(origin string) error {
7777
return nil
7878
}
7979

80-
var noopHandler = HandlerFunc(func(w ResponseWriter, r *Request) {})
80+
type noopHandler struct{}
81+
82+
func (noopHandler) ServeHTTP(ResponseWriter, *Request) {}
83+
84+
var sentinelHandler Handler = &noopHandler{}
8185

8286
// AddInsecureBypassPattern permits all requests that match the given pattern.
83-
// The pattern syntax and precedence rules are the same as [ServeMux].
8487
//
85-
// AddInsecureBypassPattern can be called concurrently with other methods
86-
// or request handling, and applies to future requests.
88+
// The pattern syntax and precedence rules are the same as [ServeMux]. Only
89+
// requests that match the pattern directly are permitted. Those that ServeMux
90+
// would redirect to a pattern (e.g. after cleaning the path or adding a
91+
// trailing slash) are not.
92+
//
93+
// AddInsecureBypassPattern can be called concurrently with other methods or
94+
// request handling, and applies to future requests.
8795
func (c *CrossOriginProtection) AddInsecureBypassPattern(pattern string) {
8896
var bypass *ServeMux
8997

@@ -99,7 +107,7 @@ func (c *CrossOriginProtection) AddInsecureBypassPattern(pattern string) {
99107
}
100108
}
101109

102-
bypass.Handle(pattern, noopHandler)
110+
bypass.Handle(pattern, sentinelHandler)
103111
}
104112

105113
// SetDenyHandler sets a handler to invoke when a request is rejected.
@@ -172,7 +180,7 @@ var (
172180
// be deferred until the last moment.
173181
func (c *CrossOriginProtection) isRequestExempt(req *Request) bool {
174182
if bypass := c.bypass.Load(); bypass != nil {
175-
if _, pattern := bypass.Handler(req); pattern != "" {
183+
if h, _ := bypass.Handler(req); h == sentinelHandler {
176184
// The request matches a bypass pattern.
177185
return true
178186
}

src/net/http/csrf_test.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ func TestCrossOriginProtectionPatternBypass(t *testing.T) {
113113
protection := http.NewCrossOriginProtection()
114114
protection.AddInsecureBypassPattern("/bypass/")
115115
protection.AddInsecureBypassPattern("/only/{foo}")
116+
protection.AddInsecureBypassPattern("/no-trailing")
117+
protection.AddInsecureBypassPattern("/yes-trailing/")
118+
protection.AddInsecureBypassPattern("PUT /put-only/")
119+
protection.AddInsecureBypassPattern("GET /get-only/")
120+
protection.AddInsecureBypassPattern("POST /post-only/")
116121
handler := protection.Handler(okHandler)
117122

118123
tests := []struct {
@@ -126,13 +131,23 @@ func TestCrossOriginProtectionPatternBypass(t *testing.T) {
126131
{"non-bypass path without sec-fetch-site", "/api/", "", http.StatusForbidden},
127132
{"non-bypass path with cross-site", "/api/", "cross-site", http.StatusForbidden},
128133

129-
{"redirect to bypass path without ..", "/foo/../bypass/bar", "", http.StatusOK},
130-
{"redirect to bypass path with trailing slash", "/bypass", "", http.StatusOK},
134+
{"redirect to bypass path without ..", "/foo/../bypass/bar", "", http.StatusForbidden},
135+
{"redirect to bypass path with trailing slash", "/bypass", "", http.StatusForbidden},
131136
{"redirect to non-bypass path with ..", "/foo/../api/bar", "", http.StatusForbidden},
132137
{"redirect to non-bypass path with trailing slash", "/api", "", http.StatusForbidden},
133138

134139
{"wildcard bypass", "/only/123", "", http.StatusOK},
135140
{"non-wildcard", "/only/123/foo", "", http.StatusForbidden},
141+
142+
// https://go.dev/issue/75054
143+
{"no trailing slash exact match", "/no-trailing", "", http.StatusOK},
144+
{"no trailing slash with slash", "/no-trailing/", "", http.StatusForbidden},
145+
{"yes trailing slash exact match", "/yes-trailing/", "", http.StatusOK},
146+
{"yes trailing slash without slash", "/yes-trailing", "", http.StatusForbidden},
147+
148+
{"method-specific hit", "/post-only/", "", http.StatusOK},
149+
{"method-specific miss (PUT)", "/put-only/", "", http.StatusForbidden},
150+
{"method-specific miss (GET)", "/get-only/", "", http.StatusForbidden},
136151
}
137152

138153
for _, tc := range tests {

0 commit comments

Comments
 (0)