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.

Files changed (64) hide show
  1. bbot/__init__.py +1 -1
  2. bbot/core/event/base.py +64 -4
  3. bbot/core/helpers/diff.py +10 -7
  4. bbot/core/helpers/helper.py +5 -1
  5. bbot/core/helpers/misc.py +48 -11
  6. bbot/core/helpers/regex.py +4 -0
  7. bbot/core/helpers/regexes.py +45 -8
  8. bbot/core/helpers/url.py +21 -5
  9. bbot/core/helpers/web/client.py +25 -5
  10. bbot/core/helpers/web/engine.py +9 -1
  11. bbot/core/helpers/web/envelopes.py +352 -0
  12. bbot/core/helpers/web/web.py +10 -2
  13. bbot/core/helpers/yara_helper.py +50 -0
  14. bbot/core/modules.py +23 -7
  15. bbot/defaults.yml +26 -1
  16. bbot/modules/base.py +4 -2
  17. bbot/modules/{deadly/dastardly.py → dastardly.py} +1 -1
  18. bbot/modules/{deadly/ffuf.py → ffuf.py} +1 -1
  19. bbot/modules/ffuf_shortnames.py +1 -1
  20. bbot/modules/httpx.py +14 -0
  21. bbot/modules/hunt.py +24 -6
  22. bbot/modules/internal/aggregate.py +1 -0
  23. bbot/modules/internal/excavate.py +356 -197
  24. bbot/modules/lightfuzz/lightfuzz.py +203 -0
  25. bbot/modules/lightfuzz/submodules/__init__.py +0 -0
  26. bbot/modules/lightfuzz/submodules/base.py +312 -0
  27. bbot/modules/lightfuzz/submodules/cmdi.py +106 -0
  28. bbot/modules/lightfuzz/submodules/crypto.py +474 -0
  29. bbot/modules/lightfuzz/submodules/nosqli.py +183 -0
  30. bbot/modules/lightfuzz/submodules/path.py +154 -0
  31. bbot/modules/lightfuzz/submodules/serial.py +179 -0
  32. bbot/modules/lightfuzz/submodules/sqli.py +187 -0
  33. bbot/modules/lightfuzz/submodules/ssti.py +39 -0
  34. bbot/modules/lightfuzz/submodules/xss.py +191 -0
  35. bbot/modules/{deadly/nuclei.py → nuclei.py} +1 -1
  36. bbot/modules/paramminer_headers.py +2 -0
  37. bbot/modules/reflected_parameters.py +80 -0
  38. bbot/modules/{deadly/vhost.py → vhost.py} +2 -2
  39. bbot/presets/web/lightfuzz-heavy.yml +16 -0
  40. bbot/presets/web/lightfuzz-light.yml +20 -0
  41. bbot/presets/web/lightfuzz-medium.yml +14 -0
  42. bbot/presets/web/lightfuzz-superheavy.yml +13 -0
  43. bbot/presets/web/lightfuzz-xss.yml +21 -0
  44. bbot/presets/web/paramminer.yml +8 -5
  45. bbot/scanner/preset/args.py +26 -0
  46. bbot/scanner/scanner.py +6 -0
  47. bbot/test/test_step_1/test__module__tests.py +1 -1
  48. bbot/test/test_step_1/test_helpers.py +7 -0
  49. bbot/test/test_step_1/test_presets.py +2 -2
  50. bbot/test/test_step_1/test_web.py +20 -0
  51. bbot/test/test_step_1/test_web_envelopes.py +343 -0
  52. bbot/test/test_step_2/module_tests/test_module_excavate.py +404 -29
  53. bbot/test/test_step_2/module_tests/test_module_httpx.py +29 -0
  54. bbot/test/test_step_2/module_tests/test_module_hunt.py +18 -1
  55. bbot/test/test_step_2/module_tests/test_module_lightfuzz.py +1947 -0
  56. bbot/test/test_step_2/module_tests/test_module_paramminer_getparams.py +4 -1
  57. bbot/test/test_step_2/module_tests/test_module_paramminer_headers.py +46 -2
  58. bbot/test/test_step_2/module_tests/test_module_reflected_parameters.py +226 -0
  59. bbot/wordlists/paramminer_parameters.txt +0 -8
  60. {bbot-2.4.2.dist-info → bbot-2.4.2.6590rc0.dist-info}/METADATA +2 -1
  61. {bbot-2.4.2.dist-info → bbot-2.4.2.6590rc0.dist-info}/RECORD +64 -42
  62. {bbot-2.4.2.dist-info → bbot-2.4.2.6590rc0.dist-info}/LICENSE +0 -0
  63. {bbot-2.4.2.dist-info → bbot-2.4.2.6590rc0.dist-info}/WHEEL +0 -0
  64. {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
- page_relative_detection_1 = False
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="q" value="flowers"><br><br>
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="q" value="boats"><br><br>
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
- </body>
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 [q] (GET Form Submodule)":
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 [q] (POST Form Submodule)":
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&#x3a;&#x2f;&#x2f;127.0.0.1&#x3a;8080&#x2f;sso-web&#x2f;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 = {"modules": {"paramminer_getparams": {"wordlist": tempwordlist([]), "recycle_words": True}}}
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 = {"modules": {"paramminer_getparams": {"wordlist": tempwordlist([]), "recycle_words": True}}}
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
- "web_spider_depth": 4,
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": False,
1144
+ "url_querystring_remove": True,
801
1145
  "url_querystring_collapse": False,
802
1146
  "interactsh_disable": True,
803
- "web_spider_depth": 4,
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" and e.data["description"] == "Found potential INSECURE CRYPTOGRAPHY parameter [cipher]"
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
  )