bbot 2.4.2__py3-none-any.whl → 2.4.2.6590rc0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of bbot might be problematic. Click here for more details.
- bbot/__init__.py +1 -1
- bbot/core/event/base.py +64 -4
- bbot/core/helpers/diff.py +10 -7
- bbot/core/helpers/helper.py +5 -1
- bbot/core/helpers/misc.py +48 -11
- bbot/core/helpers/regex.py +4 -0
- bbot/core/helpers/regexes.py +45 -8
- bbot/core/helpers/url.py +21 -5
- bbot/core/helpers/web/client.py +25 -5
- bbot/core/helpers/web/engine.py +9 -1
- bbot/core/helpers/web/envelopes.py +352 -0
- bbot/core/helpers/web/web.py +10 -2
- bbot/core/helpers/yara_helper.py +50 -0
- bbot/core/modules.py +23 -7
- bbot/defaults.yml +26 -1
- bbot/modules/base.py +4 -2
- bbot/modules/{deadly/dastardly.py → dastardly.py} +1 -1
- bbot/modules/{deadly/ffuf.py → ffuf.py} +1 -1
- bbot/modules/ffuf_shortnames.py +1 -1
- bbot/modules/httpx.py +14 -0
- bbot/modules/hunt.py +24 -6
- bbot/modules/internal/aggregate.py +1 -0
- bbot/modules/internal/excavate.py +356 -197
- bbot/modules/lightfuzz/lightfuzz.py +203 -0
- bbot/modules/lightfuzz/submodules/__init__.py +0 -0
- bbot/modules/lightfuzz/submodules/base.py +312 -0
- bbot/modules/lightfuzz/submodules/cmdi.py +106 -0
- bbot/modules/lightfuzz/submodules/crypto.py +474 -0
- bbot/modules/lightfuzz/submodules/nosqli.py +183 -0
- bbot/modules/lightfuzz/submodules/path.py +154 -0
- bbot/modules/lightfuzz/submodules/serial.py +179 -0
- bbot/modules/lightfuzz/submodules/sqli.py +187 -0
- bbot/modules/lightfuzz/submodules/ssti.py +39 -0
- bbot/modules/lightfuzz/submodules/xss.py +191 -0
- bbot/modules/{deadly/nuclei.py → nuclei.py} +1 -1
- bbot/modules/paramminer_headers.py +2 -0
- bbot/modules/reflected_parameters.py +80 -0
- bbot/modules/{deadly/vhost.py → vhost.py} +2 -2
- bbot/presets/web/lightfuzz-heavy.yml +16 -0
- bbot/presets/web/lightfuzz-light.yml +20 -0
- bbot/presets/web/lightfuzz-medium.yml +14 -0
- bbot/presets/web/lightfuzz-superheavy.yml +13 -0
- bbot/presets/web/lightfuzz-xss.yml +21 -0
- bbot/presets/web/paramminer.yml +8 -5
- bbot/scanner/preset/args.py +26 -0
- bbot/scanner/scanner.py +6 -0
- bbot/test/test_step_1/test__module__tests.py +1 -1
- bbot/test/test_step_1/test_helpers.py +7 -0
- bbot/test/test_step_1/test_presets.py +2 -2
- bbot/test/test_step_1/test_web.py +20 -0
- bbot/test/test_step_1/test_web_envelopes.py +343 -0
- bbot/test/test_step_2/module_tests/test_module_excavate.py +404 -29
- bbot/test/test_step_2/module_tests/test_module_httpx.py +29 -0
- bbot/test/test_step_2/module_tests/test_module_hunt.py +18 -1
- bbot/test/test_step_2/module_tests/test_module_lightfuzz.py +1947 -0
- bbot/test/test_step_2/module_tests/test_module_paramminer_getparams.py +4 -1
- bbot/test/test_step_2/module_tests/test_module_paramminer_headers.py +46 -2
- bbot/test/test_step_2/module_tests/test_module_reflected_parameters.py +226 -0
- bbot/wordlists/paramminer_parameters.txt +0 -8
- {bbot-2.4.2.dist-info → bbot-2.4.2.6590rc0.dist-info}/METADATA +2 -1
- {bbot-2.4.2.dist-info → bbot-2.4.2.6590rc0.dist-info}/RECORD +64 -42
- {bbot-2.4.2.dist-info → bbot-2.4.2.6590rc0.dist-info}/LICENSE +0 -0
- {bbot-2.4.2.dist-info → bbot-2.4.2.6590rc0.dist-info}/WHEEL +0 -0
- {bbot-2.4.2.dist-info → bbot-2.4.2.6590rc0.dist-info}/entry_points.txt +0 -0
|
@@ -140,7 +140,7 @@ class TestExcavate2(TestExcavate):
|
|
|
140
140
|
def check(self, module_test, events):
|
|
141
141
|
root_relative_detection = False
|
|
142
142
|
page_relative_detection_1 = False
|
|
143
|
-
|
|
143
|
+
page_relative_detection_2 = False
|
|
144
144
|
root_page_confusion_1 = False
|
|
145
145
|
root_page_confusion_2 = False
|
|
146
146
|
|
|
@@ -458,26 +458,38 @@ class TestExcavateParameterExtraction(TestExcavate):
|
|
|
458
458
|
$.post("/test", {jquerypost: "value2"});
|
|
459
459
|
</script>
|
|
460
460
|
</head>
|
|
461
|
-
<body>
|
|
462
461
|
<body>
|
|
463
462
|
<h1>Simple GET Form</h1>
|
|
464
463
|
<p>Use the form below to submit a GET request:</p>
|
|
465
464
|
<form action="/search" method="get">
|
|
466
465
|
<label for="searchQuery">Search Query:</label>
|
|
467
|
-
<input type="text" id="searchQuery" name="
|
|
466
|
+
<input type="text" id="searchQuery" name="q1" value="flowers"><br><br>
|
|
467
|
+
<input type="text" id="searchQueryspaces" name="q4" value="trees and forests"><br><br>
|
|
468
468
|
<input type="submit" value="Search">
|
|
469
469
|
</form>
|
|
470
470
|
<h1>Simple POST Form</h1>
|
|
471
471
|
<p>Use the form below to submit a POST request:</p>
|
|
472
472
|
<form action="/search" method="post">
|
|
473
473
|
<label for="searchQuery">Search Query:</label>
|
|
474
|
-
<input type="text" id="searchQuery" name="
|
|
474
|
+
<input type="text" id="searchQuery" name="q2" value="boats"><br><br>
|
|
475
|
+
<input type="text" id="searchQuery2" name="q5" value="submarines"><br><br>
|
|
476
|
+
<input type="submit" value="Search">
|
|
477
|
+
</form>
|
|
478
|
+
<h1>Simple Generic Form</h1>
|
|
479
|
+
<p>Use the form below to submit a request:</p>
|
|
480
|
+
<form action="/search">
|
|
481
|
+
<label for="searchQuery">Search Query:</label>
|
|
482
|
+
<input type="text" id="searchQuery" name="q3" value="candles"><br><br>
|
|
475
483
|
<input type="submit" value="Search">
|
|
476
484
|
</form>
|
|
477
485
|
<p>Links</p>
|
|
478
486
|
<a href="/validPath?id=123&age=456">href</a>
|
|
479
487
|
<img src="http://127.0.0.1:8888/validPath?size=m&fit=slim">img</a>
|
|
480
|
-
|
|
488
|
+
<form class="login-form" name="change-email-form" action="/my-account/change-email" method="POST">
|
|
489
|
+
<select id=blog-post-author-display name=blog-post-author-display form=blog-post-author-display-form>
|
|
490
|
+
<option value=user.name selected>Name</option>
|
|
491
|
+
<input required type="hidden" name="csrf" value="O0A5UIhlB2ezuMGC1oWr6XA6GhG4sUVj">
|
|
492
|
+
</form>
|
|
481
493
|
</body>
|
|
482
494
|
</html>
|
|
483
495
|
"""
|
|
@@ -490,12 +502,19 @@ class TestExcavateParameterExtraction(TestExcavate):
|
|
|
490
502
|
found_jquery_post = False
|
|
491
503
|
found_form_get = False
|
|
492
504
|
found_form_post = False
|
|
505
|
+
found_form_generic = False
|
|
493
506
|
found_jquery_get_original_value = False
|
|
494
507
|
found_jquery_post_original_value = False
|
|
495
508
|
found_form_get_original_value = False
|
|
496
509
|
found_form_post_original_value = False
|
|
510
|
+
found_form_generic_original_value = False
|
|
497
511
|
found_htmltags_a = False
|
|
498
512
|
found_htmltags_img = False
|
|
513
|
+
found_select_noquotes = False
|
|
514
|
+
avoid_truncated_values = True
|
|
515
|
+
found_form_input_with_spaces = False
|
|
516
|
+
found_form_get_additional_params = False
|
|
517
|
+
found_form_post_additional_params = False
|
|
499
518
|
|
|
500
519
|
for e in events:
|
|
501
520
|
if e.type == "WEB_PARAMETER":
|
|
@@ -509,15 +528,24 @@ class TestExcavateParameterExtraction(TestExcavate):
|
|
|
509
528
|
if e.data["original_value"] == "value2":
|
|
510
529
|
found_jquery_post_original_value = True
|
|
511
530
|
|
|
512
|
-
if e.data["description"] == "HTTP Extracted Parameter [
|
|
531
|
+
if e.data["description"] == "HTTP Extracted Parameter [q1] (GET Form Submodule)":
|
|
513
532
|
found_form_get = True
|
|
514
533
|
if e.data["original_value"] == "flowers":
|
|
515
534
|
found_form_get_original_value = True
|
|
535
|
+
if "q4" in e.data["additional_params"].keys():
|
|
536
|
+
found_form_get_additional_params = True
|
|
516
537
|
|
|
517
|
-
if e.data["description"] == "HTTP Extracted Parameter [
|
|
538
|
+
if e.data["description"] == "HTTP Extracted Parameter [q2] (POST Form Submodule)":
|
|
518
539
|
found_form_post = True
|
|
519
540
|
if e.data["original_value"] == "boats":
|
|
520
541
|
found_form_post_original_value = True
|
|
542
|
+
if "q5" in e.data["additional_params"].keys():
|
|
543
|
+
found_form_post_additional_params = True
|
|
544
|
+
|
|
545
|
+
if e.data["description"] == "HTTP Extracted Parameter [q3] (Generic Form Submodule)":
|
|
546
|
+
found_form_generic = True
|
|
547
|
+
if e.data["original_value"] == "candles":
|
|
548
|
+
found_form_generic_original_value = True
|
|
521
549
|
|
|
522
550
|
if e.data["description"] == "HTTP Extracted Parameter [age] (HTML Tags Submodule)":
|
|
523
551
|
if e.data["original_value"] == "456":
|
|
@@ -529,16 +557,164 @@ class TestExcavateParameterExtraction(TestExcavate):
|
|
|
529
557
|
if "fit" in e.data["additional_params"].keys():
|
|
530
558
|
found_htmltags_img = True
|
|
531
559
|
|
|
560
|
+
if (
|
|
561
|
+
e.data["description"]
|
|
562
|
+
== "HTTP Extracted Parameter [blog-post-author-display] (POST Form Submodule)"
|
|
563
|
+
):
|
|
564
|
+
if e.data["original_value"] == "user.name":
|
|
565
|
+
if "csrf" in e.data["additional_params"].keys():
|
|
566
|
+
found_select_noquotes = True
|
|
567
|
+
|
|
568
|
+
if e.data["description"] == "HTTP Extracted Parameter [q4] (GET Form Submodule)":
|
|
569
|
+
if e.data["original_value"] == "trees and forests":
|
|
570
|
+
found_form_input_with_spaces = True
|
|
571
|
+
if e.data["original_value"] == "trees":
|
|
572
|
+
avoid_truncated_values = False
|
|
573
|
+
|
|
532
574
|
assert found_jquery_get, "Did not extract Jquery GET parameters"
|
|
533
575
|
assert found_jquery_post, "Did not extract Jquery POST parameters"
|
|
534
576
|
assert found_form_get, "Did not extract Form GET parameters"
|
|
535
577
|
assert found_form_post, "Did not extract Form POST parameters"
|
|
578
|
+
assert found_form_generic, "Did not extract Form (Generic) parameters"
|
|
579
|
+
assert found_form_input_with_spaces, "Did not extract Form input with spaces"
|
|
580
|
+
assert avoid_truncated_values, "Emitted a parameter with spaces without the entire value"
|
|
536
581
|
assert found_jquery_get_original_value, "Did not extract Jquery GET parameter original_value"
|
|
537
582
|
assert found_jquery_post_original_value, "Did not extract Jquery POST parameter original_value"
|
|
538
583
|
assert found_form_get_original_value, "Did not extract Form GET parameter original_value"
|
|
539
584
|
assert found_form_post_original_value, "Did not extract Form POST parameter original_value"
|
|
585
|
+
assert found_form_generic_original_value, "Did not extract Form (Generic) parameter original_value"
|
|
540
586
|
assert found_htmltags_a, "Did not extract parameter(s) from a-tag"
|
|
541
587
|
assert found_htmltags_img, "Did not extract parameter(s) from img-tag"
|
|
588
|
+
assert found_select_noquotes, "Did not extract parameter(s) from select-tag"
|
|
589
|
+
assert found_form_get_additional_params, "Did not extract additional parameters from GET form"
|
|
590
|
+
assert found_form_post_additional_params, "Did not extract additional parameters from POST form"
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
class TestExcavateParameterExtraction_postform_noaction(ModuleTestBase):
|
|
594
|
+
targets = ["http://127.0.0.1:8888/"]
|
|
595
|
+
|
|
596
|
+
# hunt is added as parameter extraction is only activated by one or more modules that consume WEB_PARAMETER
|
|
597
|
+
modules_overrides = ["httpx", "excavate", "hunt"]
|
|
598
|
+
postform_extract_html = """
|
|
599
|
+
<body>
|
|
600
|
+
<h1>Post for without action</h1>
|
|
601
|
+
<form method="post">
|
|
602
|
+
<label for="state">Encrypted State:</label>
|
|
603
|
+
<input type="text" name="state" id="state" value="voCcc4U5jnFWOYYF4Oueau3l8gDsTecHMxniZJSKvh4bSA0WCgEYAxFkdWJzbGJ+" size="100">
|
|
604
|
+
<br><br>
|
|
605
|
+
<input type="submit" value="Decrypt">
|
|
606
|
+
</form>
|
|
607
|
+
</body>
|
|
608
|
+
"""
|
|
609
|
+
|
|
610
|
+
async def setup_after_prep(self, module_test):
|
|
611
|
+
respond_args = {"response_data": self.postform_extract_html, "headers": {"Content-Type": "text/html"}}
|
|
612
|
+
module_test.set_expect_requests(respond_args=respond_args)
|
|
613
|
+
|
|
614
|
+
def check(self, module_test, events):
|
|
615
|
+
excavate_formnoaction_extraction = False
|
|
616
|
+
for e in events:
|
|
617
|
+
if e.type == "WEB_PARAMETER":
|
|
618
|
+
if "HTTP Extracted Parameter [state] (POST Form (no action) Submodule)" in e.data["description"]:
|
|
619
|
+
excavate_formnoaction_extraction = True
|
|
620
|
+
assert excavate_formnoaction_extraction, "Excavate failed to extract web parameter"
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
class TestExcavateParameterExtraction_postform_htmlencodedaction(TestExcavateParameterExtraction_postform_noaction):
|
|
624
|
+
postform_extract_html = """
|
|
625
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
|
|
626
|
+
<body onload="document.forms[0].submit()">
|
|
627
|
+
<noscript>
|
|
628
|
+
<p>
|
|
629
|
+
<strong>Note:</strong> Since your browser does not support JavaScript,
|
|
630
|
+
you must press the Continue button once to proceed.
|
|
631
|
+
</p>
|
|
632
|
+
</noscript>
|
|
633
|
+
<form action="https://127.0.0.1:8080/sso-web/singleSignOn.action" method="post">
|
|
634
|
+
<div>
|
|
635
|
+
<input type="hidden" name="value" value="PD94"/>
|
|
636
|
+
</div>
|
|
637
|
+
<noscript>
|
|
638
|
+
<div>
|
|
639
|
+
<input type="submit" value="Continue"/>
|
|
640
|
+
</div>
|
|
641
|
+
</noscript>
|
|
642
|
+
</form>
|
|
643
|
+
</body>
|
|
644
|
+
</html>
|
|
645
|
+
"""
|
|
646
|
+
|
|
647
|
+
def check(self, module_test, events):
|
|
648
|
+
excavate_handle_htmlencoded_action = True
|
|
649
|
+
for e in events:
|
|
650
|
+
if e.type == "WEB_PARAMETER":
|
|
651
|
+
if (
|
|
652
|
+
"HTTP Extracted Parameter [value] (POST Form Submodule)" in e.data["description"]
|
|
653
|
+
and e.data["url"] == "https://127.0.0.1:8080/sso-web/singleSignOn.action"
|
|
654
|
+
):
|
|
655
|
+
excavate_handle_htmlencoded_action = True
|
|
656
|
+
assert excavate_handle_htmlencoded_action, "Excavate failed to extract web parameter"
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
class TestExcavateParameterExtraction_additionalparams(ModuleTestBase):
|
|
660
|
+
targets = ["http://127.0.0.1:8888/"]
|
|
661
|
+
|
|
662
|
+
# hunt is added as parameter extraction is only activated by one or more modules that consume WEB_PARAMETER
|
|
663
|
+
modules_overrides = ["httpx", "excavate", "hunt"]
|
|
664
|
+
postformnoaction_extract_multiparams_html = """
|
|
665
|
+
<body>
|
|
666
|
+
<h1>Post for without action</h1>
|
|
667
|
+
<form id="templateForm" method="POST">
|
|
668
|
+
<input required type="hidden" name="csrf" value="MwARfZ19btvV2OjHIvTU5vVSGp9OyrcI">
|
|
669
|
+
<label>Template:</label>
|
|
670
|
+
<textarea required rows="12" cols="300" name="template">somenonsense</textarea>
|
|
671
|
+
<button class="button" type="submit" name="template-action" value="save">
|
|
672
|
+
Save
|
|
673
|
+
</button>
|
|
674
|
+
</form>
|
|
675
|
+
</body>
|
|
676
|
+
"""
|
|
677
|
+
|
|
678
|
+
async def setup_after_prep(self, module_test):
|
|
679
|
+
respond_args = {
|
|
680
|
+
"response_data": self.postformnoaction_extract_multiparams_html,
|
|
681
|
+
"headers": {"Content-Type": "text/html"},
|
|
682
|
+
}
|
|
683
|
+
module_test.set_expect_requests(respond_args=respond_args)
|
|
684
|
+
|
|
685
|
+
def check(self, module_test, events):
|
|
686
|
+
excavate_additionalparam_extraction_param1 = False
|
|
687
|
+
excavate_additionalparam_extraction_param2 = False
|
|
688
|
+
excavate_additionalparam_extraction_param3 = False
|
|
689
|
+
for e in events:
|
|
690
|
+
if e.type == "WEB_PARAMETER":
|
|
691
|
+
if (
|
|
692
|
+
e.data["name"] == "template-action"
|
|
693
|
+
and "csrf" in e.data["additional_params"].keys()
|
|
694
|
+
and "template" in e.data["additional_params"].keys()
|
|
695
|
+
):
|
|
696
|
+
excavate_additionalparam_extraction_param1 = True
|
|
697
|
+
if (
|
|
698
|
+
e.data["name"] == "template"
|
|
699
|
+
and "csrf" in e.data["additional_params"].keys()
|
|
700
|
+
and "template-action" in e.data["additional_params"].keys()
|
|
701
|
+
):
|
|
702
|
+
excavate_additionalparam_extraction_param2 = True
|
|
703
|
+
if (
|
|
704
|
+
e.data["name"] == "csrf"
|
|
705
|
+
and "template" in e.data["additional_params"].keys()
|
|
706
|
+
and "template-action" in e.data["additional_params"].keys()
|
|
707
|
+
):
|
|
708
|
+
excavate_additionalparam_extraction_param3 = True
|
|
709
|
+
assert excavate_additionalparam_extraction_param1, (
|
|
710
|
+
"Excavate failed to extract web parameter with correct additional data (param 1)"
|
|
711
|
+
)
|
|
712
|
+
assert excavate_additionalparam_extraction_param2, (
|
|
713
|
+
"Excavate failed to extract web parameter with correct additional data (param 2)"
|
|
714
|
+
)
|
|
715
|
+
assert excavate_additionalparam_extraction_param3, (
|
|
716
|
+
"Excavate failed to extract web parameter with correct additional data (param 3)"
|
|
717
|
+
)
|
|
542
718
|
|
|
543
719
|
|
|
544
720
|
class TestExcavateParameterExtraction_getparam(ModuleTestBase):
|
|
@@ -563,10 +739,91 @@ class TestExcavateParameterExtraction_getparam(ModuleTestBase):
|
|
|
563
739
|
assert excavate_getparam_extraction, "Excavate failed to extract web parameter"
|
|
564
740
|
|
|
565
741
|
|
|
742
|
+
class TestExcavateParameterExtraction_relativeurl(ModuleTestBase):
|
|
743
|
+
targets = ["http://127.0.0.1:8888/"]
|
|
744
|
+
|
|
745
|
+
# hunt is added as parameter extraction is only activated by one or more modules that consume WEB_PARAMETER
|
|
746
|
+
modules_overrides = ["httpx", "excavate", "hunt"]
|
|
747
|
+
config_overrides = {"web": {"spider_distance": 2, "spider_depth": 3}}
|
|
748
|
+
|
|
749
|
+
# Secondary page that has a relative link to a traversal URL
|
|
750
|
+
secondary_page_html = """
|
|
751
|
+
<html>
|
|
752
|
+
<a href="../root.html">Go to root</a>
|
|
753
|
+
</html>
|
|
754
|
+
"""
|
|
755
|
+
|
|
756
|
+
# Primary page that leads to the secondary page
|
|
757
|
+
primary_page_html = """
|
|
758
|
+
<html>
|
|
759
|
+
<a href="/secondary">Go to secondary page</a>
|
|
760
|
+
</html>
|
|
761
|
+
"""
|
|
762
|
+
|
|
763
|
+
# Root page content
|
|
764
|
+
root_page_html = "<html>Root page</html>"
|
|
765
|
+
|
|
766
|
+
async def setup_after_prep(self, module_test):
|
|
767
|
+
module_test.httpserver.expect_request("/").respond_with_data(self.primary_page_html)
|
|
768
|
+
module_test.httpserver.expect_request("/secondary").respond_with_data(self.secondary_page_html)
|
|
769
|
+
module_test.httpserver.expect_request("/root.html").respond_with_data(self.root_page_html)
|
|
770
|
+
|
|
771
|
+
def check(self, module_test, events):
|
|
772
|
+
# Validate that the traversal was successful and WEB_PARAMETER was extracted
|
|
773
|
+
traversed_to_root = False
|
|
774
|
+
parameter_extraction_found = False
|
|
775
|
+
for e in events:
|
|
776
|
+
if e.type == "WEB_PARAMETER":
|
|
777
|
+
if "HTTP Extracted Parameter" in e.data["description"]:
|
|
778
|
+
parameter_extraction_found = True
|
|
779
|
+
|
|
780
|
+
if e.type == "URL":
|
|
781
|
+
if "root.html" in e.parsed_url.path:
|
|
782
|
+
traversed_to_root = True
|
|
783
|
+
|
|
784
|
+
assert traversed_to_root, "Failed to follow the relative traversal to /root.html"
|
|
785
|
+
assert parameter_extraction_found, "Excavate failed to extract parameter after traversal"
|
|
786
|
+
|
|
787
|
+
|
|
788
|
+
class TestExcavateParameterExtraction_getparam_novalue(TestExcavateParameterExtraction_getparam):
|
|
789
|
+
getparam_extract_html = """
|
|
790
|
+
<section class=search>
|
|
791
|
+
<form action="/catalog" method=GET>
|
|
792
|
+
<input type=text id="searchBar" placeholder="Search products" name="searchTerm">
|
|
793
|
+
<input type=text id="searchBar2" placeholder="Search products2" name="searchTerm2">
|
|
794
|
+
<script>
|
|
795
|
+
var searchText = '';
|
|
796
|
+
document.getElementById('searchBar').value = searchText;
|
|
797
|
+
</script>
|
|
798
|
+
<button type=submit class=button>Search</button>
|
|
799
|
+
</form>
|
|
800
|
+
</section>
|
|
801
|
+
"""
|
|
802
|
+
|
|
803
|
+
def check(self, module_test, events):
|
|
804
|
+
excavate_getparam_extraction = False
|
|
805
|
+
found_no_value_additional_params = False
|
|
806
|
+
for e in events:
|
|
807
|
+
if e.type == "WEB_PARAMETER":
|
|
808
|
+
if "HTTP Extracted Parameter [searchTerm] (GET Form Submodule)" in e.data["description"]:
|
|
809
|
+
excavate_getparam_extraction = True
|
|
810
|
+
if "searchTerm2" in e.data["additional_params"].keys():
|
|
811
|
+
found_no_value_additional_params = True
|
|
812
|
+
assert excavate_getparam_extraction, "Excavate failed to extract web parameter"
|
|
813
|
+
assert found_no_value_additional_params, (
|
|
814
|
+
"Excavate failed to extract additional parameters for input tag with no value"
|
|
815
|
+
)
|
|
816
|
+
|
|
817
|
+
|
|
566
818
|
class TestExcavateParameterExtraction_json(ModuleTestBase):
|
|
567
819
|
targets = ["http://127.0.0.1:8888/"]
|
|
568
820
|
modules_overrides = ["httpx", "excavate", "paramminer_getparams"]
|
|
569
|
-
config_overrides = {
|
|
821
|
+
config_overrides = {
|
|
822
|
+
"modules": {
|
|
823
|
+
"excavate": {"speculate_params": True},
|
|
824
|
+
"paramminer_getparams": {"wordlist": tempwordlist([]), "recycle_words": True},
|
|
825
|
+
}
|
|
826
|
+
}
|
|
570
827
|
getparam_extract_json = """
|
|
571
828
|
{
|
|
572
829
|
"obscureParameter": 1,
|
|
@@ -593,7 +850,12 @@ class TestExcavateParameterExtraction_json(ModuleTestBase):
|
|
|
593
850
|
class TestExcavateParameterExtraction_xml(ModuleTestBase):
|
|
594
851
|
targets = ["http://127.0.0.1:8888/"]
|
|
595
852
|
modules_overrides = ["httpx", "excavate", "paramminer_getparams"]
|
|
596
|
-
config_overrides = {
|
|
853
|
+
config_overrides = {
|
|
854
|
+
"modules": {
|
|
855
|
+
"excavate": {"speculate_params": True},
|
|
856
|
+
"paramminer_getparams": {"wordlist": tempwordlist([]), "recycle_words": True},
|
|
857
|
+
}
|
|
858
|
+
}
|
|
597
859
|
getparam_extract_xml = """
|
|
598
860
|
<data>
|
|
599
861
|
<obscureParameter>1</obscureParameter>
|
|
@@ -617,6 +879,99 @@ class TestExcavateParameterExtraction_xml(ModuleTestBase):
|
|
|
617
879
|
assert excavate_xml_extraction, "Excavate failed to extract xml parameter"
|
|
618
880
|
|
|
619
881
|
|
|
882
|
+
class TestExcavateParameterExtraction_xml_invalid(TestExcavateParameterExtraction_xml):
|
|
883
|
+
getparam_extract_xml = """
|
|
884
|
+
<data>
|
|
885
|
+
<obscureParameter>1</obscureParameter>
|
|
886
|
+
<newlines>invalid\nwith\nnewlines</newlines>
|
|
887
|
+
</data>
|
|
888
|
+
"""
|
|
889
|
+
|
|
890
|
+
async def setup_after_prep(self, module_test):
|
|
891
|
+
respond_args = {"response_data": self.getparam_extract_xml, "headers": {"Content-Type": "application/xml"}}
|
|
892
|
+
module_test.set_expect_requests(respond_args=respond_args)
|
|
893
|
+
|
|
894
|
+
def check(self, module_test, events):
|
|
895
|
+
excavate_xml_extraction = False
|
|
896
|
+
for e in events:
|
|
897
|
+
if e.type == "WEB_PARAMETER":
|
|
898
|
+
if (
|
|
899
|
+
"HTTP Extracted Parameter (speculative from xml content) [newlines]" in e.data["description"]
|
|
900
|
+
and "\n" not in e.data["original_value"]
|
|
901
|
+
):
|
|
902
|
+
excavate_xml_extraction = True
|
|
903
|
+
assert excavate_xml_extraction, "Excavate failed to extract xml parameter"
|
|
904
|
+
|
|
905
|
+
|
|
906
|
+
class TestExcavateParameterExtraction_inputtagnovalue(ModuleTestBase):
|
|
907
|
+
targets = ["http://127.0.0.1:8888/"]
|
|
908
|
+
|
|
909
|
+
# hunt is added as parameter extraction is only activated by one or more modules that consume WEB_PARAMETER
|
|
910
|
+
modules_overrides = ["httpx", "excavate", "hunt"]
|
|
911
|
+
getparam_extract_html = """
|
|
912
|
+
<form action=/ method=GET><input type=text name="novalue"><button type=submit class=button>Submit</button></form>
|
|
913
|
+
"""
|
|
914
|
+
|
|
915
|
+
async def setup_after_prep(self, module_test):
|
|
916
|
+
respond_args = {"response_data": self.getparam_extract_html, "headers": {"Content-Type": "text/html"}}
|
|
917
|
+
module_test.set_expect_requests(respond_args=respond_args)
|
|
918
|
+
|
|
919
|
+
def check(self, module_test, events):
|
|
920
|
+
excavate_getparam_extraction = False
|
|
921
|
+
for e in events:
|
|
922
|
+
if e.type == "WEB_PARAMETER":
|
|
923
|
+
if "HTTP Extracted Parameter [novalue] (GET Form Submodule)" in e.data["description"]:
|
|
924
|
+
excavate_getparam_extraction = True
|
|
925
|
+
assert excavate_getparam_extraction, "Excavate failed to extract web parameter"
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
class TestExcavateParameterExtraction_jqueryjsonajax(ModuleTestBase):
|
|
929
|
+
targets = ["http://127.0.0.1:8888/"]
|
|
930
|
+
modules_overrides = ["httpx", "excavate", "hunt"]
|
|
931
|
+
jsonajax_extract_html = """
|
|
932
|
+
<html>
|
|
933
|
+
<script>
|
|
934
|
+
function doLogin(e) {
|
|
935
|
+
e.preventDefault();
|
|
936
|
+
var username = $("#usernamefield").val();
|
|
937
|
+
var password = $("#passwordfield").val();
|
|
938
|
+
$.ajax({
|
|
939
|
+
url: '/api/auth',
|
|
940
|
+
type: 'POST',
|
|
941
|
+
contentType: 'application/json',
|
|
942
|
+
data: JSON.stringify({ username: username, password: password }),
|
|
943
|
+
success: function (r) {
|
|
944
|
+
window.location.replace("/demo");
|
|
945
|
+
},
|
|
946
|
+
error: function (r) {
|
|
947
|
+
if (r.status == 401) {
|
|
948
|
+
notify("Access denied");
|
|
949
|
+
} else {
|
|
950
|
+
notify(r.responseText);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
</html>
|
|
956
|
+
<form action=/ method=GET><input type=text name="novalue"><button type=submit class=button>Submit</button></form>
|
|
957
|
+
"""
|
|
958
|
+
|
|
959
|
+
async def setup_after_prep(self, module_test):
|
|
960
|
+
respond_args = {"response_data": self.jsonajax_extract_html, "headers": {"Content-Type": "text/html"}}
|
|
961
|
+
module_test.set_expect_requests(respond_args=respond_args)
|
|
962
|
+
|
|
963
|
+
def check(self, module_test, events):
|
|
964
|
+
excavate_ajaxpost_extraction = False
|
|
965
|
+
for e in events:
|
|
966
|
+
if e.type == "WEB_PARAMETER":
|
|
967
|
+
if (
|
|
968
|
+
"HTTP Extracted Parameter [username] (JQuery Extractor Submodule)" == e.data["description"]
|
|
969
|
+
and e.data["original_value"] is None
|
|
970
|
+
):
|
|
971
|
+
excavate_ajaxpost_extraction = True
|
|
972
|
+
assert excavate_ajaxpost_extraction, "Excavate failed to extract web parameter"
|
|
973
|
+
|
|
974
|
+
|
|
620
975
|
class excavateTestRule(ExcavateRule):
|
|
621
976
|
yara_rules = {
|
|
622
977
|
"SearchForText": 'rule SearchForText { meta: description = "Contains the text AAAABBBBCCCC" strings: $text = "AAAABBBBCCCC" condition: $text }',
|
|
@@ -737,11 +1092,6 @@ class TestExcavateParameterExtraction_targeturl(ModuleTestBase):
|
|
|
737
1092
|
"url_querystring_remove": False,
|
|
738
1093
|
"url_querystring_collapse": False,
|
|
739
1094
|
"interactsh_disable": True,
|
|
740
|
-
"modules": {
|
|
741
|
-
"excavate": {
|
|
742
|
-
"retain_querystring": True,
|
|
743
|
-
}
|
|
744
|
-
},
|
|
745
1095
|
}
|
|
746
1096
|
|
|
747
1097
|
async def setup_after_prep(self, module_test):
|
|
@@ -768,13 +1118,7 @@ class TestExcavate_retain_querystring(ModuleTestBase):
|
|
|
768
1118
|
"url_querystring_remove": False,
|
|
769
1119
|
"url_querystring_collapse": False,
|
|
770
1120
|
"interactsh_disable": True,
|
|
771
|
-
"
|
|
772
|
-
"web_spider_distance": 4,
|
|
773
|
-
"modules": {
|
|
774
|
-
"excavate": {
|
|
775
|
-
"retain_querystring": True,
|
|
776
|
-
}
|
|
777
|
-
},
|
|
1121
|
+
"web": {"spider_distance": 4, "spider_depth": 4},
|
|
778
1122
|
}
|
|
779
1123
|
|
|
780
1124
|
async def setup_after_prep(self, module_test):
|
|
@@ -797,16 +1141,10 @@ class TestExcavate_retain_querystring(ModuleTestBase):
|
|
|
797
1141
|
|
|
798
1142
|
class TestExcavate_retain_querystring_not(TestExcavate_retain_querystring):
|
|
799
1143
|
config_overrides = {
|
|
800
|
-
"url_querystring_remove":
|
|
1144
|
+
"url_querystring_remove": True,
|
|
801
1145
|
"url_querystring_collapse": False,
|
|
802
1146
|
"interactsh_disable": True,
|
|
803
|
-
"
|
|
804
|
-
"web_spider_distance": 4,
|
|
805
|
-
"modules": {
|
|
806
|
-
"excavate": {
|
|
807
|
-
"retain_querystring": True,
|
|
808
|
-
}
|
|
809
|
-
},
|
|
1147
|
+
"web": {"spider_distance": 4, "spider_depth": 4},
|
|
810
1148
|
}
|
|
811
1149
|
|
|
812
1150
|
def check(self, module_test, events):
|
|
@@ -1020,6 +1358,43 @@ A href <a href='/donot_detect.js'>Click me</a>"""
|
|
|
1020
1358
|
)
|
|
1021
1359
|
|
|
1022
1360
|
|
|
1361
|
+
class TestExcavateHeaders_blacklist(ModuleTestBase):
|
|
1362
|
+
targets = ["http://127.0.0.1:8888/"]
|
|
1363
|
+
modules_overrides = ["excavate", "httpx", "hunt"]
|
|
1364
|
+
config_overrides = {"web": {"spider_distance": 1, "spider_depth": 1}}
|
|
1365
|
+
|
|
1366
|
+
async def setup_before_prep(self, module_test):
|
|
1367
|
+
module_test.httpserver.expect_request("/").respond_with_data(
|
|
1368
|
+
"<html><p>test</p></html>",
|
|
1369
|
+
status=200,
|
|
1370
|
+
headers={
|
|
1371
|
+
"Set-Cookie": [
|
|
1372
|
+
"COOKIE1=aaaa; Secure; HttpOnly",
|
|
1373
|
+
"TS0113CC91=bbbb; Secure; HttpOnly; SameSite=None",
|
|
1374
|
+
"PHPSESSID=cccc; Secure; HttpOnly; SameSite=None",
|
|
1375
|
+
]
|
|
1376
|
+
},
|
|
1377
|
+
)
|
|
1378
|
+
|
|
1379
|
+
def check(self, module_test, events):
|
|
1380
|
+
found_first_cookie = False
|
|
1381
|
+
found_second_cookie = False
|
|
1382
|
+
found_third_cookie = False
|
|
1383
|
+
|
|
1384
|
+
for e in events:
|
|
1385
|
+
if e.type == "WEB_PARAMETER":
|
|
1386
|
+
if e.data["name"] == "COOKIE1":
|
|
1387
|
+
found_first_cookie = True
|
|
1388
|
+
if e.data["name"] == "PHPSESSID":
|
|
1389
|
+
found_second_cookie = True
|
|
1390
|
+
if e.data["name"] == "TS0113CC91":
|
|
1391
|
+
found_third_cookie = True
|
|
1392
|
+
|
|
1393
|
+
assert found_first_cookie is True
|
|
1394
|
+
assert found_second_cookie is False
|
|
1395
|
+
assert found_third_cookie is False
|
|
1396
|
+
|
|
1397
|
+
|
|
1023
1398
|
class TestExcavateBadURLs(ModuleTestBase):
|
|
1024
1399
|
targets = ["http://127.0.0.1:8888/"]
|
|
1025
1400
|
modules_overrides = ["excavate", "httpx", "hunt"]
|
|
@@ -144,3 +144,32 @@ class TestHTTPX_querystring_notremoved(TestHTTPX_querystring_removed):
|
|
|
144
144
|
|
|
145
145
|
def check(self, module_test, events):
|
|
146
146
|
assert [e for e in events if e.type == "URL_UNVERIFIED" and e.data == "http://127.0.0.1:8888/test.php?foo=bar"]
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class TestHTTPX_custom_headers(ModuleTestBase):
|
|
150
|
+
targets = ["http://127.0.0.1:8888"]
|
|
151
|
+
modules_overrides = ["httpx", "speculate", "excavate"]
|
|
152
|
+
config_overrides = {"web": {"http_headers": {"testheader": "testvalue"}}}
|
|
153
|
+
|
|
154
|
+
async def setup_after_prep(self, module_test):
|
|
155
|
+
module_test.httpserver.expect_request("/", headers={"testheader": "testvalue"}).respond_with_data("alive")
|
|
156
|
+
|
|
157
|
+
def check(self, module_test, events):
|
|
158
|
+
# Ensure we received the expected response when the header was present
|
|
159
|
+
assert [e for e in events if e.type == "URL" and "status-200" in e.tags]
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class TestHTTPX_custom_cookies(ModuleTestBase):
|
|
163
|
+
targets = ["http://127.0.0.1:8888"]
|
|
164
|
+
modules_overrides = ["httpx", "speculate", "excavate"]
|
|
165
|
+
config_overrides = {"web": {"http_cookies": {"testcookie": "cookievalue"}}}
|
|
166
|
+
|
|
167
|
+
async def setup_after_prep(self, module_test):
|
|
168
|
+
# Expect a request to "/" with the custom cookie 'testcookie=cookievalue'
|
|
169
|
+
module_test.httpserver.expect_request("/", headers={"cookie": "testcookie=cookievalue"}).respond_with_data(
|
|
170
|
+
"alive"
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
def check(self, module_test, events):
|
|
174
|
+
# Ensure we received the expected response when the cookie was present
|
|
175
|
+
assert [e for e in events if e.type == "URL" and "status-200" in e.tags]
|
|
@@ -15,6 +15,23 @@ class TestHunt(ModuleTestBase):
|
|
|
15
15
|
|
|
16
16
|
def check(self, module_test, events):
|
|
17
17
|
assert any(
|
|
18
|
-
e.type == "FINDING"
|
|
18
|
+
e.type == "FINDING"
|
|
19
|
+
and e.data["description"]
|
|
20
|
+
== "Found potentially interesting parameter. Name: [cipher] Parameter Type: [GETPARAM] Categories: [Insecure Cryptography] Original Value: [xor]"
|
|
21
|
+
for e in events
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TestHunt_Multiple(TestHunt):
|
|
26
|
+
async def setup_after_prep(self, module_test):
|
|
27
|
+
expect_args = {"method": "GET", "uri": "/"}
|
|
28
|
+
respond_args = {"response_data": '<html><a href="/hackme.php?id=1234">ping</a></html>'}
|
|
29
|
+
module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args)
|
|
30
|
+
|
|
31
|
+
def check(self, module_test, events):
|
|
32
|
+
assert any(
|
|
33
|
+
e.type == "FINDING"
|
|
34
|
+
and e.data["description"]
|
|
35
|
+
== "Found potentially interesting parameter. Name: [id] Parameter Type: [GETPARAM] Categories: [Insecure Direct Object Reference, SQL Injection, Server-Side Template Injection] Original Value: [1234]"
|
|
19
36
|
for e in events
|
|
20
37
|
)
|