bbot 2.0.1.4654rc0__py3-none-any.whl → 2.3.0.5397rc0__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 (270) hide show
  1. bbot/__init__.py +1 -1
  2. bbot/cli.py +3 -7
  3. bbot/core/config/files.py +0 -1
  4. bbot/core/config/logger.py +34 -4
  5. bbot/core/core.py +21 -6
  6. bbot/core/engine.py +9 -8
  7. bbot/core/event/base.py +162 -63
  8. bbot/core/helpers/bloom.py +10 -3
  9. bbot/core/helpers/command.py +9 -8
  10. bbot/core/helpers/depsinstaller/installer.py +89 -32
  11. bbot/core/helpers/depsinstaller/sudo_askpass.py +38 -2
  12. bbot/core/helpers/diff.py +10 -10
  13. bbot/core/helpers/dns/brute.py +18 -14
  14. bbot/core/helpers/dns/dns.py +16 -15
  15. bbot/core/helpers/dns/engine.py +159 -132
  16. bbot/core/helpers/dns/helpers.py +2 -2
  17. bbot/core/helpers/dns/mock.py +26 -8
  18. bbot/core/helpers/files.py +1 -1
  19. bbot/core/helpers/helper.py +7 -4
  20. bbot/core/helpers/interactsh.py +3 -3
  21. bbot/core/helpers/libmagic.py +65 -0
  22. bbot/core/helpers/misc.py +65 -22
  23. bbot/core/helpers/names_generator.py +17 -3
  24. bbot/core/helpers/process.py +0 -20
  25. bbot/core/helpers/regex.py +1 -1
  26. bbot/core/helpers/regexes.py +12 -6
  27. bbot/core/helpers/validators.py +1 -2
  28. bbot/core/helpers/web/client.py +1 -1
  29. bbot/core/helpers/web/engine.py +18 -13
  30. bbot/core/helpers/web/web.py +25 -116
  31. bbot/core/helpers/wordcloud.py +5 -5
  32. bbot/core/modules.py +36 -27
  33. bbot/core/multiprocess.py +58 -0
  34. bbot/core/shared_deps.py +46 -3
  35. bbot/db/sql/models.py +147 -0
  36. bbot/defaults.yml +15 -10
  37. bbot/errors.py +0 -8
  38. bbot/modules/anubisdb.py +2 -2
  39. bbot/modules/apkpure.py +63 -0
  40. bbot/modules/azure_tenant.py +2 -2
  41. bbot/modules/baddns.py +35 -19
  42. bbot/modules/baddns_direct.py +92 -0
  43. bbot/modules/baddns_zone.py +3 -8
  44. bbot/modules/badsecrets.py +4 -3
  45. bbot/modules/base.py +195 -51
  46. bbot/modules/bevigil.py +7 -7
  47. bbot/modules/binaryedge.py +7 -4
  48. bbot/modules/bufferoverrun.py +47 -0
  49. bbot/modules/builtwith.py +6 -10
  50. bbot/modules/bypass403.py +5 -5
  51. bbot/modules/c99.py +10 -7
  52. bbot/modules/censys.py +9 -13
  53. bbot/modules/certspotter.py +5 -3
  54. bbot/modules/chaos.py +9 -7
  55. bbot/modules/code_repository.py +1 -0
  56. bbot/modules/columbus.py +3 -3
  57. bbot/modules/crt.py +5 -3
  58. bbot/modules/deadly/dastardly.py +1 -1
  59. bbot/modules/deadly/ffuf.py +9 -9
  60. bbot/modules/deadly/nuclei.py +3 -3
  61. bbot/modules/deadly/vhost.py +4 -3
  62. bbot/modules/dehashed.py +1 -1
  63. bbot/modules/digitorus.py +1 -1
  64. bbot/modules/dnsbimi.py +145 -0
  65. bbot/modules/dnscaa.py +3 -3
  66. bbot/modules/dnsdumpster.py +4 -4
  67. bbot/modules/dnstlsrpt.py +144 -0
  68. bbot/modules/docker_pull.py +7 -5
  69. bbot/modules/dockerhub.py +2 -2
  70. bbot/modules/dotnetnuke.py +18 -19
  71. bbot/modules/emailformat.py +1 -1
  72. bbot/modules/extractous.py +122 -0
  73. bbot/modules/filedownload.py +9 -7
  74. bbot/modules/fullhunt.py +7 -4
  75. bbot/modules/generic_ssrf.py +5 -5
  76. bbot/modules/github_codesearch.py +3 -2
  77. bbot/modules/github_org.py +4 -4
  78. bbot/modules/github_workflows.py +4 -4
  79. bbot/modules/gitlab.py +2 -5
  80. bbot/modules/google_playstore.py +93 -0
  81. bbot/modules/gowitness.py +48 -50
  82. bbot/modules/hackertarget.py +5 -3
  83. bbot/modules/host_header.py +5 -5
  84. bbot/modules/httpx.py +1 -4
  85. bbot/modules/hunterio.py +3 -9
  86. bbot/modules/iis_shortnames.py +19 -30
  87. bbot/modules/internal/cloudcheck.py +27 -12
  88. bbot/modules/internal/dnsresolve.py +250 -276
  89. bbot/modules/internal/excavate.py +100 -64
  90. bbot/modules/internal/speculate.py +42 -33
  91. bbot/modules/internetdb.py +4 -2
  92. bbot/modules/ip2location.py +3 -5
  93. bbot/modules/ipneighbor.py +1 -1
  94. bbot/modules/ipstack.py +3 -8
  95. bbot/modules/jadx.py +87 -0
  96. bbot/modules/leakix.py +11 -10
  97. bbot/modules/myssl.py +2 -2
  98. bbot/modules/newsletters.py +2 -2
  99. bbot/modules/otx.py +5 -3
  100. bbot/modules/output/asset_inventory.py +7 -7
  101. bbot/modules/output/base.py +1 -1
  102. bbot/modules/output/csv.py +1 -2
  103. bbot/modules/output/http.py +20 -14
  104. bbot/modules/output/mysql.py +51 -0
  105. bbot/modules/output/neo4j.py +7 -2
  106. bbot/modules/output/postgres.py +49 -0
  107. bbot/modules/output/slack.py +0 -1
  108. bbot/modules/output/sqlite.py +29 -0
  109. bbot/modules/output/stdout.py +2 -2
  110. bbot/modules/output/teams.py +107 -6
  111. bbot/modules/paramminer_headers.py +5 -8
  112. bbot/modules/passivetotal.py +13 -13
  113. bbot/modules/portscan.py +32 -6
  114. bbot/modules/postman.py +50 -126
  115. bbot/modules/postman_download.py +220 -0
  116. bbot/modules/rapiddns.py +3 -8
  117. bbot/modules/report/asn.py +11 -11
  118. bbot/modules/robots.py +3 -3
  119. bbot/modules/securitytrails.py +7 -10
  120. bbot/modules/securitytxt.py +128 -0
  121. bbot/modules/shodan_dns.py +7 -9
  122. bbot/modules/sitedossier.py +1 -1
  123. bbot/modules/skymem.py +2 -2
  124. bbot/modules/social.py +2 -1
  125. bbot/modules/subdomaincenter.py +1 -1
  126. bbot/modules/subdomainradar.py +160 -0
  127. bbot/modules/telerik.py +8 -8
  128. bbot/modules/templates/bucket.py +1 -1
  129. bbot/modules/templates/github.py +22 -14
  130. bbot/modules/templates/postman.py +21 -0
  131. bbot/modules/templates/shodan.py +14 -13
  132. bbot/modules/templates/sql.py +95 -0
  133. bbot/modules/templates/subdomain_enum.py +53 -17
  134. bbot/modules/templates/webhook.py +2 -4
  135. bbot/modules/trickest.py +8 -37
  136. bbot/modules/trufflehog.py +18 -3
  137. bbot/modules/url_manipulation.py +3 -3
  138. bbot/modules/urlscan.py +1 -1
  139. bbot/modules/viewdns.py +1 -1
  140. bbot/modules/virustotal.py +8 -30
  141. bbot/modules/wafw00f.py +1 -1
  142. bbot/modules/wayback.py +1 -1
  143. bbot/modules/wpscan.py +17 -11
  144. bbot/modules/zoomeye.py +11 -6
  145. bbot/presets/baddns-thorough.yml +12 -0
  146. bbot/presets/fast.yml +16 -0
  147. bbot/presets/kitchen-sink.yml +1 -0
  148. bbot/presets/spider.yml +4 -0
  149. bbot/presets/subdomain-enum.yml +7 -7
  150. bbot/scanner/manager.py +5 -16
  151. bbot/scanner/preset/args.py +44 -26
  152. bbot/scanner/preset/environ.py +7 -2
  153. bbot/scanner/preset/path.py +7 -4
  154. bbot/scanner/preset/preset.py +36 -23
  155. bbot/scanner/scanner.py +176 -63
  156. bbot/scanner/target.py +236 -434
  157. bbot/scripts/docs.py +1 -1
  158. bbot/test/bbot_fixtures.py +22 -3
  159. bbot/test/conftest.py +132 -100
  160. bbot/test/fastapi_test.py +17 -0
  161. bbot/test/owasp_mastg.apk +0 -0
  162. bbot/test/run_tests.sh +4 -4
  163. bbot/test/test.conf +2 -0
  164. bbot/test/test_step_1/test_bbot_fastapi.py +82 -0
  165. bbot/test/test_step_1/test_bloom_filter.py +2 -0
  166. bbot/test/test_step_1/test_cli.py +138 -64
  167. bbot/test/test_step_1/test_dns.py +392 -70
  168. bbot/test/test_step_1/test_engine.py +17 -17
  169. bbot/test/test_step_1/test_events.py +203 -37
  170. bbot/test/test_step_1/test_helpers.py +64 -28
  171. bbot/test/test_step_1/test_manager_deduplication.py +1 -1
  172. bbot/test/test_step_1/test_manager_scope_accuracy.py +336 -338
  173. bbot/test/test_step_1/test_modules_basic.py +69 -71
  174. bbot/test/test_step_1/test_presets.py +184 -96
  175. bbot/test/test_step_1/test_python_api.py +7 -2
  176. bbot/test/test_step_1/test_regexes.py +35 -5
  177. bbot/test/test_step_1/test_scan.py +39 -5
  178. bbot/test/test_step_1/test_scope.py +5 -4
  179. bbot/test/test_step_1/test_target.py +243 -145
  180. bbot/test/test_step_1/test_web.py +48 -10
  181. bbot/test/test_step_2/module_tests/base.py +17 -20
  182. bbot/test/test_step_2/module_tests/test_module_anubisdb.py +1 -1
  183. bbot/test/test_step_2/module_tests/test_module_apkpure.py +71 -0
  184. bbot/test/test_step_2/module_tests/test_module_asset_inventory.py +0 -1
  185. bbot/test/test_step_2/module_tests/test_module_azure_realm.py +1 -1
  186. bbot/test/test_step_2/module_tests/test_module_baddns.py +6 -6
  187. bbot/test/test_step_2/module_tests/test_module_baddns_direct.py +62 -0
  188. bbot/test/test_step_2/module_tests/test_module_bevigil.py +29 -2
  189. bbot/test/test_step_2/module_tests/test_module_binaryedge.py +4 -2
  190. bbot/test/test_step_2/module_tests/test_module_bucket_amazon.py +2 -2
  191. bbot/test/test_step_2/module_tests/test_module_bucket_azure.py +1 -1
  192. bbot/test/test_step_2/module_tests/test_module_bufferoverrun.py +35 -0
  193. bbot/test/test_step_2/module_tests/test_module_builtwith.py +2 -2
  194. bbot/test/test_step_2/module_tests/test_module_bypass403.py +1 -1
  195. bbot/test/test_step_2/module_tests/test_module_c99.py +126 -0
  196. bbot/test/test_step_2/module_tests/test_module_censys.py +4 -1
  197. bbot/test/test_step_2/module_tests/test_module_cloudcheck.py +4 -0
  198. bbot/test/test_step_2/module_tests/test_module_code_repository.py +11 -1
  199. bbot/test/test_step_2/module_tests/test_module_columbus.py +1 -1
  200. bbot/test/test_step_2/module_tests/test_module_credshed.py +3 -3
  201. bbot/test/test_step_2/module_tests/test_module_dastardly.py +2 -1
  202. bbot/test/test_step_2/module_tests/test_module_dehashed.py +2 -2
  203. bbot/test/test_step_2/module_tests/test_module_digitorus.py +1 -1
  204. bbot/test/test_step_2/module_tests/test_module_discord.py +1 -1
  205. bbot/test/test_step_2/module_tests/test_module_dnsbimi.py +103 -0
  206. bbot/test/test_step_2/module_tests/test_module_dnsbrute.py +9 -10
  207. bbot/test/test_step_2/module_tests/test_module_dnsbrute_mutations.py +1 -2
  208. bbot/test/test_step_2/module_tests/test_module_dnscommonsrv.py +1 -2
  209. bbot/test/test_step_2/module_tests/test_module_dnsdumpster.py +4 -4
  210. bbot/test/test_step_2/module_tests/test_module_dnstlsrpt.py +64 -0
  211. bbot/test/test_step_2/module_tests/test_module_dotnetnuke.py +0 -8
  212. bbot/test/test_step_2/module_tests/test_module_excavate.py +17 -37
  213. bbot/test/test_step_2/module_tests/test_module_extractous.py +54 -0
  214. bbot/test/test_step_2/module_tests/test_module_ffuf_shortnames.py +1 -1
  215. bbot/test/test_step_2/module_tests/test_module_filedownload.py +14 -14
  216. bbot/test/test_step_2/module_tests/test_module_git_clone.py +2 -2
  217. bbot/test/test_step_2/module_tests/test_module_github_org.py +19 -8
  218. bbot/test/test_step_2/module_tests/test_module_github_workflows.py +1 -1
  219. bbot/test/test_step_2/module_tests/test_module_gitlab.py +9 -4
  220. bbot/test/test_step_2/module_tests/test_module_google_playstore.py +83 -0
  221. bbot/test/test_step_2/module_tests/test_module_gowitness.py +4 -4
  222. bbot/test/test_step_2/module_tests/test_module_host_header.py +1 -1
  223. bbot/test/test_step_2/module_tests/test_module_http.py +4 -4
  224. bbot/test/test_step_2/module_tests/test_module_httpx.py +10 -8
  225. bbot/test/test_step_2/module_tests/test_module_hunterio.py +68 -4
  226. bbot/test/test_step_2/module_tests/test_module_jadx.py +55 -0
  227. bbot/test/test_step_2/module_tests/test_module_json.py +24 -11
  228. bbot/test/test_step_2/module_tests/test_module_leakix.py +7 -3
  229. bbot/test/test_step_2/module_tests/test_module_mysql.py +76 -0
  230. bbot/test/test_step_2/module_tests/test_module_myssl.py +1 -1
  231. bbot/test/test_step_2/module_tests/test_module_neo4j.py +1 -1
  232. bbot/test/test_step_2/module_tests/test_module_newsletters.py +6 -6
  233. bbot/test/test_step_2/module_tests/test_module_ntlm.py +7 -7
  234. bbot/test/test_step_2/module_tests/test_module_oauth.py +1 -1
  235. bbot/test/test_step_2/module_tests/test_module_otx.py +1 -1
  236. bbot/test/test_step_2/module_tests/test_module_paramminer_cookies.py +1 -2
  237. bbot/test/test_step_2/module_tests/test_module_paramminer_getparams.py +0 -6
  238. bbot/test/test_step_2/module_tests/test_module_paramminer_headers.py +2 -9
  239. bbot/test/test_step_2/module_tests/test_module_passivetotal.py +3 -1
  240. bbot/test/test_step_2/module_tests/test_module_portscan.py +9 -8
  241. bbot/test/test_step_2/module_tests/test_module_postgres.py +74 -0
  242. bbot/test/test_step_2/module_tests/test_module_postman.py +84 -253
  243. bbot/test/test_step_2/module_tests/test_module_postman_download.py +439 -0
  244. bbot/test/test_step_2/module_tests/test_module_rapiddns.py +93 -1
  245. bbot/test/test_step_2/module_tests/test_module_securitytxt.py +50 -0
  246. bbot/test/test_step_2/module_tests/test_module_shodan_dns.py +20 -1
  247. bbot/test/test_step_2/module_tests/test_module_sitedossier.py +2 -2
  248. bbot/test/test_step_2/module_tests/test_module_smuggler.py +1 -1
  249. bbot/test/test_step_2/module_tests/test_module_social.py +11 -1
  250. bbot/test/test_step_2/module_tests/test_module_speculate.py +2 -6
  251. bbot/test/test_step_2/module_tests/test_module_splunk.py +4 -4
  252. bbot/test/test_step_2/module_tests/test_module_sqlite.py +18 -0
  253. bbot/test/test_step_2/module_tests/test_module_sslcert.py +1 -1
  254. bbot/test/test_step_2/module_tests/test_module_stdout.py +5 -3
  255. bbot/test/test_step_2/module_tests/test_module_subdomaincenter.py +1 -1
  256. bbot/test/test_step_2/module_tests/test_module_subdomainradar.py +208 -0
  257. bbot/test/test_step_2/module_tests/test_module_subdomains.py +1 -1
  258. bbot/test/test_step_2/module_tests/test_module_teams.py +8 -6
  259. bbot/test/test_step_2/module_tests/test_module_telerik.py +1 -1
  260. bbot/test/test_step_2/module_tests/test_module_trufflehog.py +317 -11
  261. bbot/test/test_step_2/module_tests/test_module_wayback.py +1 -1
  262. bbot/test/test_step_2/template_tests/test_template_subdomain_enum.py +135 -0
  263. {bbot-2.0.1.4654rc0.dist-info → bbot-2.3.0.5397rc0.dist-info}/METADATA +48 -18
  264. bbot-2.3.0.5397rc0.dist-info/RECORD +421 -0
  265. {bbot-2.0.1.4654rc0.dist-info → bbot-2.3.0.5397rc0.dist-info}/WHEEL +1 -1
  266. bbot/modules/unstructured.py +0 -163
  267. bbot/test/test_step_2/module_tests/test_module_unstructured.py +0 -102
  268. bbot-2.0.1.4654rc0.dist-info/RECORD +0 -385
  269. {bbot-2.0.1.4654rc0.dist-info → bbot-2.3.0.5397rc0.dist-info}/LICENSE +0 -0
  270. {bbot-2.0.1.4654rc0.dist-info → bbot-2.3.0.5397rc0.dist-info}/entry_points.txt +0 -0
