bbot 2.5.0__py3-none-any.whl → 2.7.2.7424rc0__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.
Files changed (144) hide show
  1. bbot/__init__.py +1 -1
  2. bbot/cli.py +22 -8
  3. bbot/core/engine.py +1 -1
  4. bbot/core/event/__init__.py +2 -2
  5. bbot/core/event/base.py +138 -110
  6. bbot/core/flags.py +1 -0
  7. bbot/core/helpers/bloom.py +6 -7
  8. bbot/core/helpers/command.py +5 -2
  9. bbot/core/helpers/depsinstaller/installer.py +78 -7
  10. bbot/core/helpers/dns/dns.py +0 -1
  11. bbot/core/helpers/dns/engine.py +0 -2
  12. bbot/core/helpers/files.py +2 -2
  13. bbot/core/helpers/git.py +17 -0
  14. bbot/core/helpers/helper.py +6 -5
  15. bbot/core/helpers/misc.py +15 -28
  16. bbot/core/helpers/names_generator.py +5 -0
  17. bbot/core/helpers/ntlm.py +0 -2
  18. bbot/core/helpers/regex.py +1 -1
  19. bbot/core/helpers/regexes.py +25 -8
  20. bbot/core/helpers/web/engine.py +1 -1
  21. bbot/core/helpers/web/web.py +2 -1
  22. bbot/core/modules.py +22 -60
  23. bbot/core/shared_deps.py +38 -0
  24. bbot/defaults.yml +4 -2
  25. bbot/modules/apkpure.py +2 -2
  26. bbot/modules/aspnet_bin_exposure.py +80 -0
  27. bbot/modules/baddns.py +1 -1
  28. bbot/modules/baddns_direct.py +1 -1
  29. bbot/modules/baddns_zone.py +1 -1
  30. bbot/modules/badsecrets.py +1 -1
  31. bbot/modules/base.py +129 -40
  32. bbot/modules/bucket_amazon.py +1 -1
  33. bbot/modules/bucket_digitalocean.py +1 -1
  34. bbot/modules/bucket_firebase.py +1 -1
  35. bbot/modules/bucket_google.py +1 -1
  36. bbot/modules/{bucket_azure.py → bucket_microsoft.py} +2 -2
  37. bbot/modules/builtwith.py +4 -2
  38. bbot/modules/c99.py +1 -1
  39. bbot/modules/dnsbimi.py +1 -4
  40. bbot/modules/dnsbrute.py +6 -1
  41. bbot/modules/dnscommonsrv.py +1 -0
  42. bbot/modules/dnsdumpster.py +35 -52
  43. bbot/modules/dnstlsrpt.py +0 -6
  44. bbot/modules/docker_pull.py +2 -2
  45. bbot/modules/emailformat.py +17 -1
  46. bbot/modules/ffuf.py +4 -1
  47. bbot/modules/ffuf_shortnames.py +6 -3
  48. bbot/modules/filedownload.py +8 -5
  49. bbot/modules/fullhunt.py +1 -1
  50. bbot/modules/git_clone.py +47 -22
  51. bbot/modules/gitdumper.py +5 -15
  52. bbot/modules/github_workflows.py +6 -5
  53. bbot/modules/gitlab_com.py +31 -0
  54. bbot/modules/gitlab_onprem.py +84 -0
  55. bbot/modules/gowitness.py +60 -30
  56. bbot/modules/graphql_introspection.py +145 -0
  57. bbot/modules/httpx.py +2 -0
  58. bbot/modules/hunt.py +10 -3
  59. bbot/modules/iis_shortnames.py +16 -7
  60. bbot/modules/internal/cloudcheck.py +65 -72
  61. bbot/modules/internal/unarchive.py +9 -3
  62. bbot/modules/lightfuzz/lightfuzz.py +6 -2
  63. bbot/modules/lightfuzz/submodules/esi.py +42 -0
  64. bbot/modules/{deadly/medusa.py → medusa.py} +4 -7
  65. bbot/modules/nuclei.py +2 -2
  66. bbot/modules/otx.py +9 -2
  67. bbot/modules/output/base.py +3 -11
  68. bbot/modules/paramminer_headers.py +10 -7
  69. bbot/modules/passivetotal.py +1 -1
  70. bbot/modules/portfilter.py +2 -0
  71. bbot/modules/portscan.py +1 -1
  72. bbot/modules/postman_download.py +2 -2
  73. bbot/modules/retirejs.py +232 -0
  74. bbot/modules/securitytxt.py +0 -3
  75. bbot/modules/sslcert.py +2 -2
  76. bbot/modules/subdomaincenter.py +1 -16
  77. bbot/modules/telerik.py +7 -2
  78. bbot/modules/templates/bucket.py +24 -4
  79. bbot/modules/templates/gitlab.py +98 -0
  80. bbot/modules/trufflehog.py +7 -4
  81. bbot/modules/wafw00f.py +2 -2
  82. bbot/presets/web/dotnet-audit.yml +1 -0
  83. bbot/presets/web/lightfuzz-heavy.yml +1 -1
  84. bbot/presets/web/lightfuzz-medium.yml +1 -1
  85. bbot/presets/web/lightfuzz-superheavy.yml +1 -1
  86. bbot/scanner/manager.py +44 -37
  87. bbot/scanner/scanner.py +17 -4
  88. bbot/scripts/benchmark_report.py +433 -0
  89. bbot/test/benchmarks/__init__.py +2 -0
  90. bbot/test/benchmarks/test_bloom_filter_benchmarks.py +105 -0
  91. bbot/test/benchmarks/test_closest_match_benchmarks.py +76 -0
  92. bbot/test/benchmarks/test_event_validation_benchmarks.py +438 -0
  93. bbot/test/benchmarks/test_excavate_benchmarks.py +291 -0
  94. bbot/test/benchmarks/test_ipaddress_benchmarks.py +143 -0
  95. bbot/test/benchmarks/test_weighted_shuffle_benchmarks.py +70 -0
  96. bbot/test/conftest.py +1 -1
  97. bbot/test/test_step_1/test_bbot_fastapi.py +2 -2
  98. bbot/test/test_step_1/test_events.py +22 -21
  99. bbot/test/test_step_1/test_helpers.py +20 -0
  100. bbot/test/test_step_1/test_manager_scope_accuracy.py +45 -0
  101. bbot/test/test_step_1/test_modules_basic.py +40 -15
  102. bbot/test/test_step_1/test_python_api.py +2 -2
  103. bbot/test/test_step_1/test_regexes.py +21 -4
  104. bbot/test/test_step_1/test_scan.py +7 -8
  105. bbot/test/test_step_1/test_web.py +46 -0
  106. bbot/test/test_step_2/module_tests/base.py +6 -1
  107. bbot/test/test_step_2/module_tests/test_module_aspnet_bin_exposure.py +73 -0
  108. bbot/test/test_step_2/module_tests/test_module_bucket_amazon.py +52 -18
  109. bbot/test/test_step_2/module_tests/test_module_bucket_google.py +1 -1
  110. bbot/test/test_step_2/module_tests/{test_module_bucket_azure.py → test_module_bucket_microsoft.py} +7 -5
  111. bbot/test/test_step_2/module_tests/test_module_cloudcheck.py +19 -31
  112. bbot/test/test_step_2/module_tests/test_module_dnsbimi.py +2 -1
  113. bbot/test/test_step_2/module_tests/test_module_dnsdumpster.py +3 -5
  114. bbot/test/test_step_2/module_tests/test_module_emailformat.py +1 -1
  115. bbot/test/test_step_2/module_tests/test_module_emails.py +2 -2
  116. bbot/test/test_step_2/module_tests/test_module_excavate.py +64 -5
  117. bbot/test/test_step_2/module_tests/test_module_extractous.py +13 -1
  118. bbot/test/test_step_2/module_tests/test_module_github_workflows.py +10 -1
  119. bbot/test/test_step_2/module_tests/test_module_gitlab_com.py +66 -0
  120. bbot/test/test_step_2/module_tests/{test_module_gitlab.py → test_module_gitlab_onprem.py} +4 -69
  121. bbot/test/test_step_2/module_tests/test_module_gowitness.py +5 -5
  122. bbot/test/test_step_2/module_tests/test_module_graphql_introspection.py +34 -0
  123. bbot/test/test_step_2/module_tests/test_module_iis_shortnames.py +46 -1
  124. bbot/test/test_step_2/module_tests/test_module_jadx.py +9 -0
  125. bbot/test/test_step_2/module_tests/test_module_lightfuzz.py +71 -3
  126. bbot/test/test_step_2/module_tests/test_module_nuclei.py +8 -6
  127. bbot/test/test_step_2/module_tests/test_module_otx.py +3 -0
  128. bbot/test/test_step_2/module_tests/test_module_portfilter.py +2 -0
  129. bbot/test/test_step_2/module_tests/test_module_retirejs.py +161 -0
  130. bbot/test/test_step_2/module_tests/test_module_telerik.py +1 -1
  131. bbot/test/test_step_2/module_tests/test_module_trufflehog.py +10 -1
  132. bbot/test/test_step_2/module_tests/test_module_unarchive.py +9 -0
  133. {bbot-2.5.0.dist-info → bbot-2.7.2.7424rc0.dist-info}/METADATA +12 -9
  134. {bbot-2.5.0.dist-info → bbot-2.7.2.7424rc0.dist-info}/RECORD +137 -124
  135. {bbot-2.5.0.dist-info → bbot-2.7.2.7424rc0.dist-info}/WHEEL +1 -1
  136. {bbot-2.5.0.dist-info → bbot-2.7.2.7424rc0.dist-info/licenses}/LICENSE +98 -58
  137. bbot/modules/binaryedge.py +0 -42
  138. bbot/modules/censys.py +0 -98
  139. bbot/modules/gitlab.py +0 -141
  140. bbot/modules/zoomeye.py +0 -77
  141. bbot/test/test_step_2/module_tests/test_module_binaryedge.py +0 -33
  142. bbot/test/test_step_2/module_tests/test_module_censys.py +0 -83
  143. bbot/test/test_step_2/module_tests/test_module_zoomeye.py +0 -35
  144. {bbot-2.5.0.dist-info → bbot-2.7.2.7424rc0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,34 @@
1
+ from .base import ModuleTestBase
2
+
3
+
4
+ class TestGraphQLIntrospectionNon200(ModuleTestBase):
5
+ targets = ["http://127.0.0.1:8888"]
6
+ modules_overrides = ["graphql_introspection"]
7
+
8
+ async def setup_after_prep(self, module_test):
9
+ module_test.set_expect_requests(
10
+ expect_args={"method": "POST", "uri": "/"},
11
+ respond_args={"response_data": "ok"},
12
+ )
13
+
14
+ def check(self, module_test, events):
15
+ assert all(e.type != "FINDING" for e in events), "should have raised 0 events"
16
+
17
+
18
+ class TestGraphQLIntrospection(ModuleTestBase):
19
+ targets = ["http://127.0.0.1:8888"]
20
+ modules_overrides = ["graphql_introspection"]
21
+
22
+ async def setup_after_prep(self, module_test):
23
+ module_test.set_expect_requests(
24
+ expect_args={"method": "POST", "uri": "/"},
25
+ respond_args={
26
+ "response_data": """{"data": {"__schema": {"types": ["dummy"]}}}""",
27
+ },
28
+ )
29
+
30
+ def check(self, module_test, events):
31
+ finding = [e for e in events if e.type == "FINDING"]
32
+ assert finding, "should have raised 1 FINDING event"
33
+ assert finding[0].data["url"] == "http://127.0.0.1:8888/"
34
+ assert finding[0].data["description"] == "GraphQL schema"
@@ -43,19 +43,64 @@ class TestIIS_Shortnames(ModuleTestBase):
43
43
  respond_args = {"response_data": "", "status": 400}
44
44
  module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args)
