Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/protocols/headless/engine/page_actions_test.go
2072 views
1
package engine
2
3
import (
4
"context"
5
"fmt"
6
"io"
7
"math/rand"
8
"net/http"
9
"net/http/cookiejar"
10
"net/http/httptest"
11
"os"
12
"os/exec"
13
"path/filepath"
14
"strconv"
15
"strings"
16
"testing"
17
"time"
18
19
"github.com/stretchr/testify/require"
20
21
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
22
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
23
"github.com/projectdiscovery/nuclei/v3/pkg/testutils/testheadless"
24
"github.com/projectdiscovery/nuclei/v3/pkg/types"
25
envutil "github.com/projectdiscovery/utils/env"
26
stringsutil "github.com/projectdiscovery/utils/strings"
27
)
28
29
func TestActionNavigate(t *testing.T) {
30
response := `
31
<html>
32
<head>
33
<title>Nuclei Test Page</title>
34
</head>
35
<body>
36
<h1>Nuclei Test</h1>
37
</body>
38
</html>`
39
40
actions := []*Action{{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}}, {ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}}}
41
42
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {
43
require.Nilf(t, err, "could not run page actions")
44
require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")
45
})
46
}
47
48
func TestActionScript(t *testing.T) {
49
response := `
50
<html>
51
<head>
52
<title>Nuclei Test Page</title>
53
</head>
54
<body>Nuclei Test Page</body>
55
<script>window.test = 'some-data';</script>
56
</html>`
57
58
timeout := 180 * time.Second
59
60
t.Run("run-and-results", func(t *testing.T) {
61
actions := []*Action{
62
{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},
63
{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},
64
{ActionType: ActionTypeHolder{ActionType: ActionScript}, Name: "test", Data: map[string]string{"code": "() => window.test"}},
65
}
66
67
testHeadlessSimpleResponse(t, response, actions, timeout, func(page *Page, err error, out ActionData) {
68
require.Nil(t, err, "could not run page actions")
69
require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")
70
require.Equal(t, "some-data", out["test"], "could not run js and get results correctly")
71
})
72
})
73
74
t.Run("hook", func(t *testing.T) {
75
actions := []*Action{
76
{ActionType: ActionTypeHolder{ActionType: ActionScript}, Data: map[string]string{"code": "() => window.test = 'some-data';", "hook": "true"}},
77
{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},
78
{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},
79
{ActionType: ActionTypeHolder{ActionType: ActionScript}, Name: "test", Data: map[string]string{"code": "() => window.test"}},
80
}
81
testHeadlessSimpleResponse(t, response, actions, timeout, func(page *Page, err error, out ActionData) {
82
require.Nil(t, err, "could not run page actions")
83
require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")
84
require.Equal(t, "some-data", out["test"], "could not run js and get results correctly with js hook")
85
})
86
})
87
}
88
89
func TestActionClick(t *testing.T) {
90
response := `
91
<html>
92
<head>
93
<title>Nuclei Test Page</title>
94
</head>
95
<body>Nuclei Test Page</body>
96
<button onclick='this.setAttribute("a", "ok")'>click me</button>
97
</html>`
98
99
actions := []*Action{
100
{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},
101
{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},
102
{ActionType: ActionTypeHolder{ActionType: ActionClick}, Data: map[string]string{"selector": "button"}}, // Use css selector for clicking
103
}
104
105
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {
106
require.Nil(t, err, "could not run page actions")
107
require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")
108
el := page.Page().MustElement("button")
109
val := el.MustAttribute("a")
110
require.Equal(t, "ok", *val, "could not click button")
111
})
112
}
113
114
func TestActionRightClick(t *testing.T) {
115
response := `
116
<html>
117
<head>
118
<title>Nuclei Test Page</title>
119
</head>
120
<body>Nuclei Test Page</body>
121
<button id="test" onrightclick=''>click me</button>
122
<script>
123
elm = document.getElementById("test");
124
elm.onmousedown = function(event) {
125
if (event.which == 3) {
126
elm.setAttribute("a", "ok")
127
}
128
}
129
</script>
130
</html>`
131
132
actions := []*Action{
133
{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},
134
{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},
135
{ActionType: ActionTypeHolder{ActionType: ActionRightClick}, Data: map[string]string{"selector": "button"}}, // Use css selector for clicking
136
}
137
138
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {
139
require.Nil(t, err, "could not run page actions")
140
require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")
141
el := page.Page().MustElement("button")
142
val := el.MustAttribute("a")
143
require.Equal(t, "ok", *val, "could not click button")
144
})
145
}
146
147
func TestActionTextInput(t *testing.T) {
148
response := `
149
<html>
150
<head>
151
<title>Nuclei Test Page</title>
152
</head>
153
<body>Nuclei Test Page</body>
154
<input type="text" onchange="this.setAttribute('event', 'input-change')">
155
</html>`
156
157
actions := []*Action{
158
{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},
159
{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},
160
{ActionType: ActionTypeHolder{ActionType: ActionTextInput}, Data: map[string]string{"selector": "input", "value": "test"}},
161
}
162
163
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {
164
require.Nil(t, err, "could not run page actions")
165
require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")
166
el := page.Page().MustElement("input")
167
val := el.MustAttribute("event")
168
require.Equal(t, "input-change", *val, "could not get input change")
169
require.Equal(t, "test", el.MustText(), "could not get input change value")
170
})
171
}
172
173
func TestActionHeadersChange(t *testing.T) {
174
actions := []*Action{
175
{ActionType: ActionTypeHolder{ActionType: ActionSetHeader}, Data: map[string]string{"part": "request", "key": "Test", "value": "Hello"}},
176
{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},
177
{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},
178
}
179
180
handler := func(w http.ResponseWriter, r *http.Request) {
181
if r.Header.Get("Test") == "Hello" {
182
_, _ = fmt.Fprintln(w, `found`)
183
}
184
}
185
186
testHeadless(t, actions, 20*time.Second, handler, func(page *Page, err error, out ActionData) {
187
require.Nil(t, err, "could not run page actions")
188
require.Equal(t, "found", strings.ToLower(strings.TrimSpace(page.Page().MustElement("html").MustText())), "could not set header correctly")
189
})
190
}
191
192
func TestActionScreenshot(t *testing.T) {
193
response := `
194
<html>
195
<head>
196
<title>Nuclei Test Page</title>
197
</head>
198
<body>Nuclei Test Page</body>
199
</html>`
200
201
// filePath where screenshot is saved
202
filePath := filepath.Join(os.TempDir(), "test.png")
203
actions := []*Action{
204
{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},
205
{ActionType: ActionTypeHolder{ActionType: ActionWaitFMP}},
206
{ActionType: ActionTypeHolder{ActionType: ActionScreenshot}, Data: map[string]string{"to": filePath}},
207
}
208
209
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {
210
require.Nil(t, err, "could not run page actions")
211
require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")
212
_ = page.Page()
213
require.FileExists(t, filePath, "could not find screenshot file %v", filePath)
214
if err := os.RemoveAll(filePath); err != nil {
215
t.Logf("got error %v while deleting temp file", err)
216
}
217
})
218
}
219
220
func TestActionScreenshotToDir(t *testing.T) {
221
response := `
222
<html>
223
<head>
224
<title>Nuclei Test Page</title>
225
</head>
226
<body>Nuclei Test Page</body>
227
</html>`
228
229
filePath := filepath.Join(os.TempDir(), "screenshot-"+strconv.Itoa(rand.Intn(1000)), "test.png")
230
231
actions := []*Action{
232
{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},
233
{ActionType: ActionTypeHolder{ActionType: ActionWaitFMP}},
234
{ActionType: ActionTypeHolder{ActionType: ActionScreenshot}, Data: map[string]string{"to": filePath, "mkdir": "true"}},
235
}
236
237
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {
238
require.Nil(t, err, "could not run page actions")
239
require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")
240
_ = page.Page()
241
require.FileExists(t, filePath, "could not find screenshot file %v", filePath)
242
if err := os.RemoveAll(filePath); err != nil {
243
t.Logf("got error %v while deleting temp file", err)
244
}
245
})
246
}
247
248
func TestActionTimeInput(t *testing.T) {
249
response := `
250
<html>
251
<head>
252
<title>Nuclei Test Page</title>
253
</head>
254
<body>Nuclei Test Page</body>
255
<input type="date">
256
</html>`
257
258
actions := []*Action{
259
{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},
260
{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},
261
{ActionType: ActionTypeHolder{ActionType: ActionTimeInput}, Data: map[string]string{"selector": "input", "value": "2006-01-02T15:04:05Z"}},
262
}
263
264
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {
265
require.Nil(t, err, "could not run page actions")
266
require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")
267
el := page.Page().MustElement("input")
268
require.Equal(t, "2006-01-02", el.MustText(), "could not get input time value")
269
})
270
}
271
272
func TestActionSelectInput(t *testing.T) {
273
response := `
274
<html>
275
<head>
276
<title>Nuclei Test Page</title>
277
</head>
278
<body>
279
<select name="test" id="test">
280
<option value="test1">Test1</option>
281
<option value="test2">Test2</option>
282
</select>
283
</body>
284
</html>`
285
286
actions := []*Action{
287
{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},
288
{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},
289
{ActionType: ActionTypeHolder{ActionType: ActionSelectInput}, Data: map[string]string{"by": "x", "xpath": "//select[@id='test']", "value": "Test2", "selected": "true"}},
290
}
291
292
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {
293
require.Nil(t, err, "could not run page actions")
294
el := page.Page().MustElement("select")
295
require.Equal(t, "Test2", el.MustText(), "could not get input change value")
296
})
297
}
298
299
func TestActionFilesInput(t *testing.T) {
300
response := `
301
<html>
302
<head>
303
<title>Nuclei Test Page</title>
304
</head>
305
<body>Nuclei Test Page</body>
306
<input type="file">
307
</html>`
308
309
actions := []*Action{
310
{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},
311
{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},
312
{ActionType: ActionTypeHolder{ActionType: ActionFilesInput}, Data: map[string]string{"selector": "input", "value": "test1.pdf"}},
313
}
314
315
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {
316
require.Nil(t, err, "could not run page actions")
317
require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")
318
el := page.Page().MustElement("input")
319
require.Equal(t, "C:\\fakepath\\test1.pdf", el.MustText(), "could not get input file")
320
})
321
}
322
323
// Negative testcase for files input where it should fail
324
func TestActionFilesInputNegative(t *testing.T) {
325
response := `
326
<html>
327
<head>
328
<title>Nuclei Test Page</title>
329
</head>
330
<body>Nuclei Test Page</body>
331
<input type="file">
332
</html>`
333
334
actions := []*Action{
335
{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},
336
{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},
337
{ActionType: ActionTypeHolder{ActionType: ActionFilesInput}, Data: map[string]string{"selector": "input", "value": "test1.pdf"}},
338
}
339
t.Setenv("LOCAL_FILE_ACCESS", "false")
340
341
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {
342
require.ErrorContains(t, err, ErrLFAccessDenied.Error(), "got file access when -lfa is false")
343
})
344
}
345
346
func TestActionWaitLoad(t *testing.T) {
347
response := `
348
<html>
349
<head>
350
<title>Nuclei Test Page</title>
351
</head>
352
<button id="test">Wait for me!</button>
353
<script>
354
window.onload = () => document.querySelector('#test').style.color = 'red';
355
</script>
356
</html>`
357
358
actions := []*Action{
359
{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},
360
{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},
361
}
362
363
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {
364
require.Nil(t, err, "could not run page actions")
365
el := page.Page().MustElement("button")
366
style, attributeErr := el.Attribute("style")
367
require.Nil(t, attributeErr)
368
require.Equal(t, "color: red;", *style, "could not get color")
369
})
370
}
371
372
func TestActionGetResource(t *testing.T) {
373
response := `
374
<html>
375
<head>
376
<title>Nuclei Test Page</title>
377
</head>
378
<body>
379
<img id="test" src="https://raw.githubusercontent.com/projectdiscovery/wallpapers/main/pd-floppy.jpg">
380
</body>
381
</html>`
382
383
actions := []*Action{
384
{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},
385
{ActionType: ActionTypeHolder{ActionType: ActionGetResource}, Data: map[string]string{"by": "x", "xpath": "//img[@id='test']"}, Name: "src"},
386
}
387
388
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {
389
require.Nil(t, err, "could not run page actions")
390
391
src, ok := out["src"].(string)
392
require.True(t, ok, "could not assert src to string")
393
require.Equal(t, len(src), 121808, "could not find resource")
394
})
395
}
396
397
func TestActionExtract(t *testing.T) {
398
response := `
399
<html>
400
<head>
401
<title>Nuclei Test Page</title>
402
</head>
403
<button id="test">Wait for me!</button>
404
</html>`
405
406
actions := []*Action{
407
{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},
408
{ActionType: ActionTypeHolder{ActionType: ActionExtract}, Data: map[string]string{"by": "x", "xpath": "//button[@id='test']"}, Name: "extract"},
409
}
410
411
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {
412
require.Nil(t, err, "could not run page actions")
413
require.Equal(t, "Wait for me!", out["extract"], "could not extract text")
414
})
415
}
416
417
func TestActionSetMethod(t *testing.T) {
418
response := `
419
<html>
420
<head>
421
<title>Nuclei Test Page</title>
422
</head>
423
</html>`
424
425
actions := []*Action{
426
{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},
427
{ActionType: ActionTypeHolder{ActionType: ActionSetMethod}, Data: map[string]string{"part": "x", "method": "SET"}},
428
}
429
430
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {
431
require.Nil(t, err, "could not run page actions")
432
require.Equal(t, "SET", page.rules[0].Args["method"], "could not find resource")
433
})
434
}
435
436
func TestActionAddHeader(t *testing.T) {
437
actions := []*Action{
438
{ActionType: ActionTypeHolder{ActionType: ActionAddHeader}, Data: map[string]string{"part": "request", "key": "Test", "value": "Hello"}},
439
{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},
440
{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},
441
}
442
443
handler := func(w http.ResponseWriter, r *http.Request) {
444
if r.Header.Get("Test") == "Hello" {
445
_, _ = fmt.Fprintln(w, `found`)
446
}
447
}
448
449
testHeadless(t, actions, 20*time.Second, handler, func(page *Page, err error, out ActionData) {
450
require.Nil(t, err, "could not run page actions")
451
require.Equal(t, "found", strings.ToLower(strings.TrimSpace(page.Page().MustElement("html").MustText())), "could not set header correctly")
452
})
453
}
454
455
func TestActionDeleteHeader(t *testing.T) {
456
actions := []*Action{
457
{ActionType: ActionTypeHolder{ActionType: ActionAddHeader}, Data: map[string]string{"part": "request", "key": "Test1", "value": "Hello"}},
458
{ActionType: ActionTypeHolder{ActionType: ActionAddHeader}, Data: map[string]string{"part": "request", "key": "Test2", "value": "World"}},
459
{ActionType: ActionTypeHolder{ActionType: ActionDeleteHeader}, Data: map[string]string{"part": "request", "key": "Test2"}},
460
{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},
461
{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},
462
}
463
464
handler := func(w http.ResponseWriter, r *http.Request) {
465
if r.Header.Get("Test1") == "Hello" && r.Header.Get("Test2") == "" {
466
_, _ = fmt.Fprintln(w, `header deleted`)
467
}
468
}
469
470
testHeadless(t, actions, 20*time.Second, handler, func(page *Page, err error, out ActionData) {
471
require.Nil(t, err, "could not run page actions")
472
require.Equal(t, "header deleted", strings.ToLower(strings.TrimSpace(page.Page().MustElement("html").MustText())), "could not delete header correctly")
473
})
474
}
475
476
func TestActionSetBody(t *testing.T) {
477
actions := []*Action{
478
{ActionType: ActionTypeHolder{ActionType: ActionSetBody}, Data: map[string]string{"part": "request", "body": "hello"}},
479
{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},
480
{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},
481
}
482
483
handler := func(w http.ResponseWriter, r *http.Request) {
484
body, _ := io.ReadAll(r.Body)
485
_, _ = fmt.Fprintln(w, string(body))
486
}
487
488
testHeadless(t, actions, 20*time.Second, handler, func(page *Page, err error, out ActionData) {
489
require.Nil(t, err, "could not run page actions")
490
require.Equal(t, "hello", strings.ToLower(strings.TrimSpace(page.Page().MustElement("html").MustText())), "could not set header correctly")
491
})
492
}
493
494
func TestActionKeyboard(t *testing.T) {
495
response := `
496
<html>
497
<head>
498
<title>Nuclei Test Page</title>
499
</head>
500
<body>
501
<input type="text" name="test" id="test">
502
</body>
503
</html>`
504
505
actions := []*Action{
506
{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},
507
{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},
508
{ActionType: ActionTypeHolder{ActionType: ActionClick}, Data: map[string]string{"selector": "input"}},
509
{ActionType: ActionTypeHolder{ActionType: ActionKeyboard}, Data: map[string]string{"keys": "Test2"}},
510
}
511
512
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {
513
require.Nil(t, err, "could not run page actions")
514
el := page.Page().MustElement("input")
515
require.Equal(t, "Test2", el.MustText(), "could not get input change value")
516
})
517
}
518
519
func TestActionSleep(t *testing.T) {
520
response := `
521
<html>
522
<head>
523
<title>Nuclei Test Page</title>
524
</head>
525
<button style="display:none" id="test">Wait for me!</button>
526
<script>
527
setTimeout(() => document.querySelector('#test').style.display = '', 1000);
528
</script>
529
</html>`
530
531
actions := []*Action{
532
{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},
533
{ActionType: ActionTypeHolder{ActionType: ActionSleep}, Data: map[string]string{"duration": "2"}},
534
}
535
536
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {
537
require.Nil(t, err, "could not run page actions")
538
require.True(t, page.Page().MustElement("button").MustVisible(), "could not get button")
539
})
540
}
541
542
func TestActionWaitVisible(t *testing.T) {
543
response := `
544
<html>
545
<head>
546
<title>Nuclei Test Page</title>
547
</head>
548
<button style="display:none" id="test">Wait for me!</button>
549
<script>
550
setTimeout(() => document.querySelector('#test').style.display = '', 1000);
551
</script>
552
</html>`
553
554
actions := []*Action{
555
{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},
556
{ActionType: ActionTypeHolder{ActionType: ActionWaitVisible}, Data: map[string]string{"by": "x", "xpath": "//button[@id='test']"}},
557
}
558
559
t.Run("wait for an element being visible", func(t *testing.T) {
560
testHeadlessSimpleResponse(t, response, actions, 2*time.Second, func(page *Page, err error, out ActionData) {
561
require.Nil(t, err, "could not run page actions")
562
563
page.Page().MustElement("button").MustVisible()
564
})
565
})
566
567
t.Run("timeout because of element not visible", func(t *testing.T) {
568
// increased timeout from time.Second/2 to time.Second due to random fails (probably due to overhead and system)
569
testHeadlessSimpleResponse(t, response, actions, time.Second, func(page *Page, err error, out ActionData) {
570
require.Error(t, err)
571
require.Contains(t, err.Error(), "Element did not appear in the given amount of time")
572
})
573
})
574
}
575
576
func TestActionWaitDialog(t *testing.T) {
577
response := `<html>
578
<head>
579
<title>Nuclei Test Page</title>
580
</head>
581
<body>
582
<script type="text/javascript">
583
const urlParams = new URLSearchParams(window.location.search);
584
const scriptContent = urlParams.get('script');
585
if (scriptContent) {
586
const scriptElement = document.createElement('script');
587
scriptElement.textContent = scriptContent;
588
589
document.body.appendChild(scriptElement);
590
}
591
</script>
592
</body>
593
</html>`
594
595
t.Run("Triggered", func(t *testing.T) {
596
actions := []*Action{
597
{
598
ActionType: ActionTypeHolder{ActionType: ActionNavigate},
599
Data: map[string]string{"url": "{{BaseURL}}/?script=alert%281%29"},
600
},
601
{
602
ActionType: ActionTypeHolder{ActionType: ActionWaitDialog},
603
Name: "test",
604
},
605
}
606
607
testHeadlessSimpleResponse(t, response, actions, 1*time.Second, func(page *Page, err error, out ActionData) {
608
require.Nil(t, err, "could not run page actions")
609
610
test, ok := out["test"].(bool)
611
require.True(t, ok, "could not assert test to bool")
612
require.True(t, test, "could not find test")
613
})
614
})
615
616
t.Run("Invalid", func(t *testing.T) {
617
actions := []*Action{
618
{
619
ActionType: ActionTypeHolder{ActionType: ActionNavigate},
620
Data: map[string]string{"url": "{{BaseURL}}/?script=foo"},
621
},
622
{
623
ActionType: ActionTypeHolder{ActionType: ActionWaitDialog},
624
Name: "test",
625
},
626
}
627
628
testHeadlessSimpleResponse(t, response, actions, 1*time.Second, func(page *Page, err error, out ActionData) {
629
require.Nil(t, err, "could not run page actions")
630
631
_, ok := out["test"].(bool)
632
require.False(t, ok, "output assertion is success")
633
})
634
})
635
}
636
637
func testHeadlessSimpleResponse(t *testing.T, response string, actions []*Action, timeout time.Duration, assert func(page *Page, pageErr error, out ActionData)) {
638
t.Helper()
639
testHeadless(t, actions, timeout, func(w http.ResponseWriter, r *http.Request) {
640
_, _ = fmt.Fprintln(w, response)
641
}, assert)
642
}
643
644
func testHeadless(t *testing.T, actions []*Action, timeout time.Duration, handler func(w http.ResponseWriter, r *http.Request), assert func(page *Page, pageErr error, extractedData ActionData)) {
645
t.Helper()
646
647
lfa := envutil.GetEnvOrDefault("LOCAL_FILE_ACCESS", true)
648
rna := envutil.GetEnvOrDefault("RESTRICED_LOCAL_NETWORK_ACCESS", false)
649
650
opts := &types.Options{AllowLocalFileAccess: lfa, RestrictLocalNetworkAccess: rna}
651
652
_ = protocolstate.Init(opts)
653
654
browser, err := New(&types.Options{
655
ShowBrowser: false,
656
UseInstalledChrome: testheadless.HeadlessLocal,
657
})
658
require.Nil(t, err, "could not create browser")
659
defer browser.Close()
660
661
instance, err := browser.NewInstance()
662
require.Nil(t, err, "could not create browser instance")
663
defer func() {
664
_ = instance.Close()
665
}()
666
667
ts := httptest.NewServer(http.HandlerFunc(handler))
668
defer ts.Close()
669
670
input := contextargs.NewWithInput(context.Background(), ts.URL)
671
input.CookieJar, err = cookiejar.New(nil)
672
require.Nil(t, err)
673
674
extractedData, page, err := instance.Run(input, actions, nil, &Options{Timeout: timeout, Options: opts}) // allow file access in test
675
assert(page, err, extractedData)
676
677
if page != nil {
678
page.Close()
679
}
680
}
681
682
func TestContainsAnyModificationActionType(t *testing.T) {
683
if containsAnyModificationActionType() {
684
t.Error("Expected false, got true")
685
}
686
if containsAnyModificationActionType(ActionClick) {
687
t.Error("Expected false, got true")
688
}
689
if !containsAnyModificationActionType(ActionSetMethod, ActionAddHeader, ActionExtract) {
690
t.Error("Expected true, got false")
691
}
692
if !containsAnyModificationActionType(ActionSetMethod, ActionAddHeader, ActionSetHeader, ActionDeleteHeader, ActionSetBody) {
693
t.Error("Expected true, got false")
694
}
695
}
696
697
func TestBlockedHeadlessURLS(t *testing.T) {
698
699
// run this test from binary since we are changing values
700
// of global variables
701
if os.Getenv("TEST_BLOCK_HEADLESS_URLS") != "1" {
702
cmd := exec.Command(os.Args[0], "-test.run=TestBlockedHeadlessURLS", "-test.v")
703
cmd.Env = append(cmd.Env, "TEST_BLOCK_HEADLESS_URLS=1")
704
out, err := cmd.CombinedOutput()
705
if !strings.Contains(string(out), "PASS\n") || err != nil {
706
t.Fatalf("%s\n(exit status %v)", string(out), err)
707
}
708
return
709
}
710
711
opts := &types.Options{
712
AllowLocalFileAccess: false,
713
RestrictLocalNetworkAccess: true,
714
}
715
err := protocolstate.Init(opts)
716
require.Nil(t, err, "could not init protocol state")
717
718
browser, err := New(&types.Options{ShowBrowser: false, UseInstalledChrome: testheadless.HeadlessLocal})
719
require.Nil(t, err, "could not create browser")
720
defer browser.Close()
721
722
instance, err := browser.NewInstance()
723
require.Nil(t, err, "could not create browser instance")
724
defer func() {
725
_ = instance.Close()
726
}()
727
728
ts := httptest.NewServer(nil)
729
defer ts.Close()
730
731
testcases := []string{
732
"file:/etc/hosts",
733
" file:///etc/hosts\r\n",
734
" fILe:/../../../../etc/hosts",
735
ts.URL, // local test server
736
"fTP://example.com:21\r\n",
737
"ftp://example.com:21",
738
"chrome://settings",
739
" chROme://version",
740
"chrome-extension://version\r",
741
" chrOme-EXTension://settings",
742
"view-source:file:/etc/hosts",
743
}
744
745
for _, testcase := range testcases {
746
actions := []*Action{
747
{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": testcase}},
748
{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},
749
}
750
751
data, page, err := instance.Run(contextargs.NewWithInput(context.Background(), ts.URL), actions, nil, &Options{Timeout: 20 * time.Second, Options: opts}) // allow file access in test
752
require.Error(t, err, "expected error for url %s got %v", testcase, data)
753
require.True(t, stringsutil.ContainsAny(err.Error(), "net::ERR_ACCESS_DENIED", "failed to parse url", "Cannot navigate to invalid URL", "net::ERR_ABORTED", "net::ERR_INVALID_URL"), "found different error %v for testcases %v", err, testcase)
754
require.Len(t, data, 0, "expected no data for url %s got %v", testcase, data)
755
if page != nil {
756
page.Close()
757
}
758
}
759
}
760
761