@@ -6,6 +6,7 @@ import regex as re
6
6
  from pathlib import Path
7
7
  from bbot.errors import ExcavateError
8
8
  import bbot.core.helpers.regexes as bbot_regexes
9
+ from bbot.modules.base import BaseInterceptModule
9
10
  from bbot.modules.internal.base import BaseInternalModule
10
11
  from urllib.parse import urlparse, urljoin, parse_qs, urlunparse
11
12
 
@@ -61,7 +62,6 @@ def _exclude_key(original_dict, key_to_exclude):
61
62
 
62
63
 
63
64
  def extract_params_url(parsed_url):
64
-
65
65
  params = parse_qs(parsed_url.query)
66
66
  flat_params = {k: v[0] for k, v in params.items()}
67
67
 
@@ -93,7 +93,6 @@ def extract_params_location(location_header_value, original_parsed_url):
93
93
 
94
94
 
95
95
  class YaraRuleSettings:
96
-
97
96
  def __init__(self, description, tags, emit_match):
98
97
  self.description = description
99
98
  self.tags = tags
@@ -153,7 +152,9 @@ class ExcavateRule:
153
152
  yara_rule_settings = YaraRuleSettings(description, tags, emit_match)
154
153
  yara_results = {}
155
154
  for h in r.strings:
