diff --git a/cmd/integration-test/headless.go b/cmd/integration-test/headless.go index bd2c273c..1452bcbf 100644 --- a/cmd/integration-test/headless.go +++ b/cmd/integration-test/headless.go @@ -12,6 +12,7 @@ import ( var headlessTestcases = []TestCaseInfo{ {Path: "protocols/headless/headless-basic.yaml", TestCase: &headlessBasic{}}, + {Path: "protocols/headless/headless-self-contained.yaml", TestCase: &headlessSelfContained{}}, {Path: "protocols/headless/headless-header-action.yaml", TestCase: &headlessHeaderActions{}}, {Path: "protocols/headless/headless-extract-values.yaml", TestCase: &headlessExtractValues{}}, {Path: "protocols/headless/headless-payloads.yaml", TestCase: &headlessPayloads{}}, @@ -41,6 +42,18 @@ func (h *headlessBasic) Execute(filePath string) error { return expectResultsCount(results, 1) } +type headlessSelfContained struct{} + +// Execute executes a test case and returns an error if occurred +func (h *headlessSelfContained) Execute(filePath string) error { + results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "", debug, "-headless", "-var query=selfcontained") + if err != nil { + return err + } + + return expectResultsCount(results, 1) +} + type headlessLocal struct{} // Execute executes a test case and returns an error if occurred diff --git a/integration_tests/protocols/headless/headless-self-contained.yaml b/integration_tests/protocols/headless/headless-self-contained.yaml new file mode 100644 index 00000000..8f83eaf5 --- /dev/null +++ b/integration_tests/protocols/headless/headless-self-contained.yaml @@ -0,0 +1,20 @@ +id: headless-self-contained +info: + name: Headless Self Contained + author: pdteam + severity: info + tags: headless + +self-contained: true + +headless: + - steps: + - action: navigate + args: + url: "https://postman-echo.com/get?q={{query}}" + + - action: waitload + matchers: + - type: word + words: + - "selfcontained" \ No newline at end of file diff --git a/pkg/protocols/headless/headless.go b/pkg/protocols/headless/headless.go index d2d88efa..f4064799 100644 --- a/pkg/protocols/headless/headless.go +++ b/pkg/protocols/headless/headless.go @@ -59,6 +59,10 @@ type Request struct { // Fuzzing describes schema to fuzz headless requests Fuzzing []*fuzz.Rule `yaml:"fuzzing,omitempty" json:"fuzzing,omitempty" jsonschema:"title=fuzzin rules for http fuzzing,description=Fuzzing describes rule schema to fuzz headless requests"` + // description: | + // SelfContained specifies if the request is self-contained. + SelfContained bool `yaml:"-" json:"-"` + // description: | // CookieReuse is an optional setting that enables cookie reuse CookieReuse bool `yaml:"cookie-reuse,omitempty" json:"cookie-reuse,omitempty" jsonschema:"title=optional cookie reuse enable,description=Optional setting that enables cookie reuse"` diff --git a/pkg/protocols/headless/request.go b/pkg/protocols/headless/request.go index 2cfded16..e09583e6 100644 --- a/pkg/protocols/headless/request.go +++ b/pkg/protocols/headless/request.go @@ -39,6 +39,14 @@ func (request *Request) Type() templateTypes.ProtocolType { // ExecuteWithResults executes the protocol requests and returns results instead of writing them. func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata, previous output.InternalEvent, callback protocols.OutputEventCallback) error { + if request.SelfContained { + url, err := extractBaseURLFromActions(request.Steps) + if err != nil { + return err + } + input = contextargs.NewWithInput(url) + } + if request.options.Browser.UserAgent() == "" { request.options.Browser.SetUserAgent(request.compiledUserAgent) } @@ -86,6 +94,21 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata, return nil } +// This function extracts the base URL from actions. +func extractBaseURLFromActions(steps []*engine.Action) (string, error) { + for _, action := range steps { + if action.ActionType.ActionType == engine.ActionNavigate { + navigateURL := action.GetArg("url") + url, err := urlutil.Parse(navigateURL) + if err != nil { + return "", errors.Errorf("could not parse URL '%s': %s", navigateURL, err.Error()) + } + return fmt.Sprintf("%s://%s", url.Scheme, url.Host), nil + } + } + return "", errors.New("no navigation action found") +} + func (request *Request) executeRequestWithPayloads(input *contextargs.Context, payloads map[string]interface{}, previous output.InternalEvent, callback protocols.OutputEventCallback) error { instance, err := request.options.Browser.NewInstance() if err != nil { @@ -157,7 +180,7 @@ func (request *Request) executeRequestWithPayloads(input *contextargs.Context, p responseBody, _ = html.HTML() } - outputEvent := request.responseToDSLMap(responseBody, out["header"], out["status_code"], reqBuilder.String(), input.MetaInput.Input, input.MetaInput.Input, page.DumpHistory()) + outputEvent := request.responseToDSLMap(responseBody, out["header"], out["status_code"], reqBuilder.String(), input.MetaInput.Input, navigatedURL, page.DumpHistory()) // add response fields to template context and merge templatectx variables to output event request.options.AddTemplateVars(input.MetaInput, request.Type(), request.ID, outputEvent) outputEvent = generators.MergeMaps(outputEvent, request.options.GetTemplateCtx(input.MetaInput).GetAll()) diff --git a/pkg/templates/compile.go b/pkg/templates/compile.go index 5f5e6345..55f329aa 100644 --- a/pkg/templates/compile.go +++ b/pkg/templates/compile.go @@ -110,6 +110,9 @@ func (template *Template) parseSelfContainedRequests() { for _, request := range template.RequestsNetwork { request.SelfContained = true } + for _, request := range template.RequestsHeadless { + request.SelfContained = true + } } // Requests returns the total request count for the template