From 7d90713bda6f90f398f42dced466942912b44fd6 Mon Sep 17 00:00:00 2001 From: Erik Dubbelboer Date: Sat, 28 Mar 2026 11:10:23 +0900 Subject: [PATCH] Validate request URI format during header parsing to reject malformed requests (#2168) --- header.go | 30 ++++++++++++++++++++++++++++++ strings.go | 1 + 2 files changed, 31 insertions(+) diff --git a/header.go b/header.go index a28254d..45ce267 100644 --- a/header.go +++ b/header.go @@ -2866,6 +2866,13 @@ func (h *RequestHeader) parseFirstLine(buf []byte) (int, error) { return 0, fmt.Errorf("requestURI cannot be empty in %q", buf) } + if err := validateRequestURI(h.method, b[:n]); err != nil { + if h.secureErrorLogMessage { + return 0, fmt.Errorf("invalid requestURI %q", b[:n]) + } + return 0, fmt.Errorf("invalid requestURI %q in %q: %w", b[:n], buf, err) + } + // Check for extra whitespace - only one space should separate URI from HTTP version if n+1 < len(b) && b[n+1] == ' ' { if h.secureErrorLogMessage { @@ -2903,6 +2910,29 @@ func (h *RequestHeader) parseFirstLine(buf []byte) (int, error) { return len(buf) - len(bNext), nil } +func validateRequestURI(method, requestURI []byte) error { + if stringContainsCTLByte(requestURI) { + return ErrorInvalidURI + } + if len(requestURI) == 1 && requestURI[0] == '*' { + return nil + } + if len(requestURI) > 0 && requestURI[0] == '/' { + return nil + } + if before, _, ok := bytes.Cut(requestURI, strColonSlashSlash); ok { + if !isValidScheme(before) { + return ErrorInvalidURI + } + return nil + } + // net/http treats CONNECT request-targets without a leading slash as authority-form. + if bytes.Equal(method, strConnect) { + return nil + } + return ErrorInvalidURI +} + func readRawHeaders(dst, buf []byte) ([]byte, int, error) { n := bytes.IndexByte(buf, nChar) if n < 0 { diff --git a/strings.go b/strings.go index 60246cf..ea08367 100644 --- a/strings.go +++ b/strings.go @@ -90,6 +90,7 @@ var ( strBytes = []byte("bytes") strBasicSpace = []byte("Basic ") strLink = []byte("Link") + strConnect = []byte("CONNECT") strApplicationSlash = []byte("application/") strImageSVG = []byte("image/svg")