From 7845723de27fc623b080b7700e4c0caf36e80d65 Mon Sep 17 00:00:00 2001 From: Dan Kortschak Date: Wed, 6 Sep 2023 07:57:44 +0930 Subject: [PATCH] packetbeat/protos/http: don't panic when host is empty 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 | 25 ++++++++++++++++--------- packetbeat/protos/http/http_test.go | 28 ++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index d9418e40c701..32fe82c619a7 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -136,6 +136,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..211392a0420f 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,26 @@ 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 header, port + return trimSquareBracket(host), int(pi) +} + +func trimSquareBracket(s string) string { + s = strings.TrimPrefix(s, "[") + 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..5669512ec702 100644 --- a/packetbeat/protos/http/http_test.go +++ b/packetbeat/protos/http/http_test.go @@ -1901,6 +1901,34 @@ 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: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: ":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)