156
- yara_results[h.identifier.lstrip("$")] = sorted(set([i.matched_data.decode("utf-8") for i in h.instances]))
155
+ yara_results[h.identifier.lstrip("$")] = sorted(
156
+ {i.matched_data.decode("utf-8", errors="ignore") for i in h.instances}
157
+ )
157
158
  await self.process(yara_results, event, yara_rule_settings, discovery_context)
158
159
 
159
160
  async def process(self, yara_results, event, yara_rule_settings, discovery_context):
@@ -179,7 +180,7 @@ class ExcavateRule:
179
180
  Returns:
180
181
  None
181
182
  """
182
- for identifier, results in yara_results.items():
183
+ for results in yara_results.values():
183
184
  for result in results:
184
185
  event_data = {"description": f"{discovery_context} {yara_rule_settings.description}"}
185
186
  if yara_rule_settings.emit_match:
@@ -260,7 +261,6 @@ class ExcavateRule:
260
261
 
261
262
 
262
263
  class CustomExtractor(ExcavateRule):
263
-
264
264
  def __init__(self, excavate):
265
265
  super().__init__(excavate)
266
266
 
@@ -279,7 +279,7 @@ class CustomExtractor(ExcavateRule):
279
279
  await self.report(event_data, event, yara_rule_settings, discovery_context)
280
280
 
281
281
 
282
- class excavate(BaseInternalModule):
282
+ class excavate(BaseInternalModule, BaseInterceptModule):
283
283
  """
