From 7760a5b5062313206a83d4dfa3b2ebc8fbc52fdc Mon Sep 17 00:00:00 2001 From: Erik Dubbelboer Date: Sun, 14 Jul 2024 09:43:40 +0200 Subject: [PATCH] Validate request method Use the same validation as net/http. Fixes https://github.com/valyala/fasthttp/issues/1803 --- bytesconv_table.go | 1 + bytesconv_table_gen.go | 96 ++++++++++++++++++++++++++++++++++++++++++ header.go | 17 ++++++++ header_test.go | 7 ++- 4 files changed, 117 insertions(+), 4 deletions(-) diff --git a/bytesconv_table.go b/bytesconv_table.go index b00ec6a..591470f 100644 --- a/bytesconv_table.go +++ b/bytesconv_table.go @@ -10,3 +10,4 @@ const quotedArgShouldEscapeTable = "\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01 const quotedPathShouldEscapeTable = "\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x01\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01" const validHeaderFieldByteTable = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01\x01\x01\x01\x01\x00\x00\x01\x01\x00\x01\x01\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x01\x00\x01\x00" const validHeaderValueByteTable = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01" +const validMethodValueByteTable = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01\x01\x01\x01\x01\x00\x00\x01\x01\x00\x01\x01\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" diff --git a/bytesconv_table_gen.go b/bytesconv_table_gen.go index 56c33c5..3e66238 100644 --- a/bytesconv_table_gen.go +++ b/bytesconv_table_gen.go @@ -146,6 +146,101 @@ func main() { return table }() + validMethodValueByteTable := [256]byte{ + /* + Same as net/http + + Method = "OPTIONS" ; Section 9.2 + | "GET" ; Section 9.3 + | "HEAD" ; Section 9.4 + | "POST" ; Section 9.5 + | "PUT" ; Section 9.6 + | "DELETE" ; Section 9.7 + | "TRACE" ; Section 9.8 + | "CONNECT" ; Section 9.9 + | extension-method + extension-method = token + token = 1* + */ + '!': 1, + '#': 1, + '$': 1, + '%': 1, + '&': 1, + '\'': 1, + '*': 1, + '+': 1, + '-': 1, + '.': 1, + '0': 1, + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + '6': 1, + '7': 1, + '8': 1, + '9': 1, + 'A': 1, + 'B': 1, + 'C': 1, + 'D': 1, + 'E': 1, + 'F': 1, + 'G': 1, + 'H': 1, + 'I': 1, + 'J': 1, + 'K': 1, + 'L': 1, + 'M': 1, + 'N': 1, + 'O': 1, + 'P': 1, + 'Q': 1, + 'R': 1, + 'S': 1, + 'T': 1, + 'U': 1, + 'W': 1, + 'V': 1, + 'X': 1, + 'Y': 1, + 'Z': 1, + '^': 1, + '_': 1, + '`': 1, + 'a': 1, + 'b': 1, + 'c': 1, + 'd': 1, + 'e': 1, + 'f': 1, + 'g': 1, + 'h': 1, + 'i': 1, + 'j': 1, + 'k': 1, + 'l': 1, + 'm': 1, + 'n': 1, + 'o': 1, + 'p': 1, + 'q': 1, + 'r': 1, + 's': 1, + 't': 1, + 'u': 1, + 'v': 1, + 'w': 1, + 'x': 1, + 'y': 1, + 'z': 1, + '|': 1, + '~': 1, + } + w := bytes.NewBufferString(pre) fmt.Fprintf(w, "const hex2intTable = %q\n", hex2intTable) fmt.Fprintf(w, "const toLowerTable = %q\n", toLowerTable) @@ -154,6 +249,7 @@ func main() { fmt.Fprintf(w, "const quotedPathShouldEscapeTable = %q\n", quotedPathShouldEscapeTable) fmt.Fprintf(w, "const validHeaderFieldByteTable = %q\n", validHeaderFieldByteTable) fmt.Fprintf(w, "const validHeaderValueByteTable = %q\n", validHeaderValueByteTable) + fmt.Fprintf(w, "const validMethodValueByteTable = %q\n", validMethodValueByteTable) if err := os.WriteFile("bytesconv_table.go", w.Bytes(), 0o660); err != nil { log.Fatal(err) diff --git a/header.go b/header.go index 26e5266..bfef5ff 100644 --- a/header.go +++ b/header.go @@ -2855,6 +2855,15 @@ func (h *ResponseHeader) parseFirstLine(buf []byte) (int, error) { return len(buf) - len(bNext), nil } +func isValidMethod(method []byte) bool { + for _, ch := range method { + if validMethodValueByteTable[ch] == 0 { + return false + } + } + return true +} + func (h *RequestHeader) parseFirstLine(buf []byte) (int, error) { bNext := buf var b []byte @@ -2874,6 +2883,14 @@ func (h *RequestHeader) parseFirstLine(buf []byte) (int, error) { return 0, fmt.Errorf("cannot find http request method in %q", buf) } h.method = append(h.method[:0], b[:n]...) + + if !isValidMethod(h.method) { + if h.secureErrorLogMessage { + return 0, errors.New("unsupported http request method") + } + return 0, fmt.Errorf("unsupported http request method %q in %q", h.method, buf) + } + b = b[n+1:] // parse requestURI diff --git a/header_test.go b/header_test.go index 527d499..89eea83 100644 --- a/header_test.go +++ b/header_test.go @@ -2668,10 +2668,6 @@ func TestRequestHeaderReadSuccess(t *testing.T) { testRequestHeaderReadSuccess(t, h, "POST /abc HTTP/1.1\r\nHost: aa.com\r\nContent-Type: adv\r\n\r\n123456", -2, "/abc", "aa.com", "", "adv") - // invalid method - testRequestHeaderReadSuccess(t, h, "POST /foo/bar HTTP/1.1\r\nHost: google.com\r\n\r\nmnbv", - -2, "/foo/bar", "google.com", "", "") - // put request testRequestHeaderReadSuccess(t, h, "PUT /faa HTTP/1.1\r\nHost: aaa.com\r\nContent-Length: 123\r\nContent-Type: aaa\r\n\r\nxwwere", 123, "/faa", "aaa.com", "", "aaa") @@ -2767,6 +2763,9 @@ func TestRequestHeaderReadError(t *testing.T) { // Zero-length header testRequestHeaderReadError(t, h, "GET /foo/bar HTTP/1.1\r\n: zero-key\r\n\r\n") + + // Invalid method + testRequestHeaderReadError(t, h, "G(ET /foo/bar HTTP/1.1\r\n: zero-key\r\n\r\n") } func TestRequestHeaderReadSecuredError(t *testing.T) {