Skip to content

Commit 6e11f45

Browse files
committed
net/http: make Server validate Host headers
Fixes #11206 (that we accept invalid bytes) Fixes #13624 (that we don't require a Host header in HTTP/1.1 per spec) Change-Id: I4138281d513998789163237e83bb893aeda43336 Reviewed-on: https://go-review.googlesource.com/17892 Reviewed-by: Russ Cox <[email protected]> Run-TryBot: Brad Fitzpatrick <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent c2fb457 commit 6e11f45

File tree

3 files changed

+139
-9
lines changed

3 files changed

+139
-9
lines changed

src/net/http/request.go

+61-2
Original file line numberDiff line numberDiff line change
@@ -689,8 +689,9 @@ func putTextprotoReader(r *textproto.Reader) {
689689
}
690690

691691
// ReadRequest reads and parses an incoming request from b.
692-
func ReadRequest(b *bufio.Reader) (req *Request, err error) {
692+
func ReadRequest(b *bufio.Reader) (req *Request, err error) { return readRequest(b, true) }
693693

694+
func readRequest(b *bufio.Reader, deleteHostHeader bool) (req *Request, err error) {
694695
tp := newTextprotoReader(b)
695696
req = new(Request)
696697

@@ -757,7 +758,9 @@ func ReadRequest(b *bufio.Reader) (req *Request, err error) {
757758
if req.Host == "" {
758759
req.Host = req.Header.get("Host")
759760
}
760-
delete(req.Header, "Host")
761+
if deleteHostHeader {
762+
delete(req.Header, "Host")
763+
}
761764

762765
fixPragmaCacheControl(req.Header)
763766

@@ -1060,3 +1063,59 @@ func (r *Request) isReplayable() bool {
10601063
r.Method == "OPTIONS" ||
10611064
r.Method == "TRACE")
10621065
}
1066+
1067+
func validHostHeader(h string) bool {
1068+
// The latests spec is actually this:
1069+
//
1070+
// http://tools.ietf.org/html/rfc7230#section-5.4
1071+
// Host = uri-host [ ":" port ]
1072+
//
1073+
// Where uri-host is:
1074+
// http://tools.ietf.org/html/rfc3986#section-3.2.2
1075+
//
1076+
// But we're going to be much more lenient for now and just
1077+
// search for any byte that's not a valid byte in any of those
1078+
// expressions.
1079+
for i := 0; i < len(h); i++ {
1080+
if !validHostByte[h[i]] {
1081+
return false
1082+
}
1083+
}
1084+
return true
1085+
}
1086+
1087+
// See the validHostHeader comment.
1088+
var validHostByte = [256]bool{
1089+
'0': true, '1': true, '2': true, '3': true, '4': true, '5': true, '6': true, '7': true,
1090+
'8': true, '9': true,
1091+
1092+
'a': true, 'b': true, 'c': true, 'd': true, 'e': true, 'f': true, 'g': true, 'h': true,
1093+
'i': true, 'j': true, 'k': true, 'l': true, 'm': true, 'n': true, 'o': true, 'p': true,
1094+
'q': true, 'r': true, 's': true, 't': true, 'u': true, 'v': true, 'w': true, 'x': true,
1095+
'y': true, 'z': true,
1096+
1097+
'A': true, 'B': true, 'C': true, 'D': true, 'E': true, 'F': true, 'G': true, 'H': true,
1098+
'I': true, 'J': true, 'K': true, 'L': true, 'M': true, 'N': true, 'O': true, 'P': true,
1099+
'Q': true, 'R': true, 'S': true, 'T': true, 'U': true, 'V': true, 'W': true, 'X': true,
1100+
'Y': true, 'Z': true,
1101+
1102+
'!': true, // sub-delims
1103+
'$': true, // sub-delims
1104+
'%': true, // pct-encoded (and used in IPv6 zones)
1105+
'&': true, // sub-delims
1106+
'(': true, // sub-delims
1107+
')': true, // sub-delims
1108+
'*': true, // sub-delims
1109+
'+': true, // sub-delims
1110+
',': true, // sub-delims
1111+
'-': true, // unreserved
1112+
'.': true, // unreserved
1113+
':': true, // IPv6address + Host expression's optional port
1114+
';': true, // sub-delims
1115+
'=': true, // sub-delims
1116+
'[': true,
1117+
'\'': true, // sub-delims
1118+
']': true,
1119+
'_': true, // unreserved
1120+
'~': true, // unreserved
1121+
}

src/net/http/serve_test.go

+53-5
Original file line numberDiff line numberDiff line change
@@ -2201,7 +2201,7 @@ func TestClientWriteShutdown(t *testing.T) {
22012201
// buffered before chunk headers are added, not after chunk headers.
22022202
func TestServerBufferedChunking(t *testing.T) {
22032203
conn := new(testConn)
2204-
conn.readBuf.Write([]byte("GET / HTTP/1.1\r\n\r\n"))
2204+
conn.readBuf.Write([]byte("GET / HTTP/1.1\r\nHost: foo\r\n\r\n"))
22052205
conn.closec = make(chan bool, 1)
22062206
ls := &oneConnListener{conn}
22072207
go Serve(ls, HandlerFunc(func(rw ResponseWriter, req *Request) {
@@ -2934,9 +2934,9 @@ func TestCodesPreventingContentTypeAndBody(t *testing.T) {
29342934
"GET / HTTP/1.0",
29352935
"GET /header HTTP/1.0",
29362936
"GET /more HTTP/1.0",
2937-
"GET / HTTP/1.1",
2938-
"GET /header HTTP/1.1",
2939-
"GET /more HTTP/1.1",
2937+
"GET / HTTP/1.1\nHost: foo",
2938+
"GET /header HTTP/1.1\nHost: foo",
2939+
"GET /more HTTP/1.1\nHost: foo",
29402940
} {
29412941
got := ht.rawResponse(req)
29422942
wantStatus := fmt.Sprintf("%d %s", code, StatusText(code))
@@ -2957,7 +2957,7 @@ func TestContentTypeOkayOn204(t *testing.T) {
29572957
w.Header().Set("Content-Type", "foo/bar")
29582958
w.WriteHeader(204)
29592959
}))
2960-
got := ht.rawResponse("GET / HTTP/1.1")
2960+
got := ht.rawResponse("GET / HTTP/1.1\nHost: foo")
29612961
if !strings.Contains(got, "Content-Type: foo/bar") {
29622962
t.Errorf("Response = %q; want Content-Type: foo/bar", got)
29632963
}
@@ -3628,6 +3628,54 @@ func testHandlerSetsBodyNil(t *testing.T, h2 bool) {
36283628
}
36293629
}
36303630

3631+
// Test that we validate the Host header.
3632+
func TestServerValidatesHostHeader(t *testing.T) {
3633+
tests := []struct {
3634+
proto string
3635+
host string
3636+
want int
3637+
}{
3638+
{"HTTP/1.1", "", 400},
3639+
{"HTTP/1.1", "Host: \r\n", 200},
3640+
{"HTTP/1.1", "Host: 1.2.3.4\r\n", 200},
3641+
{"HTTP/1.1", "Host: foo.com\r\n", 200},
3642+
{"HTTP/1.1", "Host: foo-bar_baz.com\r\n", 200},
3643+
{"HTTP/1.1", "Host: foo.com:80\r\n", 200},
3644+
{"HTTP/1.1", "Host: ::1\r\n", 200},
3645+
{"HTTP/1.1", "Host: [::1]\r\n", 200}, // questionable without port, but accept it
3646+
{"HTTP/1.1", "Host: [::1]:80\r\n", 200},
3647+
{"HTTP/1.1", "Host: [::1%25en0]:80\r\n", 200},
3648+
{"HTTP/1.1", "Host: 1.2.3.4\r\n", 200},
3649+
{"HTTP/1.1", "Host: \x06\r\n", 400},
3650+
{"HTTP/1.1", "Host: \xff\r\n", 400},
3651+
{"HTTP/1.1", "Host: {\r\n", 400},
3652+
{"HTTP/1.1", "Host: }\r\n", 400},
3653+
{"HTTP/1.1", "Host: first\r\nHost: second\r\n", 400},
3654+
3655+
// HTTP/1.0 can lack a host header, but if present
3656+
// must play by the rules too:
3657+
{"HTTP/1.0", "", 200},
3658+
{"HTTP/1.0", "Host: first\r\nHost: second\r\n", 400},
3659+
{"HTTP/1.0", "Host: \xff\r\n", 400},
3660+
}
3661+
for _, tt := range tests {
3662+
conn := &testConn{closec: make(chan bool)}
3663+
io.WriteString(&conn.readBuf, "GET / "+tt.proto+"\r\n"+tt.host+"\r\n")
3664+
3665+
ln := &oneConnListener{conn}
3666+
go Serve(ln, HandlerFunc(func(ResponseWriter, *Request) {}))
3667+
<-conn.closec
3668+
res, err := ReadResponse(bufio.NewReader(&conn.writeBuf), nil)
3669+
if err != nil {
3670+
t.Errorf("For %s %q, ReadResponse: %v", tt.proto, tt.host, res)
3671+
continue
3672+
}
3673+
if res.StatusCode != tt.want {
3674+
t.Errorf("For %s %q, Status = %d; want %d", tt.proto, tt.host, res.StatusCode, tt.want)
3675+
}
3676+
}
3677+
}
3678+
36313679
func BenchmarkClientServer(b *testing.B) {
36323680
b.ReportAllocs()
36333681
b.StopTimer()

src/net/http/server.go

+25-2
Original file line numberDiff line numberDiff line change
@@ -686,7 +686,7 @@ func (c *conn) readRequest() (w *response, err error) {
686686
peek, _ := c.bufr.Peek(4) // ReadRequest will get err below
687687
c.bufr.Discard(numLeadingCRorLF(peek))
688688
}
689-
req, err := ReadRequest(c.bufr)
689+
req, err := readRequest(c.bufr, false)
690690
c.mu.Unlock()
691691
if err != nil {
692692
if c.r.hitReadLimit() {
@@ -697,6 +697,18 @@ func (c *conn) readRequest() (w *response, err error) {
697697
c.lastMethod = req.Method
698698
c.r.setInfiniteReadLimit()
699699

700+
hosts, haveHost := req.Header["Host"]
701+
if req.ProtoAtLeast(1, 1) && (!haveHost || len(hosts) == 0) {
702+
return nil, badRequestError("missing required Host header")
703+
}
704+
if len(hosts) > 1 {
705+
return nil, badRequestError("too many Host headers")
706+
}
707+
if len(hosts) == 1 && !validHostHeader(hosts[0]) {
708+
return nil, badRequestError("malformed Host header")
709+
}
710+
delete(req.Header, "Host")
711+
700712
req.RemoteAddr = c.remoteAddr
701713
req.TLS = c.tlsState
702714
if body, ok := req.Body.(*body); ok {
@@ -1334,6 +1346,13 @@ func (c *conn) setState(nc net.Conn, state ConnState) {
13341346
}
13351347
}
13361348

1349+
// badRequestError is a literal string (used by in the server in HTML,
1350+
// unescaped) to tell the user why their request was bad. It should
1351+
// be plain text without user info or other embeddded errors.
1352+
type badRequestError string
1353+
1354+
func (e badRequestError) Error() string { return "Bad Request: " + string(e) }
1355+
13371356
// Serve a new connection.
13381357
func (c *conn) serve() {
13391358
c.remoteAddr = c.rwc.RemoteAddr().String()
@@ -1399,7 +1418,11 @@ func (c *conn) serve() {
13991418
if neterr, ok := err.(net.Error); ok && neterr.Timeout() {
14001419
return // don't reply
14011420
}
1402-
io.WriteString(c.rwc, "HTTP/1.1 400 Bad Request\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n400 Bad Request")
1421+
var publicErr string
1422+
if v, ok := err.(badRequestError); ok {
1423+
publicErr = ": " + string(v)
1424+
}
1425+
io.WriteString(c.rwc, "HTTP/1.1 400 Bad Request\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n400 Bad Request"+publicErr)
14031426
return
14041427
}
14051428

0 commit comments

Comments
 (0)