284
284
  Example (simple) Excavate Rules:
285
285
 
@@ -310,32 +310,32 @@ class excavate(BaseInternalModule):
310
310
  "custom_yara_rules": "Include custom Yara rules",
311
311
  }
312
312
  scope_distance_modifier = None
313
+ accept_dupes = False
313
314
 
314
315
  _module_threads = 8
315
316
 
316
- parameter_blacklist = [
317
- "__VIEWSTATE",
318
- "__EVENTARGUMENT",
319
- "__EVENTVALIDATION",
320
- "__EVENTTARGET",
321
- "__EVENTARGUMENT",
322
- "__VIEWSTATEGENERATOR",
323
- "__SCROLLPOSITIONY",
324
- "__SCROLLPOSITIONX",
325
- "ASP.NET_SessionId",
326
- "JSESSIONID",
327
- "PHPSESSID",
328
- ]
317
+ parameter_blacklist = {
318
+ p.lower()
319
+ for p in [
320
+ "__VIEWSTATE",
321
+ "__EVENTARGUMENT",
322
+ "__EVENTVALIDATION",
323
+ "__EVENTTARGET",
324
+ "__EVENTARGUMENT",
325
+ "__VIEWSTATEGENERATOR",
326
+ "__SCROLLPOSITIONY",
327
+ "__SCROLLPOSITIONX",
328
+ "ASP.NET_SessionId",
329
+ "JSESSIONID",
330
+ "PHPSESSID",
331
+ ]
332
+ }
329
333
 
330
334
  yara_rule_name_regex = re.compile(r"rule\s(\w+)\s{")
331
335
  yara_rule_regex = re.compile(r"(?s)((?:rule\s+\w+\s*{[^{}]*(?:{[^{}]*}[^{}]*)*[^{}]*(?:/\S*?}[^/]*?/)*)*})")
332
336
 
333
337
  def in_bl(self, value):
334
- in_bl = False
335
- for bl_param in self.parameter_blacklist:
336
- if bl_param.lower() == value.lower():
337
- in_bl = True
338
- return in_bl
338
+ return value.lower() in self.parameter_blacklist
339
339
 
340
340
  def url_unparse(self, param_type, parsed_url):
341
341
  if param_type == "GETPARAM":
@@ -355,7 +355,6 @@ class excavate(BaseInternalModule):
355
355
  )
356
356
 
357
357
  class ParameterExtractor(ExcavateRule):
358
-
359
358
  yara_rules = {}
360
359
 
361
360
  class ParameterExtractorRule:
@@ -369,7 +368,6 @@ class excavate(BaseInternalModule):
369
368
  self.result = result
370
369
 
371
370
  class GetJquery(ParameterExtractorRule):
372
-
373
371
  name = "GET jquery"
374
372
  discovery_regex = r"/\$.get\([^\)].+\)/ nocase"
375
373
  extraction_regex = re.compile(r"\$.get\([\'\"](.+)[\'\"].+(\{.+\})\)")
@@ -390,8 +388,12 @@ class excavate(BaseInternalModule):
390
388
  for action, extracted_parameters in extracted_results:
391
389
  extracted_parameters_dict = self.convert_to_dict(extracted_parameters)
392
390
  for parameter_name, original_value in extracted_parameters_dict.items():
