From c8855a70e0d54641346d1dd1e4defad3d5a77600 Mon Sep 17 00:00:00 2001 From: Dan Kortschak <90160302+efd6@users.noreply.github.com> Date: Thu, 7 Sep 2023 06:45:16 +0930 Subject: [PATCH] packetbeat/protos/http: don't panic when host is empty (#36518) Previously, extractHostHeader would panic if the host part of header was empty. Avoid this by using standard library functions to do splits and clean up IPv6 addresses. Add tests to confirm old behaviour and test to cover panic case. --- CHANGELOG.next.asciidoc | 1 + packetbeat/protos/http/http.go | 28 ++++++++++++++++++--------- packetbeat/protos/http/http_test.go | 30 +++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 8f016aff7079..5305f7fc08f4 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -137,6 +137,7 @@ https://github.com/elastic/beats/compare/v8.8.1\...main[Check the HEAD diff] *Packetbeat* +- Fix panic in HTTP protocol parsing when host header has empty host part. {issue}36497[36497] {issue}36518[36518] *Winlogbeat* diff --git a/packetbeat/protos/http/http.go b/packetbeat/protos/http/http.go index 6c291e8e83a4..c6347c2b257b 100644 --- a/packetbeat/protos/http/http.go +++ b/packetbeat/protos/http/http.go @@ -20,6 +20,7 @@ package http import ( "bytes" "encoding/base64" + "errors" "fmt" "net" "net/url" @@ -735,20 +736,29 @@ func parseCookieValue(raw string) string { } func extractHostHeader(header string) (host string, port int) { - if len(header) == 0 || net.ParseIP(header) != nil { + if header == "" || net.ParseIP(header) != nil { return header, port } - // Split :port trailer - if pos := strings.LastIndexByte(header, ':'); pos != -1 { - if num, err := strconv.Atoi(header[pos+1:]); err == nil && num > 0 && num < 65536 { - header, port = header[:pos], num + host, ps, err := net.SplitHostPort(header) + if err != nil { + var addrError *net.AddrError + if errors.As(err, &addrError) && addrError.Err == "missing port in address" { + return trimSquareBracket(header), port } } - // Remove square bracket boxing of IPv6 address. - if last := len(header) - 1; header[0] == '[' && header[last] == ']' && net.ParseIP(header[1:last]) != nil { - header = header[1:last] + pi, err := strconv.ParseInt(ps, 10, 16) + if err != nil || pi == 0 { + return header, port + } + return trimSquareBracket(host), int(pi) +} + +func trimSquareBracket(s string) string { + s, ok := strings.CutPrefix(s, "[") + if !ok { + return s } - return header, port + return strings.TrimSuffix(s, "]") } func (http *httpPlugin) hideHeaders(m *message) { diff --git a/packetbeat/protos/http/http_test.go b/packetbeat/protos/http/http_test.go index 73b549894e6d..c8c7a9c73448 100644 --- a/packetbeat/protos/http/http_test.go +++ b/packetbeat/protos/http/http_test.go @@ -1901,6 +1901,36 @@ func TestHttpParser_Extension(t *testing.T) { } } +func TestExtractHostHeader(t *testing.T) { + tests := []struct { + header string + wantHost string + wantPort int + }{ + {header: "", wantHost: "", wantPort: 0}, + {header: "localhost:0", wantHost: "localhost:0", wantPort: 0}, + {header: "127.0.0.1:0", wantHost: "127.0.0.1:0", wantPort: 0}, + {header: "[::]:0", wantHost: "[::]:0", wantPort: 0}, + {header: "localhost", wantHost: "localhost", wantPort: 0}, + {header: "localhost:9001", wantHost: "localhost", wantPort: 9001}, + {header: "localhost:9000000", wantHost: "localhost:9000000", wantPort: 0}, + {header: "127.0.0.1:9001", wantHost: "127.0.0.1", wantPort: 9001}, + {header: "127.0.0.1", wantHost: "127.0.0.1", wantPort: 0}, + {header: "[::]", wantHost: "::", wantPort: 0}, + {header: ":0", wantHost: ":0", wantPort: 0}, + {header: ":9001", wantHost: "", wantPort: 9001}, + } + for _, test := range tests { + host, port := extractHostHeader(test.header) + if host != test.wantHost { + t.Errorf("unexpected host for %q: got:%q want:%q", test.header, host, test.wantHost) + } + if port != test.wantPort { + t.Errorf("unexpected port for %q: got:%d want:%d", test.header, port, test.wantPort) + } + } +} + func benchmarkHTTPMessage(b *testing.B, data []byte) { http := httpModForTests(nil) parser := newParser(&http.parserConfig)