Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/core/workflow_execute.go
2070 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
swg.Add()
38
39
func(template *workflows.WorkflowTemplate) {
40
defer swg.Done()
41
42
if err := e.runWorkflowStep(template, ctx, results, swg, w); err != nil {
43
gologger.Warning().Msgf(workflowStepExecutionError, template.Template, err)
44
}
45
}(template)
46
}
47
swg.Wait()
48
return results.Load()
49
}
50
51
// runWorkflowStep runs a workflow step for the workflow. It executes the workflow
52
// in a recursive manner running all subtemplates and matchers.
53
func (e *Engine) runWorkflowStep(template *workflows.WorkflowTemplate, ctx *scan.ScanContext, results *atomic.Bool, swg *syncutil.AdaptiveWaitGroup, w *workflows.Workflow) error {
54
var firstMatched bool
55
var err error
56
var mainErr error
57
58
if len(template.Matchers) == 0 {
59
for _, executer := range template.Executers {
60
executer.Options.Progress.AddToTotal(int64(executer.Executer.Requests()))
61
62
// Don't print results with subtemplates, only print results on template.
63
if len(template.Subtemplates) > 0 {
64
ctx.OnResult = func(result *output.InternalWrappedEvent) {
65
if result.OperatorsResult == nil {
66
return
67
}
68
if len(result.Results) > 0 {
69
firstMatched = true
70
}
71
72
if result.OperatorsResult != nil && result.OperatorsResult.Extracts != nil {
73
for k, v := range result.OperatorsResult.Extracts {
74
// normalize items:
75
switch len(v) {
76
case 0, 1:
77
// - key:[item] => key: item
78
ctx.Input.Set(k, v[0])
79
default:
80
// - key:[item_0, ..., item_n] => key0:item_0, keyn:item_n
81
for vIdx, vVal := range v {
82
normalizedKIdx := fmt.Sprintf("%s%d", k, vIdx)
83
ctx.Input.Set(normalizedKIdx, vVal)
84
}
85
// also add the original name with full slice
86
ctx.Input.Set(k, v)
87
}
88
}
89
}
90
}
91
_, err = executer.Executer.ExecuteWithResults(ctx)
92
} else {
93
var matched bool
94
matched, err = executer.Executer.Execute(ctx)
95
if matched {
96
firstMatched = true
97
}
98
}
99
if w.Options.HostErrorsCache != nil {
100
w.Options.HostErrorsCache.MarkFailedOrRemove(w.Options.ProtocolType.String(), ctx.Input, err)
101
}
102
if err != nil {
103
if len(template.Executers) == 1 {
104
mainErr = err
105
} else {
106
gologger.Warning().Msgf(workflowStepExecutionError, template.Template, err)
107
}
108
continue
109
}
110
}
111
}
112
if len(template.Subtemplates) == 0 {
113
results.CompareAndSwap(false, firstMatched)
114
}
115
if len(template.Matchers) > 0 {
116
for _, executer := range template.Executers {
117
executer.Options.Progress.AddToTotal(int64(executer.Executer.Requests()))
118
119
ctx.OnResult = func(event *output.InternalWrappedEvent) {
120
if event.OperatorsResult == nil {
121
return
122
}
123
124
if event.OperatorsResult.Extracts != nil {
125
for k, v := range event.OperatorsResult.Extracts {
126
ctx.Input.Set(k, v)
127
}
128
}
129
130
for _, matcher := range template.Matchers {
131
if !matcher.Match(event.OperatorsResult) {
132
continue
133
}
134
135
for _, subtemplate := range matcher.Subtemplates {
136
swg.Add()
137
138
go func(subtemplate *workflows.WorkflowTemplate) {
139
defer swg.Done()
140
141
// create a new context with the same input but with unset callbacks
142
// clone the Input so that other parallel executions won't overwrite the shared variables when subsequent templates are running
143
subCtx := scan.NewScanContext(ctx.Context(), ctx.Input.Clone())
144
if err := e.runWorkflowStep(subtemplate, subCtx, results, swg, w); err != nil {
145
gologger.Warning().Msgf(workflowStepExecutionError, subtemplate.Template, err)
146
}
147
}(subtemplate)
148
}
149
}
150
}
151
_, err := executer.Executer.ExecuteWithResults(ctx)
152
if err != nil {
153
if len(template.Executers) == 1 {
154
mainErr = err
155
} else {
156
gologger.Warning().Msgf(workflowStepExecutionError, template.Template, err)
157
}
158
continue
159
}
160
}
161
return mainErr
162
}
163
if len(template.Subtemplates) > 0 && firstMatched {
164
for _, subtemplate := range template.Subtemplates {
165
swg.Add()
166
167
go func(template *workflows.WorkflowTemplate) {
168
// create a new context with the same input but with unset callbacks
169
subCtx := scan.NewScanContext(ctx.Context(), ctx.Input)
170
if err := e.runWorkflowStep(template, subCtx, results, swg, w); err != nil {
171
gologger.Warning().Msgf(workflowStepExecutionError, template.Template, err)
172
}
173
swg.Done()
174
}(subtemplate)
175
}
176
}
177
return mainErr
178
}
179
180