393
- yield self.output_type, parameter_name, original_value, action, _exclude_key(
394
- extracted_parameters_dict, parameter_name
391
+ yield (
392
+ self.output_type,
393
+ parameter_name,
394
+ original_value,
395
+ action,
396
+ _exclude_key(extracted_parameters_dict, parameter_name),
395
397
  )
396
398
 
397
399
  class PostJquery(GetJquery):
@@ -415,8 +417,12 @@ class excavate(BaseInternalModule):
415
417
  k: v[0] if isinstance(v, list) and len(v) == 1 else v for k, v in query_strings.items()
416
418
  }
417
419
  for parameter_name, original_value in query_strings_dict.items():
418
- yield self.output_type, parameter_name, original_value, url, _exclude_key(
419
- query_strings_dict, parameter_name
420
+ yield (
421
+ self.output_type,
422
+ parameter_name,
423
+ original_value,
424
+ url,
425
+ _exclude_key(query_strings_dict, parameter_name),
420
426
  )
421
427
 
422
428
  class GetForm(ParameterExtractorRule):
@@ -441,8 +447,12 @@ class excavate(BaseInternalModule):
441
447
  form_parameters[parameter_name] = original_value
442
448
 
443
449
  for parameter_name, original_value in form_parameters.items():
444
- yield self.output_type, parameter_name, original_value, form_action, _exclude_key(
445
- form_parameters, parameter_name
450
+ yield (
451
+ self.output_type,
452
+ parameter_name,
453
+ original_value,
454
+ form_action,
455
+ _exclude_key(form_parameters, parameter_name),
446
456
  )
447
457
 
448
458
  class PostForm(GetForm):
@@ -482,7 +492,6 @@ class excavate(BaseInternalModule):
482
492
  endpoint,
483
493
  additional_params,
484
494
  ) in extracted_params:
485
-
486
495
  self.excavate.debug(
487
496
  f"Found Parameter [{parameter_name}] in [{parameterExtractorSubModule.name}] ParameterExtractor Submodule"
488
497
  )
@@ -494,7 +503,6 @@ class excavate(BaseInternalModule):
494
503
  )
495
504
 
496
505
  if self.excavate.helpers.validate_parameter(parameter_name, parameter_type):
497
-
498
506
  if self.excavate.in_bl(parameter_name) == False:
499
507
  parsed_url = urlparse(url)
500
508
  description = f"HTTP Extracted Parameter [{parameter_name}] ({parameterExtractorSubModule.name} Submodule)"
@@ -524,13 +532,11 @@ class excavate(BaseInternalModule):
524
532
  async def process(self, yara_results, event, yara_rule_settings, discovery_context):
525
533
  for identifier in yara_results.keys():
526
534
  for csp_str in yara_results[identifier]:
527
- domains = await self.helpers.re.findall(bbot_regexes.dns_name_regex, csp_str)
528
- unique_domains = set(domains)
529
- for domain in unique_domains:
535
+ domains = await self.excavate.scan.extract_in_scope_hostnames(csp_str)
536
+ for domain in domains:
530
537
  await self.report(domain, event, yara_rule_settings, discovery_context, event_type="DNS_NAME")
531
538
 
532
539
  class EmailExtractor(ExcavateRule):
533
-
534
540
  yara_rules = {
535
541
  "email": 'rule email { meta: description = "contains email address" strings: $email = /[^\\W_][\\w\\-\\.\\+\']{0,100}@[a-zA-Z0-9\\-]{1,100}(\\.[a-zA-Z0-9\\-]{1,100})*\\.[a-zA-Z]{2,63}/ nocase fullword condition: $email }',
536
542
  }
@@ -549,7 +555,6 @@ class excavate(BaseInternalModule):
549
555
  }
550
556
 
551
557
  class ErrorExtractor(ExcavateRule):
