package core import ( "context" "sync" "sync/atomic" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/input/provider" "github.com/projectdiscovery/nuclei/v3/pkg/output" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs" "github.com/projectdiscovery/nuclei/v3/pkg/templates" "github.com/projectdiscovery/nuclei/v3/pkg/templates/types" "github.com/projectdiscovery/nuclei/v3/pkg/types/scanstrategy" stringsutil "github.com/projectdiscovery/utils/strings" syncutil "github.com/projectdiscovery/utils/sync" ) // Execute takes a list of templates/workflows that have been compiled // and executes them based on provided concurrency options. // // All the execution logic for the templates/workflows happens in this part // of the engine. func (e *Engine) Execute(ctx context.Context, templates []*templates.Template, target provider.InputProvider) *atomic.Bool { return e.ExecuteScanWithOpts(ctx, templates, target, false) } // ExecuteWithResults a list of templates with results func (e *Engine) ExecuteWithResults(ctx context.Context, templatesList []*templates.Template, target provider.InputProvider, callback func(*output.ResultEvent)) *atomic.Bool { e.Callback = callback return e.ExecuteScanWithOpts(ctx, templatesList, target, false) } // ExecuteScanWithOpts executes scan with given scanStrategy func (e *Engine) ExecuteScanWithOpts(ctx context.Context, templatesList []*templates.Template, target provider.InputProvider, noCluster bool) *atomic.Bool { results := &atomic.Bool{} selfcontainedWg := &sync.WaitGroup{} totalReqBeforeCluster := getRequestCount(templatesList) * int(target.Count()) // attempt to cluster templates if noCluster is false var finalTemplates []*templates.Template clusterCount := 0 if !noCluster { finalTemplates, clusterCount = templates.ClusterTemplates(templatesList, e.executerOpts) } else { finalTemplates = templatesList } totalReqAfterClustering := getRequestCount(finalTemplates) * int(target.Count()) if !noCluster && totalReqAfterClustering < totalReqBeforeCluster { gologger.Info().Msgf("Templates clustered: %d (Reduced %d Requests)", clusterCount, totalReqBeforeCluster-totalReqAfterClustering) } // 0 matches means no templates were found in the directory if len(finalTemplates) == 0 { return &atomic.Bool{} } if e.executerOpts.Progress != nil { // Notes: // workflow requests are not counted as they can be conditional // templateList count is user requested templates count (before clustering) // totalReqAfterClustering is total requests count after clustering e.executerOpts.Progress.Init(target.Count(), len(templatesList), int64(totalReqAfterClustering)) } if stringsutil.EqualFoldAny(e.options.ScanStrategy, scanstrategy.Auto.String(), "") { // TODO: this is only a placeholder, auto scan strategy should choose scan strategy // based on no of hosts , templates , stream and other optimization parameters e.options.ScanStrategy = scanstrategy.TemplateSpray.String() } filtered := []*templates.Template{} selfContained := []*templates.Template{} // Filter Self Contained templates since they are not bound to target for _, v := range finalTemplates { if v.SelfContained { selfContained = append(selfContained, v) } else { filtered = append(filtered, v) } } // Execute All SelfContained in parallel e.executeAllSelfContained(ctx, selfContained, results, selfcontainedWg) strategyResult := &atomic.Bool{} switch e.options.ScanStrategy { case scanstrategy.TemplateSpray.String(): strategyResult = e.executeTemplateSpray(ctx, filtered, target) case scanstrategy.HostSpray.String(): strategyResult = e.executeHostSpray(ctx, filtered, target) } results.CompareAndSwap(false, strategyResult.Load()) selfcontainedWg.Wait() return results } // executeTemplateSpray executes scan using template spray strategy where targets are iterated over each template func (e *Engine) executeTemplateSpray(ctx context.Context, templatesList []*templates.Template, target provider.InputProvider) *atomic.Bool { results := &atomic.Bool{} // wp is workpool that contains different waitgroups for // headless and non-headless templates wp := e.GetWorkPool() for _, template := range templatesList { select { case <-ctx.Done(): return results default: } // resize check point - nop if there are no changes wp.RefreshWithConfig(e.GetWorkPoolConfig()) templateType := template.Type() var wg *syncutil.AdaptiveWaitGroup if templateType == types.HeadlessProtocol { wg = wp.Headless } else { wg = wp.Default } wg.Add() go func(tpl *templates.Template) { defer wg.Done() // All other request types are executed here // Note: executeTemplateWithTargets creates goroutines and blocks // given template is executed on all targets e.executeTemplateWithTargets(ctx, tpl, target, results) }(template) } wp.Wait() return results } // executeHostSpray executes scan using host spray strategy where templates are iterated over each target func (e *Engine) executeHostSpray(ctx context.Context, templatesList []*templates.Template, target provider.InputProvider) *atomic.Bool { results := &atomic.Bool{} wp, _ := syncutil.New(syncutil.WithSize(e.options.BulkSize + e.options.HeadlessBulkSize)) target.Iterate(func(value *contextargs.MetaInput) bool { select { case <-ctx.Done(): return false default: } wp.Add() go func(targetval *contextargs.MetaInput) { defer wp.Done() e.executeTemplatesOnTarget(ctx, templatesList, targetval, results) }(value) return true }) wp.Wait() return results } // returns total requests count func getRequestCount(templates []*templates.Template) int { count := 0 for _, template := range templates { // ignore requests in workflows as total requests in workflow // depends on what templates will be called in workflow if len(template.Workflows) > 0 { continue } count += template.TotalRequests } return count }