Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/protocols/http/operators_test.go
2070 views
1
package http
2
3
import (
4
"net/http"
5
"testing"
6
"time"
7
8
"github.com/stretchr/testify/require"
9
10
"github.com/projectdiscovery/nuclei/v3/pkg/model"
11
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity"
12
"github.com/projectdiscovery/nuclei/v3/pkg/operators"
13
"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors"
14
"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers"
15
"github.com/projectdiscovery/nuclei/v3/pkg/output"
16
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
17
)
18
19
func TestResponseToDSLMap(t *testing.T) {
20
options := testutils.DefaultOptions
21
22
testutils.Init(options)
23
templateID := "testing-http"
24
request := &Request{
25
ID: templateID,
26
Name: "testing",
27
Path: []string{"{{BaseURL}}?test=1"},
28
Method: HTTPMethodTypeHolder{MethodType: HTTPGet},
29
}
30
executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{
31
ID: templateID,
32
Info: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: "test"},
33
})
34
err := request.Compile(executerOpts)
35
require.Nil(t, err, "could not compile file request")
36
37
resp := &http.Response{}
38
resp.Header = make(http.Header)
39
resp.Header.Set("Test", "Test-Response")
40
host := "http://example.com/test/"
41
matched := "http://example.com/test/?test=1"
42
43
event := request.responseToDSLMap(resp, host, matched, exampleRawRequest, exampleRawResponse, exampleResponseBody, exampleResponseHeader, 1*time.Second, map[string]interface{}{})
44
require.Len(t, event, 15, "could not get correct number of items in dsl map")
45
require.Equal(t, exampleRawResponse, event["response"], "could not get correct resp")
46
require.Equal(t, "Test-Response", event["test"], "could not get correct resp for header")
47
}
48
49
func TestHTTPOperatorMatch(t *testing.T) {
50
options := testutils.DefaultOptions
51
52
testutils.Init(options)
53
templateID := "testing-http"
54
request := &Request{
55
ID: templateID,
56
Name: "testing",
57
Path: []string{"{{BaseURL}}?test=1"},
58
Method: HTTPMethodTypeHolder{MethodType: HTTPGet},
59
}
60
executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{
61
ID: templateID,
62
Info: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: "test"},
63
})
64
err := request.Compile(executerOpts)
65
require.Nil(t, err, "could not compile file request")
66
67
resp := &http.Response{}
68
resp.Header = make(http.Header)
69
resp.Header.Set("Test", "Test-Response")
70
host := "http://example.com/test/"
71
matched := "http://example.com/test/?test=1"
72
73
event := request.responseToDSLMap(resp, host, matched, exampleRawRequest, exampleRawResponse, exampleResponseBody, exampleResponseHeader, 1*time.Second, map[string]interface{}{})
74
require.Len(t, event, 15, "could not get correct number of items in dsl map")
75
require.Equal(t, exampleRawResponse, event["response"], "could not get correct resp")
76
require.Equal(t, "Test-Response", event["test"], "could not get correct resp for header")
77
78
t.Run("valid", func(t *testing.T) {
79
matcher := &matchers.Matcher{
80
Part: "body",
81
Type: matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},
82
Words: []string{"1.1.1.1"},
83
}
84
err = matcher.CompileMatchers()
85
require.Nil(t, err, "could not compile matcher")
86
87
isMatched, matched := request.Match(event, matcher)
88
require.True(t, isMatched, "could not match valid response")
89
require.Equal(t, matcher.Words, matched)
90
})
91
92
t.Run("negative", func(t *testing.T) {
93
matcher := &matchers.Matcher{
94
Part: "body",
95
Type: matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},
96
Negative: true,
97
Words: []string{"random"},
98
}
99
err := matcher.CompileMatchers()
100
require.Nil(t, err, "could not compile negative matcher")
101
102
isMatched, matched := request.Match(event, matcher)
103
require.True(t, isMatched, "could not match valid negative response matcher")
104
require.Equal(t, []string{}, matched)
105
})
106
107
t.Run("invalid", func(t *testing.T) {
108
matcher := &matchers.Matcher{
109
Part: "body",
110
Type: matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},
111
Words: []string{"random"},
112
}
113
err := matcher.CompileMatchers()
114
require.Nil(t, err, "could not compile matcher")
115
116
isMatched, matched := request.Match(event, matcher)
117
require.False(t, isMatched, "could match invalid response matcher")
118
require.Equal(t, []string{}, matched)
119
})
120
121
t.Run("caseInsensitive", func(t *testing.T) {
122
matcher := &matchers.Matcher{
123
Part: "body",
124
Type: matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher}, // only applies to word
125
Words: []string{"EXAMPLE DOMAIN"},
126
CaseInsensitive: true,
127
}
128
err = matcher.CompileMatchers()
129
require.Nil(t, err, "could not compile matcher")
130
131
isMatched, matched := request.Match(event, matcher)
132
require.True(t, isMatched, "could not match valid response")
133
require.Equal(t, []string{"example domain"}, matched)
134
})
135
}
136
137
func TestHTTPOperatorExtract(t *testing.T) {
138
options := testutils.DefaultOptions
139
140
testutils.Init(options)
141
templateID := "testing-http"
142
request := &Request{
143
ID: templateID,
144
Name: "testing",
145
Path: []string{"{{BaseURL}}?test=1"},
146
Method: HTTPMethodTypeHolder{MethodType: HTTPGet},
147
}
148
executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{
149
ID: templateID,
150
Info: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: "test"},
151
})
152
err := request.Compile(executerOpts)
153
require.Nil(t, err, "could not compile file request")
154
155
resp := &http.Response{}
156
resp.Header = make(http.Header)
157
resp.Header.Set("Test-Header", "Test-Response")
158
host := "http://example.com/test/"
159
matched := "http://example.com/test/?test=1"
160
161
event := request.responseToDSLMap(resp, host, matched, exampleRawRequest, exampleRawResponse, exampleResponseBody, exampleResponseHeader, 1*time.Second, map[string]interface{}{})
162
require.Len(t, event, 15, "could not get correct number of items in dsl map")
163
require.Equal(t, exampleRawResponse, event["response"], "could not get correct resp")
164
require.Equal(t, "Test-Response", event["test_header"], "could not get correct resp for header")
165
166
t.Run("extract", func(t *testing.T) {
167
extractor := &extractors.Extractor{
168
Part: "body",
169
Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.RegexExtractor},
170
Regex: []string{"[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+"},
171
}
172
err = extractor.CompileExtractors()
173
require.Nil(t, err, "could not compile extractor")
174
175
data := request.Extract(event, extractor)
176
require.Greater(t, len(data), 0, "could not extractor valid response")
177
require.Equal(t, map[string]struct{}{"1.1.1.1": {}}, data, "could not extract correct data")
178
})
179
180
t.Run("kval", func(t *testing.T) {
181
extractor := &extractors.Extractor{
182
Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.KValExtractor},
183
KVal: []string{"test_header"},
184
}
185
err = extractor.CompileExtractors()
186
require.Nil(t, err, "could not compile kval extractor")
187
188
data := request.Extract(event, extractor)
189
require.Greater(t, len(data), 0, "could not extractor kval valid response")
190
require.Equal(t, map[string]struct{}{"Test-Response": {}}, data, "could not extract correct kval data")
191
})
192
193
t.Run("json", func(t *testing.T) {
194
event["body"] = exampleJSONResponseBody
195
196
t.Run("jq-simple", func(t *testing.T) {
197
extractor := &extractors.Extractor{
198
Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.JSONExtractor},
199
JSON: []string{".batters | .batter | .[] | .id"},
200
}
201
err = extractor.CompileExtractors()
202
require.Nil(t, err, "could not compile json extractor")
203
204
data := request.Extract(event, extractor)
205
require.Greater(t, len(data), 0, "could not extractor json valid response")
206
require.Equal(t, map[string]struct{}{"1001": {}, "1002": {}, "1003": {}, "1004": {}}, data, "could not extract correct json data")
207
})
208
t.Run("jq-array", func(t *testing.T) {
209
extractor := &extractors.Extractor{
210
Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.JSONExtractor},
211
JSON: []string{".array"},
212
}
213
err = extractor.CompileExtractors()
214
require.Nil(t, err, "could not compile json extractor")
215
216
data := request.Extract(event, extractor)
217
require.Greater(t, len(data), 0, "could not extractor json valid response")
218
require.Equal(t, map[string]struct{}{"[\"hello\",\"world\"]": {}}, data, "could not extract correct json data")
219
})
220
t.Run("jq-object", func(t *testing.T) {
221
extractor := &extractors.Extractor{
222
Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.JSONExtractor},
223
JSON: []string{".batters"},
224
}
225
err = extractor.CompileExtractors()
226
require.Nil(t, err, "could not compile json extractor")
227
228
data := request.Extract(event, extractor)
229
require.Greater(t, len(data), 0, "could not extractor json valid response")
230
require.Equal(t, map[string]struct{}{"{\"batter\":[{\"id\":\"1001\",\"type\":\"Regular\"},{\"id\":\"1002\",\"type\":\"Chocolate\"},{\"id\":\"1003\",\"type\":\"Blueberry\"},{\"id\":\"1004\",\"type\":\"Devil's Food\"}]}": {}}, data, "could not extract correct json data")
231
})
232
})
233
234
t.Run("caseInsensitive", func(t *testing.T) {
235
event["body"] = exampleResponseBody
236
237
extractor := &extractors.Extractor{
238
Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.KValExtractor},
239
KVal: []string{"TEST_HEADER"}, // only applies to KVal
240
CaseInsensitive: true,
241
}
242
err = extractor.CompileExtractors()
243
require.Nil(t, err, "could not compile kval extractor")
244
245
data := request.Extract(event, extractor)
246
require.Greater(t, len(data), 0, "could not extractor kval valid response")
247
require.Equal(t, map[string]struct{}{"test-response": {}}, data, "could not extract correct kval data")
248
})
249
}
250
251
func TestHTTPMakeResult(t *testing.T) {
252
options := testutils.DefaultOptions
253
254
testutils.Init(options)
255
templateID := "testing-http"
256
request := &Request{
257
ID: templateID,
258
Name: "testing",
259
Path: []string{"{{BaseURL}}?test=1"},
260
Method: HTTPMethodTypeHolder{MethodType: HTTPGet},
261
Operators: operators.Operators{
262
Matchers: []*matchers.Matcher{{
263
Name: "test",
264
Part: "body",
265
Type: matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},
266
Words: []string{"1.1.1.1"},
267
}},
268
Extractors: []*extractors.Extractor{{
269
Part: "body",
270
Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.RegexExtractor},
271
Regex: []string{"[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+"},
272
}},
273
},
274
}
275
executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{
276
ID: templateID,
277
Info: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: "test"},
278
})
279
err := request.Compile(executerOpts)
280
require.Nil(t, err, "could not compile file request")
281
282
resp := &http.Response{}
283
resp.Header = make(http.Header)
284
resp.Header.Set("Test", "Test-Response")
285
host := "http://example.com/test/"
286
matched := "http://example.com/test/?test=1"
287
288
event := request.responseToDSLMap(resp, host, matched, exampleRawRequest, exampleRawResponse, exampleResponseBody, exampleResponseHeader, 1*time.Second, map[string]interface{}{})
289
require.Len(t, event, 15, "could not get correct number of items in dsl map")
290
require.Equal(t, exampleRawResponse, event["response"], "could not get correct resp")
291
require.Equal(t, "Test-Response", event["test"], "could not get correct resp for header")
292
293
event["ip"] = "192.169.1.1"
294
finalEvent := &output.InternalWrappedEvent{InternalEvent: event}
295
if request.CompiledOperators != nil {
296
result, ok := request.CompiledOperators.Execute(event, request.Match, request.Extract, false)
297
if ok && result != nil {
298
finalEvent.OperatorsResult = result
299
finalEvent.Results = request.MakeResultEvent(finalEvent)
300
}
301
}
302
require.Equal(t, 1, len(finalEvent.Results), "could not get correct number of results")
303
require.Equal(t, "test", finalEvent.Results[0].MatcherName, "could not get correct matcher name of results")
304
require.Equal(t, "1.1.1.1", finalEvent.Results[0].ExtractedResults[0], "could not get correct extracted results")
305
}
306
307
const exampleRawRequest = `GET / HTTP/1.1
308
Host: example.com
309
Upgrade-Insecure-Requests: 1
310
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36
311
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
312
Accept-Encoding: gzip, deflate
313
Accept-Language: en-US,en;q=0.9,hi;q=0.8
314
If-None-Match: "3147526947+gzip"
315
If-Modified-Since: Thu, 17 Oct 2019 07:18:26 GMT
316
Connection: close
317
318
`
319
320
const exampleRawResponse = exampleResponseHeader + exampleResponseBody
321
const exampleResponseHeader = `
322
HTTP/1.1 200 OK
323
Accept-Ranges: bytes
324
Age: 493322
325
Cache-Control: max-age=604800
326
Content-Type: text/html; charset=UTF-8
327
Date: Thu, 04 Feb 2021 12:15:51 GMT
328
Etag: "3147526947+ident"
329
Expires: Thu, 11 Feb 2021 12:15:51 GMT
330
Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT
331
Server: ECS (nyb/1D1C)
332
Vary: Accept-Encoding
333
X-Cache: HIT
334
Content-Length: 1256
335
Connection: close
336
`
337
338
const exampleResponseBody = `
339
<!doctype html>
340
<html>
341
<head>
342
<title>Example Domain</title>
343
344
<meta charset="utf-8" />
345
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
346
<meta name="viewport" content="width=device-width, initial-scale=1" />
347
<style type="text/css">
348
body {
349
background-color: #f0f0f2;
350
margin: 0;
351
padding: 0;
352
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
353
354
}
355
div {
356
width: 600px;
357
margin: 5em auto;
358
padding: 2em;
359
background-color: #fdfdff;
360
border-radius: 0.5em;
361
box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);
362
}
363
a:link, a:visited {
364
color: #38488f;
365
text-decoration: none;
366
}
367
@media (max-width: 700px) {
368
div {
369
margin: 0 auto;
370
width: auto;
371
}
372
}
373
</style>
374
</head>
375
<a>1.1.1.1</a>
376
<body>
377
<div>
378
<h1>Example Domain</h1>
379
<p>This domain is for use in illustrative examples in documents. You may use this
380
domain in literature without prior coordination or asking for permission.</p>
381
<p><a href="https://www.iana.org/domains/example">More information...</a></p>
382
</div>
383
</body>
384
</html>
385
`
386
387
const exampleJSONResponseBody = `
388
{
389
"id": "0001",
390
"type": "donut",
391
"name": "Cake",
392
"ppu": 0.55,
393
"array": ["hello", "world"],
394
"batters": {
395
"batter": [
396
{
397
"id": "1001",
398
"type": "Regular"
399
},
400
{
401
"id": "1002",
402
"type": "Chocolate"
403
},
404
{
405
"id": "1003",
406
"type": "Blueberry"
407
},
408
{
409
"id": "1004",
410
"type": "Devil's Food"
411
}
412
]
413
},
414
"topping": [
415
{
416
"id": "5001",
417
"type": "None"
418
},
419
{
420
"id": "5002",
421
"type": "Glazed"
422
},
423
{
424
"id": "5005",
425
"type": "Sugar"
426
},
427
{
428
"id": "5007",
429
"type": "Powdered Sugar"
430
},
431
{
432
"id": "5006",
433
"type": "Chocolate with Sprinkles"
434
},
435
{
436
"id": "5003",
437
"type": "Chocolate"
438
},
439
{
440
"id": "5004",
441
"type": "Maple"
442
}
443
]
444
}
445
`
446
447