From 2d662650bb88913dd43764054addce4d20022892 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 | 20 ++++++++++---------- packetbeat/protos/http/http_test.go | 25 +++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index d9418e40c701..eeb5a93af85f 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}[] *Winlogbeat* diff --git a/packetbeat/protos/http/http.go b/packetbeat/protos/http/http.go index 6c291e8e83a4..d377139d99e8 100644 --- a/packetbeat/protos/http/http.go +++ b/packetbeat/protos/http/http.go @@ -735,20 +735,20 @@ 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 { + return 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] + host = strings.TrimPrefix(host, "[") + host = strings.TrimSuffix(host, "]") + pi, err := strconv.ParseInt(ps, 10, 16) + if err != nil || pi == 0 { + return header, port } - return header, port + return host, int(pi) } func (http *httpPlugin) hideHeaders(m *message) { diff --git a/packetbeat/protos/http/http_test.go b/packetbeat/protos/http/http_test.go index 73b549894e6d..d4632bb7e493 100644 --- a/packetbeat/protos/http/http_test.go +++ b/packetbeat/protos/http/http_test.go @@ -1901,6 +1901,31 @@ 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: "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: "[::]:9001", wantHost: "::", wantPort: 9001}, + {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)