From 6347efa5900c34f80384d93df43d11562f9b76a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Levente=20Kov=C3=A1ts?= <22732484+tovask@users.noreply.github.com> Date: Thu, 19 Sep 2024 15:32:03 +0200 Subject: [PATCH] Clone Input when the workflow execution forks (#5621) * clone Input when the workflow forks, add integration test * fix line endings --- cmd/integration-test/workflow.go | 40 +++++++++++++++++++ .../multimatch-value-share-template.yaml | 23 +++++++++++ .../multimatch-value-share-workflow.yaml | 21 ++++++++++ pkg/core/workflow_execute.go | 3 +- 4 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 integration_tests/workflow/multimatch-value-share-template.yaml create mode 100644 integration_tests/workflow/multimatch-value-share-workflow.yaml diff --git a/cmd/integration-test/workflow.go b/cmd/integration-test/workflow.go index e11575f90e..442e5169f6 100644 --- a/cmd/integration-test/workflow.go +++ b/cmd/integration-test/workflow.go @@ -13,6 +13,7 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/templates" "github.com/projectdiscovery/nuclei/v3/pkg/templates/signer" "github.com/projectdiscovery/nuclei/v3/pkg/testutils" + sliceutil "github.com/projectdiscovery/utils/slice" ) var workflowTestcases = []TestCaseInfo{ @@ -25,6 +26,7 @@ var workflowTestcases = []TestCaseInfo{ {Path: "workflow/dns-value-share-workflow.yaml", TestCase: &workflowDnsKeyValueShare{}}, {Path: "workflow/code-value-share-workflow.yaml", TestCase: &workflowCodeKeyValueShare{}, DisableOn: isCodeDisabled}, // isCodeDisabled declared in code.go {Path: "workflow/multiprotocol-value-share-workflow.yaml", TestCase: &workflowMultiProtocolKeyValueShare{}}, + {Path: "workflow/multimatch-value-share-workflow.yaml", TestCase: &workflowMultiMatchKeyValueShare{}}, {Path: "workflow/shared-cookie.yaml", TestCase: &workflowSharedCookies{}}, } @@ -229,6 +231,44 @@ func (h *workflowMultiProtocolKeyValueShare) Execute(filePath string) error { return expectResultsCount(results, 2) } +type workflowMultiMatchKeyValueShare struct{} + +// Execute executes a test case and returns an error if occurred +func (h *workflowMultiMatchKeyValueShare) Execute(filePath string) error { + var receivedData []string + router := httprouter.New() + router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + fmt.Fprintf(w, "This is test matcher text") + }) + router.GET("/path1", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + fmt.Fprintf(w, "href=\"test-value-%s\"", r.URL.Query().Get("v")) + }) + router.GET("/path2", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + body, _ := io.ReadAll(r.Body) + receivedData = append(receivedData, string(body)) + fmt.Fprintf(w, "test-value") + }) + ts := httptest.NewServer(router) + defer ts.Close() + + results, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug) + if err != nil { + return err + } + + // Check if we received the data from both request to /path1 and it is not overwritten by the later one. + // They will appear in brackets because of another bug: https://github.com/orgs/projectdiscovery/discussions/3766 + if !sliceutil.Contains(receivedData, "[test-value-1]") || !sliceutil.Contains(receivedData, "[test-value-2]") { + return fmt.Errorf( + "incorrect data: did not receive both extracted data from the first request!\nReceived Data:\n\t%s\nResults:\n\t%s", + strings.Join(receivedData, "\n\t"), + strings.Join(results, "\n\t"), + ) + } + // The number of expected results is 3: the workflow's Matcher Name based condition check forwards both match, and the other branch with simple subtemplates goes with one + return expectResultsCount(results, 3) +} + type workflowSharedCookies struct{} // Execute executes a test case and returns an error if occurred diff --git a/integration_tests/workflow/multimatch-value-share-template.yaml b/integration_tests/workflow/multimatch-value-share-template.yaml new file mode 100644 index 0000000000..fd66975e26 --- /dev/null +++ b/integration_tests/workflow/multimatch-value-share-template.yaml @@ -0,0 +1,23 @@ +id: multimatch-value-share-template + +info: + name: MultiMatch Value Share Template + author: tovask + severity: info + +http: + - path: + - "{{BaseURL}}/path1?v=1" + - "{{BaseURL}}/path1?v=2" + matchers: + - type: word + name: test-matcher + words: + - "href" + extractors: + - type: regex + part: body + name: extracted + regex: + - 'href="(.*)"' + group: 1 diff --git a/integration_tests/workflow/multimatch-value-share-workflow.yaml b/integration_tests/workflow/multimatch-value-share-workflow.yaml new file mode 100644 index 0000000000..f197f28864 --- /dev/null +++ b/integration_tests/workflow/multimatch-value-share-workflow.yaml @@ -0,0 +1,21 @@ +id: multimatch-value-share-workflow + +info: + name: MultiMatch Value Share Workflow + author: tovask + severity: info + description: Workflow to test value sharing when multiple matches occur in the extractor template + +workflows: + - template: workflow/multimatch-value-share-template.yaml + subtemplates: + - template: workflow/match-1.yaml + subtemplates: + - template: workflow/http-value-share-template-2.yaml + - template: workflow/multimatch-value-share-template.yaml + matchers: + - name: test-matcher + subtemplates: + - template: workflow/match-1.yaml + subtemplates: + - template: workflow/http-value-share-template-2.yaml diff --git a/pkg/core/workflow_execute.go b/pkg/core/workflow_execute.go index 028df4d01f..0b5d7e8722 100644 --- a/pkg/core/workflow_execute.go +++ b/pkg/core/workflow_execute.go @@ -139,7 +139,8 @@ func (e *Engine) runWorkflowStep(template *workflows.WorkflowTemplate, ctx *scan defer swg.Done() // create a new context with the same input but with unset callbacks - subCtx := scan.NewScanContext(ctx.Context(), ctx.Input) + // clone the Input so that other parallel executions won't overwrite the shared variables when subsequent templates are running + subCtx := scan.NewScanContext(ctx.Context(), ctx.Input.Clone()) if err := e.runWorkflowStep(subtemplate, subCtx, results, swg, w); err != nil { gologger.Warning().Msgf(workflowStepExecutionError, subtemplate.Template, err) }