Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/tmplexec/multiproto/multi.go
2070 views
1
package multiproto
2
3
import (
4
"strconv"
5
"sync/atomic"
6
7
"github.com/projectdiscovery/nuclei/v3/pkg/output"
8
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
9
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
10
"github.com/projectdiscovery/nuclei/v3/pkg/scan"
11
"github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
12
"github.com/projectdiscovery/nuclei/v3/pkg/tmplexec/utils"
13
mapsutil "github.com/projectdiscovery/utils/maps"
14
stringsutil "github.com/projectdiscovery/utils/strings"
15
)
16
17
// Mutliprotocol is a template executer engine that executes multiple protocols
18
// with logic in between
19
type MultiProtocol struct {
20
requests []protocols.Request
21
options *protocols.ExecutorOptions
22
results *atomic.Bool
23
readOnlyArgs map[string]interface{} // readOnlyArgs are readonly args that are available after compilation
24
}
25
26
// NewMultiProtocol creates a new multiprotocol template engine from a list of requests
27
func NewMultiProtocol(requests []protocols.Request, options *protocols.ExecutorOptions, results *atomic.Bool) *MultiProtocol {
28
if results == nil {
29
results = &atomic.Bool{}
30
}
31
return &MultiProtocol{requests: requests, options: options, results: results}
32
}
33
34
// Compile engine specific compilation
35
func (m *MultiProtocol) Compile() error {
36
// load all variables and evaluate with existing data
37
variableMap := m.options.Variables.GetAll()
38
// cli options
39
optionVars := generators.BuildPayloadFromOptions(m.options.Options)
40
// constants
41
constants := m.options.Constants
42
allVars := generators.MergeMaps(variableMap, constants, optionVars)
43
allVars = m.options.Variables.Evaluate(allVars)
44
m.readOnlyArgs = allVars
45
// no need to load files since they are done at template level
46
return nil
47
}
48
49
// ExecuteWithResults executes the template and returns results
50
func (m *MultiProtocol) ExecuteWithResults(ctx *scan.ScanContext) error {
51
select {
52
case <-ctx.Context().Done():
53
return ctx.Context().Err()
54
default:
55
}
56
57
// put all readonly args into template context
58
m.options.GetTemplateCtx(ctx.Input.MetaInput).Merge(m.readOnlyArgs)
59
60
// add all input args to template context
61
ctx.Input.ForEach(func(key string, value interface{}) {
62
m.options.GetTemplateCtx(ctx.Input.MetaInput).Set(key, value)
63
})
64
65
previous := mapsutil.NewSyncLockMap[string, any]()
66
67
// template context: contains values extracted using `internal` extractor from previous protocols
68
// these values are extracted from each protocol in queue and are passed to next protocol in queue
69
// instead of adding seperator field to handle such cases these values are appended to `dynamicValues` (which are meant to be used in workflows)
70
// this makes it possible to use multi protocol templates in workflows
71
// Note: internal extractor values take precedence over dynamicValues from workflows (i.e other templates in workflow)
72
73
// execute all protocols in the queue
74
for _, req := range m.requests {
75
select {
76
case <-ctx.Context().Done():
77
return ctx.Context().Err()
78
default:
79
}
80
inputItem := ctx.Input.Clone()
81
if m.options.InputHelper != nil && ctx.Input.MetaInput.Input != "" {
82
if inputItem.MetaInput.Input = m.options.InputHelper.Transform(inputItem.MetaInput.Input, req.Type()); inputItem.MetaInput.Input == "" {
83
return nil
84
}
85
}
86
// FIXME: this hack of using hash to get templateCtx has known issues scan context based approach should be adopted ASAP
87
values := m.options.GetTemplateCtx(inputItem.MetaInput).GetAll()
88
err := req.ExecuteWithResults(inputItem, output.InternalEvent(values), output.InternalEvent(previous.GetAll()), func(event *output.InternalWrappedEvent) {
89
if event == nil {
90
return
91
}
92
93
utils.FillPreviousEvent(req.GetID(), event, previous)
94
95
// log event and generate result for the event
96
ctx.LogEvent(event)
97
// export dynamic values from operators (i.e internal:true)
98
if event.OperatorsResult != nil && len(event.OperatorsResult.DynamicValues) > 0 {
99
for k, v := range event.OperatorsResult.DynamicValues {
100
// TBD: iterate-all is only supported in `http` protocol
101
// we either need to add support for iterate-all in other protocols or implement a different logic (specific to template context)
102
// currently if dynamic value array only contains one value we replace it with the value
103
if len(v) == 1 {
104
m.options.GetTemplateCtx(ctx.Input.MetaInput).Set(k, v[0])
105
} else {
106
// Note: if extracted value contains multiple values then they can be accessed by indexing
107
// ex: if values are dynamic = []string{"a","b","c"} then they are available as
108
// dynamic = "a" , dynamic1 = "b" , dynamic2 = "c"
109
// we intentionally omit first index for unknown situations (where no of extracted values are not known)
110
for i, val := range v {
111
if i == 0 {
112
m.options.GetTemplateCtx(ctx.Input.MetaInput).Set(k, val)
113
} else {
114
m.options.GetTemplateCtx(ctx.Input.MetaInput).Set(k+strconv.Itoa(i), val)
115
}
116
}
117
}
118
}
119
}
120
121
// evaluate all variables after execution of each protocol
122
variableMap := m.options.Variables.Evaluate(m.options.GetTemplateCtx(ctx.Input.MetaInput).GetAll())
123
m.options.GetTemplateCtx(ctx.Input.MetaInput).Merge(variableMap) // merge all variables into template context
124
})
125
// in case of fatal error skip execution of next protocols
126
if err != nil {
127
// always log errors
128
ctx.LogError(err)
129
// for some classes of protocols (i.e ssl) errors like tls handshake are a legitimate behavior so we don't stop execution
130
// connection failures are already tracked by the internal host error cache
131
// we use strings comparison as the error is not formalized into instance within the standard library
132
// within a flow instead we consider ssl errors as fatal, since a specific logic was requested
133
if req.Type() == types.SSLProtocol && stringsutil.ContainsAnyI(err.Error(), "protocol version not supported", "could not do tls handshake") {
134
continue
135
}
136
}
137
}
138
return nil
139
}
140
141
// Name of the template engine
142
func (m *MultiProtocol) Name() string {
143
return "multiproto"
144
}
145
146