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