From 260b7eb26678aa78bd6aabe7132994a238be95d1 Mon Sep 17 00:00:00 2001 From: Hidde-Jan Jongsma Date: Fri, 15 Mar 2024 16:49:57 +0100 Subject: [PATCH] Parse http headers as array of strings instead as string (#64) --- internal/capability/http/http.go | 6 ++- models/cacao/cacao.go | 18 ++++---- .../capability/http/http_integration_test.go | 6 +-- .../unittest/capability/openc2/openc2_test.go | 2 +- test/unittest/utils/http/http_test.go | 46 +++++++++---------- utils/http/http.go | 6 ++- 6 files changed, 44 insertions(+), 40 deletions(-) diff --git a/internal/capability/http/http.go b/internal/capability/http/http.go index 4c462685..0d067c68 100644 --- a/internal/capability/http/http.go +++ b/internal/capability/http/http.go @@ -67,8 +67,10 @@ func (httpCapability *HttpCapability) Execute( return cacao.NewVariables(), err } - for key, httpCapability := range command.Headers { - request.Header.Add(key, httpCapability) + for key, httpHeaders := range command.Headers { + for _, httpHeader := range httpHeaders { + request.Header.Add(key, httpHeader) + } } if target.ID != "" { if err := verifyAuthInfoMatchesAgentTarget(&target, &authentication); err != nil { diff --git a/models/cacao/cacao.go b/models/cacao/cacao.go index 291319dd..e37acce7 100644 --- a/models/cacao/cacao.go +++ b/models/cacao/cacao.go @@ -155,15 +155,15 @@ type ExtensionDefinition struct { } type Command struct { - Type string `bson:"type" json:"type" validate:"required"` - Command string `bson:"command" json:"command" validate:"required"` - Description string `bson:"description,omitempty" json:"description,omitempty"` - CommandB64 string `bson:"command_b64,omitempty" json:"command_b64,omitempty"` - Version string `bson:"version,omitempty" json:"version,omitempty"` - PlaybookActivity string `bson:"playbook_activity,omitempty" json:"playbook_activity,omitempty"` - Headers map[string]string `bson:"headers,omitempty" json:"headers,omitempty"` - Content string `bson:"content,omitempty" json:"content,omitempty"` - ContentB64 string `bson:"content_b64,omitempty" json:"content_b64,omitempty"` + Type string `bson:"type" json:"type" validate:"required"` + Command string `bson:"command" json:"command" validate:"required"` + Description string `bson:"description,omitempty" json:"description,omitempty"` + CommandB64 string `bson:"command_b64,omitempty" json:"command_b64,omitempty"` + Version string `bson:"version,omitempty" json:"version,omitempty"` + PlaybookActivity string `bson:"playbook_activity,omitempty" json:"playbook_activity,omitempty"` + Headers map[string][]string `bson:"headers,omitempty" json:"headers,omitempty"` + Content string `bson:"content,omitempty" json:"content,omitempty"` + ContentB64 string `bson:"content_b64,omitempty" json:"content_b64,omitempty"` } type Step struct { diff --git a/test/integration/capability/http/http_integration_test.go b/test/integration/capability/http/http_integration_test.go index 6e146393..398be247 100644 --- a/test/integration/capability/http/http_integration_test.go +++ b/test/integration/capability/http/http_integration_test.go @@ -16,7 +16,7 @@ func TestHttpConnection(t *testing.T) { expectedCommand := cacao.Command{ Type: "http-api", Command: "GET https://httpbin.org/", - Headers: map[string]string{"accept": "application/json"}, + Headers: map[string][]string{"accept": {"application/json"}}, } var variable1 = cacao.Variable{ @@ -56,7 +56,7 @@ func TestHttpOAuth2(t *testing.T) { expectedCommand := cacao.Command{ Type: "http-api", Command: "GET https://httpbin.org/bearer", - Headers: map[string]string{"accept": "application/json"}, + Headers: map[string][]string{"accept": {"application/json"}}, } var variable1 = cacao.Variable{ @@ -102,7 +102,7 @@ func TestHttpBasicAuth(t *testing.T) { expectedCommand := cacao.Command{ Type: "http-api", Command: "GET https://httpbin.org/hidden-basic-auth/username_test/password_test", - Headers: map[string]string{"accept": "application/json"}, + Headers: map[string][]string{"accept": {"application/json"}}, } var variable1 = cacao.Variable{ diff --git a/test/unittest/capability/openc2/openc2_test.go b/test/unittest/capability/openc2/openc2_test.go index cf4eca7b..92cd3df0 100644 --- a/test/unittest/capability/openc2/openc2_test.go +++ b/test/unittest/capability/openc2/openc2_test.go @@ -38,7 +38,7 @@ func TestOpenC2Request(t *testing.T) { command := cacao.Command{ Type: "http-api", Command: "POST / HTTP/1.1", - Headers: map[string]string{"accept": "application/json"}, + Headers: map[string][]string{"accept": {"application/json"}}, } cacaoVariable := cacao.Variable{ diff --git a/test/unittest/utils/http/http_test.go b/test/unittest/utils/http/http_test.go index c1d1b913..5ec6eab5 100644 --- a/test/unittest/utils/http/http_test.go +++ b/test/unittest/utils/http/http_test.go @@ -44,7 +44,7 @@ func TestHttpGetConnection(t *testing.T) { command := cacao.Command{ Type: "http-api", Command: "GET / HTTP/1.1", - Headers: map[string]string{"accept": "application/json"}, + Headers: map[string][]string{"accept": {"application/json"}}, } httpOptions := http.HttpOptions{ Command: &command, @@ -72,7 +72,7 @@ func TestHttpPostConnection(t *testing.T) { command := cacao.Command{ Type: "http-api", Command: "POST / HTTP/1.1", - Headers: map[string]string{"accept": "application/json"}, + Headers: map[string][]string{"accept": {"application/json"}}, } httpOptions := http.HttpOptions{ Command: &command, @@ -99,7 +99,7 @@ func TestHttpPutConnection(t *testing.T) { command := cacao.Command{ Type: "http-api", Command: "PUT / HTTP/1.1", - Headers: map[string]string{"accept": "application/json"}, + Headers: map[string][]string{"accept": {"application/json"}}, } httpOptions := http.HttpOptions{ Command: &command, @@ -125,7 +125,7 @@ func TestHttpDeleteConnection(t *testing.T) { command := cacao.Command{ Type: "http-api", Command: "DELETE / HTTP/1.1", - Headers: map[string]string{"accept": "application/json"}, + Headers: map[string][]string{"accept": {"application/json"}}, } httpOptions := http.HttpOptions{ Command: &command, @@ -153,7 +153,7 @@ func TestHttpStatus200(t *testing.T) { command := cacao.Command{ Type: "http-api", Command: "GET / HTTP/1.1", - Headers: map[string]string{"accept": "application/json"}, + Headers: map[string][]string{"accept": {"application/json"}}, } httpOptions := http.HttpOptions{ Command: &command, @@ -186,7 +186,7 @@ func TestHttpBearerToken(t *testing.T) { command := cacao.Command{ Type: "http-api", Command: "GET / HTTP/1.1", - Headers: map[string]string{"accept": "application/json"}, + Headers: map[string][]string{"accept": {"application/json"}}, } httpOptions := http.HttpOptions{ Command: &command, @@ -219,7 +219,7 @@ func TestHttpBasicAuth(t *testing.T) { target := cacao.AgentTarget{ Address: map[cacao.NetAddressType][]string{ - "url": []string{url}, + "url": {url}, }, AuthInfoIdentifier: "d0c7e6a0-f7fe-464e-9935-e6b3443f5b91", } @@ -234,7 +234,7 @@ func TestHttpBasicAuth(t *testing.T) { command := cacao.Command{ Type: "http-api", Command: "GET / HTTP/1.1", - Headers: map[string]string{"accept": "application/json"}, + Headers: map[string][]string{"accept": {"application/json"}}, } httpOptions := http.HttpOptions{ Command: &command, @@ -281,7 +281,7 @@ func TestHttpPostWithContentConnection(t *testing.T) { command := cacao.Command{ Type: "http-api", Command: "POST / HTTP/1.1", - Headers: map[string]string{"accept": "application/json"}, + Headers: map[string][]string{"accept": {"application/json"}}, ContentB64: base64EncodedBody, } httpOptions := http.HttpOptions{ @@ -313,7 +313,7 @@ func TestHttpPathDnameParser(t *testing.T) { command := cacao.Command{ Type: "http-api", Command: "POST /url HTTP/1.1", - Headers: map[string]string{"accept": "application/json"}, + Headers: map[string][]string{"accept": {"application/json"}}, } httpOptions := http.HttpOptions{ Target: &target, @@ -335,7 +335,7 @@ func TestHttpPathDnamePortParser(t *testing.T) { command := cacao.Command{ Type: "http-api", Command: "POST /url HTTP/1.1", - Headers: map[string]string{"accept": "application/json"}, + Headers: map[string][]string{"accept": {"application/json"}}, } httpOptions := http.HttpOptions{ Target: &target, @@ -357,7 +357,7 @@ func TestHttpPathDnameRandomPortParser(t *testing.T) { command := cacao.Command{ Type: "http-api", Command: "POST /url HTTP/1.1", - Headers: map[string]string{"accept": "application/json"}, + Headers: map[string][]string{"accept": {"application/json"}}, } httpOptions := http.HttpOptions{ Target: &target, @@ -379,7 +379,7 @@ func TestHttpPathIpv4Parser(t *testing.T) { command := cacao.Command{ Type: "http-api", Command: "POST / HTTP/1.1", - Headers: map[string]string{"accept": "application/json"}, + Headers: map[string][]string{"accept": {"application/json"}}, } httpOptions := http.HttpOptions{ Target: &target, @@ -403,7 +403,7 @@ func TestHttpPathParser(t *testing.T) { command := cacao.Command{ Type: "http-api", Command: "POST / HTTP/1.1", - Headers: map[string]string{"accept": "application/json"}, + Headers: map[string][]string{"accept": {"application/json"}}, } httpOptions := http.HttpOptions{ Target: &target, @@ -427,7 +427,7 @@ func TestHttpPathBreakingParser(t *testing.T) { command := cacao.Command{ Type: "http-api", Command: "POST / HTTP/1.1", - Headers: map[string]string{"accept": "application/json"}, + Headers: map[string][]string{"accept": {"application/json"}}, } httpOptions := http.HttpOptions{ Target: &target, @@ -445,7 +445,7 @@ func TestMethodExtract(t *testing.T) { command := cacao.Command{ Type: "http-api", Command: "POST /api1/newObject HTTP/1.1", - Headers: map[string]string{"accept": "application/json"}, + Headers: map[string][]string{"accept": {"application/json"}}, } method, err := http.GetMethodFrom(&command) if err != nil { @@ -458,7 +458,7 @@ func TestPathExtract(t *testing.T) { command := cacao.Command{ Type: "http-api", Command: "POST /api1/newObject HTTP/1.1", - Headers: map[string]string{"accept": "application/json"}, + Headers: map[string][]string{"accept": {"application/json"}}, } path, err := http.GetPathFrom(&command) if err != nil { @@ -471,7 +471,7 @@ func TestVersionExtract(t *testing.T) { command := cacao.Command{ Type: "http-api", Command: "POST /api1/newObject HTTP/1.1", - Headers: map[string]string{"accept": "application/json"}, + Headers: map[string][]string{"accept": {"application/json"}}, } version, err := http.GetVersionFrom(&command) if err != nil { @@ -484,7 +484,7 @@ func TestCommandFailedExtract(t *testing.T) { command := cacao.Command{ Type: "http-api", Command: "POST /api1/newObject", - Headers: map[string]string{"accept": "application/json"}, + Headers: map[string][]string{"accept": {"application/json"}}, } version, err := http.GetVersionFrom(&command) if err == nil { @@ -501,7 +501,7 @@ func TestDnameWithInvalidPathParser(t *testing.T) { command := cacao.Command{ Type: "http-api", Command: "POST /url HTTP/1.1", - Headers: map[string]string{"accept": "application/json"}, + Headers: map[string][]string{"accept": {"application/json"}}, } httpOptions := http.HttpOptions{ Target: &target, @@ -523,7 +523,7 @@ func TestHttpPathIpv4WithRandomPort(t *testing.T) { command := cacao.Command{ Type: "http-api", Command: "POST /url HTTP/1.1", - Headers: map[string]string{"accept": "application/json"}, + Headers: map[string][]string{"accept": {"application/json"}}, } httpOptions := http.HttpOptions{ Target: &target, @@ -545,7 +545,7 @@ func TestInvalidDname(t *testing.T) { command := cacao.Command{ Type: "http-api", Command: "POST /url HTTP/1.1", - Headers: map[string]string{"accept": "application/json"}, + Headers: map[string][]string{"accept": {"application/json"}}, } httpOptions := http.HttpOptions{ Target: &target, @@ -568,7 +568,7 @@ func TestInvalidIpv4(t *testing.T) { command := cacao.Command{ Type: "http-api", Command: "POST /url HTTP/1.1", - Headers: map[string]string{"accept": "application/json"}, + Headers: map[string][]string{"accept": {"application/json"}}, } httpOptions := http.HttpOptions{ Target: &target, diff --git a/utils/http/http.go b/utils/http/http.go index 5fe2de4c..9d531fd5 100644 --- a/utils/http/http.go +++ b/utils/http/http.go @@ -115,8 +115,10 @@ func verifyAuthInfoMatchesAgentTarget( } func (httpOptions *HttpOptions) addHeaderTo(request *http.Request) { - for header_key, header_value := range httpOptions.Command.Headers { - request.Header.Add(header_key, header_value) + for headerKey, headerValues := range httpOptions.Command.Headers { + for _, headerValue := range headerValues { + request.Header.Add(headerKey, headerValue) + } } }