diff options
| author | Damien Neil <dneil@google.com> | 2023-06-28 13:20:08 -0700 |
|---|---|---|
| committer | Damien Neil <dneil@google.com> | 2023-06-29 17:00:06 +0000 |
| commit | 499458f7ca04087958987a33c2703c3ef03e27e2 (patch) | |
| tree | ba0a1dc6e81eb34d677972cb9613ff760bb93e33 /src/net/http/request.go | |
| parent | fe73c186eba2c849a2f2aeaca091ddb5bac3aef1 (diff) | |
| download | go-499458f7ca04087958987a33c2703c3ef03e27e2.tar.xz | |
net/http: validate Host header before sending
Verify that the Host header we send is valid.
Avoids surprising behavior such as a Host of "go.dev\r\nX-Evil:oops"
adding an X-Evil header to HTTP/1 requests.
Add a test, skip the test for HTTP/2. HTTP/2 is not vulnerable to
header injection in the way HTTP/1 is, but x/net/http2 doesn't validate
the header and will go into a retry loop when the server rejects it.
CL 506995 adds the necessary validation to x/net/http2.
For #60374
Change-Id: I05cb6866a9bead043101954dfded199258c6dd04
Reviewed-on: https://go-review.googlesource.com/c/go/+/506996
Reviewed-by: Tatiana Bradley <tatianabradley@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Damien Neil <dneil@google.com>
Diffstat (limited to 'src/net/http/request.go')
| -rw-r--r-- | src/net/http/request.go | 47 |
1 files changed, 10 insertions, 37 deletions
diff --git a/src/net/http/request.go b/src/net/http/request.go index 4e9190493c..bd868373c5 100644 --- a/src/net/http/request.go +++ b/src/net/http/request.go @@ -17,7 +17,6 @@ import ( "io" "mime" "mime/multipart" - "net" "net/http/httptrace" "net/http/internal/ascii" "net/textproto" @@ -27,6 +26,7 @@ import ( "strings" "sync" + "golang.org/x/net/http/httpguts" "golang.org/x/net/idna" ) @@ -580,12 +580,19 @@ func (r *Request) write(w io.Writer, usingProxy bool, extraHeaders Header, waitF // is not given, use the host from the request URL. // // Clean the host, in case it arrives with unexpected stuff in it. - host := cleanHost(r.Host) + host := r.Host if host == "" { if r.URL == nil { return errMissingHost } - host = cleanHost(r.URL.Host) + host = r.URL.Host + } + host, err = httpguts.PunycodeHostPort(host) + if err != nil { + return err + } + if !httpguts.ValidHostHeader(host) { + return errors.New("http: invalid Host header") } // According to RFC 6874, an HTTP client, proxy, or other @@ -742,40 +749,6 @@ func idnaASCII(v string) (string, error) { return idna.Lookup.ToASCII(v) } -// cleanHost cleans up the host sent in request's Host header. -// -// It both strips anything after '/' or ' ', and puts the value -// into Punycode form, if necessary. -// -// Ideally we'd clean the Host header according to the spec: -// -// https://tools.ietf.org/html/rfc7230#section-5.4 (Host = uri-host [ ":" port ]") -// https://tools.ietf.org/html/rfc7230#section-2.7 (uri-host -> rfc3986's host) -// https://tools.ietf.org/html/rfc3986#section-3.2.2 (definition of host) -// -// But practically, what we are trying to avoid is the situation in -// issue 11206, where a malformed Host header used in the proxy context -// would create a bad request. So it is enough to just truncate at the -// first offending character. -func cleanHost(in string) string { - if i := strings.IndexAny(in, " /"); i != -1 { - in = in[:i] - } - host, port, err := net.SplitHostPort(in) - if err != nil { // input was just a host - a, err := idnaASCII(in) - if err != nil { - return in // garbage in, garbage out - } - return a - } - a, err := idnaASCII(host) - if err != nil { - return in // garbage in, garbage out - } - return net.JoinHostPort(a, port) -} - // removeZone removes IPv6 zone identifier from host. // E.g., "[fe80::1%en0]:8080" to "[fe80::1]:8080" func removeZone(host string) string { |
