Skip to content

Commit

Permalink
net/http: permit requests with invalid Host headers
Browse files Browse the repository at this point in the history
Historically, the Transport has silently truncated invalid
Host headers at the first '/' or ' ' character. CL 506996 changed
this behavior to reject invalid Host headers entirely.
Unfortunately, Docker appears to rely on the previous behavior.

When sending a HTTP/1 request with an invalid Host, send an empty
Host header. This is safer than truncation: If you care about the
Host, then you should get the one you set; if you don't care,
then an empty Host should be fine.

Continue to fully validate Host headers sent to a proxy,
since proxies generally can't productively forward requests
without a Host.

For #60374
Fixes #61431

Change-Id: If170c7dd860aa20eb58fe32990fc93af832742b6
Reviewed-on: https://go-review.googlesource.com/c/go/+/511155
TryBot-Result: Gopher Robot <[email protected]>
Reviewed-by: Roland Shoemaker <[email protected]>
Run-TryBot: Damien Neil <[email protected]>
  • Loading branch information
neild committed Aug 7, 2023
1 parent 26e0660 commit b9153f6
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 6 deletions.
23 changes: 22 additions & 1 deletion src/net/http/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -591,8 +591,29 @@ func (r *Request) write(w io.Writer, usingProxy bool, extraHeaders Header, waitF
if err != nil {
return err
}
// Validate that the Host header is a valid header in general,
// but don't validate the host itself. This is sufficient to avoid
// header or request smuggling via the Host field.
// The server can (and will, if it's a net/http server) reject
// the request if it doesn't consider the host valid.
if !httpguts.ValidHostHeader(host) {
return errors.New("http: invalid Host header")
// Historically, we would truncate the Host header after '/' or ' '.
// Some users have relied on this truncation to convert a network
// address such as Unix domain socket path into a valid, ignored
// Host header (see https://go.dev/issue/61431).
//
// We don't preserve the truncation, because sending an altered
// header field opens a smuggling vector. Instead, zero out the
// Host header entirely if it isn't valid. (An empty Host is valid;
// see RFC 9112 Section 3.2.)
//
// Return an error if we're sending to a proxy, since the proxy
// probably can't do anything useful with an empty Host header.
if !usingProxy {
host = ""
} else {
return errors.New("http: invalid Host header")
}
}

// According to RFC 6874, an HTTP client, proxy, or other
Expand Down
17 changes: 12 additions & 5 deletions src/net/http/request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -767,16 +767,23 @@ func TestRequestWriteBufferedWriter(t *testing.T) {
}
}

func TestRequestBadHost(t *testing.T) {
func TestRequestBadHostHeader(t *testing.T) {
got := []string{}
req, err := NewRequest("GET", "http://foo/after", nil)
if err != nil {
t.Fatal(err)
}
req.Host = "foo.com with spaces"
req.URL.Host = "foo.com with spaces"
if err := req.Write(logWrites{t, &got}); err == nil {
t.Errorf("Writing request with invalid Host: succeded, want error")
req.Host = "foo.com\nnewline"
req.URL.Host = "foo.com\nnewline"
req.Write(logWrites{t, &got})
want := []string{
"GET /after HTTP/1.1\r\n",
"Host: \r\n",
"User-Agent: " + DefaultUserAgent + "\r\n",
"\r\n",
}
if !reflect.DeepEqual(got, want) {
t.Errorf("Writes = %q\n Want = %q", got, want)
}
}

Expand Down

0 comments on commit b9153f6

Please sign in to comment.