diff --git a/http_modifier.go b/http_modifier.go index 776db6a9..7e90208d 100644 --- a/http_modifier.go +++ b/http_modifier.go @@ -2,7 +2,9 @@ package main import ( "bytes" + "strings" "hash/fnv" + "encoding/base64" "github.com/buger/goreplay/proto" ) @@ -19,6 +21,7 @@ func NewHTTPModifier(config *HTTPModifierConfig) *HTTPModifier { len(config.headerRewrite) == 0 && len(config.headerFilters) == 0 && len(config.headerNegativeFilters) == 0 && + len(config.headerBasicAuthFilters) == 0 && len(config.headerHashFilters) == 0 && len(config.paramHashFilters) == 0 && len(config.params) == 0 && @@ -111,6 +114,23 @@ func (m *HTTPModifier) Rewrite(payload []byte) (response []byte) { } } + if len(m.config.headerBasicAuthFilters) > 0 { + for _, f := range m.config.headerBasicAuthFilters { + value := proto.Header(payload, []byte("Authorization")) + + if len(value) > 0 { + valueString := string(value) + trimmedBasicAuthEncoded := strings.TrimPrefix(valueString, "Basic ") + if strings.Compare(valueString, trimmedBasicAuthEncoded) != 0 { + decodedAuth, _ := base64.StdEncoding.DecodeString(trimmedBasicAuthEncoded) + if !f.regexp.Match(decodedAuth) { + return + } + } + } + } + } + if len(m.config.headerHashFilters) > 0 { for _, f := range m.config.headerHashFilters { value := proto.Header(payload, f.name) diff --git a/http_modifier_settings.go b/http_modifier_settings.go index e08c5e66..5ae70e62 100644 --- a/http_modifier_settings.go +++ b/http_modifier_settings.go @@ -10,14 +10,15 @@ import ( // HTTPModifierConfig holds configuration options for built-in traffic modifier type HTTPModifierConfig struct { - urlNegativeRegexp HTTPUrlRegexp - urlRegexp HTTPUrlRegexp - urlRewrite UrlRewriteMap - headerRewrite HeaderRewriteMap - headerFilters HTTPHeaderFilters - headerNegativeFilters HTTPHeaderFilters - headerHashFilters HTTPHashFilters - paramHashFilters HTTPHashFilters + urlNegativeRegexp HTTPUrlRegexp + urlRegexp HTTPUrlRegexp + urlRewrite UrlRewriteMap + headerRewrite HeaderRewriteMap + headerFilters HTTPHeaderFilters + headerNegativeFilters HTTPHeaderFilters + headerBasicAuthFilters HTTPHeaderBasicAuthFilters + headerHashFilters HTTPHashFilters + paramHashFilters HTTPHashFilters params HTTPParams headers HTTPHeaders @@ -55,6 +56,32 @@ func (h *HTTPHeaderFilters) Set(value string) error { return nil } +// +// Handling of --http-basic-auth-filter option +// +type basicAuthFilter struct { + regexp *regexp.Regexp +} + +// HTTPHeaderFilters holds list of headers and their regexps +type HTTPHeaderBasicAuthFilters []basicAuthFilter + +func (h *HTTPHeaderBasicAuthFilters) String() string { + return fmt.Sprint(*h) +} + +func (h *HTTPHeaderBasicAuthFilters) Set(value string) error { + r, err := regexp.Compile(value) + if err != nil { + return err + } + + *h = append(*h, basicAuthFilter{regexp: r}) + + return nil +} + + // // Handling of --http-allow-header-hash and --http-allow-param-hash options // diff --git a/http_modifier_test.go b/http_modifier_test.go index 2d25fe96..e39e0af1 100644 --- a/http_modifier_test.go +++ b/http_modifier_test.go @@ -79,6 +79,45 @@ func TestHTTPModifierHeaderNegativeFilters(t *testing.T) { } } +func TestHTTPHeaderBasicAuthFilters(t *testing.T) { + filters := HTTPHeaderBasicAuthFilters{} + filters.Set("^customer[0-9].*") + + modifier := NewHTTPModifier(&HTTPModifierConfig{ + headerBasicAuthFilters: filters, + }) + + //Encoded UserId:Password = customer3:welcome + payload := []byte("POST /post HTTP/1.1\r\nContent-Length: 7\r\nAuthorization: Basic Y3VzdG9tZXIzOndlbGNvbWU=\r\n\r\na=1&b=2") + if len(modifier.Rewrite(payload)) == 0 { + t.Error("Request should pass filters") + } + + //customer6:rest@123^TEST + payload = []byte("POST /post HTTP/1.1\r\nContent-Length: 88\r\nAuthorization: Basic Y3VzdG9tZXI2OnJlc3RAMTIzXlRFU1Q==\r\n\r\na=1&b=2") + if len(modifier.Rewrite(payload)) == 0 { + t.Error("Request should pass filters") + } + + filters = HTTPHeaderBasicAuthFilters{} + // Setting filter that not match our header + filters.Set("^(homer simpson|mickey mouse).*") + + modifier = NewHTTPModifier(&HTTPModifierConfig{ + headerBasicAuthFilters: filters, + }) + + if len(modifier.Rewrite(payload)) != 0 { + t.Error("Request should not pass filters") + } + + //mickey mouse:happy123 + payload = []byte("POST /post HTTP/1.1\r\nContent-Length: 88\r\nAuthorization: Basic bWlja2V5IG1vdXNlOmhhcHB5MTIz\r\n\r\na=1&b=2") + if len(modifier.Rewrite(payload)) == 0 { + t.Error("Request should pass filters") + } +} + func TestHTTPModifierURLRewrite(t *testing.T) { var url, newURL []byte diff --git a/settings.go b/settings.go index 1b80b314..4815282e 100644 --- a/settings.go +++ b/settings.go @@ -178,7 +178,10 @@ func init() { flag.Var(&Settings.modifierConfig.headerNegativeFilters, "http-disallow-header", "A regexp to match a specific header against. Requests with matching headers will be dropped:\n\t gor --input-raw :8080 --output-http staging.com --http-disallow-header \"User-Agent: Replayed by Gor\"") - flag.Var(&Settings.modifierConfig.headerHashFilters, "http-header-limiter", "Takes a fraction of requests, consistently taking or rejecting a request based on the FNV32-1A hash of a specific header:\n\t gor --input-raw :8080 --output-http staging.com --http-header-limiter user-id:25%") + flag.Var(&Settings.modifierConfig.headerBasicAuthFilters, "http-basic-auth-filter", "A regexp to match the decoded basic auth string against. Requests with non-matching headers will be dropped:\n\t gor --input-raw :8080 --output-http staging.com --http-basic-auth-filter \"^customer[0-9].*\"") + + flag.Var(&Settings.modifierConfig.headerHashFilters, "http-header-limiter", "Takes a fraction of requests, consistently taking or rejecting a request based on the FNV32-1A hash of a specific header:\n\t gor --input-raw :8080 --output-http staging.com --http-header-limiter user-id:25%") + flag.Var(&Settings.modifierConfig.headerHashFilters, "output-http-header-hash-filter", "WARNING: `output-http-header-hash-filter` DEPRECATED, use `--http-header-hash-limiter` instead") flag.Var(&Settings.modifierConfig.paramHashFilters, "http-param-limiter", "Takes a fraction of requests, consistently taking or rejecting a request based on the FNV32-1A hash of a specific GET param:\n\t gor --input-raw :8080 --output-http staging.com --http-param-limiter user_id:25%")