diff --git a/README.md b/README.md index 8e5471ba..5fc98ae7 100644 --- a/README.md +++ b/README.md @@ -367,6 +367,7 @@ Usage: ./gotestwaf [OPTIONS] --url Options: --addDebugHeader Add header "X-GoTestWAF-Test" with a hash of the test information in each request + --addContentLength If present, manually set Content-Length header for requests --addHeader string An HTTP header to add to requests --blockConnReset If present, connection resets will be considered as block --blockRegex string Regex to detect a blocking page with the same HTTP response status code as a not blocked request diff --git a/cmd/gotestwaf/flags.go b/cmd/gotestwaf/flags.go index d3564cda..75381727 100644 --- a/cmd/gotestwaf/flags.go +++ b/cmd/gotestwaf/flags.go @@ -115,6 +115,7 @@ func parseFlags() (args []string, err error) { flag.String("proxy", "", "Proxy URL to use") flag.String("addHeader", "", "An HTTP header to add to requests") flag.Bool("addDebugHeader", false, "Add header \"X-GoTestWAF-Test\" with a hash of the test information in each request") + flag.Bool("addContentLength", false, "If present, manually set Content-Length header for requests") // GoHTTP client only settings flag.Int("maxIdleConns", 2, "The maximum number of keep-alive connections (gohttp only)") diff --git a/internal/config/config.go b/internal/config/config.go index cc38814b..a971b028 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -22,6 +22,7 @@ type Config struct { Proxy string `mapstructure:"proxy"` AddHeader string `mapstructure:"addHeader"` AddDebugHeader bool `mapstructure:"addDebugHeader"` + AddContentLength bool `mapstructure:"addContentLength"` // GoHTTP client only settings MaxIdleConns int `mapstructure:"maxIdleConns"` diff --git a/internal/payload/placeholder/htmlform.go b/internal/payload/placeholder/htmlform.go index 0a428063..8ec9921c 100644 --- a/internal/payload/placeholder/htmlform.go +++ b/internal/payload/placeholder/htmlform.go @@ -5,6 +5,7 @@ import ( "html/template" "net/http" "net/url" + "strconv" "strings" "github.com/chromedp/chromedp" @@ -59,6 +60,10 @@ func (p *HTMLForm) prepareGoHTTPClientRequest(requestURL, payload string, config } req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + // Manually set Content-Length header if configured + if globalConfig != nil && globalConfig.AddContentLength { + req.Header.Set("Content-Length", strconv.Itoa(len(payload))) + } return &types.GoHTTPRequest{Req: req}, nil } diff --git a/internal/payload/placeholder/htmlmultpartform.go b/internal/payload/placeholder/htmlmultpartform.go index de2993eb..ffa82384 100644 --- a/internal/payload/placeholder/htmlmultpartform.go +++ b/internal/payload/placeholder/htmlmultpartform.go @@ -7,6 +7,7 @@ import ( "mime/multipart" "net/http" "net/url" + "strconv" "github.com/chromedp/chromedp" "github.com/wallarm/gotestwaf/internal/scanner/clients/chrome/helpers" @@ -47,32 +48,39 @@ func (p *HTMLMultipartForm) CreateRequest(requestURL, payload string, config Pla } func (p *HTMLMultipartForm) prepareGoHTTPClientRequest(requestURL, payload string, config PlaceholderConfig) (*types.GoHTTPRequest, error) { + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + randomName, err := RandomHex(Seed) if err != nil { return nil, err } - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - - fw, err := writer.CreateFormField(randomName) + part, err := writer.CreateFormFile(randomName, "file") if err != nil { return nil, err } - _, err = fw.Write([]byte(payload)) + _, err = part.Write([]byte(payload)) if err != nil { return nil, err } - writer.Close() + err = writer.Close() + if err != nil { + return nil, err + } - req, err := http.NewRequest(http.MethodPost, requestURL, bytes.NewReader(body.Bytes())) + req, err := http.NewRequest(http.MethodPost, requestURL, body) if err != nil { return nil, err } req.Header.Add("Content-Type", writer.FormDataContentType()) + // Manually set Content-Length header if configured + if globalConfig != nil && globalConfig.AddContentLength { + req.Header.Set("Content-Length", strconv.Itoa(body.Len())) + } return &types.GoHTTPRequest{Req: req}, nil } diff --git a/internal/payload/placeholder/jsonbody.go b/internal/payload/placeholder/jsonbody.go index 98fb1d67..974083cd 100644 --- a/internal/payload/placeholder/jsonbody.go +++ b/internal/payload/placeholder/jsonbody.go @@ -5,6 +5,7 @@ import ( "html/template" "net/http" "net/url" + "strconv" "strings" "github.com/chromedp/chromedp" @@ -52,6 +53,10 @@ func (p *JSONBody) prepareGoHTTPClientRequest(requestURL, payload string, config } req.Header.Add("Content-Type", "application/json") + // Manually set Content-Length header if configured + if globalConfig != nil && globalConfig.AddContentLength { + req.Header.Set("Content-Length", strconv.Itoa(len(payload))) + } return &types.GoHTTPRequest{Req: req}, nil } diff --git a/internal/payload/placeholder/jsonrequest.go b/internal/payload/placeholder/jsonrequest.go index e0127efa..d19ba6d7 100644 --- a/internal/payload/placeholder/jsonrequest.go +++ b/internal/payload/placeholder/jsonrequest.go @@ -5,6 +5,7 @@ import ( "html/template" "net/http" "net/url" + "strconv" "strings" "github.com/chromedp/chromedp" @@ -68,6 +69,10 @@ func (p *JSONRequest) prepareGoHTTPClientRequest(requestURL, payload string, con } req.Header.Add("Content-Type", "application/json") + // Manually set Content-Length header if configured + if globalConfig != nil && globalConfig.AddContentLength { + req.Header.Set("Content-Length", strconv.Itoa(len(payload))) + } return &types.GoHTTPRequest{Req: req}, nil } diff --git a/internal/payload/placeholder/placeholder.go b/internal/payload/placeholder/placeholder.go index 5f431d02..47451930 100644 --- a/internal/payload/placeholder/placeholder.go +++ b/internal/payload/placeholder/placeholder.go @@ -5,6 +5,7 @@ import ( "github.com/wallarm/gotestwaf/internal/helpers" "github.com/wallarm/gotestwaf/internal/scanner/types" + "github.com/wallarm/gotestwaf/internal/config" ) const ( @@ -28,6 +29,13 @@ type PlaceholderConfig interface { helpers.Hash } +var globalConfig *config.Config + +// SetGlobalConfig sets the global configuration for placeholders +func SetGlobalConfig(cfg *config.Config) { + globalConfig = cfg +} + var Placeholders map[string]Placeholder var placeholders = []Placeholder{ diff --git a/internal/payload/placeholder/requestbody.go b/internal/payload/placeholder/requestbody.go index 0f2d3d72..49a7ac89 100644 --- a/internal/payload/placeholder/requestbody.go +++ b/internal/payload/placeholder/requestbody.go @@ -5,6 +5,7 @@ import ( "html/template" "net/http" "net/url" + "strconv" "strings" "github.com/wallarm/gotestwaf/internal/scanner/clients/chrome/helpers" @@ -47,12 +48,16 @@ func (p *RequestBody) CreateRequest(requestURL, payload string, config Placehold } func (p *RequestBody) prepareGoHTTPClientRequest(requestURL, payload string, config PlaceholderConfig) (*types.GoHTTPRequest, error) { - // check if we need to set Content-Length manually here req, err := http.NewRequest(http.MethodPost, requestURL, strings.NewReader(payload)) if err != nil { return nil, err } + // Manually set Content-Length header if configured + if globalConfig != nil && globalConfig.AddContentLength { + req.Header.Set("Content-Length", strconv.Itoa(len(payload))) + } + return &types.GoHTTPRequest{Req: req}, nil } diff --git a/internal/payload/placeholder/soapbody.go b/internal/payload/placeholder/soapbody.go index 1757ca6d..0292f887 100644 --- a/internal/payload/placeholder/soapbody.go +++ b/internal/payload/placeholder/soapbody.go @@ -5,6 +5,7 @@ import ( "html/template" "net/http" "net/url" + "strconv" "strings" "github.com/chromedp/chromedp" @@ -86,6 +87,10 @@ func (p *SOAPBody) prepareGoHTTPClientRequest(requestURL, payload string, config req.Header.Add("SOAPAction", `"http://schemas.xmlsoap.org/soap/actor/next"`) req.Header.Add("Content-Type", "text/xml") + // Manually set Content-Length header if configured + if globalConfig != nil && globalConfig.AddContentLength { + req.Header.Set("Content-Length", strconv.Itoa(len(payload))) + } return &types.GoHTTPRequest{Req: req}, nil } diff --git a/internal/payload/placeholder/xmlbody.go b/internal/payload/placeholder/xmlbody.go index 35c9e95b..487e024f 100644 --- a/internal/payload/placeholder/xmlbody.go +++ b/internal/payload/placeholder/xmlbody.go @@ -5,6 +5,7 @@ import ( "html/template" "net/http" "net/url" + "strconv" "strings" "github.com/chromedp/chromedp" @@ -52,6 +53,10 @@ func (p *XMLBody) prepareGoHTTPClientRequest(requestURL, payload string, config } req.Header.Add("Content-Type", "application/xml") + // Manually set Content-Length header if configured + if globalConfig != nil && globalConfig.AddContentLength { + req.Header.Set("Content-Length", strconv.Itoa(len(payload))) + } return &types.GoHTTPRequest{Req: req}, nil } diff --git a/internal/scanner/scanner.go b/internal/scanner/scanner.go index 7b45feb8..8e15865e 100644 --- a/internal/scanner/scanner.go +++ b/internal/scanner/scanner.go @@ -126,7 +126,7 @@ func New( return nil, errors.Wrap(err, "couldn't create GraphQL client") } - return &Scanner{ + scanner := &Scanner{ logger: logger, cfg: cfg, db: db, @@ -136,7 +136,12 @@ func New( requestTemplates: requestTemplates, router: router, enableDebugHeader: enableDebugHeader, - }, nil + } + + // Set global configuration for placeholders + placeholder.SetGlobalConfig(cfg) + + return scanner, nil } func (s *Scanner) CheckIfJavaScriptRequired(ctx context.Context) (bool, error) {