diff --git a/Dockerfile b/Dockerfile index 196ba439c..7e50f5196 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Base -FROM golang:1.20.6-alpine AS builder +FROM golang:1.21.4-alpine AS builder RUN apk add --no-cache git build-base gcc musl-dev WORKDIR /app diff --git a/README.md b/README.md index c20a76d65..31a66e2b8 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ # Installation Instructions -`httpx` requires **go1.20** to install successfully. Run the following command to get the repo: +`httpx` requires **go1.21** to install successfully. Run the following command to get the repo: ```sh go install -v github.com/projectdiscovery/httpx/cmd/httpx@latest @@ -118,6 +118,7 @@ PROBES: HEADLESS: -ss, -screenshot enable saving screenshot of the page using headless browser -system-chrome enable using local installed chrome for screenshot + -ho, -headless-options string[] start headless chrome with additional options -esb, -exclude-screenshot-bytes enable excluding screenshot bytes from json output -ehb, -exclude-headless-body enable excluding headless header from json output diff --git a/runner/headless.go b/runner/headless.go index 40b81eb62..fb76e3448 100644 --- a/runner/headless.go +++ b/runner/headless.go @@ -7,6 +7,7 @@ import ( "github.com/go-rod/rod" "github.com/go-rod/rod/lib/launcher" + "github.com/go-rod/rod/lib/launcher/flags" "github.com/go-rod/rod/lib/proto" "github.com/pkg/errors" fileutil "github.com/projectdiscovery/utils/file" @@ -29,7 +30,7 @@ type Browser struct { // pids map[int32]struct{} } -func NewBrowser(proxy string, useLocal bool) (*Browser, error) { +func NewBrowser(proxy string, useLocal bool, optionalArgs map[string]string) (*Browser, error) { dataStore, err := os.MkdirTemp("", "nuclei-*") if err != nil { return nil, errors.Wrap(err, "could not create temporary directory") @@ -74,6 +75,11 @@ func NewBrowser(proxy string, useLocal bool) (*Browser, error) { if proxy != "" { chromeLauncher = chromeLauncher.Proxy(proxy) } + + for k, v := range optionalArgs { + chromeLauncher.Set(flags.Flag(k), v) + } + launcherURL, err := chromeLauncher.Launch() if err != nil { return nil, err diff --git a/runner/options.go b/runner/options.go index 36b9c43f3..898ff8f8e 100644 --- a/runner/options.go +++ b/runner/options.go @@ -287,8 +287,10 @@ type Options struct { UseInstalledChrome bool TlsImpersonate bool DisableStdin bool - NoScreenshotBytes bool - NoHeadlessBody bool + // HeadlessOptionalArguments specifies optional arguments to pass to Chrome + HeadlessOptionalArguments goflags.StringSlice + NoScreenshotBytes bool + NoHeadlessBody bool } // ParseOptions parses the command line options for application @@ -332,6 +334,7 @@ func ParseOptions() *Options { flagSet.CreateGroup("headless", "Headless", flagSet.BoolVarP(&options.Screenshot, "screenshot", "ss", false, "enable saving screenshot of the page using headless browser"), flagSet.BoolVar(&options.UseInstalledChrome, "system-chrome", false, "enable using local installed chrome for screenshot"), + flagSet.StringSliceVarP(&options.HeadlessOptionalArguments, "headless-options", "ho", nil, "start headless chrome with additional options", goflags.FileCommaSeparatedStringSliceOptions), flagSet.BoolVarP(&options.NoScreenshotBytes, "exclude-screenshot-bytes", "esb", false, "enable excluding screenshot bytes from json output"), flagSet.BoolVarP(&options.NoHeadlessBody, "exclude-headless-body", "ehb", false, "enable excluding headless header from json output"), ) @@ -647,6 +650,32 @@ func (options *Options) ValidateOptions() error { return nil } +// redundant with katana +func (options *Options) ParseHeadlessOptionalArguments() map[string]string { + var ( + lastKey string + optionalArguments = make(map[string]string) + ) + for _, v := range options.HeadlessOptionalArguments { + if v == "" { + continue + } + if argParts := strings.SplitN(v, "=", 2); len(argParts) >= 2 { + key := strings.TrimSpace(argParts[0]) + value := strings.TrimSpace(argParts[1]) + if key != "" && value != "" { + optionalArguments[key] = value + lastKey = key + } + } else if !strings.HasPrefix(v, "--") { + optionalArguments[lastKey] += "," + v + } else { + optionalArguments[v] = "" + } + } + return optionalArguments +} + // configureOutput configures the output on the screen func (options *Options) configureOutput() { // If the user desires verbose output, show verbose output diff --git a/runner/runner.go b/runner/runner.go index 85f570c30..4236cdf20 100644 --- a/runner/runner.go +++ b/runner/runner.go @@ -256,7 +256,7 @@ func New(options *Options) (*Runner, error) { scanopts.MaxResponseBodySizeToRead = options.MaxResponseBodySizeToRead scanopts.extractRegexps = make(map[string]*regexp.Regexp) if options.Screenshot { - browser, err := NewBrowser(options.HTTPProxy, options.UseInstalledChrome) + browser, err := NewBrowser(options.HTTPProxy, options.UseInstalledChrome, options.ParseHeadlessOptionalArguments()) if err != nil { return nil, err }