Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/protocols/protocols.go
2070 views
1
package protocols
2
3
import (
4
"context"
5
"encoding/base64"
6
"sync/atomic"
7
8
"github.com/projectdiscovery/fastdialer/fastdialer"
9
"github.com/projectdiscovery/gologger"
10
"github.com/projectdiscovery/ratelimit"
11
mapsutil "github.com/projectdiscovery/utils/maps"
12
stringsutil "github.com/projectdiscovery/utils/strings"
13
14
"github.com/logrusorgru/aurora"
15
16
"github.com/projectdiscovery/nuclei/v3/pkg/authprovider"
17
"github.com/projectdiscovery/nuclei/v3/pkg/catalog"
18
"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/frequency"
19
"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/stats"
20
"github.com/projectdiscovery/nuclei/v3/pkg/input"
21
"github.com/projectdiscovery/nuclei/v3/pkg/js/compiler"
22
"github.com/projectdiscovery/nuclei/v3/pkg/loader/parser"
23
"github.com/projectdiscovery/nuclei/v3/pkg/model"
24
"github.com/projectdiscovery/nuclei/v3/pkg/operators"
25
"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors"
26
"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers"
27
"github.com/projectdiscovery/nuclei/v3/pkg/output"
28
"github.com/projectdiscovery/nuclei/v3/pkg/progress"
29
"github.com/projectdiscovery/nuclei/v3/pkg/projectfile"
30
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
31
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/globalmatchers"
32
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/hosterrorscache"
33
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh"
34
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/excludematchers"
35
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/variables"
36
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless/engine"
37
"github.com/projectdiscovery/nuclei/v3/pkg/reporting"
38
"github.com/projectdiscovery/nuclei/v3/pkg/scan"
39
templateTypes "github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
40
"github.com/projectdiscovery/nuclei/v3/pkg/types"
41
unitutils "github.com/projectdiscovery/utils/unit"
42
)
43
44
var (
45
MaxTemplateFileSizeForEncoding = unitutils.Mega
46
)
47
48
// Executer is an interface implemented any protocol based request executer.
49
type Executer interface {
50
// Compile compiles the execution generators preparing any requests possible.
51
Compile() error
52
// Requests returns the total number of requests the rule will perform
53
Requests() int
54
// Execute executes the protocol group and returns true or false if results were found.
55
Execute(ctx *scan.ScanContext) (bool, error)
56
// ExecuteWithResults executes the protocol requests and returns results instead of writing them.
57
ExecuteWithResults(ctx *scan.ScanContext) ([]*output.ResultEvent, error)
58
}
59
60
// ExecutorOptions contains the configuration options for executer clients
61
type ExecutorOptions struct {
62
// TemplateID is the ID of the template for the request
63
TemplateID string
64
// TemplatePath is the path of the template for the request
65
TemplatePath string
66
// TemplateInfo contains information block of the template request
67
TemplateInfo model.Info
68
// TemplateVerifier is the verifier for the template
69
TemplateVerifier string
70
// RawTemplate is the raw template for the request
71
RawTemplate []byte
72
// Output is a writer interface for writing output events from executer.
73
Output output.Writer
74
// Options contains configuration options for the executer.
75
Options *types.Options
76
// IssuesClient is a client for nuclei issue tracker reporting
77
IssuesClient reporting.Client
78
// Progress is a progress client for scan reporting
79
Progress progress.Progress
80
// RateLimiter is a rate-limiter for limiting sent number of requests.
81
RateLimiter *ratelimit.Limiter
82
// Catalog is a template catalog implementation for nuclei
83
Catalog catalog.Catalog
84
// ProjectFile is the project file for nuclei
85
ProjectFile *projectfile.ProjectFile
86
// Browser is a browser engine for running headless templates
87
Browser *engine.Browser
88
// Interactsh is a client for interactsh oob polling server
89
Interactsh *interactsh.Client
90
// HostErrorsCache is an optional cache for handling host errors
91
HostErrorsCache hosterrorscache.CacheInterface
92
// Stop execution once first match is found (Assigned while parsing templates)
93
// Note: this is different from Options.StopAtFirstMatch (Assigned from CLI option)
94
StopAtFirstMatch bool
95
// Variables is a list of variables from template
96
Variables variables.Variable
97
// Constants is a list of constants from template
98
Constants map[string]interface{}
99
// ExcludeMatchers is the list of matchers to exclude
100
ExcludeMatchers *excludematchers.ExcludeMatchers
101
// InputHelper is a helper for input normalization
102
InputHelper *input.Helper
103
// FuzzParamsFrequency is a cache for parameter frequency
104
FuzzParamsFrequency *frequency.Tracker
105
// FuzzStatsDB is a database for fuzzing stats
106
FuzzStatsDB *stats.Tracker
107
108
Operators []*operators.Operators // only used by offlinehttp module
109
110
// DoNotCache bool disables optional caching of the templates structure
111
DoNotCache bool
112
113
Colorizer aurora.Aurora
114
WorkflowLoader model.WorkflowLoader
115
ResumeCfg *types.ResumeCfg
116
// ProtocolType is the type of the template
117
ProtocolType templateTypes.ProtocolType
118
// Flow is execution flow for the template (written in javascript)
119
Flow string
120
// IsMultiProtocol is true if template has more than one protocol
121
IsMultiProtocol bool
122
// templateStore is a map which contains template context for each scan (i.e input * template-id pair)
123
templateCtxStore *mapsutil.SyncLockMap[string, *contextargs.Context]
124
// JsCompiler is abstracted javascript compiler which adds node modules and provides execution
125
// environment for javascript templates
126
JsCompiler *compiler.Compiler
127
// AuthProvider is a provider for auth strategies
128
AuthProvider authprovider.AuthProvider
129
//TemporaryDirectory is the directory to store temporary files
130
TemporaryDirectory string
131
Parser parser.Parser
132
// ExportReqURLPattern exports the request URL pattern
133
// in ResultEvent it contains the exact url pattern (ex: {{BaseURL}}/{{randstr}}/xyz) used in the request
134
ExportReqURLPattern bool
135
// GlobalMatchers is the storage for global matchers with http passive templates
136
GlobalMatchers *globalmatchers.Storage
137
// Logger is the shared logging instance
138
Logger *gologger.Logger
139
// CustomFastdialer is a fastdialer dialer instance
140
CustomFastdialer *fastdialer.Dialer
141
}
142
143
// todo: centralizing components is not feasible with current clogged architecture
144
// a possible approach could be an internal event bus with pub-subs? This would be less invasive than
145
// reworking dep injection from scratch
146
func (e *ExecutorOptions) RateLimitTake() {
147
// The code below can race and there isn't a great way to fix this without adding an idempotent
148
// function to the rate limiter implementation. For now, stick with whatever rate is already set.
149
/*
150
if e.RateLimiter.GetLimit() != uint(e.Options.RateLimit) {
151
e.RateLimiter.SetLimit(uint(e.Options.RateLimit))
152
e.RateLimiter.SetDuration(e.Options.RateLimitDuration)
153
}
154
*/
155
if e.RateLimiter != nil {
156
e.RateLimiter.Take()
157
}
158
}
159
160
// GetThreadsForNPayloadRequests returns the number of threads to use as default for
161
// given max-request of payloads
162
func (e *ExecutorOptions) GetThreadsForNPayloadRequests(totalRequests int, currentThreads int) int {
163
if currentThreads > 0 {
164
return currentThreads
165
}
166
167
return e.Options.PayloadConcurrency
168
}
169
170
// CreateTemplateCtxStore creates template context store (which contains templateCtx for every scan)
171
func (e *ExecutorOptions) CreateTemplateCtxStore() {
172
e.templateCtxStore = &mapsutil.SyncLockMap[string, *contextargs.Context]{
173
Map: make(map[string]*contextargs.Context),
174
ReadOnly: atomic.Bool{},
175
}
176
}
177
178
// RemoveTemplateCtx removes template context of given scan from store
179
func (e *ExecutorOptions) RemoveTemplateCtx(input *contextargs.MetaInput) {
180
scanId := input.GetScanHash(e.TemplateID)
181
if e.templateCtxStore != nil {
182
e.templateCtxStore.Delete(scanId)
183
}
184
}
185
186
// HasTemplateCtx returns true if template context exists for given input
187
func (e *ExecutorOptions) HasTemplateCtx(input *contextargs.MetaInput) bool {
188
scanId := input.GetScanHash(e.TemplateID)
189
if e.templateCtxStore != nil {
190
return e.templateCtxStore.Has(scanId)
191
}
192
return false
193
}
194
195
// GetTemplateCtx returns template context for given input
196
func (e *ExecutorOptions) GetTemplateCtx(input *contextargs.MetaInput) *contextargs.Context {
197
scanId := input.GetScanHash(e.TemplateID)
198
if e.templateCtxStore == nil {
199
// if template context store is not initialized create it
200
e.CreateTemplateCtxStore()
201
}
202
// get template context from store
203
templateCtx, ok := e.templateCtxStore.Get(scanId)
204
if !ok {
205
// if template context does not exist create new and add it to store and return it
206
templateCtx = contextargs.New(context.Background())
207
templateCtx.MetaInput = input
208
_ = e.templateCtxStore.Set(scanId, templateCtx)
209
}
210
return templateCtx
211
}
212
213
// AddTemplateVars adds vars to template context with given template type as prefix
214
// this method is no-op if template is not multi protocol
215
func (e *ExecutorOptions) AddTemplateVars(input *contextargs.MetaInput, reqType templateTypes.ProtocolType, reqID string, vars map[string]interface{}) {
216
// if we wan't to disable adding response variables and other variables to template context
217
// this is the statement that does it . template context is currently only enabled for
218
// multiprotocol and flow templates
219
if !e.IsMultiProtocol && e.Flow == "" {
220
// no-op if not multi protocol template or flow template
221
return
222
}
223
templateCtx := e.GetTemplateCtx(input)
224
for k, v := range vars {
225
if stringsutil.HasPrefixAny(k, templateTypes.SupportedProtocolsStrings()...) {
226
// this was inherited from previous protocols no need to modify it we can directly set it or omit
227
templateCtx.Set(k, v)
228
continue
229
}
230
if !stringsutil.EqualFoldAny(k, "template-id", "template-info", "template-path") {
231
if reqID != "" {
232
k = reqID + "_" + k
233
} else if reqType < templateTypes.InvalidProtocol {
234
k = reqType.String() + "_" + k
235
}
236
templateCtx.Set(k, v)
237
}
238
}
239
}
240
241
// AddTemplateVar adds given var to template context with given template type as prefix
242
// this method is no-op if template is not multi protocol
243
func (e *ExecutorOptions) AddTemplateVar(input *contextargs.MetaInput, templateType templateTypes.ProtocolType, reqID string, key string, value interface{}) {
244
if !e.IsMultiProtocol && e.Flow == "" {
245
// no-op if not multi protocol template or flow template
246
return
247
}
248
templateCtx := e.GetTemplateCtx(input)
249
if stringsutil.HasPrefixAny(key, templateTypes.SupportedProtocolsStrings()...) {
250
// this was inherited from previous protocols no need to modify it we can directly set it or omit
251
templateCtx.Set(key, value)
252
return
253
}
254
if reqID != "" {
255
key = reqID + "_" + key
256
} else if templateType < templateTypes.InvalidProtocol {
257
key = templateType.String() + "_" + key
258
}
259
templateCtx.Set(key, value)
260
}
261
262
// Copy returns a copy of the executeroptions structure
263
func (e *ExecutorOptions) Copy() *ExecutorOptions {
264
copy := &ExecutorOptions{
265
TemplateID: e.TemplateID,
266
TemplatePath: e.TemplatePath,
267
TemplateInfo: e.TemplateInfo,
268
TemplateVerifier: e.TemplateVerifier,
269
RawTemplate: e.RawTemplate,
270
Output: e.Output,
271
Options: e.Options,
272
IssuesClient: e.IssuesClient,
273
Progress: e.Progress,
274
RateLimiter: e.RateLimiter,
275
Catalog: e.Catalog,
276
ProjectFile: e.ProjectFile,
277
Browser: e.Browser,
278
Interactsh: e.Interactsh,
279
HostErrorsCache: e.HostErrorsCache,
280
StopAtFirstMatch: e.StopAtFirstMatch,
281
Variables: e.Variables,
282
Constants: e.Constants,
283
ExcludeMatchers: e.ExcludeMatchers,
284
InputHelper: e.InputHelper,
285
FuzzParamsFrequency: e.FuzzParamsFrequency,
286
FuzzStatsDB: e.FuzzStatsDB,
287
Operators: e.Operators,
288
DoNotCache: e.DoNotCache,
289
Colorizer: e.Colorizer,
290
WorkflowLoader: e.WorkflowLoader,
291
ResumeCfg: e.ResumeCfg,
292
ProtocolType: e.ProtocolType,
293
Flow: e.Flow,
294
IsMultiProtocol: e.IsMultiProtocol,
295
JsCompiler: e.JsCompiler,
296
AuthProvider: e.AuthProvider,
297
TemporaryDirectory: e.TemporaryDirectory,
298
Parser: e.Parser,
299
ExportReqURLPattern: e.ExportReqURLPattern,
300
GlobalMatchers: e.GlobalMatchers,
301
Logger: e.Logger,
302
}
303
copy.CreateTemplateCtxStore()
304
return copy
305
}
306
307
// Request is an interface implemented any protocol based request generator.
308
type Request interface {
309
// Compile compiles the request generators preparing any requests possible.
310
Compile(options *ExecutorOptions) error
311
// Requests returns the total number of requests the rule will perform
312
Requests() int
313
// GetID returns the ID for the request if any. IDs are used for multi-request
314
// condition matching. So, two requests can be sent and their match can
315
// be evaluated from the third request by using the IDs for both requests.
316
GetID() string
317
// Match performs matching operation for a matcher on model and returns:
318
// true and a list of matched snippets if the matcher type is supports it
319
// otherwise false and an empty string slice
320
Match(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string)
321
// Extract performs extracting operation for an extractor on model and returns true or false.
322
Extract(data map[string]interface{}, matcher *extractors.Extractor) map[string]struct{}
323
// ExecuteWithResults executes the protocol requests and returns results instead of writing them.
324
ExecuteWithResults(input *contextargs.Context, dynamicValues, previous output.InternalEvent, callback OutputEventCallback) error
325
// MakeResultEventItem creates a result event from internal wrapped event. Intended to be used by MakeResultEventItem internally
326
MakeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent
327
// MakeResultEvent creates a flat list of result events from an internal wrapped event, based on successful matchers and extracted data
328
MakeResultEvent(wrapped *output.InternalWrappedEvent) []*output.ResultEvent
329
// GetCompiledOperators returns a list of the compiled operators
330
GetCompiledOperators() []*operators.Operators
331
// Type returns the type of the protocol request
332
Type() templateTypes.ProtocolType
333
}
334
335
// OutputEventCallback is a callback event for any results found during scanning.
336
type OutputEventCallback func(result *output.InternalWrappedEvent)
337
338
func MakeDefaultResultEvent(request Request, wrapped *output.InternalWrappedEvent) []*output.ResultEvent {
339
// Note: operator result is generated if something was succesfull match/extract/dynamic-extract
340
// but results should not be generated if
341
// 1. no match was found and some dynamic values were extracted
342
// 2. if something was extracted (matchers exist but no match was found)
343
if len(wrapped.OperatorsResult.DynamicValues) > 0 && !wrapped.OperatorsResult.Matched {
344
return nil
345
}
346
// check if something was extracted (except dynamic values)
347
extracted := len(wrapped.OperatorsResult.Extracts) > 0 || len(wrapped.OperatorsResult.OutputExtracts) > 0
348
if extracted && len(wrapped.OperatorsResult.Operators.Matchers) > 0 && !wrapped.OperatorsResult.Matched {
349
// if extracted and matchers exist but no match was found then don't generate result
350
return nil
351
}
352
353
results := make([]*output.ResultEvent, 0, len(wrapped.OperatorsResult.Matches)+1)
354
355
// If we have multiple matchers with names, write each of them separately.
356
if len(wrapped.OperatorsResult.Matches) > 0 {
357
for matcherNames := range wrapped.OperatorsResult.Matches {
358
data := request.MakeResultEventItem(wrapped)
359
data.MatcherName = matcherNames
360
results = append(results, data)
361
}
362
} else if len(wrapped.OperatorsResult.Extracts) > 0 {
363
for k, v := range wrapped.OperatorsResult.Extracts {
364
data := request.MakeResultEventItem(wrapped)
365
data.ExtractorName = k
366
data.ExtractedResults = v
367
results = append(results, data)
368
}
369
} else {
370
data := request.MakeResultEventItem(wrapped)
371
results = append(results, data)
372
}
373
return results
374
}
375
376
// MakeDefaultExtractFunc performs extracting operation for an extractor on model and returns true or false.
377
func MakeDefaultExtractFunc(data map[string]interface{}, extractor *extractors.Extractor) map[string]struct{} {
378
part := extractor.Part
379
if part == "" {
380
part = "response"
381
}
382
383
item, ok := data[part]
384
if !ok && !extractors.SupportsMap(extractor) {
385
return nil
386
}
387
itemStr := types.ToString(item)
388
389
switch extractor.GetType() {
390
case extractors.RegexExtractor:
391
return extractor.ExtractRegex(itemStr)
392
case extractors.KValExtractor:
393
return extractor.ExtractKval(data)
394
case extractors.JSONExtractor:
395
return extractor.ExtractJSON(itemStr)
396
case extractors.XPathExtractor:
397
return extractor.ExtractXPath(itemStr)
398
case extractors.DSLExtractor:
399
return extractor.ExtractDSL(data)
400
}
401
return nil
402
}
403
404
// MakeDefaultMatchFunc performs matching operation for a matcher on model and returns true or false.
405
func MakeDefaultMatchFunc(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string) {
406
part := matcher.Part
407
if part == "" {
408
part = "response"
409
}
410
411
partItem, ok := data[part]
412
if !ok && matcher.Type.MatcherType != matchers.DSLMatcher {
413
return false, nil
414
}
415
item := types.ToString(partItem)
416
417
switch matcher.GetType() {
418
case matchers.SizeMatcher:
419
result := matcher.Result(matcher.MatchSize(len(item)))
420
return result, nil
421
case matchers.WordsMatcher:
422
return matcher.ResultWithMatchedSnippet(matcher.MatchWords(item, nil))
423
case matchers.RegexMatcher:
424
return matcher.ResultWithMatchedSnippet(matcher.MatchRegex(item))
425
case matchers.BinaryMatcher:
426
return matcher.ResultWithMatchedSnippet(matcher.MatchBinary(item))
427
case matchers.DSLMatcher:
428
return matcher.Result(matcher.MatchDSL(data)), nil
429
case matchers.XPathMatcher:
430
return matcher.Result(matcher.MatchXPath(item)), []string{}
431
}
432
return false, nil
433
}
434
435
func (e *ExecutorOptions) EncodeTemplate() string {
436
if !e.Options.OmitTemplate && len(e.RawTemplate) <= MaxTemplateFileSizeForEncoding {
437
return base64.StdEncoding.EncodeToString(e.RawTemplate)
438
}
439
return ""
440
}
441
442
// ApplyNewEngineOptions updates an existing ExecutorOptions with options from a new engine. This
443
// handles things like the ExecutionID that need to be updated.
444
func (e *ExecutorOptions) ApplyNewEngineOptions(n *ExecutorOptions) {
445
// TODO: cached code|headless templates have nil ExecuterOptions if -code or -headless are not enabled
446
if e == nil || n == nil || n.Options == nil {
447
return
448
}
449
450
e.Options = n.Options.Copy()
451
e.Output = n.Output
452
e.IssuesClient = n.IssuesClient
453
e.Progress = n.Progress
454
e.RateLimiter = n.RateLimiter
455
e.Catalog = n.Catalog
456
e.ProjectFile = n.ProjectFile
457
e.Browser = n.Browser
458
e.Interactsh = n.Interactsh
459
e.HostErrorsCache = n.HostErrorsCache
460
e.InputHelper = n.InputHelper
461
e.FuzzParamsFrequency = n.FuzzParamsFrequency
462
e.FuzzStatsDB = n.FuzzStatsDB
463
e.DoNotCache = n.DoNotCache
464
e.Colorizer = n.Colorizer
465
e.WorkflowLoader = n.WorkflowLoader
466
e.ResumeCfg = n.ResumeCfg
467
e.JsCompiler = n.JsCompiler
468
e.AuthProvider = n.AuthProvider
469
e.TemporaryDirectory = n.TemporaryDirectory
470
e.Parser = n.Parser
471
e.ExportReqURLPattern = n.ExportReqURLPattern
472
e.GlobalMatchers = n.GlobalMatchers
473
e.Logger = n.Logger
474
e.CustomFastdialer = n.CustomFastdialer
475
}
476
477