Skip to content

Commit 0410005

Browse files
FiloSottilekatiehockman
authored andcommitted
[release-branch.go1.16] net/http/httputil: always remove hop-by-hop headers
Previously, we'd fail to remove the Connection header from a request like this: Connection: Connection: x-header Updates #46313 Fixes #46315 Fixes CVE-2021-33197 Change-Id: Ie3009e926ceecfa86dfa6bcc6fe14ff01086be7d Reviewed-on: https://go-review.googlesource.com/c/go/+/321929 Run-TryBot: Filippo Valsorda <[email protected]> Reviewed-by: Katie Hockman <[email protected]> Trust: Katie Hockman <[email protected]> Trust: Filippo Valsorda <[email protected]> TryBot-Result: Go Bot <[email protected]> (cherry picked from commit 950fa11) Reviewed-on: https://go-review.googlesource.com/c/go/+/323090
1 parent 895fb1b commit 0410005

File tree

2 files changed

+70
-15
lines changed

2 files changed

+70
-15
lines changed

src/net/http/httputil/reverseproxy.go

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -248,22 +248,18 @@ func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
248248
// important is "Connection" because we want a persistent
249249
// connection, regardless of what the client sent to us.
250250
for _, h := range hopHeaders {
251-
hv := outreq.Header.Get(h)
252-
if hv == "" {
253-
continue
254-
}
255-
if h == "Te" && hv == "trailers" {
256-
// Issue 21096: tell backend applications that
257-
// care about trailer support that we support
258-
// trailers. (We do, but we don't go out of
259-
// our way to advertise that unless the
260-
// incoming client request thought it was
261-
// worth mentioning)
262-
continue
263-
}
264251
outreq.Header.Del(h)
265252
}
266253

254+
// Issue 21096: tell backend applications that care about trailer support
255+
// that we support trailers. (We do, but we don't go out of our way to
256+
// advertise that unless the incoming client request thought it was worth
257+
// mentioning.) Note that we look at req.Header, not outreq.Header, since
258+
// the latter has passed through removeConnectionHeaders.
259+
if httpguts.HeaderValuesContainsToken(req.Header["Te"], "trailers") {
260+
outreq.Header.Set("Te", "trailers")
261+
}
262+
267263
// After stripping all the hop-by-hop connection headers above, add back any
268264
// necessary for protocol upgrades, such as for websockets.
269265
if reqUpType != "" {

src/net/http/httputil/reverseproxy_test.go

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,9 @@ func TestReverseProxy(t *testing.T) {
9090

9191
getReq, _ := http.NewRequest("GET", frontend.URL, nil)
9292
getReq.Host = "some-name"
93-
getReq.Header.Set("Connection", "close")
94-
getReq.Header.Set("Te", "trailers")
93+
getReq.Header.Set("Connection", "close, TE")
94+
getReq.Header.Add("Te", "foo")
95+
getReq.Header.Add("Te", "bar, trailers")
9596
getReq.Header.Set("Proxy-Connection", "should be deleted")
9697
getReq.Header.Set("Upgrade", "foo")
9798
getReq.Close = true
@@ -235,6 +236,64 @@ func TestReverseProxyStripHeadersPresentInConnection(t *testing.T) {
235236
}
236237
}
237238

239+
func TestReverseProxyStripEmptyConnection(t *testing.T) {
240+
// See Issue 46313.
241+
const backendResponse = "I am the backend"
242+
243+
// someConnHeader is some arbitrary header to be declared as a hop-by-hop header
244+
// in the Request's Connection header.
245+
const someConnHeader = "X-Some-Conn-Header"
246+
247+
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
248+
if c := r.Header.Values("Connection"); len(c) != 0 {
249+
t.Errorf("handler got header %q = %v; want empty", "Connection", c)
250+
}
251+
if c := r.Header.Get(someConnHeader); c != "" {
252+
t.Errorf("handler got header %q = %q; want empty", someConnHeader, c)
253+
}
254+
w.Header().Add("Connection", "")
255+
w.Header().Add("Connection", someConnHeader)
256+
w.Header().Set(someConnHeader, "should be deleted")
257+
io.WriteString(w, backendResponse)
258+
}))
259+
defer backend.Close()
260+
backendURL, err := url.Parse(backend.URL)
261+
if err != nil {
262+
t.Fatal(err)
263+
}
264+
proxyHandler := NewSingleHostReverseProxy(backendURL)
265+
frontend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
266+
proxyHandler.ServeHTTP(w, r)
267+
if c := r.Header.Get(someConnHeader); c != "should be deleted" {
268+
t.Errorf("handler modified header %q = %q; want %q", someConnHeader, c, "should be deleted")
269+
}
270+
}))
271+
defer frontend.Close()
272+
273+
getReq, _ := http.NewRequest("GET", frontend.URL, nil)
274+
getReq.Header.Add("Connection", "")
275+
getReq.Header.Add("Connection", someConnHeader)
276+
getReq.Header.Set(someConnHeader, "should be deleted")
277+
res, err := frontend.Client().Do(getReq)
278+
if err != nil {
279+
t.Fatalf("Get: %v", err)
280+
}
281+
defer res.Body.Close()
282+
bodyBytes, err := io.ReadAll(res.Body)
283+
if err != nil {
284+
t.Fatalf("reading body: %v", err)
285+
}
286+
if got, want := string(bodyBytes), backendResponse; got != want {
287+
t.Errorf("got body %q; want %q", got, want)
288+
}
289+
if c := res.Header.Get("Connection"); c != "" {
290+
t.Errorf("handler got header %q = %q; want empty", "Connection", c)
291+
}
292+
if c := res.Header.Get(someConnHeader); c != "" {
293+
t.Errorf("handler got header %q = %q; want empty", someConnHeader, c)
294+
}
295+
}
296+
238297
func TestXForwardedFor(t *testing.T) {
239298
const prevForwardedFor = "client ip"
240299
const backendResponse = "I am the backend"

0 commit comments

Comments
 (0)