Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/core/workflow_execute.go
2843 views
1
package core
2
3
import (
4
"fmt"
5
"net/http/cookiejar"
6
"sync/atomic"
7
8
"github.com/projectdiscovery/gologger"
9
"github.com/projectdiscovery/nuclei/v3/pkg/output"
10
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
11
"github.com/projectdiscovery/nuclei/v3/pkg/scan"
12
"github.com/projectdiscovery/nuclei/v3/pkg/workflows"
13
syncutil "github.com/projectdiscovery/utils/sync"
14
)
15
16
const workflowStepExecutionError = "[%s] Could not execute workflow step: %s\n"
17
18
// executeWorkflow runs a workflow on an input and returns true or false
19
func (e *Engine) executeWorkflow(ctx *scan.ScanContext, w *workflows.Workflow) bool {
20
results := &atomic.Bool{}
21
22
// at this point we should be at the start root execution of a workflow tree, hence we create global shared instances
23
workflowCookieJar, _ := cookiejar.New(nil)
24
ctxArgs := contextargs.New(ctx.Context())
25
ctxArgs.MetaInput = ctx.Input.MetaInput
26
ctxArgs.CookieJar = workflowCookieJar
27
28
// 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
29
// 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
30
templateThreads := w.Options.Options.TemplateThreads
31
if templateThreads == 1 {
32
templateThreads++
33
}
34
swg, _ := syncutil.New(syncutil.WithSize(templateThreads))
35
36
for _, template := range w.Workflows {
37
newCtx := scan.NewScanContext(ctx.Context(), ctx.Input.Clone())
38
if err := e.runWorkflowStep(template, newCtx, results, swg, w); err != nil {
39
gologger.Warning().Msgf(workflowStepExecutionError, template.Template, err)
40
}
41
}
42
43
swg.Wait()
44
45
return results.Load()
46
}
47
48
// runWorkflowStep runs a workflow step for the workflow. It executes the workflow
49
// in a recursive manner running all subtemplates and matchers.
50
func (e *Engine) runWorkflowStep(template *workflows.WorkflowTemplate, ctx *scan.ScanContext, results *atomic.Bool, swg *syncutil.AdaptiveWaitGroup, w *workflows.Workflow) error {
51
var firstMatched bool
52
var err error
53
var mainErr error
54
55
if len(template.Matchers) == 0 {
56
for _, executer := range template.Executers {
57
executer.Options.Progress.AddToTotal(int64(executer.Executer.Requests()))
58
59
// Don't print results with subtemplates, only print results on template.
60
if len(template.Subtemplates) > 0 {
61
ctx.OnResult = func(result *output.InternalWrappedEvent) {
62
if result.OperatorsResult == nil {
63
return
64
}
65
if len(result.Results) > 0 {
66
firstMatched = true
67
}
68
69
if result.OperatorsResult != nil && result.OperatorsResult.Extracts != nil {
70
for k, v := range result.OperatorsResult.Extracts {
71
// normalize items:
72
switch len(v) {
73
case 0, 1:
74
// - key:[item] => key: item
75
ctx.Input.Set(k, v[0])
76
default:
77
// - key:[item_0, ..., item_n] => key0:item_0, keyn:item_n
78
for vIdx, vVal := range v {
79
normalizedKIdx := fmt.Sprintf("%s%d", k, vIdx)
80
ctx.Input.Set(normalizedKIdx, vVal)
81
}
82
// also add the original name with full slice
83
ctx.Input.Set(k, v)
84
}
85
}
86
}
87
}
88
_, err = executer.Executer.ExecuteWithResults(ctx)
89
} else {
90
var matched bool
91
matched, err = executer.Executer.Execute(ctx)
92
if matched {
93
firstMatched = true
94
}
95
}
96
if w.Options.HostErrorsCache != nil {
97
w.Options.HostErrorsCache.MarkFailedOrRemove(w.Options.ProtocolType.String(), ctx.Input, err)
98
}
99
if err != nil {
100
if len(template.Executers) == 1 {
101
mainErr = err
102
} else {
103
gologger.Warning().Msgf(workflowStepExecutionError, template.Template, err)
104
}
105
continue
106
}
107
}
108
}
109
if len(template.Subtemplates) == 0 {
110
results.CompareAndSwap(false, firstMatched)
111
}
112
if len(template.Matchers) > 0 {
113
for _, executer := range template.Executers {
114
executer.Options.Progress.AddToTotal(int64(executer.Executer.Requests()))
115
116
ctx.OnResult = func(event *output.InternalWrappedEvent) {
117
if event.OperatorsResult == nil {
118
return
119
}
120
121
if event.OperatorsResult.Extracts != nil {
122
for k, v := range event.OperatorsResult.Extracts {
123
ctx.Input.Set(k, v)
124
}
125
}
126
127
for _, matcher := range template.Matchers {
128
if !matcher.Match(event.OperatorsResult) {
129
continue
130
}
131
132
for _, subtemplate := range matcher.Subtemplates {
133
swg.Add()
134
135
go func(subtemplate *workflows.WorkflowTemplate) {
136
defer swg.Done()
137
138
// create a new context with the same input but with unset callbacks
139
// clone the Input so that other parallel executions won't overwrite the shared variables when subsequent templates are running
140
subCtx := scan.NewScanContext(ctx.Context(), ctx.Input.Clone())
141
if err := e.runWorkflowStep(subtemplate, subCtx, results, swg, w); err != nil {
142
gologger.Warning().Msgf(workflowStepExecutionError, subtemplate.Template, err)
143
}
144
}(subtemplate)
145
}
146
}
147
}
148
_, err := executer.Executer.ExecuteWithResults(ctx)
149
if err != nil {
150
if len(template.Executers) == 1 {
151
mainErr = err
152
} else {
153
gologger.Warning().Msgf(workflowStepExecutionError, template.Template, err)
154
}
155
continue
156
}
157
}
158
return mainErr
159
}
160
if len(template.Subtemplates) > 0 && firstMatched {
161
for _, subtemplate := range template.Subtemplates {
162
swg.Add()
163
164
go func(template *workflows.WorkflowTemplate) {
165
// create a new context with the same input but with unset callbacks
166
subCtx := scan.NewScanContext(ctx.Context(), ctx.Input)
167
if err := e.runWorkflowStep(template, subCtx, results, swg, w); err != nil {
168
gologger.Warning().Msgf(workflowStepExecutionError, template.Template, err)
169
}
170
swg.Done()
171
}(subtemplate)
172
}
173
}
174
return mainErr
175
}
176
177