45
45
 
46
- for char in "BLSHAX":
46
+ expect_args = {"method": "GET", "uri": re.compile(r"\/BA\*~1\*.*$")}
47
+ respond_args = {"response_data": "", "status": 400}
48
+ module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args)
49
+
50
+ expect_args = {"method": "GET", "uri": re.compile(r"\/BAC\*~1\*.*$")}
51
+ respond_args = {"response_data": "", "status": 400}
52
+ module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args)
53
+
54
+ expect_args = {"method": "GET", "uri": re.compile(r"\/BACK\*~1\*.*$")}
55
+ respond_args = {"response_data": "", "status": 400}
56
+ module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args)
57
+
58
+ expect_args = {"method": "GET", "uri": re.compile(r"\/BACKU\*~1\*.*$")}
59
+ respond_args = {"response_data": "", "status": 400}
60
+ module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args)
61
+
62
+ expect_args = {"method": "GET", "uri": re.compile(r"\/BACKUP\*~1\*/a.aspx$")}
63
+ respond_args = {"response_data": "", "status": 400}
64
+ module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args)
65
+
66
+ expect_args = {"method": "GET", "uri": re.compile(r"\/BACKUP~1\*$")}
67
+ respond_args = {"response_data": "", "status": 400}
68
+ module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args)
69
+
70
+ expect_args = {"method": "GET", "uri": re.compile(r"\/BACKUP~1\.Z\*/a.aspx$")}
71
+ respond_args = {"response_data": "", "status": 400}
72
+ module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args)
73
+
74
+ expect_args = {"method": "GET", "uri": re.compile(r"\/BACKUP~1\.ZI\*/a.aspx$")}
75
+ respond_args = {"response_data": "", "status": 400}
76
+ module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args)
77
+
78
+ expect_args = {"method": "GET", "uri": re.compile(r"\/BACKUP~1\.ZIP\*/a.aspx$")}
79
+ respond_args = {"response_data": "", "status": 400}
80
+ module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args)
81
+
82
+ for char in "BLSHAXCKUP":
47
83
  expect_args = {"method": "GET", "uri": re.compile(rf"\/\*{char}\*~1\*.*$")}
48
84
  respond_args = {"response_data": "", "status": 400}
49
85
  module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args)
50
86
 
87
+ for char in "ZIP":
88
+ expect_args = {"method": "GET", "uri": re.compile(rf"\/\*~1\*{char}\*.*$")}
89
+ respond_args = {"response_data": "", "status": 400}
90
+ module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args)
91
+
51
92
  def check(self, module_test, events):
52
93
  vulnerabilityEmitted = False
53
94
  url_hintEmitted = False
95
+ zip_findingEmitted = False
54
96
  for e in events:
55
97
  if e.type == "VULNERABILITY" and "iis-magic-url" not in e.tags:
56
98
  vulnerabilityEmitted = True
57
99
  if e.type == "URL_HINT" and e.data == "http://127.0.0.1:8888/BLSHAX~1":
58
100
  url_hintEmitted = True
101
+ if e.type == "FINDING" and "Possible backup file (zip) in web root" in e.data["description"]:
102
+ zip_findingEmitted = True
59
103
 
60
104
  assert vulnerabilityEmitted
61
105
  assert url_hintEmitted
106
+ assert zip_findingEmitted
@@ -2,9 +2,18 @@ from pathlib import Path
2
2
  from bbot.core.helpers.libmagic import get_magic_info
3
3
  from bbot.test.test_step_2.module_tests.base import ModuleTestBase, tempapkfile
4
4
 
5
+ from ...bbot_fixtures import *
6
+
5
7
 
6
8
  class TestJadx(ModuleTestBase):
7
9
  modules_overrides = ["apkpure", "google_playstore", "speculate", "jadx"]
10
+ config_overrides = {
11
+ "modules": {
12
+ "apkpure": {
13
+ "output_folder": bbot_test_dir / "apkpure",
14
+ },
15
+ }
16
+ }
8
17
  apk_file = tempapkfile()
9
18
 
10
19
  async def setup_after_prep(self, module_test):
@@ -608,7 +608,7 @@ class Test_Lightfuzz_urlencoding(Test_Lightfuzz_xss_injs):
608
608
  "interactsh_disable": True,
609
609
  "modules": {
610
610
  "lightfuzz": {
611
- "enabled_submodules": ["cmdi", "crypto", "path", "serial", "sqli", "ssti", "xss"],
611
+ "enabled_submodules": ["cmdi", "crypto", "path", "serial", "sqli", "ssti", "xss", "esi"],
612
612
  }
613
613
  },
614
614
  }
@@ -1723,7 +1723,7 @@ class Test_Lightfuzz_XSS_jsquotecontext(ModuleTestBase):
1723
1723
  input_value = param.split("=")[1]
1724
1724
  break
1725
1725
 
1726
- if input_value:
1726
+ if input_value is not None:
1727
1727
  # Simulate flawed escaping
1728
1728
  sanitized_input = input_value.replace('"', '\\"').replace("'", "\\'")
1729
1729
  sanitized_input = sanitized_input.replace("<", "%3C").replace(">", "%3E")
@@ -1786,7 +1786,7 @@ class Test_Lightfuzz_XSS_jsquotecontext_doublequote(Test_Lightfuzz_XSS_jsquoteco
1786
1786
  input_value = param.split("=")[1]
1787
1787
  break
1788
1788
 
1789
- if input_value:
1789
+ if input_value is not None:
1790
1790
  # Simulate flawed escaping with opposite quotes
1791
1791
  sanitized_input = input_value.replace("'", "\\").replace("%22", '\\"')
1792
1792
  sanitized_input = sanitized_input.replace("<", "%3C").replace(">", "%3E")
@@ -1817,3 +1817,71 @@ class Test_Lightfuzz_XSS_jsquotecontext_doublequote(Test_Lightfuzz_XSS_jsquoteco
1817
1817
 
1818
1818
  assert web_parameter_emitted, "WEB_PARAMETER for was not emitted"
1819
1819
  assert xss_finding_emitted, "XSS FINDING not emitted"
1820
+
1821
+
1822
+ class Test_Lightfuzz_esi(ModuleTestBase):
1823
+ targets = ["http://127.0.0.1:8888"]
1824
+ modules_overrides = ["httpx", "lightfuzz", "excavate"]
1825
+ config_overrides = {
1826
+ "interactsh_disable": True,
1827
+ "modules": {
1828
+ "lightfuzz": {
1829
+ "enabled_submodules": ["esi"],
1830
+ }
1831
+ },
1832
+ }
1833
+
1834
+ def request_handler(self, request):
1835
+ qs = str(request.query_string.decode())
1836
+
1837
+ parameter_block = """
1838
+ <section class=search>
1839
+ <form action=/ method=GET>
1840
+ <input type=text placeholder='Search...' name=search>
1841
+ <button type=submit class=button>Search</button>
1842
+ </form>
1843
+ </section>
1844
+ """
1845
+ if "search=" in qs:
1846
+ value = qs.split("=")[1]
1847
+ if "&" in value:
1848
+ value = value.split("&")[0]
1849
+ # Decode the URL-encoded value
1850
+ decoded_value = unquote(value)
1851
+ # Simulate ESI processing: if the payload contains <!--esi-->, remove it
1852
+ if "<!--esi-->" in decoded_value:
1853
+ # ESI processor removes <!--esi--> tag, leaving the rest
1854
+ processed_value = decoded_value.replace("<!--esi-->", "")
1855
+ else:
1856
+ # For non-ESI payloads, just reflect the value as-is
1857
+ processed_value = decoded_value
1858
+
1859
+ esi_block = f"""
1860
+ <section class=blog-header>
1861
+ <h1>Search results for '{processed_value}'</h1>
1862
+ <hr>
1863
+ </section>
1864
+ """
1865
+ return Response(esi_block, status=200)
1866
+
1867
+ return Response(parameter_block, status=200)
1868
+
1869
+ async def setup_after_prep(self, module_test):
1870
+ expect_args = re.compile("/")
1871
+ module_test.set_expect_requests_handler(expect_args=expect_args, request_handler=self.request_handler)
1872
+
1873
+ def check(self, module_test, events):
1874
+ web_parameter_emitted = False
1875
+ esi_finding_emitted = False
1876
+
1877
+ for e in events:
1878
+ if e.type == "WEB_PARAMETER":
1879
+ if "HTTP Extracted Parameter [search]" in e.data["description"]:
1880
+ web_parameter_emitted = True
1881
+
1882
+ if e.type == "FINDING":
1883
+ if "Edge Side Include. Parameter: [search] Parameter Type: [GETPARAM]" in e.data["description"]:
1884
+ esi_finding_emitted = True
1885
+
1886
+ assert web_parameter_emitted, "WEB_PARAMETER was not emitted"
1887
+ assert esi_finding_emitted, "ESI FINDING not emitted"
@@ -12,7 +12,6 @@ class TestNucleiManual(ModuleTestBase):
12
12
  },
13
13
  "modules": {
14
14
  "nuclei": {
15
- "version": "2.9.4",
16
15
  "mode": "manual",
17
16
  "concurrency": 2,
18
17
  "ratelimit": 10,
@@ -67,21 +66,24 @@ class TestNucleiSevere(TestNucleiManual):
67
66
  "nuclei": {
68
67
  "mode": "severe",
69
68
  "concurrency": 1,
70
- "templates": "/tmp/.bbot_test/tools/nuclei-templates/vulnerabilities/generic/generic-linux-lfi.yaml",
69
+ "templates": "/tmp/.bbot_test/tools/nuclei-templates/http/vulnerabilities/generic/generic-env.yaml",
71
70
  }
72
71
  },
73
72
  "interactsh_disable": True,
74
73
  }
75
74
 
76
75
  async def setup_after_prep(self, module_test):
77
- expect_args = {"method": "GET", "uri": "/etc/passwd"}
78
- respond_args = {"response_data": "<html>root:.*:0:0:</html>"}
76
+ expect_args = {"method": "GET", "uri": "/.env"}
77
+ respond_args = {"response_data": "AAAKEYBBB="}
78
+ module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args)
79
+
80
+ expect_args = {"method": "GET", "uri": "/"}
81
+ respond_args = {"response_data": "<html>alive</html>"}
79
82
  module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args)
80
83
 
81
84
  def check(self, module_test, events):
82
85
  assert any(
83
- e.type == "VULNERABILITY" and "Generic Linux - Local File Inclusion" in e.data["description"]
84
- for e in events
86
+ e.type == "VULNERABILITY" and "Generic Env File Disclosure" in e.data["description"] for e in events
85
87
  )
86
88
 
87
89
 
@@ -2,6 +2,8 @@ from .base import ModuleTestBase
2
2
 
3
3
 
4
4
  class TestOTX(ModuleTestBase):
5
+ config_overrides = {"modules": {"otx": {"api_key": "test"}}}
6
+
5
7
  async def setup_after_prep(self, module_test):
6
8
  module_test.httpx_mock.add_response(
7
9
  url="https://otx.alienvault.com/api/v1/indicators/domain/blacklanternsecurity.com/passive_dns",
@@ -21,6 +23,7 @@ class TestOTX(ModuleTestBase):
21
23
  }
22
24
  ]
23
25
  },
26
+ headers={"X-OTX-API-KEY": "test"},
24
27
  )
25
28
 
26
29
  def check(self, module_test, events):
@@ -43,6 +43,8 @@ class TestPortfilter_enabled(TestPortfilter_disabled):
43
43
  modules_overrides = ["portfilter"]
44
44
 
45
45
  def check(self, module_test, events):
46
+ # even though portfilter listens for URLs, enabling it should not automatically enable httpx
47
+ assert "httpx" not in module_test.scan.modules
46
48
  open_ports = {event.data for event in events if event.type == "OPEN_TCP_PORT"}
47
49
  # we should be missing the 8080 port because it's a CDN and not in portfilter's allowed list of open ports
48
50
  assert open_ports == {"www.blacklanternsecurity.com:443", "www.blacklanternsecurity.com:21"}
@@ -0,0 +1,161 @@
1
+ from .base import ModuleTestBase
2
+
3
+
4
+ class TestRetireJS(ModuleTestBase):
5
+ targets = ["http://127.0.0.1:8888"]
6
+ modules_overrides = ["httpx", "excavate", "retirejs"]
7
+
8
+ # HTML page with vulnerable JavaScript libraries
9
+ vulnerable_html = """<!doctype html>
10
+ <html lang="en">
11
+ <head>
12
+ <meta charset="utf-8" />
13
+ <title>retire.js test page</title>
14
+ </head>
15
+ <body>
16
+ <h1>retire.js test page</h1>
17
+ <p>This page includes JavaScript libraries for testing.</p>
18
+
19
+ <!-- jQuery 3.4.1 -->
20
+ <script src="/jquery-3.4.1.min.js"></script>
21
+
22
+ <!-- Lodash 4.17.11 -->
23
+ <script src="/lodash.min.js"></script>
24
+
25
+ <!-- Handlebars 4.0.5 -->
26
+ <script src="/handlebars.min.js"></script>
27
+
28
+ <script>
29
+ console.log('Libraries loaded');
30
+ </script>
31
+ </body>
32
+ </html>"""
33
+
34
+ # Sample jQuery 3.4.1 content
35
+ jquery_content = """/*!
36
+ * jQuery JavaScript Library v3.4.1
37
+ * https://jquery.com/
38
+ */
39
+ (function( global, factory ) {
40
+ "use strict";
41
+ factory( global );
42
+ })(typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
43
+ var jQuery = function( selector, context ) {
44
+ return new jQuery.fn.init( selector, context );
45
+ };
46
+ jQuery.fn = jQuery.prototype = {};
47
+ jQuery.fn.jquery = "3.4.1";
48
+ if ( typeof noGlobal === "undefined" ) {
49
+ window.jQuery = window.$ = jQuery;
50
+ }
51
+ return jQuery;
52
+ });"""
53
+
54
+ # Sample Lodash 4.17.11 content
55
+ lodash_content = """/**
56
+ * @license
57
+ * Lodash lodash.com/license | Underscore.js 1.8.3 underscorejs.org/LICENSE
58
+ */
59
+ ;(function(){
60
+ var i="4.17.11";
61
+ var Mn={VERSION:i};
62
+ if(typeof define=="function"&&define.amd)define(function(){return Mn});else if(typeof module=="object"&&module.exports)module.exports=Mn;else this._=Mn}());"""
63
+
64
+ # Sample Handlebars 4.0.5 content
65
+ handlebars_content = """/*!
66
+ handlebars v4.0.5
67
+ */
68
+ !function(a,b){"object"==typeof exports&&"object"==typeof module?module.exports=b():"function"==typeof define&&define.amd?define([],b):"object"==typeof exports?exports.Handlebars=b():a.Handlebars=b()}(this,function(){
69
+ var Handlebars={};
70
+ Handlebars.VERSION="4.0.5";
71
+ return Handlebars;
72
+ });"""
73
+
74
+ async def setup_after_prep(self, module_test):
75
+ expect_args = {"uri": "/"}
76
+ respond_args = {"response_data": self.vulnerable_html}
77
+ module_test.set_expect_requests(expect_args, respond_args)
78
+
79
+ expect_args = {"uri": "/jquery-3.4.1.min.js"}
80
+ respond_args = {"response_data": self.jquery_content}
81
+ module_test.set_expect_requests(expect_args, respond_args)
82
+
83
+ expect_args = {"uri": "/lodash.min.js"}
84
+ respond_args = {"response_data": self.lodash_content}
85
+ module_test.set_expect_requests(expect_args, respond_args)
86
+
87
+ expect_args = {"uri": "/handlebars.min.js"}
88
+ respond_args = {"response_data": self.handlebars_content}
89
+ module_test.set_expect_requests(expect_args, respond_args)
90
+
91
+ def check(self, module_test, events):
92
+ # Check that excavate found the JavaScript URLs
93
+ url_unverified_events = [e for e in events if e.type == "URL_UNVERIFIED"]
94
+ js_url_events = [e for e in url_unverified_events if "extension-js" in e.tags]
95
+
96
+ # Two out of the three URLs should be in the output
97
+ # The third, non-vulnerable URL (lodash.min.js) is not output because it's a "special URL", and
98
+ # nothing interesting has been discovered from it.
99
+ vuln_urls = {"http://127.0.0.1:8888/handlebars.min.js", "http://127.0.0.1:8888/jquery-3.4.1.min.js"}
100
+ assert {e.data for e in js_url_events} == vuln_urls, "Expected to find the vulnerable URLs in the output"
101
+
102
+ # Check for FINDING events generated by retirejs
103
+ finding_events = [e for e in events if e.type == "FINDING"]
104
+ retirejs_findings = [
105
+ e
106
+ for e in finding_events
107
+ if "vulnerable javascript library detected:" in e.data.get("description", "").lower()
108
+ ]
109
+
110
+ # We should have at least some findings from our vulnerable libraries
111
+ assert len(retirejs_findings) > 0, (
112
+ f"Expected retirejs to find vulnerabilities, but got {len(retirejs_findings)} findings"
113
+ )
114
+
115
+ # Check for specific expected vulnerability descriptions
116
+ descriptions = [finding.data.get("description", "") for finding in retirejs_findings]
117
+ all_descriptions = "\n".join(descriptions)
118
+
119
+ # Look for specific vulnerabilities we expect to find
120
+ expected_handlebars_vuln = "Vulnerable JavaScript library detected: handlebars v4.0.5 Severity: HIGH Summary: Regular Expression Denial of Service in Handlebars JavaScript URL: http://127.0.0.1:8888/handlebars.min.js CVE(s): CVE-2019-20922 Affected versions: [4.0.0 to 4.4.5)"
121
+ expected_jquery_vuln = "Vulnerable JavaScript library detected: jquery v3.4.1 Severity: MEDIUM Summary: Regex in its jQuery.htmlPrefilter sometimes may introduce XSS JavaScript URL: http://127.0.0.1:8888/jquery-3.4.1.min.js CVE(s): CVE-2020-11022 Affected versions: [1.2.0 to 3.5.0)"
122
+
123
+ # Verify at least one of the expected vulnerabilities is found
124
+ handlebars_found = expected_handlebars_vuln in all_descriptions
125
+ jquery_found = expected_jquery_vuln in all_descriptions
126
+
127
+ assert handlebars_found and jquery_found, (
128
+ f"Expected to find specific vulnerabilities but didn't find them. Found descriptions:\n{all_descriptions}"
129
+ )
130
+
131
+ # Basic validation of findings structure
132
+ for finding in retirejs_findings:
133
+ assert "description" in finding.data, "Finding should have description"
134
+ assert "url" in finding.data, "Finding should have url"
135
+ assert finding.parent.type == "URL_UNVERIFIED", "Parent should be URL_UNVERIFIED"
136
+
137
+
138
+ class TestRetireJSNoExcavate(ModuleTestBase):
139
+ targets = ["http://127.0.0.1:8888"]
140
+ modules_overrides = ["httpx", "retirejs"]
141
+ force_start = True # Allow scan to continue even if modules fail setup
142
+ config_overrides = {
143
+ "excavate": False,
144
+ }
145
+
146
+ def check(self, module_test, events):
147
+ # When excavate is disabled, retirejs should fail setup but scan should still run
148
+ retirejs_module = module_test.scan.modules.get("retirejs")
149
+
150
+ if retirejs_module:
151
+ # Check that the module exists but setup failed
152
+ setup_status = getattr(retirejs_module, "_setup_status", None)
153
+ if setup_status is not None:
154
+ success, error_msg = setup_status
155
+ assert success is False, "retirejs setup should have failed without excavate"
156
+ expected_error = "retirejs will not function without excavate enabled"
157
+ assert error_msg == expected_error, f"Expected error message '{expected_error}', but got '{error_msg}'"
158
+
159
+ # No retirejs findings should be generated since setup failed
160
+ retirejs_findings = [e for e in events if e.type == "FINDING" and getattr(e, "module", None) == "retirejs"]
161
+ assert len(retirejs_findings) == 0, "retirejs should not generate findings when setup fails"
@@ -11,7 +11,7 @@ class TestTelerik(ModuleTestBase):
11
11
  # Simulate Telerik.Web.UI.WebResource.axd?type=rau detection
12
12
  expect_args = {"method": "GET", "uri": "/Telerik.Web.UI.WebResource.axd", "query_string": "type=rau"}
13
13
  respond_args = {
14
- "response_data": '{ "message" : "RadAsyncUpload handler is registered successfully, however, it may not be accessed directly." }'
14
+ "response_data": '{ "message" : "RadAsyncUpload handler is registered succesfully, however, it may not be accessed directly." }'
15
15
  }
16
16
  module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args)
17
17
 
@@ -16,6 +16,7 @@ class TestTrufflehog(ModuleTestBase):
16
16
  "modules": {
17
17
  "postman_download": {"api_key": "asdf", "output_folder": str(download_dir)},
18
18
  "docker_pull": {"output_folder": str(download_dir)},
19
+ "github_org": {"api_key": "asdf"},
19
20
  "git_clone": {"output_folder": str(download_dir)},
20
21
  }
21
22
  }
@@ -34,7 +35,9 @@ class TestTrufflehog(ModuleTestBase):
34
35
  file_content = "Verifiable Secret:\nhttps://admin:admin@the-internet.herokuapp.com/basic_auth\n\nUnverifiable Secret:\nhttps://admin:admin@internal.host.com"
35
36
 
36
37
  async def setup_before_prep(self, module_test):
37
- module_test.httpx_mock.add_response(url="https://api.github.com/zen")
38
+ module_test.httpx_mock.add_response(
39
+ url="https://api.github.com/zen", match_headers={"Authorization": "token asdf"}
40
+ )
38
41
  module_test.httpx_mock.add_response(
39
42
  url="https://api.getpostman.com/me",
40
43
  json={
@@ -68,6 +71,7 @@ class TestTrufflehog(ModuleTestBase):
68
71
  )
69
72
  module_test.httpx_mock.add_response(
70
73
  url="https://api.github.com/orgs/blacklanternsecurity",
74
+ match_headers={"Authorization": "token asdf"},
71
75
  json={
72
76
  "login": "blacklanternsecurity",
73
77
  "id": 25311592,
@@ -103,6 +107,7 @@ class TestTrufflehog(ModuleTestBase):
103
107
  )
104
108
  module_test.httpx_mock.add_response(
105
109
  url="https://api.github.com/orgs/blacklanternsecurity/repos?per_page=100&page=1",
110
+ match_headers={"Authorization": "token asdf"},
106
111
  json=[
107
112
  {
108
113
  "id": 459780477,
@@ -310,6 +315,7 @@ class TestTrufflehog(ModuleTestBase):
310
315
  )
311
316
  module_test.httpx_mock.add_response(
312
317
  url="https://api.github.com/repos/blacklanternsecurity/bbot/actions/workflows?per_page=100&page=1",
318
+ match_headers={"Authorization": "token asdf"},
313
319
  json={
314
320
  "total_count": 3,
315
321
  "workflows": [
@@ -330,6 +336,7 @@ class TestTrufflehog(ModuleTestBase):
330
336
  )
331
337
  module_test.httpx_mock.add_response(
332
338
  url="https://api.github.com/repos/blacklanternsecurity/bbot/actions/workflows/22452226/runs?status=success&per_page=1",
339
+ match_headers={"Authorization": "token asdf"},
333
340
  json={
334
341
  "total_count": 2993,
335
342
  "workflow_runs": [
@@ -576,6 +583,7 @@ class TestTrufflehog(ModuleTestBase):
576
583
  )
577
584
  module_test.httpx_mock.add_response(
578
585
  url="https://api.github.com/repos/blacklanternsecurity/bbot/actions/runs/8839360698/logs",
586
+ match_headers={"Authorization": "token asdf"},
579
587
  headers={
580
588
  "location": "https://productionresultssa10.blob.core.windows.net/actions-results/7beb304e-f42c-4830-a027-4f5dec53107d/workflow-job-run-3a559e2a-952e-58d2-b8db-2e604a9266d7/logs/steps/step-logs-0e34a19a-18b0-4208-b27a-f8c031db2d17.txt?rsct=text%2Fplain&se=2024-04-26T16%3A25%3A39Z&sig=a%2FiN8dOw0e3tiBQZAfr80veI8OYChb9edJ1eFY136B4%3D&sp=r&spr=https&sr=b&st=2024-04-26T16%3A15%3A34Z&sv=2021-12-02"
581
589
  },
@@ -1221,6 +1229,7 @@ class TestTrufflehog_NonVerified(TestTrufflehog):
1221
1229
  "trufflehog": {"only_verified": False},
1222
1230
  "docker_pull": {"output_folder": str(download_dir)},
1223
1231
  "postman_download": {"api_key": "asdf", "output_folder": str(download_dir)},
1232
+ "github_org": {"api_key": "asdf"},
1224
1233
  "git_clone": {"output_folder": str(download_dir)},
1225
1234
  }
1226
1235
  }
@@ -3,10 +3,19 @@ import asyncio
3
3
  from pathlib import Path
4
4
  from .base import ModuleTestBase
5
5
 
6
+ from ...bbot_fixtures import *
7
+
6
8
 
7
9
  class TestUnarchive(ModuleTestBase):
8
10
  targets = ["http://127.0.0.1:8888"]
9
11
  modules_overrides = ["filedownload", "httpx", "excavate", "speculate", "unarchive"]
12
+ config_overrides = {
13
+ "modules": {
14
+ "filedownload": {
15
+ "output_folder": bbot_test_dir / "filedownload",
16
+ },
17
+ }
18
+ }
10
19
 
11
20
  async def setup_after_prep(self, module_test):
12
21
  temp_path = Path("/tmp/.bbot_test")
@@ -1,8 +1,9 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: bbot
3
- Version: 2.5.0
3
+ Version: 2.7.2.7424rc0
4
4
  Summary: OSINT automation for hackers.
5
5
  License: GPL-3.0
6
+ License-File: LICENSE
6
7
  Keywords: python,cli,automation,osint,threat-intel,intelligence,neo4j,scanner,python-library,hacking,recursion,pentesting,recon,command-line-tool,bugbounty,subdomains,security-tools,subdomain-scanner,osint-framework,attack-surface,subdomain-enumeration,osint-tool
7
8
  Author: TheTechromancer
8
9
  Requires-Python: >=3.9,<4.0
@@ -14,18 +15,19 @@ Classifier: Programming Language :: Python :: 3.10
14
15
  Classifier: Programming Language :: Python :: 3.11
15
16
  Classifier: Programming Language :: Python :: 3.12
16
17
  Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
17
19
  Classifier: Topic :: Security
18
20
  Requires-Dist: ansible-core (>=2.15.13,<3.0.0)
19
21
  Requires-Dist: ansible-runner (>=2.3.2,<3.0.0)
20
22
  Requires-Dist: beautifulsoup4 (>=4.12.2,<5.0.0)
21
23
  Requires-Dist: cachetools (>=5.3.2,<7.0.0)
22
- Requires-Dist: cloudcheck (>=7.2.11,<8.0.0)
24
+ Requires-Dist: cloudcheck (>=8.6.0,<9.0.0)
23
25
  Requires-Dist: deepdiff (>=8.0.0,<9.0.0)
24
- Requires-Dist: dnspython (>=2.4.2,<3.0.0)
26
+ Requires-Dist: dnspython (>=2.7.0,<2.8.0)
25
27
  Requires-Dist: httpx (>=0.28.1,<0.29.0)
26
28
  Requires-Dist: idna (>=3.4,<4.0)
27
29
  Requires-Dist: jinja2 (>=3.1.3,<4.0.0)
28
- Requires-Dist: lxml (>=4.9.2,<6.0.0)
30
+ Requires-Dist: lxml (>=4.9.2,<7.0.0)
29
31
  Requires-Dist: mmh3 (>=4.1,<6.0)
30
32
  Requires-Dist: omegaconf (>=2.3.0,<3.0.0)
31
33
  Requires-Dist: orjson (>=3.10.12,<4.0.0)
@@ -34,17 +36,18 @@ Requires-Dist: puremagic (>=1.28,<2.0)
34
36
  Requires-Dist: pycryptodome (>=3.17,<4.0)
35
37
  Requires-Dist: pydantic (>=2.9.2,<3.0.0)
36
38
  Requires-Dist: pyjwt (>=2.7.0,<3.0.0)
37
- Requires-Dist: pyzmq (>=26.0.3,<27.0.0)
39
+ Requires-Dist: pyzmq (>=26.0.3,<28.0.0)
38
40
  Requires-Dist: radixtarget (>=3.0.13,<4.0.0)
39
- Requires-Dist: regex (>=2024.4.16,<2025.0.0)
41
+ Requires-Dist: regex (>=2024.4.16,<2026.0.0)
40
42
  Requires-Dist: setproctitle (>=1.3.3,<2.0.0)
41
43
  Requires-Dist: socksio (>=1.0.0,<2.0.0)
42
44
  Requires-Dist: tabulate (==0.8.10)
43
- Requires-Dist: tldextract (>=5.1.1,<6.0.0)
45
+ Requires-Dist: tldextract (>=5.3.0,<6.0.0)
44
46
  Requires-Dist: unidecode (>=1.3.8,<2.0.0)
45
47
  Requires-Dist: websockets (>=14.0.0,<16.0.0)
46
48
  Requires-Dist: wordninja (>=2.0.0,<3.0.0)
47
49
  Requires-Dist: xmltojson (>=2.0.2,<3.0.0)
50
+ Requires-Dist: xxhash (>=3.5.0,<4.0.0)
48
51
  Requires-Dist: yara-python (>=4.5.1,<5.0.0)
49
52
  Project-URL: Documentation, https://www.blacklanternsecurity.com/bbot/
50
53
  Project-URL: Discord, https://discord.com/invite/PZqkgxu5SA
@@ -55,7 +58,7 @@ Description-Content-Type: text/markdown
55
58
 
56
59
  [![bbot_banner](https://github.com/user-attachments/assets/f02804ce-9478-4f1e-ac4d-9cf5620a3214)](https://github.com/blacklanternsecurity/bbot)
57
60
 
58
- [![Python Version](https://img.shields.io/badge/python-3.9+-FF8400)](https://www.python.org) [![License](https://img.shields.io/badge/license-GPLv3-FF8400.svg)](https://github.com/blacklanternsecurity/bbot/blob/dev/LICENSE) [![DEF CON Recon Village 2024](https://img.shields.io/badge/DEF%20CON%20Demo%20Labs-2023-FF8400.svg)](https://www.reconvillage.org/talks) [![PyPi Downloads](https://static.pepy.tech/personalized-badge/bbot?right_color=orange&left_color=grey)](https://pepy.tech/project/bbot) [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) [![Tests](https://github.com/blacklanternsecurity/bbot/actions/workflows/tests.yml/badge.svg?branch=stable)](https://github.com/blacklanternsecurity/bbot/actions?query=workflow%3A"tests") [![Codecov](https://codecov.io/gh/blacklanternsecurity/bbot/branch/dev/graph/badge.svg?token=IR5AZBDM5K)](https://codecov.io/gh/blacklanternsecurity/bbot) [![Discord](https://img.shields.io/discord/859164869970362439)](https://discord.com/invite/PZqkgxu5SA)
61
+ [![Python Version](https://img.shields.io/badge/python-3.9+-FF8400)](https://www.python.org) [![License](https://img.shields.io/badge/license-AGPLv3-FF8400.svg)](https://github.com/blacklanternsecurity/bbot/blob/dev/LICENSE) [![DEF CON Recon Village 2024](https://img.shields.io/badge/DEF%20CON%20Demo%20Labs-2023-FF8400.svg)](https://www.reconvillage.org/talks) [![PyPi Downloads](https://static.pepy.tech/personalized-badge/bbot?right_color=orange&left_color=grey)](https://pepy.tech/project/bbot) [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) [![Tests](https://github.com/blacklanternsecurity/bbot/actions/workflows/tests.yml/badge.svg?branch=stable)](https://github.com/blacklanternsecurity/bbot/actions?query=workflow%3A"tests") [![Codecov](https://codecov.io/gh/blacklanternsecurity/bbot/branch/dev/graph/badge.svg?token=IR5AZBDM5K)](https://codecov.io/gh/blacklanternsecurity/bbot) [![Discord](https://img.shields.io/discord/859164869970362439)](https://discord.com/invite/PZqkgxu5SA)
59
62
 
60
63
  ### **BEE·bot** is a multipurpose scanner inspired by [Spiderfoot](https://github.com/smicallef/spiderfoot), built to automate your **Recon**, **Bug Bounties**, and **ASM**!
61
64