Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/tmplexec/exec.go
2070 views
1
package tmplexec
2
3
import (
4
"errors"
5
"fmt"
6
"strings"
7
"sync/atomic"
8
"time"
9
10
"github.com/Mzack9999/goja"
11
"github.com/projectdiscovery/gologger"
12
"github.com/projectdiscovery/nuclei/v3/pkg/js/compiler"
13
"github.com/projectdiscovery/nuclei/v3/pkg/operators"
14
"github.com/projectdiscovery/nuclei/v3/pkg/operators/common/dsl"
15
"github.com/projectdiscovery/nuclei/v3/pkg/output"
16
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
17
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/writer"
18
"github.com/projectdiscovery/nuclei/v3/pkg/scan"
19
"github.com/projectdiscovery/nuclei/v3/pkg/scan/events"
20
"github.com/projectdiscovery/nuclei/v3/pkg/tmplexec/flow"
21
"github.com/projectdiscovery/nuclei/v3/pkg/tmplexec/generic"
22
"github.com/projectdiscovery/nuclei/v3/pkg/tmplexec/multiproto"
23
"github.com/projectdiscovery/utils/errkit"
24
)
25
26
// TemplateExecutor is an executor for a template
27
type TemplateExecuter struct {
28
requests []protocols.Request
29
options *protocols.ExecutorOptions
30
engine TemplateEngine
31
results *atomic.Bool
32
program *goja.Program
33
}
34
35
// Both executer & Executor are correct spellings (its open to interpretation)
36
37
var _ protocols.Executer = &TemplateExecuter{}
38
39
// NewTemplateExecuter creates a new request TemplateExecuter for list of requests
40
func NewTemplateExecuter(requests []protocols.Request, options *protocols.ExecutorOptions) (*TemplateExecuter, error) {
41
e := &TemplateExecuter{requests: requests, options: options, results: &atomic.Bool{}}
42
if options.Flow != "" {
43
// we use a dummy input here because goal of flow executor at this point is to just check
44
// syntax and other things are correct before proceeding to actual execution
45
// during execution new instance of flow will be created as it is tightly coupled with lot of executor options
46
p, err := compiler.SourceAutoMode(options.Flow, false)
47
if err != nil {
48
return nil, fmt.Errorf("could not compile flow: %s", err)
49
}
50
e.program = p
51
} else {
52
// only use generic if there is only 1 protocol with only 1 section
53
if len(requests) == 1 {
54
e.engine = generic.NewGenericEngine(requests, options, e.results)
55
} else {
56
e.engine = multiproto.NewMultiProtocol(requests, options, e.results)
57
}
58
}
59
return e, nil
60
}
61
62
// Compile compiles the execution generators preparing any requests possible.
63
func (e *TemplateExecuter) Compile() error {
64
cliOptions := e.options.Options
65
66
for _, request := range e.requests {
67
if err := request.Compile(e.options); err != nil {
68
var dslCompilationError *dsl.CompilationError
69
if errors.As(err, &dslCompilationError) {
70
if cliOptions.Verbose {
71
rawErrorMessage := dslCompilationError.Error()
72
formattedErrorMessage := strings.ToUpper(rawErrorMessage[:1]) + rawErrorMessage[1:] + "."
73
74
gologger.Warning().Msg(formattedErrorMessage)
75
gologger.Info().Msgf("The available custom DSL functions are:")
76
77
fmt.Println(dsl.GetPrintableDslFunctionSignatures(cliOptions.NoColor))
78
}
79
}
80
return err
81
}
82
}
83
if e.engine == nil && e.options.Flow != "" {
84
// this is true for flow executor
85
return nil
86
}
87
return e.engine.Compile()
88
}
89
90
// Requests returns the total number of requests the rule will perform
91
func (e *TemplateExecuter) Requests() int {
92
var count int
93
for _, request := range e.requests {
94
count += request.Requests()
95
}
96
return count
97
}
98
99
// Execute executes the protocol group and returns true or false if results were found.
100
func (e *TemplateExecuter) Execute(ctx *scan.ScanContext) (bool, error) {
101
102
// === when nuclei is built with -tags=stats ===
103
// Note: this is no-op (empty functions) when nuclei is built in normal or without -tags=stats
104
events.AddScanEvent(events.ScanEvent{
105
Target: ctx.Input.MetaInput.Input,
106
Time: time.Now(),
107
EventType: events.ScanStarted,
108
TemplateType: e.getTemplateType(),
109
TemplateID: e.options.TemplateID,
110
TemplatePath: e.options.TemplatePath,
111
MaxRequests: e.Requests(),
112
})
113
defer func() {
114
events.AddScanEvent(events.ScanEvent{
115
Target: ctx.Input.MetaInput.Input,
116
Time: time.Now(),
117
EventType: events.ScanFinished,
118
TemplateType: e.getTemplateType(),
119
TemplateID: e.options.TemplateID,
120
TemplatePath: e.options.TemplatePath,
121
MaxRequests: e.Requests(),
122
})
123
}()
124
// ==== end of stats ====
125
126
// executed contains status of execution if it was successfully executed or not
127
// doesn't matter if it was matched or not
128
executed := &atomic.Bool{}
129
// matched in this case means something was exported / written to output
130
matched := &atomic.Bool{}
131
// callbackCalled tracks if the callback was called or not
132
callbackCalled := &atomic.Bool{}
133
defer func() {
134
// it is essential to remove template context of `Scan i.e template x input pair`
135
// since it is of no use after scan is completed (regardless of success or failure)
136
e.options.RemoveTemplateCtx(ctx.Input.MetaInput)
137
}()
138
139
var lastMatcherEvent *output.InternalWrappedEvent
140
writeFailureCallback := func(event *output.InternalWrappedEvent, matcherStatus bool) {
141
if !matched.Load() && matcherStatus {
142
if err := e.options.Output.WriteFailure(event); err != nil {
143
gologger.Warning().Msgf("Could not write failure event to output: %s\n", err)
144
}
145
executed.CompareAndSwap(false, true)
146
}
147
}
148
149
ctx.OnResult = func(event *output.InternalWrappedEvent) {
150
callbackCalled.Store(true)
151
if event == nil {
152
// something went wrong
153
return
154
}
155
// check for internal true matcher event
156
if event.HasOperatorResult() && event.OperatorsResult.Matched && event.OperatorsResult.Operators != nil {
157
// note all matchers should have internal:true if it is a combination then print it
158
allInternalMatchers := true
159
for _, matcher := range event.OperatorsResult.Operators.Matchers {
160
if allInternalMatchers && !matcher.Internal {
161
allInternalMatchers = false
162
break
163
}
164
}
165
if allInternalMatchers {
166
// this is a internal event and no meant to be printed
167
return
168
}
169
}
170
171
// If no results were found, and also interactsh is not being used
172
// in that case we can skip it, otherwise we've to show failure in
173
// case of matcher-status flag.
174
if !event.HasOperatorResult() && event.InternalEvent != nil {
175
lastMatcherEvent = event
176
} else {
177
var isGlobalMatchers bool
178
isGlobalMatchers, _ = event.InternalEvent["global-matchers"].(bool)
179
// NOTE(dwisiswant0): Don't store `matched` on a `global-matchers` template.
180
// This will end up generating 2 events from the same `scan.ScanContext` if
181
// one of the templates has `global-matchers` enabled. This way,
182
// non-`global-matchers` templates can enter the `writeFailureCallback`
183
// func to log failure output.
184
wr := writer.WriteResult(event, e.options.Output, e.options.Progress, e.options.IssuesClient)
185
if wr && !isGlobalMatchers {
186
matched.Store(true)
187
} else {
188
lastMatcherEvent = event
189
}
190
}
191
}
192
var errx error
193
194
// Note: this is required for flow executor
195
// flow executer is tightly coupled with lot of executor options
196
// and map , wg and other types earlier we tried to use (compile once and run multiple times)
197
// but it is causing lot of panic and nil pointer dereference issues
198
// so in compile step earlier we compile it to validate javascript syntax and other things
199
// and while executing we create new instance of flow executor everytime
200
if e.options.Flow != "" {
201
flowexec, err := flow.NewFlowExecutor(e.requests, ctx, e.options, executed, e.program)
202
if err != nil {
203
ctx.LogError(err)
204
return false, fmt.Errorf("could not create flow executor: %s", err)
205
}
206
if err := flowexec.Compile(); err != nil {
207
ctx.LogError(err)
208
return false, err
209
}
210
errx = flowexec.ExecuteWithResults(ctx)
211
} else {
212
errx = e.engine.ExecuteWithResults(ctx)
213
}
214
ctx.LogError(errx)
215
216
if lastMatcherEvent != nil {
217
lastMatcherEvent.Lock()
218
defer lastMatcherEvent.Unlock()
219
220
lastMatcherEvent.InternalEvent["error"] = getErrorCause(ctx.GenerateErrorMessage())
221
222
writeFailureCallback(lastMatcherEvent, e.options.Options.MatcherStatus)
223
}
224
225
//TODO: this is a hacky way to handle the case where the callback is not called and matcher-status is true.
226
// This is a workaround and needs to be refactored.
227
// Check if callback was never called and matcher-status is true
228
if !callbackCalled.Load() && e.options.Options.MatcherStatus {
229
fakeEvent := &output.InternalWrappedEvent{
230
Results: []*output.ResultEvent{
231
{
232
TemplateID: e.options.TemplateID,
233
Info: e.options.TemplateInfo,
234
Type: e.getTemplateType(),
235
Host: ctx.Input.MetaInput.Input,
236
Error: getErrorCause(ctx.GenerateErrorMessage()),
237
},
238
},
239
OperatorsResult: &operators.Result{
240
Matched: false,
241
},
242
}
243
writeFailureCallback(fakeEvent, e.options.Options.MatcherStatus)
244
}
245
246
return executed.Load() || matched.Load(), errx
247
}
248
249
// getErrorCause tries to parse the cause of given error
250
// this is legacy support due to use of errorutil in existing libraries
251
// but this should not be required once all libraries are updated
252
func getErrorCause(err error) string {
253
if err == nil {
254
return ""
255
}
256
errx := errkit.FromError(err)
257
var cause error
258
for _, e := range errx.Errors() {
259
if e != nil && strings.Contains(e.Error(), "context deadline exceeded") {
260
continue
261
}
262
cause = e
263
break
264
}
265
if cause == nil {
266
cause = errkit.Append(errkit.New("could not get error cause"), errx)
267
}
268
// parseScanError prettifies the error message and removes everything except the cause
269
return parseScanError(cause.Error())
270
}
271
272
// ExecuteWithResults executes the protocol requests and returns results instead of writing them.
273
func (e *TemplateExecuter) ExecuteWithResults(ctx *scan.ScanContext) ([]*output.ResultEvent, error) {
274
var errx error
275
if e.options.Flow != "" {
276
flowexec, err := flow.NewFlowExecutor(e.requests, ctx, e.options, e.results, e.program)
277
if err != nil {
278
ctx.LogError(err)
279
return nil, fmt.Errorf("could not create flow executor: %s", err)
280
}
281
if err := flowexec.Compile(); err != nil {
282
ctx.LogError(err)
283
return nil, err
284
}
285
errx = flowexec.ExecuteWithResults(ctx)
286
} else {
287
errx = e.engine.ExecuteWithResults(ctx)
288
}
289
if errx != nil {
290
ctx.LogError(errx)
291
}
292
return ctx.GenerateResult(), errx
293
}
294
295
// getTemplateType returns the template type of the template
296
func (e *TemplateExecuter) getTemplateType() string {
297
if len(e.requests) == 0 {
298
return "null"
299
}
300
if e.options.Flow != "" {
301
return "flow"
302
}
303
if len(e.requests) > 1 {
304
return "multiprotocol"
305
}
306
return e.requests[0].Type().String()
307
}
308
309