552
-
553
558
  signatures = {
554
559
  "PHP_1": r"/\.php on line [0-9]+/",
555
560
  "PHP_2": r"/\.php<\/b> on line <b>[0-9]+/",
@@ -587,7 +592,6 @@ class excavate(BaseInternalModule):
587
592
  await self.report(event_data, event, yara_rule_settings, discovery_context, event_type="FINDING")
588
593
 
589
594
  class SerializationExtractor(ExcavateRule):
590
-
591
595
  regexes = {
592
596
  "Java": re.compile(r"[^a-zA-Z0-9\/+]rO0[a-zA-Z0-9+\/]+={0,2}"),
593
597
  "DOTNET": re.compile(r"[^a-zA-Z0-9\/+]AAEAAAD\/\/[a-zA-Z0-9\/+]+={0,2}"),
@@ -617,7 +621,6 @@ class excavate(BaseInternalModule):
617
621
  await self.report(event_data, event, yara_rule_settings, discovery_context, event_type="FINDING")
618
622
 
619
623
  class FunctionalityExtractor(ExcavateRule):
620
-
621
624
  yara_rules = {
622
625
  "File_Upload_Functionality": r'rule File_Upload_Functionality { meta: description = "contains file upload functionality" strings: $fileuploadfunc = /<input[^>]+type=["\']?file["\']?[^>]+>/ nocase condition: $fileuploadfunc }',
623
626
  "Web_Service_WSDL": r'rule Web_Service_WSDL { meta: emit_match = "True" description = "contains a web service WSDL URL" strings: $wsdl = /https?:\/\/[^\s]*\.(wsdl)/ nocase condition: $wsdl }',
@@ -631,7 +634,7 @@ class excavate(BaseInternalModule):
631
634
  scheme_blacklist = ["javascript", "mailto", "tel", "data", "vbscript", "about", "file"]
632
635
 
633
636
  async def process(self, yara_results, event, yara_rule_settings, discovery_context):
634
- for identifier, results in yara_results.items():
637
+ for results in yara_results.values():
635
638
  for url_str in results:
636
639
  scheme = url_str.split("://")[0]
637
640
  if scheme in self.scheme_blacklist:
@@ -670,15 +673,38 @@ class excavate(BaseInternalModule):
670
673
 
671
674
  class URLExtractor(ExcavateRule):
672
675
  yara_rules = {
673
- "url_full": r'rule url_full { meta: tags = "spider-danger" description = "contains full URL" strings: $url_full = /https?:\/\/([\w\.-]+)([:\/\w\.-]*)/ condition: $url_full }',
674
- "url_attr": r'rule url_attr { meta: tags = "spider-danger" description = "contains tag with src or href attribute" strings: $url_attr = /<[^>]+(href|src)=["\'][^"\']*["\'][^>]*>/ condition: $url_attr }',
676
+ "url_full": (
677
+ r"""
678
+ rule url_full {
679
+ meta:
680
+ tags = "spider-danger"
681
+ description = "contains full URL"
682
+ strings:
683
+ $url_full = /https?:\/\/([\w\.-]+)(:\d{1,5})?([\/\w\.-]*)/
684
+ condition:
685
+ $url_full
686
+ }
687
+ """
688
+ ),
689
+ "url_attr": (
690
+ r"""
691
+ rule url_attr {
692
+ meta:
693
+ tags = "spider-danger"
694
+ description = "contains tag with src or href attribute"
695
+ strings:
696
+ $url_attr = /<[^>]+(href|src)=["\'][^"\']*["\'][^>]*>/
697
+ condition:
698
+ $url_attr
699
+ }
700
+ """
701
+ ),
675
702
  }
676
703
  full_url_regex = re.compile(r"(https?)://((?:\w|\d)(?:[\d\w-]+\.?)+(?::\d{1,5})?(?:/[-\w\.\(\)]*[-\w\.]+)*/?)")
677
704
  full_url_regex_strict = re.compile(r"^(https?):\/\/([\w.-]+)(?::\d{1,5})?(\/[\w\/\.-]*)?(\?[^\s]+)?$")
678
705
  tag_attribute_regex = bbot_regexes.tag_attribute_regex
679
706
 
680
707
  async def process(self, yara_results, event, yara_rule_settings, discovery_context):
681
-
682
708
  for identifier, results in yara_results.items():
683
709
  urls_found = 0
684
710
  final_url = ""
@@ -742,20 +768,33 @@ class excavate(BaseInternalModule):
742
768
 
743
769
  def __init__(self, excavate):
744
770
  super().__init__(excavate)
745
- regexes_component_list = []
746
- if excavate.scan.dns_regexes_yara:
747
- for i, r in enumerate(excavate.scan.dns_regexes_yara):
748
- regexes_component_list.append(rf"$dns_name_{i} = /\b{r.pattern}/ nocase")
749
- regexes_component = " ".join(regexes_component_list)
750
- self.yara_rules[f"hostname_extraction"] = (
751
- f'rule hostname_extraction {{meta: description = "matches DNS hostname pattern derived from target(s)" strings: {regexes_component} condition: any of them}}'
752
- )
771
+ if excavate.scan.dns_yara_rules_uncompiled:
772
+ self.yara_rules[f"hostname_extraction"] = excavate.scan.dns_yara_rules_uncompiled
753
773
 
754
774
  async def process(self, yara_results, event, yara_rule_settings, discovery_context):
755
775
  for identifier in yara_results.keys():
756
776
  for domain_str in yara_results[identifier]:
757
777
  await self.report(domain_str, event, yara_rule_settings, discovery_context, event_type="DNS_NAME")
758
778
 
779
+ class LoginPageExtractor(ExcavateRule):
780
+ yara_rules = {
781
+ "login_page": r"""
782
+ rule login_page {
783
+ meta:
784
+ description = "Detects login pages with username and password fields"
785
+ strings:
786
+ $username_field = /<input[^>]+name=["']?(user|login|email)/ nocase
787
+ $password_field = /<input[^>]+name=["']?passw?/ nocase
788
+ condition:
789
+ $username_field and $password_field
790
+ }
791
+ """
792
+ }
793
+
794
+ async def process(self, yara_results, event, yara_rule_settings, discovery_context):
795
+ if yara_results:
796
+ event.add_tag("login-page")
797
+
759
798
  def add_yara_rule(self, rule_name, rule_content, rule_instance):
760
799
  rule_instance.name = rule_name
761
800
  self.yara_rules_dict[rule_name] = rule_content
@@ -806,9 +845,9 @@ class excavate(BaseInternalModule):
806
845
  if Path(self.custom_yara_rules).is_file():
807
846
  with open(self.custom_yara_rules) as f:
808
847
  rules_content = f.read()
809
- self.debug(f"Successfully loaded secrets file [{self.custom_yara_rules}]")
848
+ self.debug(f"Successfully loaded custom yara rules file [{self.custom_yara_rules}]")
810
849
  else:
811
- self.debug(f"Custom secrets is NOT a file. Will attempt to treat it as rule content")
850
+ self.debug(f"Custom yara rules file is NOT a file. Will attempt to treat it as rule content")
812
851
  rules_content = self.custom_yara_rules
813
852
 
814
853
  self.debug(f"Final combined yara rule contents: {rules_content}")
@@ -817,13 +856,11 @@ class excavate(BaseInternalModule):
817
856
  try:
818
857
  yara.compile(source=rule_content)
819
858
  except yara.SyntaxError as e:
820
- self.hugewarning(f"Custom Yara rule failed to compile: {e}")
821
- return False
859
+ return False, f"Custom Yara rule failed to compile: {e}"
822
860
 
823
861
  rule_match = await self.helpers.re.search(self.yara_rule_name_regex, rule_content)
824
862
  if not rule_match:
825
- self.hugewarning(f"Custom Yara formatted incorrectly: could not find rule name")
826
- return False
863
+ return False, f"Custom Yara formatted incorrectly: could not find rule name"
827
864
 
828
865
  rule_name = rule_match.groups(1)[0]
829
866
  c = CustomExtractor(self)
@@ -837,11 +874,13 @@ class excavate(BaseInternalModule):
837
874
  yara.set_config(max_match_data=yara_max_match_data)
838
875
  yara_rules_combined = "\n".join(self.yara_rules_dict.values())
839
876
  try:
877
+ self.info(f"Compiling {len(self.yara_rules_dict):,} YARA rules")
878
+ for rule_name, rule_content in self.yara_rules_dict.items():
879
+ self.debug(f" - {rule_name}")
840
880
  self.yara_rules = yara.compile(source=yara_rules_combined)
841
881
  except yara.SyntaxError as e:
842
- self.hugewarning(f"Yara Rules failed to compile with error: [{e}]")
843
882
  self.debug(yara_rules_combined)
844
- return False
883
+ return False, f"Yara Rules failed to compile with error: [{e}]"
845
884
 
846
885
  # pre-load valid URL schemes
847
886
  valid_schemes_filename = self.helpers.wordlist_dir / "valid_url_schemes.txt"
@@ -858,7 +897,6 @@ class excavate(BaseInternalModule):
858
897
  decoded_data = await self.helpers.re.recursive_decode(data)
859
898
 
860
899
  if self.parameter_extraction:
861
-
862
900
  content_type_lower = content_type.lower() if content_type else ""
863
901
  extraction_map = {
864
902
  "json": self.helpers.extract_params_json,
@@ -895,7 +933,6 @@ class excavate(BaseInternalModule):
895
933
  self.hugewarning(f"YARA Rule {rule_name} not found in pre-compiled rules")
896
934
 
897
935
  async def handle_event(self, event):
898
-
899
936
  if event.type == "HTTP_RESPONSE":
900
937
  # Harvest GET parameters from URL, if it came directly from the target, and parameter extraction is enabled
901
938
  if (
@@ -984,7 +1021,6 @@ class excavate(BaseInternalModule):
984
1021
 
985
1022
  # Try to extract parameters from the redirect URL
986
1023
  if self.parameter_extraction:
987
-
988
1024
  for (
989
1025
  method,
990
1026
  parsed_url,
@@ -32,10 +32,11 @@ class speculate(BaseInternalModule):
32
32
  "author": "@liquidsec",
33
33
  }
34
34
 
35
- options = {"max_hosts": 65536, "ports": "80,443"}
35
+ options = {"max_hosts": 65536, "ports": "80,443", "essential_only": False}
36
36
  options_desc = {
37
37
  "max_hosts": "Max number of IP_RANGE hosts to convert into IP_ADDRESS events",
38
38
  "ports": "The set of ports to speculate on",
39
+ "essential_only": "Only enable essential speculate features (no extra discovery)",
39
40
  }
40
41
  scope_distance_modifier = 1
41
42
  _priority = 4
@@ -44,14 +45,15 @@ class speculate(BaseInternalModule):
44
45
 
45
46
  async def setup(self):
46
47
  scan_modules = [m for m in self.scan.modules.values() if m._type == "scan"]
47
- self.open_port_consumers = any(["OPEN_TCP_PORT" in m.watched_events for m in scan_modules])
48
+ self.open_port_consumers = any("OPEN_TCP_PORT" in m.watched_events for m in scan_modules)
48
49
  # only consider active portscanners (still speculate if only passive ones are enabled)
49
50
  self.portscanner_enabled = any(
50
- ["portscan" in m.flags and "active" in m.flags for m in self.scan.modules.values()]
51
+ "portscan" in m.flags and "active" in m.flags for m in self.scan.modules.values()
51
52
  )
52
53
  self.emit_open_ports = self.open_port_consumers and not self.portscanner_enabled
53
54
  self.range_to_ip = True
54
55
  self.dns_disable = self.scan.config.get("dns", {}).get("disable", False)
56
+ self.essential_only = self.config.get("essential_only", False)
55
57
  self.org_stubs_seen = set()
56
58
 
57
59
  port_string = self.config.get("ports", "80,443")
@@ -63,18 +65,26 @@ class speculate(BaseInternalModule):
63
65
  if not self.portscanner_enabled:
64
66
  self.info(f"No portscanner enabled. Assuming open ports: {', '.join(str(x) for x in self.ports)}")
65
67
 
66
- target_len = len(self.scan.target)
68
+ target_len = len(self.scan.target.seeds)
67
69
  if target_len > self.config.get("max_hosts", 65536):
68
70
  if not self.portscanner_enabled:
69
71
  self.hugewarning(
70
72
  f"Selected target ({target_len:,} hosts) is too large, skipping IP_RANGE --> IP_ADDRESS speculation"
71
73
  )
72
- self.hugewarning(f'Enabling the "portscan" module is highly recommended')
74
+ self.hugewarning('Enabling the "portscan" module is highly recommended')
73
75
  self.range_to_ip = False
74
76
 
75
77
  return True
76
78
 
77
79
  async def handle_event(self, event):
80
+ ### BEGIN ESSENTIAL SPECULATION ###
81
+ # These features are required for smooth operation of bbot
82
+ # I.e. they are not "osinty" or intended to discover anything, they only compliment other modules
83
+
84
+ # we speculate on distance-1 stuff too, because distance-1 open ports are needed by certain modules like sslcert
85
+ event_in_scope_distance = event.scope_distance <= (self.scan.scope_search_distance + 1)
86
+ speculate_open_ports = self.emit_open_ports and event_in_scope_distance
87
+
78
88
  # generate individual IP addresses from IP range
79
89
  if event.type == "IP_RANGE" and self.range_to_ip:
80
90
  net = ipaddress.ip_network(event.data)
@@ -89,28 +99,46 @@ class speculate(BaseInternalModule):
89
99
  context=f"speculate converted range into individual IP_ADDRESS: {ip}",
90
100
  )
91
101
 
102
+ # IP_ADDRESS / DNS_NAME --> OPEN_TCP_PORT
103
+ if speculate_open_ports:
104
+ # don't act on unresolved DNS_NAMEs
105
+ usable_dns = False
106
+ if event.type == "DNS_NAME":
107
+ if self.dns_disable or ("a-record" in event.tags or "aaaa-record" in event.tags):
108
+ usable_dns = True
109
+
110
+ if event.type == "IP_ADDRESS" or usable_dns:
111
+ for port in self.ports:
112
+ await self.emit_event(
113
+ self.helpers.make_netloc(event.data, port),
114
+ "OPEN_TCP_PORT",
115
+ parent=event,
116
+ internal=True,
117
+ context="speculated {event.type}: {event.data}",
118
+ )
119
+
120
+ ### END ESSENTIAL SPECULATION ###
121
+ if self.essential_only:
122
+ return
123
+
92
124
  # parent domains
93
125
  if event.type.startswith("DNS_NAME"):
94
- parent = self.helpers.parent_domain(event.data)
126
+ parent = self.helpers.parent_domain(event.host_original)
95
127
  if parent != event.data:
96
128
  await self.emit_event(
97
- parent, "DNS_NAME", parent=event, context=f"speculated parent {{event.type}}: {{event.data}}"
129
+ parent, "DNS_NAME", parent=event, context="speculated parent {event.type}: {event.data}"
98
130
  )
99
131
 
100
- # we speculate on distance-1 stuff too, because distance-1 open ports are needed by certain modules like sslcert
101
- event_in_scope_distance = event.scope_distance <= (self.scan.scope_search_distance + 1)
102
- speculate_open_ports = self.emit_open_ports and event_in_scope_distance
103
-
104
132
  # URL --> OPEN_TCP_PORT
105
- if event.type == "URL" or (event.type == "URL_UNVERIFIED" and self.open_port_consumers):
133
+ event_is_url = event.type == "URL"
134
+ if event_is_url or (event.type == "URL_UNVERIFIED" and self.open_port_consumers):
106
135
  # only speculate port from a URL if it wouldn't be speculated naturally from the host
107
136
  if event.host and (event.port not in self.ports or not speculate_open_ports):
108
137
  await self.emit_event(
109
138
  self.helpers.make_netloc(event.host, event.port),
110
139
  "OPEN_TCP_PORT",
111
140
  parent=event,
112
- internal=True,
113
- quick=(event.type == "URL"),
141
+ internal=not event_is_url, # if the URL is verified, the port is definitely open
114
142
  context=f"speculated {{event.type}} from {event.type}: {{event.data}}",
115
143
  )
116
144
 
@@ -144,25 +172,6 @@ class speculate(BaseInternalModule):
144
172
  context="speculated {event.type}: {event.data}",
145
173
  )
146
174
 
147
- # IP_ADDRESS / DNS_NAME --> OPEN_TCP_PORT
148
- if speculate_open_ports:
149
- # don't act on unresolved DNS_NAMEs
150
- usable_dns = False
151
- if event.type == "DNS_NAME":
152
- if self.dns_disable or ("a-record" in event.tags or "aaaa-record" in event.tags):
153
- usable_dns = True
154
-
155
- if event.type == "IP_ADDRESS" or usable_dns:
156
- for port in self.ports:
157
- await self.emit_event(
158
- self.helpers.make_netloc(event.data, port),
159
- "OPEN_TCP_PORT",
160
- parent=event,
161
- internal=True,
162
- quick=True,
163
- context="speculated {event.type}: {event.data}",
164
- )
165
-
166
175
  # ORG_STUB from TLD, SOCIAL, AZURE_TENANT
167
176
  org_stubs = set()
168
177
  if event.type == "DNS_NAME" and event.scope_distance == 0:
@@ -48,6 +48,9 @@ class internetdb(BaseModule):
48
48
  "show_open_ports": "Display OPEN_TCP_PORT events in output, even if they didn't lead to an interesting discovery"
49
49
  }
50
50
 
51
+ # we get lots of 404s, that's normal
52
+ _api_failure_abort_threshold = 9999999999
53
+
51
54
  _qsize = 500
52
55
 
53
56
  base_url = "https://internetdb.shodan.io"
@@ -64,7 +67,7 @@ class internetdb(BaseModule):
64
67
  if ip is None:
65
68
  return
66
69
  url = f"{self.base_url}/{ip}"
67
- r = await self.request_with_fail_count(url)
70
+ r = await self.api_request(url)
68
71
  if r is None:
69
72
  self.debug(f"No response for {event.data}")
70
73
  return
@@ -113,7 +116,6 @@ class internetdb(BaseModule):
113
116
  "OPEN_TCP_PORT",
114
117
  parent=event,
115
118
  internal=(not self.show_open_ports),
116
- quick=True,
117
119
  context=f'{{module}} queried Shodan\'s InternetDB API for "{query_host}" and found {{event.type}}: {{event.data}}',
118
120
  )
119
121
  vulns = data.get("vulns", [])
@@ -32,12 +32,10 @@ class IP2Location(BaseModule):
32
32
 
33
33
  async def ping(self):
34
34
  url = self.build_url("8.8.8.8")
35
- r = await self.request_with_fail_count(url)
36
- resp_content = getattr(r, "text", "")
37
- assert getattr(r, "status_code", 0) == 200, resp_content
35
+ await super().ping(url)
38
36
 
39
37
  def build_url(self, data):
40
- url = f"{self.base_url}/?key={self.api_key}&ip={data}&format=json&source=bbot"
38
+ url = f"{self.base_url}/?key={{api_key}}&ip={data}&format=json&source=bbot"
41
39
  if self.lang:
42
40
  url = f"{url}&lang={self.lang}"
43
41
  return url
@@ -45,7 +43,7 @@ class IP2Location(BaseModule):
45
43
  async def handle_event(self, event):
46
44
  try:
47
45
  url = self.build_url(event.data)
48
- result = await self.request_with_fail_count(url)
46
+ result = await self.api_request(url)
49
47
  if result:
50
48
  geo_data = result.json()
51
49
  if not geo_data:
@@ -31,7 +31,7 @@ class ipneighbor(BaseModule):
31
31
  netmask = main_ip.max_prefixlen - min(main_ip.max_prefixlen, self.num_bits)
32
32
  network = ipaddress.ip_network(f"{main_ip}/{netmask}", strict=False)
33
33
  subnet_hash = hash(network)
34
- if not subnet_hash in self.processed:
34
+ if subnet_hash not in self.processed:
35
35
  self.processed.add(subnet_hash)
36
36
  for ip in network:
37
37
  if ip != main_ip:
bbot/modules/ipstack.py CHANGED
@@ -23,20 +23,15 @@ class Ipstack(BaseModule):
23
23
  suppress_dupes = False
24
24
 
25
25
  base_url = "http://api.ipstack.com"
26
+ ping_url = f"{base_url}/check?access_key={{api_key}}"
26
27
 
27
28
  async def setup(self):
28
29
  return await self.require_api_key()
29
30
 
30
- async def ping(self):
31
- url = f"{self.base_url}/check?access_key={self.api_key}"
32
- r = await self.request_with_fail_count(url)
33
- resp_content = getattr(r, "text", "")
34
- assert getattr(r, "status_code", 0) == 200, resp_content
35
-
36
31
  async def handle_event(self, event):
37
32
  try:
38
- url = f"{self.base_url}/{event.data}?access_key={self.api_key}"
39
- result = await self.request_with_fail_count(url)
33
+ url = f"{self.base_url}/{event.data}?access_key={{api_key}}"
34
+ result = await self.api_request(url)
40
35
  if result:
41
36
  geo_data = result.json()
42
37
  if not geo_data: