Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/operators/operators.go
2070 views
1
package operators
2
3
import (
4
"fmt"
5
"maps"
6
"strconv"
7
"strings"
8
9
"github.com/pkg/errors"
10
11
"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors"
12
"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers"
13
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
14
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/excludematchers"
15
sliceutil "github.com/projectdiscovery/utils/slice"
16
)
17
18
// Operators contains the operators that can be applied on protocols
19
type Operators struct {
20
// description: |
21
// Matchers contains the detection mechanism for the request to identify
22
// whether the request was successful by doing pattern matching
23
// on request/responses.
24
//
25
// Multiple matchers can be combined with `matcher-condition` flag
26
// which accepts either `and` or `or` as argument.
27
Matchers []*matchers.Matcher `yaml:"matchers,omitempty" json:"matchers,omitempty" jsonschema:"title=matchers to run on response,description=Detection mechanism to identify whether the request was successful by doing pattern matching"`
28
// description: |
29
// Extractors contains the extraction mechanism for the request to identify
30
// and extract parts of the response.
31
Extractors []*extractors.Extractor `yaml:"extractors,omitempty" json:"extractors,omitempty" jsonschema:"title=extractors to run on response,description=Extractors contains the extraction mechanism for the request to identify and extract parts of the response"`
32
// description: |
33
// MatchersCondition is the condition between the matchers. Default is OR.
34
// values:
35
// - "and"
36
// - "or"
37
MatchersCondition string `yaml:"matchers-condition,omitempty" json:"matchers-condition,omitempty" jsonschema:"title=condition between the matchers,description=Conditions between the matchers,enum=and,enum=or"`
38
// cached variables that may be used along with request.
39
matchersCondition matchers.ConditionType
40
41
// TemplateID is the ID of the template for matcher
42
TemplateID string `json:"-" yaml:"-" jsonschema:"-"`
43
// ExcludeMatchers is a list of excludeMatchers items
44
ExcludeMatchers *excludematchers.ExcludeMatchers `json:"-" yaml:"-" jsonschema:"-"`
45
}
46
47
// Compile compiles the operators as well as their corresponding matchers and extractors
48
func (operators *Operators) Compile() error {
49
if operators.MatchersCondition != "" {
50
operators.matchersCondition = matchers.ConditionTypes[operators.MatchersCondition]
51
} else {
52
operators.matchersCondition = matchers.ORCondition
53
}
54
55
for _, matcher := range operators.Matchers {
56
if err := matcher.CompileMatchers(); err != nil {
57
return errors.Wrap(err, "could not compile matcher")
58
}
59
}
60
for _, extractor := range operators.Extractors {
61
if err := extractor.CompileExtractors(); err != nil {
62
return errors.Wrap(err, "could not compile extractor")
63
}
64
}
65
return nil
66
}
67
68
func (operators *Operators) HasDSL() bool {
69
for _, matcher := range operators.Matchers {
70
if len(matcher.DSL) > 0 {
71
return true
72
}
73
}
74
75
for _, extractor := range operators.Extractors {
76
if len(extractor.DSL) > 0 {
77
return true
78
}
79
}
80
81
return false
82
}
83
84
// GetMatchersCondition returns the condition for the matchers
85
func (operators *Operators) GetMatchersCondition() matchers.ConditionType {
86
return operators.matchersCondition
87
}
88
89
// Result is a result structure created from operators running on data.
90
type Result struct {
91
// Matched is true if any matchers matched
92
Matched bool
93
// Extracted is true if any result type values were extracted
94
Extracted bool
95
// Matches is a map of matcher names that we matched
96
Matches map[string][]string
97
// Extracts contains all the data extracted from inputs
98
Extracts map[string][]string
99
// OutputExtracts is the list of extracts to be displayed on screen.
100
OutputExtracts []string
101
outputUnique map[string]struct{}
102
103
// DynamicValues contains any dynamic values to be templated
104
DynamicValues map[string][]string
105
// PayloadValues contains payload values provided by user. (Optional)
106
PayloadValues map[string]interface{}
107
108
// Optional lineCounts for file protocol
109
LineCount string
110
// Operators is reference to operators that generated this result (Read-Only)
111
Operators *Operators
112
}
113
114
func (result *Result) HasMatch(name string) bool {
115
return result.hasItem(name, result.Matches)
116
}
117
118
func (result *Result) HasExtract(name string) bool {
119
return result.hasItem(name, result.Extracts)
120
}
121
122
func (result *Result) hasItem(name string, m map[string][]string) bool {
123
for matchName := range m {
124
if strings.EqualFold(name, matchName) {
125
return true
126
}
127
}
128
return false
129
}
130
131
// MakeDynamicValuesCallback takes an input dynamic values map and calls
132
// the callback function with all variations of the data in input in form
133
// of map[string]string (interface{}).
134
func MakeDynamicValuesCallback(input map[string][]string, iterateAllValues bool, callback func(map[string]interface{}) bool) {
135
output := make(map[string]interface{}, len(input))
136
137
if !iterateAllValues {
138
for k, v := range input {
139
if len(v) > 0 {
140
output[k] = v[0]
141
}
142
}
143
callback(output)
144
return
145
}
146
inputIndex := make(map[string]int, len(input))
147
148
var maxValue int
149
for _, v := range input {
150
if len(v) > maxValue {
151
maxValue = len(v)
152
}
153
}
154
155
for i := 0; i < maxValue; i++ {
156
for k, v := range input {
157
if len(v) == 0 {
158
continue
159
}
160
if len(v) == 1 {
161
output[k] = v[0]
162
continue
163
}
164
if gotIndex, ok := inputIndex[k]; !ok {
165
inputIndex[k] = 0
166
output[k] = v[0]
167
} else {
168
newIndex := gotIndex + 1
169
if newIndex >= len(v) {
170
output[k] = v[len(v)-1]
171
continue
172
}
173
output[k] = v[newIndex]
174
inputIndex[k] = newIndex
175
}
176
}
177
// skip if the callback says so
178
if callback(output) {
179
return
180
}
181
}
182
}
183
184
// Merge merges a result structure into the other.
185
func (r *Result) Merge(result *Result) {
186
if !r.Matched && result.Matched {
187
r.Matched = result.Matched
188
}
189
if !r.Extracted && result.Extracted {
190
r.Extracted = result.Extracted
191
}
192
193
for k, v := range result.Matches {
194
r.Matches[k] = sliceutil.Dedupe(append(r.Matches[k], v...))
195
}
196
for k, v := range result.Extracts {
197
r.Extracts[k] = sliceutil.Dedupe(append(r.Extracts[k], v...))
198
}
199
200
r.outputUnique = make(map[string]struct{})
201
output := r.OutputExtracts
202
r.OutputExtracts = make([]string, 0, len(output))
203
for _, v := range output {
204
if _, ok := r.outputUnique[v]; !ok {
205
r.outputUnique[v] = struct{}{}
206
r.OutputExtracts = append(r.OutputExtracts, v)
207
}
208
}
209
for _, v := range result.OutputExtracts {
210
if _, ok := r.outputUnique[v]; !ok {
211
r.outputUnique[v] = struct{}{}
212
r.OutputExtracts = append(r.OutputExtracts, v)
213
}
214
}
215
for k, v := range result.DynamicValues {
216
if _, ok := r.DynamicValues[k]; !ok {
217
r.DynamicValues[k] = v
218
} else {
219
r.DynamicValues[k] = sliceutil.Dedupe(append(r.DynamicValues[k], v...))
220
}
221
}
222
maps.Copy(r.PayloadValues, result.PayloadValues)
223
}
224
225
// MatchFunc performs matching operation for a matcher on model and returns true or false.
226
type MatchFunc func(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string)
227
228
// ExtractFunc performs extracting operation for an extractor on model and returns true or false.
229
type ExtractFunc func(data map[string]interface{}, matcher *extractors.Extractor) map[string]struct{}
230
231
// Execute executes the operators on data and returns a result structure
232
func (operators *Operators) Execute(data map[string]interface{}, match MatchFunc, extract ExtractFunc, isDebug bool) (*Result, bool) {
233
matcherCondition := operators.GetMatchersCondition()
234
235
var matches bool
236
result := &Result{
237
Matches: make(map[string][]string),
238
Extracts: make(map[string][]string),
239
DynamicValues: make(map[string][]string),
240
outputUnique: make(map[string]struct{}),
241
Operators: operators,
242
}
243
244
// state variable to check if all extractors are internal
245
var allInternalExtractors = true
246
247
// Start with the extractors first and evaluate them.
248
for _, extractor := range operators.Extractors {
249
if !extractor.Internal && allInternalExtractors {
250
allInternalExtractors = false
251
}
252
var extractorResults []string
253
for match := range extract(data, extractor) {
254
extractorResults = append(extractorResults, match)
255
256
if extractor.Internal {
257
if data, ok := result.DynamicValues[extractor.Name]; !ok {
258
result.DynamicValues[extractor.Name] = []string{match}
259
} else {
260
result.DynamicValues[extractor.Name] = append(data, match)
261
}
262
} else {
263
if _, ok := result.outputUnique[match]; !ok {
264
result.OutputExtracts = append(result.OutputExtracts, match)
265
result.outputUnique[match] = struct{}{}
266
}
267
}
268
}
269
if len(extractorResults) > 0 && !extractor.Internal && extractor.Name != "" {
270
result.Extracts[extractor.Name] = extractorResults
271
}
272
// update data with whatever was extracted doesn't matter if it is internal or not (skip unless it empty)
273
if len(extractorResults) > 0 {
274
data[extractor.Name] = getExtractedValue(extractorResults)
275
}
276
}
277
278
// expose dynamic values to same request matchers
279
if len(result.DynamicValues) > 0 {
280
dataDynamicValues := make(map[string]interface{})
281
for dynName, dynValues := range result.DynamicValues {
282
if len(dynValues) > 1 {
283
for dynIndex, dynValue := range dynValues {
284
dynKeyName := fmt.Sprintf("%s%d", dynName, dynIndex)
285
dataDynamicValues[dynKeyName] = dynValue
286
}
287
dataDynamicValues[dynName] = dynValues
288
} else {
289
dataDynamicValues[dynName] = dynValues[0]
290
}
291
292
}
293
data = generators.MergeMaps(data, dataDynamicValues)
294
}
295
296
for matcherIndex, matcher := range operators.Matchers {
297
// Skip matchers that are in the blocklist
298
if operators.ExcludeMatchers != nil {
299
if operators.ExcludeMatchers.Match(operators.TemplateID, matcher.Name) {
300
continue
301
}
302
}
303
if isMatch, matched := match(data, matcher); isMatch {
304
if isDebug { // matchers without an explicit name or with AND condition should only be made visible if debug is enabled
305
matcherName := GetMatcherName(matcher, matcherIndex)
306
result.Matches[matcherName] = matched
307
} else { // if it's a "named" matcher with OR condition, then display it
308
if matcherCondition == matchers.ORCondition && matcher.Name != "" {
309
result.Matches[matcher.Name] = matched
310
}
311
}
312
matches = true
313
} else if matcherCondition == matchers.ANDCondition {
314
if len(result.DynamicValues) > 0 {
315
return result, true
316
}
317
return result, false
318
}
319
}
320
321
result.Matched = matches
322
result.Extracted = len(result.OutputExtracts) > 0
323
if len(result.DynamicValues) > 0 && allInternalExtractors {
324
// only return early if all extractors are internal
325
// if some are internal and some are not then followthrough
326
return result, true
327
}
328
329
// Don't print if we have matchers, and they have not matched, regardless of extractor
330
if len(operators.Matchers) > 0 && !matches {
331
// if dynamic values are present then it is not a failure
332
if len(result.DynamicValues) > 0 {
333
return result, true
334
}
335
return nil, false
336
}
337
// Write a final string of output if matcher type is
338
// AND or if we have extractors for the mechanism too.
339
if len(result.Extracts) > 0 || len(result.OutputExtracts) > 0 || matches {
340
return result, true
341
}
342
// if dynamic values are present then it is not a failure
343
if len(result.DynamicValues) > 0 {
344
return result, true
345
}
346
return nil, false
347
}
348
349
// GetMatcherName returns matchername of given matcher
350
func GetMatcherName(matcher *matchers.Matcher, matcherIndex int) string {
351
if matcher.Name != "" {
352
return matcher.Name
353
} else {
354
return matcher.Type.String() + "-" + strconv.Itoa(matcherIndex+1) // making the index start from 1 to be more readable
355
}
356
}
357
358
// ExecuteInternalExtractors executes internal dynamic extractors
359
func (operators *Operators) ExecuteInternalExtractors(data map[string]interface{}, extract ExtractFunc) map[string]interface{} {
360
dynamicValues := make(map[string]interface{})
361
362
// Start with the extractors first and evaluate them.
363
for _, extractor := range operators.Extractors {
364
if !extractor.Internal {
365
continue
366
}
367
for match := range extract(data, extractor) {
368
if _, ok := dynamicValues[extractor.Name]; !ok {
369
dynamicValues[extractor.Name] = match
370
}
371
}
372
}
373
return dynamicValues
374
}
375
376
// IsEmpty determines if the operator has matchers or extractors
377
func (operators *Operators) IsEmpty() bool {
378
return operators.Len() == 0
379
}
380
381
// Len calculates the sum of the number of matchers and extractors
382
func (operators *Operators) Len() int {
383
return len(operators.Matchers) + len(operators.Extractors)
384
}
385
386
// getExtractedValue takes array of extracted values if it only has one value
387
// then it is flattened and returned as a string else original type is returned
388
func getExtractedValue(values []string) any {
389
if len(values) == 1 {
390
return values[0]
391
} else {
392
return values
393
}
394
}
395
396
// EvalBoolSlice evaluates a slice of bools using a logical AND
397
func EvalBoolSlice(slice []bool, isAnd bool) bool {
398
if len(slice) == 0 {
399
return false
400
}
401
402
result := slice[0]
403
for _, b := range slice[1:] {
404
if isAnd {
405
result = result && b
406
} else {
407
result = result || b
408
}
409
}
410
return result
411
}
412
413