Skip to content

Commit

Permalink
packetbeat/protos/http: don't panic when host is empty (elastic#36518)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
efd6 authored and Scholar-Li committed Feb 5, 2024
1 parent 7cb85bd commit c8855a7
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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*

Expand Down
28 changes: 19 additions & 9 deletions packetbeat/protos/http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package http
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"net"
"net/url"
Expand Down Expand Up @@ -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) {
Expand Down
30 changes: 30 additions & 0 deletions packetbeat/protos/http/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit c8855a7

Please sign in to comment.