Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/protocols/common/variables/variables.go
2846 views
1
package variables
2
3
import (
4
"strings"
5
6
"github.com/Knetic/govaluate"
7
"github.com/invopop/jsonschema"
8
"github.com/projectdiscovery/nuclei/v3/pkg/operators/common/dsl"
9
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions"
10
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
11
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh"
12
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/marker"
13
protocolutils "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils"
14
"github.com/projectdiscovery/nuclei/v3/pkg/types"
15
"github.com/projectdiscovery/nuclei/v3/pkg/utils"
16
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
17
stringsutil "github.com/projectdiscovery/utils/strings"
18
)
19
20
// Variable is a key-value pair of strings that can be used
21
// throughout template.
22
type Variable struct {
23
// LazyEval is used to evaluate variables lazily if it using any expression
24
// or global variables.
25
LazyEval bool `yaml:"-" json:"-"`
26
utils.InsertionOrderedStringMap `yaml:"-" json:"-"`
27
}
28
29
func (variables Variable) JSONSchema() *jsonschema.Schema {
30
gotType := &jsonschema.Schema{
31
Type: "object",
32
Title: "variables for the request",
33
Description: "Additional variables for the request",
34
AdditionalProperties: &jsonschema.Schema{},
35
}
36
return gotType
37
}
38
39
func (variables *Variable) UnmarshalYAML(unmarshal func(interface{}) error) error {
40
variables.InsertionOrderedStringMap = utils.InsertionOrderedStringMap{}
41
if err := unmarshal(&variables.InsertionOrderedStringMap); err != nil {
42
return err
43
}
44
45
if variables.LazyEval || variables.checkForLazyEval() {
46
return nil
47
}
48
49
evaluated := variables.Evaluate(map[string]interface{}{})
50
51
for k, v := range evaluated {
52
variables.Set(k, v)
53
}
54
return nil
55
}
56
57
func (variables *Variable) UnmarshalJSON(data []byte) error {
58
variables.InsertionOrderedStringMap = utils.InsertionOrderedStringMap{}
59
if err := json.Unmarshal(data, &variables.InsertionOrderedStringMap); err != nil {
60
return err
61
}
62
evaluated := variables.Evaluate(map[string]interface{}{})
63
64
for k, v := range evaluated {
65
variables.Set(k, v)
66
}
67
return nil
68
}
69
70
// Evaluate returns a finished map of variables based on set values
71
func (variables *Variable) Evaluate(values map[string]interface{}) map[string]interface{} {
72
result := make(map[string]interface{}, variables.Len())
73
combined := make(map[string]interface{}, len(values)+variables.Len())
74
generators.MergeMapsInto(combined, values)
75
76
variables.ForEach(func(key string, value interface{}) {
77
if sliceValue, ok := value.([]interface{}); ok {
78
// slices cannot be evaluated
79
result[key] = sliceValue
80
combined[key] = sliceValue
81
return
82
}
83
valueString := types.ToString(value)
84
if existingValue, ok := combined[key]; ok {
85
valueString = types.ToString(existingValue)
86
}
87
evaluated := evaluateVariableValueWithMap(valueString, combined)
88
result[key] = evaluated
89
combined[key] = evaluated
90
})
91
return result
92
}
93
94
// GetAll returns all variables as a map
95
func (variables *Variable) GetAll() map[string]interface{} {
96
result := make(map[string]interface{}, variables.Len())
97
variables.ForEach(func(key string, value interface{}) {
98
result[key] = value
99
})
100
return result
101
}
102
103
// EvaluateWithInteractsh returns evaluation results of variables with interactsh
104
func (variables *Variable) EvaluateWithInteractsh(values map[string]interface{}, interact *interactsh.Client) (map[string]interface{}, []string) {
105
result := make(map[string]interface{}, variables.Len())
106
combined := make(map[string]interface{}, len(values)+variables.Len())
107
generators.MergeMapsInto(combined, values)
108
109
var interactURLs []string
110
variables.ForEach(func(key string, value interface{}) {
111
if sliceValue, ok := value.([]interface{}); ok {
112
// slices cannot be evaluated
113
result[key] = sliceValue
114
combined[key] = sliceValue
115
return
116
}
117
valueString := types.ToString(value)
118
if existingValue, ok := combined[key]; ok {
119
valueString = types.ToString(existingValue)
120
}
121
if strings.Contains(valueString, "interactsh-url") {
122
valueString, interactURLs = interact.Replace(valueString, interactURLs)
123
}
124
evaluated := evaluateVariableValueWithMap(valueString, combined)
125
result[key] = evaluated
126
combined[key] = evaluated
127
})
128
return result, interactURLs
129
}
130
131
// evaluateVariableValue expression and returns final value.
132
//
133
// Deprecated: use evaluateVariableValueWithMap instead to avoid repeated map
134
// merging overhead.
135
func evaluateVariableValue(expression string, values, processing map[string]interface{}) string { // nolint
136
finalMap := generators.MergeMaps(values, processing)
137
result, err := expressions.Evaluate(expression, finalMap)
138
if err != nil {
139
return expression
140
}
141
142
return result
143
}
144
145
// evaluateVariableValueWithMap evaluates an expression with a pre-merged map.
146
func evaluateVariableValueWithMap(expression string, combinedMap map[string]interface{}) string {
147
result, err := expressions.Evaluate(expression, combinedMap)
148
if err != nil {
149
return expression
150
}
151
152
return result
153
}
154
155
// checkForLazyEval checks if the variables have any lazy evaluation i.e any dsl function
156
// and sets the flag accordingly.
157
func (variables *Variable) checkForLazyEval() bool {
158
var needsLazy bool
159
160
variables.ForEach(func(key string, value interface{}) {
161
if needsLazy {
162
return
163
}
164
165
for _, v := range protocolutils.KnownVariables {
166
if stringsutil.ContainsAny(types.ToString(value), v) {
167
needsLazy = true
168
return
169
}
170
}
171
172
// this is a hotfix and not the best way to do it
173
// will be refactored once we move scan state to scanContext (see: https://github.com/projectdiscovery/nuclei/issues/4631)
174
if strings.Contains(types.ToString(value), "interactsh-url") {
175
needsLazy = true
176
return
177
}
178
179
if hasUndefinedParams(types.ToString(value), variables) {
180
needsLazy = true
181
return
182
}
183
})
184
185
variables.LazyEval = needsLazy
186
187
return variables.LazyEval
188
}
189
190
// hasUndefinedParams checks if a variable value contains expressions that ref
191
// parameters not defined in the current variable scope, indicating it needs
192
// runtime context.
193
func hasUndefinedParams(value string, variables *Variable) bool {
194
exprs := expressions.FindExpressions(value, marker.ParenthesisOpen, marker.ParenthesisClose, map[string]interface{}{})
195
if len(exprs) == 0 {
196
return false
197
}
198
199
definedVars := make(map[string]struct{})
200
variables.ForEach(func(key string, _ interface{}) {
201
definedVars[key] = struct{}{}
202
})
203
204
for _, expr := range exprs {
205
compiled, err := govaluate.NewEvaluableExpressionWithFunctions(expr, dsl.HelperFunctions)
206
if err != nil {
207
// NOTE(dwisiswant0): here, it might need runtime context.
208
return true
209
}
210
211
vars := compiled.Vars()
212
for _, paramName := range vars {
213
// NOTE(dwisiswant0): also here, if it's not in our defined vars.
214
if _, exists := definedVars[paramName]; !exists {
215
return true
216
}
217
}
218
}
219
220
return false
221
}
222
223