mirror of
https://github.com/projectdiscovery/nuclei
synced 2026-06-08 16:50:47 +00:00
805bcfe76f
Workflow inputs get updated with extracted values, but later executors don't refresh their template contexts from that state. Child subtemplates reuse the same input object, letting sibling state leak across concurrent executions. HTTP race requests also ignore dynamic values when building requests. Fix this by syncing the current workflow input into each executor's template context, cloning inputs passed to subtemplates, and forwarding dynamic values to race request generation. Fixes #5399 Signed-off-by: Dwi Siswanto <git@dw1.io>
191 lines
6.1 KiB
Go
191 lines
6.1 KiB
Go
package core
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http/cookiejar"
|
|
"sync/atomic"
|
|
|
|
"github.com/projectdiscovery/gologger"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/output"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/scan"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/workflows"
|
|
syncutil "github.com/projectdiscovery/utils/sync"
|
|
)
|
|
|
|
const workflowStepExecutionError = "[%s] Could not execute workflow step: %s\n"
|
|
|
|
// executeWorkflow runs a workflow on an input and returns true or false
|
|
func (e *Engine) executeWorkflow(ctx *scan.ScanContext, w *workflows.Workflow) bool {
|
|
results := &atomic.Bool{}
|
|
|
|
// at this point we should be at the start root execution of a workflow tree, hence we create global shared instances
|
|
workflowCookieJar, _ := cookiejar.New(nil)
|
|
ctxArgs := contextargs.New(ctx.Context())
|
|
ctxArgs.MetaInput = ctx.Input.MetaInput
|
|
ctxArgs.CookieJar = workflowCookieJar
|
|
|
|
// we can know the nesting level only at runtime, so the best we can do here is increase template threads by one unit in case it's equal to 1 to allow
|
|
// at least one subtemplate to go through, which it's idempotent to one in-flight template as the parent one is in an idle state
|
|
templateThreads := w.Options.Options.TemplateThreads
|
|
if templateThreads == 1 {
|
|
templateThreads++
|
|
}
|
|
swg, _ := syncutil.New(syncutil.WithSize(templateThreads))
|
|
|
|
for _, template := range w.Workflows {
|
|
newCtx := scan.NewScanContext(ctx.Context(), ctx.Input.Clone())
|
|
if err := e.runWorkflowStep(template, newCtx, results, swg, w); err != nil {
|
|
gologger.Warning().Msgf(workflowStepExecutionError, template.Template, err)
|
|
}
|
|
}
|
|
|
|
swg.Wait()
|
|
|
|
return results.Load()
|
|
}
|
|
|
|
// runWorkflowStep runs a workflow step for the workflow. It executes the workflow
|
|
// in a recursive manner running all subtemplates and matchers.
|
|
func (e *Engine) runWorkflowStep(template *workflows.WorkflowTemplate, ctx *scan.ScanContext, results *atomic.Bool, swg *syncutil.AdaptiveWaitGroup, w *workflows.Workflow) error {
|
|
var firstMatched bool
|
|
var err error
|
|
var mainErr error
|
|
|
|
if len(template.Matchers) == 0 {
|
|
for _, executer := range template.Executers {
|
|
e.syncWorkflowInputToTemplateCtx(executer, ctx.Input)
|
|
executer.Options.Progress.AddToTotal(int64(executer.Executer.Requests()))
|
|
|
|
// Don't print results with subtemplates, only print results on template.
|
|
if len(template.Subtemplates) > 0 {
|
|
ctx.OnResult = func(result *output.InternalWrappedEvent) {
|
|
if result.OperatorsResult == nil {
|
|
return
|
|
}
|
|
if len(result.Results) > 0 {
|
|
firstMatched = true
|
|
}
|
|
|
|
if result.OperatorsResult != nil && result.OperatorsResult.Extracts != nil {
|
|
for k, v := range result.OperatorsResult.Extracts {
|
|
// normalize items:
|
|
switch len(v) {
|
|
case 0, 1:
|
|
// - key:[item] => key: item
|
|
ctx.Input.Set(k, v[0])
|
|
default:
|
|
// - key:[item_0, ..., item_n] => key0:item_0, keyn:item_n
|
|
for vIdx, vVal := range v {
|
|
normalizedKIdx := fmt.Sprintf("%s%d", k, vIdx)
|
|
ctx.Input.Set(normalizedKIdx, vVal)
|
|
}
|
|
// also add the original name with full slice
|
|
ctx.Input.Set(k, v)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
_, err = executer.Executer.ExecuteWithResults(ctx)
|
|
} else {
|
|
var matched bool
|
|
matched, err = executer.Executer.Execute(ctx)
|
|
if matched {
|
|
firstMatched = true
|
|
}
|
|
}
|
|
if w.Options.HostErrorsCache != nil {
|
|
w.Options.HostErrorsCache.MarkFailedOrRemove(w.Options.ProtocolType.String(), ctx.Input, err)
|
|
}
|
|
if err != nil {
|
|
if len(template.Executers) == 1 {
|
|
mainErr = err
|
|
} else {
|
|
gologger.Warning().Msgf(workflowStepExecutionError, template.Template, err)
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
if len(template.Subtemplates) == 0 {
|
|
results.CompareAndSwap(false, firstMatched)
|
|
}
|
|
if len(template.Matchers) > 0 {
|
|
for _, executer := range template.Executers {
|
|
e.syncWorkflowInputToTemplateCtx(executer, ctx.Input)
|
|
executer.Options.Progress.AddToTotal(int64(executer.Executer.Requests()))
|
|
|
|
ctx.OnResult = func(event *output.InternalWrappedEvent) {
|
|
if event.OperatorsResult == nil {
|
|
return
|
|
}
|
|
|
|
if event.OperatorsResult.Extracts != nil {
|
|
for k, v := range event.OperatorsResult.Extracts {
|
|
ctx.Input.Set(k, v)
|
|
}
|
|
}
|
|
|
|
for _, matcher := range template.Matchers {
|
|
if !matcher.Match(event.OperatorsResult) {
|
|
continue
|
|
}
|
|
|
|
for _, subtemplate := range matcher.Subtemplates {
|
|
swg.Add()
|
|
|
|
go func(subtemplate *workflows.WorkflowTemplate) {
|
|
defer swg.Done()
|
|
|
|
// create a new context with the same input but with unset callbacks
|
|
// 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)
|
|
}
|
|
}(subtemplate)
|
|
}
|
|
}
|
|
}
|
|
_, err := executer.Executer.ExecuteWithResults(ctx)
|
|
if err != nil {
|
|
if len(template.Executers) == 1 {
|
|
mainErr = err
|
|
} else {
|
|
gologger.Warning().Msgf(workflowStepExecutionError, template.Template, err)
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
return mainErr
|
|
}
|
|
if len(template.Subtemplates) > 0 && firstMatched {
|
|
for _, subtemplate := range template.Subtemplates {
|
|
swg.Add()
|
|
|
|
go func(template *workflows.WorkflowTemplate) {
|
|
// create a new context with the same input but with unset callbacks
|
|
subCtx := scan.NewScanContext(ctx.Context(), ctx.Input.Clone())
|
|
if err := e.runWorkflowStep(template, subCtx, results, swg, w); err != nil {
|
|
gologger.Warning().Msgf(workflowStepExecutionError, template.Template, err)
|
|
}
|
|
swg.Done()
|
|
}(subtemplate)
|
|
}
|
|
}
|
|
return mainErr
|
|
}
|
|
|
|
func (e *Engine) syncWorkflowInputToTemplateCtx(executer *workflows.ProtocolExecuterPair, input *contextargs.Context) {
|
|
if executer == nil || executer.Options == nil || input == nil || !input.HasArgs() {
|
|
return
|
|
}
|
|
|
|
workflowValues := input.GetAll()
|
|
if len(workflowValues) == 0 {
|
|
return
|
|
}
|
|
|
|
executer.Options.GetTemplateCtx(input.MetaInput).Merge(workflowValues)
|
|
}
|