Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/protocols/common/variables/variables_test.go
2843 views
1
package variables
2
3
import (
4
"testing"
5
"time"
6
7
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh"
8
"github.com/projectdiscovery/nuclei/v3/pkg/utils"
9
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
10
"github.com/stretchr/testify/require"
11
"gopkg.in/yaml.v2"
12
)
13
14
func TestVariablesEvaluate(t *testing.T) {
15
data := `a2: "{{md5('test')}}"
16
a3: "this_is_random_text"
17
a4: "{{date_time('%Y-%M-%D')}}"
18
a5: "{{reverse(hostname)}}"
19
a6: "123456"`
20
21
variables := Variable{}
22
err := yaml.Unmarshal([]byte(data), &variables)
23
require.NoError(t, err, "could not unmarshal variables")
24
25
result := variables.Evaluate(map[string]interface{}{"hostname": "google.com"})
26
a4 := time.Now().Format("2006-01-02")
27
require.Equal(t, map[string]interface{}{"a2": "098f6bcd4621d373cade4e832627b4f6", "a3": "this_is_random_text", "a4": a4, "a5": "moc.elgoog", "a6": "123456"}, result, "could not get correct elements")
28
29
// json
30
data = `{
31
"a2": "{{md5('test')}}",
32
"a3": "this_is_random_text",
33
"a4": "{{date_time('%Y-%M-%D')}}",
34
"a5": "{{reverse(hostname)}}",
35
"a6": "123456"
36
}`
37
variables = Variable{}
38
err = json.Unmarshal([]byte(data), &variables)
39
require.NoError(t, err, "could not unmarshal json variables")
40
41
result = variables.Evaluate(map[string]interface{}{"hostname": "google.com"})
42
a4 = time.Now().Format("2006-01-02")
43
require.Equal(t, map[string]interface{}{"a2": "098f6bcd4621d373cade4e832627b4f6", "a3": "this_is_random_text", "a4": a4, "a5": "moc.elgoog", "a6": "123456"}, result, "could not get correct elements")
44
45
}
46
47
func TestCheckForLazyEval(t *testing.T) {
48
t.Run("undefined-parameters-in-expression", func(t *testing.T) {
49
// Variables with expressions that reference undefined parameters
50
// should be marked for lazy evaluation
51
variables := &Variable{
52
InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(2),
53
}
54
variables.Set("var1", "{{sha1(serial)}}") // 'serial' is undefined
55
variables.Set("var2", "{{replace(user, '.', '')}}") // 'user' is undefined
56
57
result := variables.checkForLazyEval()
58
require.True(t, result, "should detect undefined parameters and set LazyEval=true")
59
require.True(t, variables.LazyEval, "LazyEval flag should be true")
60
})
61
62
t.Run("self-referencing-variables", func(t *testing.T) {
63
// Variables that reference other defined variables should NOT be lazy
64
variables := &Variable{
65
InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(2),
66
}
67
variables.Set("base", "example")
68
variables.Set("derived", "{{base}}_suffix") // 'base' is defined
69
70
result := variables.checkForLazyEval()
71
require.False(t, result, "should not set LazyEval for self-referencing defined variables")
72
require.False(t, variables.LazyEval, "LazyEval flag should be false")
73
})
74
75
t.Run("constant-expressions", func(t *testing.T) {
76
// Constant expressions without variables should NOT be lazy
77
variables := &Variable{
78
InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(2),
79
}
80
variables.Set("const1", "{{2+2}}")
81
variables.Set("const2", "{{rand_int(1, 100)}}")
82
83
result := variables.checkForLazyEval()
84
require.False(t, result, "should not set LazyEval for constant expressions")
85
require.False(t, variables.LazyEval, "LazyEval flag should be false")
86
})
87
88
t.Run("known-runtime-variables", func(t *testing.T) {
89
// Variables with known runtime variables (Host, BaseURL, etc.) should be lazy
90
variables := &Variable{
91
InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(1),
92
}
93
variables.Set("url", "{{BaseURL}}/api")
94
95
result := variables.checkForLazyEval()
96
require.True(t, result, "should detect known runtime variables")
97
require.True(t, variables.LazyEval, "LazyEval flag should be true")
98
})
99
100
t.Run("interactsh-url", func(t *testing.T) {
101
// Variables with interactsh-url should be lazy
102
variables := &Variable{
103
InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(1),
104
}
105
variables.Set("callback", "{{interactsh-url}}")
106
107
result := variables.checkForLazyEval()
108
require.True(t, result, "should detect interactsh-url")
109
require.True(t, variables.LazyEval, "LazyEval flag should be true")
110
})
111
112
t.Run("mixed-defined-and-undefined", func(t *testing.T) {
113
// Mix of defined and undefined parameters in actual expressions
114
variables := &Variable{
115
InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(3),
116
}
117
variables.Set("defined", "value")
118
variables.Set("uses_defined", "{{base64(defined)}}") // OK - 'defined' exists
119
variables.Set("uses_undefined", "{{base64(undefined_param)}}") // NOT OK - 'undefined_param' doesn't exist
120
121
result := variables.checkForLazyEval()
122
require.True(t, result, "should detect undefined parameters even with some defined")
123
require.True(t, variables.LazyEval, "LazyEval flag should be true")
124
})
125
126
t.Run("plain-strings-no-expressions", func(t *testing.T) {
127
// Plain string values without expressions
128
variables := &Variable{
129
InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(2),
130
}
131
variables.Set("plain1", "simple value")
132
variables.Set("plain2", "another value")
133
134
result := variables.checkForLazyEval()
135
require.False(t, result, "should not set LazyEval for plain strings")
136
require.False(t, variables.LazyEval, "LazyEval flag should be false")
137
})
138
139
t.Run("complex-expression-with-undefined", func(t *testing.T) {
140
// Complex expression with multiple undefined parameters
141
variables := &Variable{
142
InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(1),
143
}
144
variables.Set("complex", "{{sha1(cert_serial + issuer)}}")
145
146
result := variables.checkForLazyEval()
147
require.True(t, result, "should detect undefined parameters in complex expressions")
148
require.True(t, variables.LazyEval, "LazyEval flag should be true")
149
})
150
}
151
152
func TestVariablesEvaluateChained(t *testing.T) {
153
t.Run("chained-variable-references", func(t *testing.T) {
154
// Test that variables can reference previously defined variables
155
// and that input values (like BaseURL) are available for evaluation
156
// but not included in the result
157
variables := &Variable{
158
LazyEval: true, // skip auto-evaluation in UnmarshalYAML
159
InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(3),
160
}
161
variables.Set("a", "hello")
162
variables.Set("b", "{{a}} world")
163
variables.Set("c", "{{b}}!")
164
165
inputValues := map[string]interface{}{
166
"BaseURL": "http://example.com",
167
"Host": "example.com",
168
}
169
170
result := variables.Evaluate(inputValues)
171
172
// Result should contain only the defined variables, not input values
173
require.Len(t, result, 3, "result should contain exactly 3 variables")
174
require.NotContains(t, result, "BaseURL", "result should not contain input values")
175
require.NotContains(t, result, "Host", "result should not contain input values")
176
177
// Chained evaluation should work correctly
178
require.Equal(t, "hello", result["a"])
179
require.Equal(t, "hello world", result["b"])
180
require.Equal(t, "hello world!", result["c"])
181
})
182
183
t.Run("variables-using-input-values", func(t *testing.T) {
184
// Test that variables can use input values in expressions
185
variables := &Variable{
186
LazyEval: true,
187
InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(2),
188
}
189
variables.Set("api_url", "{{BaseURL}}/api/v1")
190
variables.Set("full_path", "{{api_url}}/users")
191
192
inputValues := map[string]interface{}{
193
"BaseURL": "http://example.com",
194
}
195
196
result := variables.Evaluate(inputValues)
197
198
require.Len(t, result, 2)
199
require.Equal(t, "http://example.com/api/v1", result["api_url"])
200
require.Equal(t, "http://example.com/api/v1/users", result["full_path"])
201
require.NotContains(t, result, "BaseURL")
202
})
203
204
t.Run("mixed-expressions-and-chaining", func(t *testing.T) {
205
// Test combining DSL functions with chained variables
206
variables := &Variable{
207
LazyEval: true,
208
InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(3),
209
}
210
variables.Set("token", "secret123")
211
variables.Set("hashed", "{{md5(token)}}")
212
variables.Set("header", "X-Auth: {{hashed}}")
213
214
result := variables.Evaluate(map[string]interface{}{})
215
216
require.Equal(t, "secret123", result["token"])
217
require.Equal(t, "5d7845ac6ee7cfffafc5fe5f35cf666d", result["hashed"]) // md5("secret123")
218
require.Equal(t, "X-Auth: 5d7845ac6ee7cfffafc5fe5f35cf666d", result["header"])
219
})
220
221
t.Run("evaluation-order-preserved", func(t *testing.T) {
222
// Test that evaluation follows insertion order
223
// (important for variables that depend on previously defined ones)
224
variables := &Variable{
225
LazyEval: true,
226
InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(4),
227
}
228
variables.Set("step1", "A")
229
variables.Set("step2", "{{step1}}B")
230
variables.Set("step3", "{{step2}}C")
231
variables.Set("step4", "{{step3}}D")
232
233
result := variables.Evaluate(map[string]interface{}{})
234
235
require.Equal(t, "A", result["step1"])
236
require.Equal(t, "AB", result["step2"])
237
require.Equal(t, "ABC", result["step3"])
238
require.Equal(t, "ABCD", result["step4"])
239
})
240
}
241
242
func TestEvaluateWithInteractshOverrideOrder(t *testing.T) {
243
// This test demonstrates a bug where interactsh URL replacement is wasted
244
// when an input value exists for the same variable key.
245
//
246
// Bug scenario:
247
// 1. Variable "callback" is defined with "{{interactsh-url}}"
248
// 2. Input values contain "callback" with some other value
249
// 3. The interactsh-url is replaced first (wasting an interactsh URL)
250
// 4. Then immediately overwritten by the input value
251
//
252
// Expected behavior: Input override should be checked FIRST, then interactsh
253
// replacement should happen on the final valueString.
254
255
t.Run("interactsh-replacement-with-input-override", func(t *testing.T) {
256
variables := &Variable{
257
LazyEval: true,
258
InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(1),
259
}
260
variables.Set("callback", "{{interactsh-url}}")
261
262
// Input provides an override that also contains interactsh-url
263
inputValues := map[string]interface{}{
264
"callback": "https://custom.{{interactsh-url}}/path",
265
}
266
267
// Create a real interactsh client for testing
268
client, err := interactsh.New(&interactsh.Options{
269
ServerURL: "oast.fun",
270
CacheSize: 100,
271
Eviction: 60 * time.Second,
272
CooldownPeriod: 5 * time.Second,
273
PollDuration: 5 * time.Second,
274
DisableHttpFallback: true,
275
})
276
require.NoError(t, err, "could not create interactsh client")
277
defer client.Close()
278
279
result, urls := variables.EvaluateWithInteractsh(inputValues, client)
280
281
// The input override contains interactsh-url, so it should be replaced
282
// and we should have exactly 1 URL from the input override
283
require.Len(t, urls, 1, "should have 1 interactsh URL from input override")
284
285
// The result should use the input override (with interactsh replaced)
286
require.Contains(t, result["callback"], "https://custom.", "should use input override pattern")
287
require.Contains(t, result["callback"], "/path", "should use input override pattern")
288
require.NotContains(t, result["callback"], "{{interactsh-url}}", "interactsh should be replaced")
289
})
290
291
t.Run("interactsh-replacement-without-input-override", func(t *testing.T) {
292
variables := &Variable{
293
LazyEval: true,
294
InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(1),
295
}
296
variables.Set("callback", "{{interactsh-url}}")
297
298
// No input override for "callback"
299
inputValues := map[string]interface{}{
300
"other_key": "other_value",
301
}
302
303
client, err := interactsh.New(&interactsh.Options{
304
ServerURL: "oast.fun",
305
CacheSize: 100,
306
Eviction: 60 * time.Second,
307
CooldownPeriod: 5 * time.Second,
308
PollDuration: 5 * time.Second,
309
DisableHttpFallback: true,
310
})
311
require.NoError(t, err, "could not create interactsh client")
312
defer client.Close()
313
314
result, urls := variables.EvaluateWithInteractsh(inputValues, client)
315
316
// Should have 1 URL from the variable definition
317
require.Len(t, urls, 1, "should have 1 interactsh URL")
318
319
// The result should be the replaced interactsh URL
320
require.NotContains(t, result["callback"], "{{interactsh-url}}", "interactsh should be replaced")
321
require.NotEmpty(t, result["callback"], "callback should have a value")
322
})
323
}
324
325