bbot 2.0.1.4720rc0__py3-none-any.whl → 2.3.0.5401rc0__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 (278) 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 -4
  6. bbot/core/engine.py +9 -8
  7. bbot/core/event/base.py +131 -52
  8. bbot/core/helpers/bloom.py +10 -3
  9. bbot/core/helpers/command.py +8 -7
  10. bbot/core/helpers/depsinstaller/installer.py +31 -13
  11. bbot/core/helpers/diff.py +10 -10
  12. bbot/core/helpers/dns/brute.py +7 -4
  13. bbot/core/helpers/dns/dns.py +1 -2
  14. bbot/core/helpers/dns/engine.py +4 -6
  15. bbot/core/helpers/dns/helpers.py +2 -2
  16. bbot/core/helpers/dns/mock.py +0 -1
  17. bbot/core/helpers/files.py +1 -1
  18. bbot/core/helpers/helper.py +7 -4
  19. bbot/core/helpers/interactsh.py +3 -3
  20. bbot/core/helpers/libmagic.py +65 -0
  21. bbot/core/helpers/misc.py +65 -22
  22. bbot/core/helpers/names_generator.py +17 -3
  23. bbot/core/helpers/process.py +0 -20
  24. bbot/core/helpers/regex.py +1 -1
  25. bbot/core/helpers/regexes.py +12 -6
  26. bbot/core/helpers/validators.py +1 -2
  27. bbot/core/helpers/web/client.py +1 -1
  28. bbot/core/helpers/web/engine.py +1 -2
  29. bbot/core/helpers/web/web.py +4 -114
  30. bbot/core/helpers/wordcloud.py +5 -5
  31. bbot/core/modules.py +36 -27
  32. bbot/core/multiprocess.py +58 -0
  33. bbot/core/shared_deps.py +46 -3
  34. bbot/db/sql/models.py +147 -0
  35. bbot/defaults.yml +12 -10
  36. bbot/modules/anubisdb.py +2 -2
  37. bbot/modules/apkpure.py +63 -0
  38. bbot/modules/azure_tenant.py +2 -2
  39. bbot/modules/baddns.py +35 -19
  40. bbot/modules/baddns_direct.py +92 -0
  41. bbot/modules/baddns_zone.py +3 -8
  42. bbot/modules/badsecrets.py +4 -3
  43. bbot/modules/base.py +195 -51
  44. bbot/modules/bevigil.py +7 -7
  45. bbot/modules/binaryedge.py +7 -4
  46. bbot/modules/bufferoverrun.py +47 -0
  47. bbot/modules/builtwith.py +6 -10
  48. bbot/modules/bypass403.py +5 -5
  49. bbot/modules/c99.py +10 -7
  50. bbot/modules/censys.py +9 -13
  51. bbot/modules/certspotter.py +5 -3
  52. bbot/modules/chaos.py +9 -7
  53. bbot/modules/code_repository.py +1 -0
  54. bbot/modules/columbus.py +3 -3
  55. bbot/modules/crt.py +5 -3
  56. bbot/modules/deadly/dastardly.py +1 -1
  57. bbot/modules/deadly/ffuf.py +9 -9
  58. bbot/modules/deadly/nuclei.py +3 -3
  59. bbot/modules/deadly/vhost.py +4 -3
  60. bbot/modules/dehashed.py +1 -1
  61. bbot/modules/digitorus.py +1 -1
  62. bbot/modules/dnsbimi.py +145 -0
  63. bbot/modules/dnscaa.py +3 -3
  64. bbot/modules/dnsdumpster.py +4 -4
  65. bbot/modules/dnstlsrpt.py +144 -0
  66. bbot/modules/docker_pull.py +7 -5
  67. bbot/modules/dockerhub.py +2 -2
  68. bbot/modules/dotnetnuke.py +20 -21
  69. bbot/modules/emailformat.py +1 -1
  70. bbot/modules/extractous.py +122 -0
  71. bbot/modules/filedownload.py +9 -7
  72. bbot/modules/fullhunt.py +7 -4
  73. bbot/modules/generic_ssrf.py +5 -5
  74. bbot/modules/github_codesearch.py +3 -2
  75. bbot/modules/github_org.py +4 -4
  76. bbot/modules/github_workflows.py +4 -4
  77. bbot/modules/gitlab.py +2 -5
  78. bbot/modules/google_playstore.py +93 -0
  79. bbot/modules/gowitness.py +48 -50
  80. bbot/modules/hackertarget.py +5 -3
  81. bbot/modules/host_header.py +5 -5
  82. bbot/modules/httpx.py +1 -4
  83. bbot/modules/hunterio.py +3 -9
  84. bbot/modules/iis_shortnames.py +19 -30
  85. bbot/modules/internal/cloudcheck.py +29 -12
  86. bbot/modules/internal/dnsresolve.py +22 -22
  87. bbot/modules/internal/excavate.py +97 -59
  88. bbot/modules/internal/speculate.py +41 -32
  89. bbot/modules/internetdb.py +4 -2
  90. bbot/modules/ip2location.py +3 -5
  91. bbot/modules/ipneighbor.py +1 -1
  92. bbot/modules/ipstack.py +3 -8
  93. bbot/modules/jadx.py +87 -0
  94. bbot/modules/leakix.py +11 -10
  95. bbot/modules/myssl.py +2 -2
  96. bbot/modules/newsletters.py +2 -2
  97. bbot/modules/otx.py +5 -3
  98. bbot/modules/output/asset_inventory.py +7 -7
  99. bbot/modules/output/base.py +1 -1
  100. bbot/modules/output/csv.py +1 -1
  101. bbot/modules/output/http.py +20 -14
  102. bbot/modules/output/mysql.py +51 -0
  103. bbot/modules/output/neo4j.py +7 -2
  104. bbot/modules/output/postgres.py +49 -0
  105. bbot/modules/output/slack.py +0 -1
  106. bbot/modules/output/sqlite.py +29 -0
  107. bbot/modules/output/stdout.py +2 -2
  108. bbot/modules/output/teams.py +107 -6
  109. bbot/modules/paramminer_headers.py +8 -11
  110. bbot/modules/passivetotal.py +13 -13
  111. bbot/modules/portscan.py +32 -6
  112. bbot/modules/postman.py +50 -126
  113. bbot/modules/postman_download.py +220 -0
  114. bbot/modules/rapiddns.py +3 -8
  115. bbot/modules/report/asn.py +18 -11
  116. bbot/modules/robots.py +3 -3
  117. bbot/modules/securitytrails.py +7 -10
  118. bbot/modules/securitytxt.py +1 -1
  119. bbot/modules/shodan_dns.py +7 -9
  120. bbot/modules/sitedossier.py +1 -1
  121. bbot/modules/skymem.py +2 -2
  122. bbot/modules/social.py +2 -1
  123. bbot/modules/subdomaincenter.py +1 -1
  124. bbot/modules/subdomainradar.py +160 -0
  125. bbot/modules/telerik.py +8 -8
  126. bbot/modules/templates/bucket.py +1 -1
  127. bbot/modules/templates/github.py +22 -14
  128. bbot/modules/templates/postman.py +21 -0
  129. bbot/modules/templates/shodan.py +14 -13
  130. bbot/modules/templates/sql.py +95 -0
  131. bbot/modules/templates/subdomain_enum.py +51 -16
  132. bbot/modules/templates/webhook.py +2 -4
  133. bbot/modules/trickest.py +8 -37
  134. bbot/modules/trufflehog.py +10 -12
  135. bbot/modules/url_manipulation.py +3 -3
  136. bbot/modules/urlscan.py +1 -1
  137. bbot/modules/viewdns.py +1 -1
  138. bbot/modules/virustotal.py +8 -30
  139. bbot/modules/wafw00f.py +1 -1
  140. bbot/modules/wayback.py +1 -1
  141. bbot/modules/wpscan.py +17 -11
  142. bbot/modules/zoomeye.py +11 -6
  143. bbot/presets/baddns-thorough.yml +12 -0
  144. bbot/presets/fast.yml +16 -0
  145. bbot/presets/kitchen-sink.yml +1 -2
  146. bbot/presets/spider.yml +4 -0
  147. bbot/presets/subdomain-enum.yml +7 -7
  148. bbot/presets/web/dotnet-audit.yml +0 -1
  149. bbot/scanner/manager.py +5 -16
  150. bbot/scanner/preset/args.py +46 -26
  151. bbot/scanner/preset/environ.py +7 -2
  152. bbot/scanner/preset/path.py +7 -4
  153. bbot/scanner/preset/preset.py +36 -23
  154. bbot/scanner/scanner.py +172 -62
  155. bbot/scanner/target.py +236 -434
  156. bbot/scripts/docs.py +1 -1
  157. bbot/test/bbot_fixtures.py +13 -3
  158. bbot/test/conftest.py +132 -100
  159. bbot/test/fastapi_test.py +17 -0
  160. bbot/test/owasp_mastg.apk +0 -0
  161. bbot/test/run_tests.sh +4 -4
  162. bbot/test/test.conf +2 -0
  163. bbot/test/test_step_1/test__module__tests.py +0 -1
  164. bbot/test/test_step_1/test_bbot_fastapi.py +79 -0
  165. bbot/test/test_step_1/test_bloom_filter.py +2 -1
  166. bbot/test/test_step_1/test_cli.py +138 -64
  167. bbot/test/test_step_1/test_dns.py +61 -27
  168. bbot/test/test_step_1/test_engine.py +17 -19
  169. bbot/test/test_step_1/test_events.py +183 -30
  170. bbot/test/test_step_1/test_helpers.py +64 -29
  171. bbot/test/test_step_1/test_manager_deduplication.py +1 -1
  172. bbot/test/test_step_1/test_manager_scope_accuracy.py +333 -330
  173. bbot/test/test_step_1/test_modules_basic.py +68 -70
  174. bbot/test/test_step_1/test_presets.py +183 -100
  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 +4 -3
  179. bbot/test/test_step_1/test_target.py +242 -145
  180. bbot/test/test_step_1/test_web.py +14 -10
  181. bbot/test/test_step_2/module_tests/base.py +15 -7
  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 +28 -48
  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 -6
  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 +22 -9
  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 +16 -16
  233. bbot/test/test_step_2/module_tests/test_module_ntlm.py +8 -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_pgp.py +2 -2
  241. bbot/test/test_step_2/module_tests/test_module_portscan.py +9 -8
  242. bbot/test/test_step_2/module_tests/test_module_postgres.py +74 -0
  243. bbot/test/test_step_2/module_tests/test_module_postman.py +84 -253
  244. bbot/test/test_step_2/module_tests/test_module_postman_download.py +439 -0
  245. bbot/test/test_step_2/module_tests/test_module_rapiddns.py +93 -1
  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 +14 -14
  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 +4 -8
  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 -14
  261. bbot/test/test_step_2/module_tests/test_module_viewdns.py +1 -1
  262. bbot/test/test_step_2/module_tests/test_module_wayback.py +1 -1
  263. bbot/test/test_step_2/template_tests/test_template_subdomain_enum.py +2 -2
  264. bbot/wordlists/devops_mutations.txt +1 -1
  265. bbot/wordlists/ffuf_shortname_candidates.txt +1 -1
  266. bbot/wordlists/nameservers.txt +1 -1
  267. bbot/wordlists/paramminer_headers.txt +1 -1
  268. bbot/wordlists/paramminer_parameters.txt +1 -1
  269. bbot/wordlists/raft-small-extensions-lowercase_CLEANED.txt +1 -1
  270. bbot/wordlists/valid_url_schemes.txt +1 -1
  271. {bbot-2.0.1.4720rc0.dist-info → bbot-2.3.0.5401rc0.dist-info}/METADATA +48 -18
  272. bbot-2.3.0.5401rc0.dist-info/RECORD +421 -0
  273. {bbot-2.0.1.4720rc0.dist-info → bbot-2.3.0.5401rc0.dist-info}/WHEEL +1 -1
  274. bbot/modules/unstructured.py +0 -163
  275. bbot/test/test_step_2/module_tests/test_module_unstructured.py +0 -102
  276. bbot-2.0.1.4720rc0.dist-info/RECORD +0 -387
  277. {bbot-2.0.1.4720rc0.dist-info → bbot-2.3.0.5401rc0.dist-info}/LICENSE +0 -0
  278. {bbot-2.0.1.4720rc0.dist-info → bbot-2.3.0.5401rc0.dist-info}/entry_points.txt +0 -0
@@ -17,7 +17,7 @@ from bbot.core.helpers.misc import make_table, mkdir, get_closest_match
17
17
  log = logging.getLogger("bbot.presets")
18
18
 
19
19
 
20
- _preset_cache = dict()
20
+ _preset_cache = {}
21
21
 
22
22
 
23
23
  # cache default presets to prevent having to reload from disk
@@ -47,7 +47,6 @@ class Preset:
47
47
  target (Target): Target(s) of scan.
48
48
  whitelist (Target): Scan whitelist (by default this is the same as `target`).
49
49
  blacklist (Target): Scan blacklist (this takes ultimate precedence).
50
- strict_scope (bool): If True, subdomains of targets are not considered to be in-scope.
51
50
  helpers (ConfigAwareHelper): Helper containing various reusable functions, regexes, etc.
52
51
  output_dir (pathlib.Path): Output directory for scan.
53
52
  scan_name (str): Name of scan. Defaults to random value, e.g. "demonic_jimmy".
@@ -87,7 +86,6 @@ class Preset:
87
86
  *targets,
88
87
  whitelist=None,
89
88
  blacklist=None,
90
- strict_scope=False,
91
89
  modules=None,
92
90
  output_modules=None,
93
91
  exclude_modules=None,
@@ -117,7 +115,6 @@ class Preset:
117
115
  *targets (str): Target(s) to scan. Types supported: hostnames, IPs, CIDRs, emails, open ports.
118
116
  whitelist (list, optional): Whitelisted target(s) to scan. Defaults to the same as `targets`.
119
117
  blacklist (list, optional): Blacklisted target(s). Takes ultimate precedence. Defaults to empty.
120
- strict_scope (bool, optional): If True, subdomains of targets are not in-scope.
121
118
  modules (list[str], optional): List of scan modules to enable for the scan. Defaults to empty list.
122
119
  output_modules (list[str], optional): List of output modules to use. Defaults to csv, human, and json.
123
120
  exclude_modules (list[str], optional): List of modules to exclude from the scan.
@@ -234,7 +231,6 @@ class Preset:
234
231
  self.module_dirs = module_dirs
235
232
 
236
233
  # target / whitelist / blacklist
237
- self.strict_scope = strict_scope
238
234
  # these are temporary receptacles until they all get .baked() together
239
235
  self._seeds = set(targets if targets else [])
240
236
  self._whitelist = set(whitelist) if whitelist else whitelist
@@ -245,7 +241,7 @@ class Preset:
245
241
  # "presets" is alias to "include"
246
242
  if presets and include:
247
243
  raise ValueError(
248
- 'Cannot use both "presets" and "include" args at the same time (presets is only an alias to include). Please pick only one :)'
244
+ 'Cannot use both "presets" and "include" args at the same time (presets is an alias to include). Please pick one or the other :)'
249
245
  )
250
246
  if presets and not include:
251
247
  include = presets
@@ -274,6 +270,12 @@ class Preset:
274
270
  raise ValueError("Cannot access target before preset is baked (use ._seeds instead)")
275
271
  return self._target
276
272
 
273
+ @property
274
+ def seeds(self):
275
+ if self._seeds is None:
276
+ raise ValueError("Cannot access target before preset is baked (use ._seeds instead)")
277
+ return self.target.seeds
278
+
277
279
  @property
278
280
  def whitelist(self):
279
281
  if self._target is None:
@@ -353,7 +355,9 @@ class Preset:
353
355
  else:
354
356
  self._whitelist.update(other._whitelist)
355
357
  self._blacklist.update(other._blacklist)
356
- self.strict_scope = self.strict_scope or other.strict_scope
358
+
359
+ # module dirs
360
+ self.module_dirs = self.module_dirs.union(other.module_dirs)
357
361
 
358
362
  # log verbosity
359
363
  if other.silent:
@@ -373,6 +377,9 @@ class Preset:
373
377
  # misc
374
378
  self.force_start = self.force_start | other.force_start
375
379
  self._cli = self._cli | other._cli
380
+ # transfer args
381
+ if other._args is not None:
382
+ self._args = other._args
376
383
 
377
384
  def bake(self, scan=None):
378
385
  """
@@ -436,7 +443,7 @@ class Preset:
436
443
 
437
444
  # disable internal modules if requested
438
445
  for internal_module in baked_preset.internal_modules:
439
- if baked_preset.config.get(internal_module, True) == False:
446
+ if baked_preset.config.get(internal_module, True) is False:
440
447
  baked_preset.exclude_modules.add(internal_module)
441
448
 
442
449
  # enable modules by flag
@@ -531,6 +538,14 @@ class Preset:
531
538
  def web_config(self):
532
539
  return self.core.config.get("web", {})
533
540
 
541
+ @property
542
+ def scope_config(self):
543
+ return self.config.get("scope", {})
544
+
545
+ @property
546
+ def strict_scope(self):
547
+ return self.scope_config.get("strict", False)
548
+
534
549
  def apply_log_level(self, apply_core=False):
535
550
  # silent takes precedence
536
551
  if self.silent:
@@ -629,7 +644,6 @@ class Preset:
629
644
  debug=preset_dict.get("debug", False),
630
645
  silent=preset_dict.get("silent", False),
631
646
  config=preset_dict.get("config"),
632
- strict_scope=preset_dict.get("strict_scope", False),
633
647
  module_dirs=preset_dict.get("module_dirs", []),
634
648
  include=list(preset_dict.get("include", [])),
635
649
  scan_name=preset_dict.get("scan_name"),
@@ -731,6 +745,9 @@ class Preset:
731
745
  """
732
746
  preset_dict = {}
733
747
 
748
+ if self.description:
749
+ preset_dict["description"] = self.description
750
+
734
751
  # config
735
752
  if full_config:
736
753
  config = self.core.config
@@ -744,19 +761,17 @@ class Preset:
744
761
 
745
762
  # scope
746
763
  if include_target:
747
- target = sorted(str(t.data) for t in self.target.seeds)
764
+ target = sorted(self.target.seeds.inputs)
748
765
  whitelist = []
749
766
  if self.target.whitelist is not None:
750
- whitelist = sorted(str(t.data) for t in self.target.whitelist)
751
- blacklist = sorted(str(t.data) for t in self.target.blacklist)
767
+ whitelist = sorted(self.target.whitelist.inputs)
768
+ blacklist = sorted(self.target.blacklist.inputs)
752
769
  if target:
753
770
  preset_dict["target"] = target
754
771
  if whitelist and whitelist != target:
755
772
  preset_dict["whitelist"] = whitelist
756
773
  if blacklist:
757
774
  preset_dict["blacklist"] = blacklist
758
- if self.strict_scope:
759
- preset_dict["strict_scope"] = True
760
775
 
761
776
  # flags + modules
762
777
  if self.require_flags:
@@ -825,7 +840,7 @@ class Preset:
825
840
  else:
826
841
  raise ValidationError(f'Unknown module type "{module}"')
827
842
 
828
- if not module in module_choices:
843
+ if module not in module_choices:
829
844
  raise ValidationError(get_closest_match(module, module_choices, msg=f"{module_type} module"))
830
845
 
831
846
  try:
@@ -838,8 +853,6 @@ class Preset:
838
853
 
839
854
  if module in self.exclude_modules:
840
855
  reason = "the module has been excluded"
841
- if raise_error:
842
- raise ValidationError(f'Unable to add {module_type} module "{module}" because {reason}')
843
856
  return False, reason, {}
844
857
 
845
858
  module_flags = preloaded.get("flags", [])
@@ -870,21 +883,21 @@ class Preset:
870
883
 
871
884
  # validate excluded modules
872
885
  for excluded_module in self.exclude_modules:
873
- if not excluded_module in self.module_loader.all_module_choices:
886
+ if excluded_module not in self.module_loader.all_module_choices:
874
887
  raise ValidationError(
875
888
  get_closest_match(excluded_module, self.module_loader.all_module_choices, msg="module")
876
889
  )
877
890
  # validate excluded flags
878
891
  for excluded_flag in self.exclude_flags:
879
- if not excluded_flag in self.module_loader.flag_choices:
892
+ if excluded_flag not in self.module_loader.flag_choices:
880
893
  raise ValidationError(get_closest_match(excluded_flag, self.module_loader.flag_choices, msg="flag"))
881
894
  # validate required flags
882
895
  for required_flag in self.require_flags:
883
- if not required_flag in self.module_loader.flag_choices:
896
+ if required_flag not in self.module_loader.flag_choices:
884
897
  raise ValidationError(get_closest_match(required_flag, self.module_loader.flag_choices, msg="flag"))
885
898
  # validate flags
886
899
  for flag in self.flags:
887
- if not flag in self.module_loader.flag_choices:
900
+ if flag not in self.module_loader.flag_choices:
888
901
  raise ValidationError(get_closest_match(flag, self.module_loader.flag_choices, msg="flag"))
889
902
 
890
903
  @property
@@ -903,7 +916,7 @@ class Preset:
903
916
 
904
917
  global DEFAULT_PRESETS
905
918
  if DEFAULT_PRESETS is None:
906
- presets = dict()
919
+ presets = {}
907
920
  for ext in ("yml", "yaml"):
908
921
  for preset_path in PRESET_PATH:
909
922
  # for every yaml file
@@ -954,7 +967,7 @@ class Preset:
954
967
  header = ["Preset", "Category", "Description", "# Modules"]
955
968
  if include_modules:
956
969
  header.append("Modules")
957
- for yaml_file, (loaded_preset, category, preset_path, original_file) in self.all_presets.items():
970
+ for loaded_preset, category, preset_path, original_file in self.all_presets.values():
958
971
  loaded_preset = loaded_preset.bake()
959
972
  num_modules = f"{len(loaded_preset.scan_modules):,}"
960
973
  row = [loaded_preset.name, category, loaded_preset.description, num_modules]
bbot/scanner/scanner.py CHANGED
@@ -10,11 +10,11 @@ from datetime import datetime
10
10
  from collections import OrderedDict
11
11
 
12
12
  from bbot import __version__
13
-
14
13
  from bbot.core.event import make_event
15
14
  from .manager import ScanIngress, ScanEgress
16
15
  from bbot.core.helpers.misc import sha1, rand_string
17
16
  from bbot.core.helpers.names_generator import random_name
17
+ from bbot.core.multiprocess import SHARED_INTERPRETER_STATE
18
18
  from bbot.core.helpers.async_helpers import async_to_sync_gen
19
19
  from bbot.errors import BBOTError, ScanError, ValidationError
20
20
 
@@ -115,25 +115,37 @@ class Scanner:
115
115
  dispatcher (Dispatcher, optional): Dispatcher object to use. Defaults to new Dispatcher.
116
116
  **kwargs (list[str], optional): Additional keyword arguments (passed through to `Preset`).
117
117
  """
118
+ self._root_event = None
119
+ self._finish_event = None
120
+ self.start_time = None
121
+ self.end_time = None
122
+ self.duration = None
123
+ self.duration_human = None
124
+ self.duration_seconds = None
125
+
126
+ self._success = False
127
+
118
128
  if scan_id is not None:
119
- self.id = str(id)
129
+ self.id = str(scan_id)
120
130
  else:
121
131
  self.id = f"SCAN:{sha1(rand_string(20)).hexdigest()}"
122
132
 
123
- preset = kwargs.pop("preset", None)
133
+ custom_preset = kwargs.pop("preset", None)
124
134
  kwargs["_log"] = True
125
135
 
126
136
  from .preset import Preset
127
137
 
128
- if preset is None:
129
- preset = Preset(*targets, **kwargs)
130
- else:
131
- if not isinstance(preset, Preset):
132
- raise ValidationError(f'Preset must be of type Preset, not "{type(preset).__name__}"')
133
- self.preset = preset.bake(self)
138
+ base_preset = Preset(*targets, **kwargs)
139
+
140
+ if custom_preset is not None:
141
+ if not isinstance(custom_preset, Preset):
142
+ raise ValidationError(f'Preset must be of type Preset, not "{type(custom_preset).__name__}"')
143
+ base_preset.merge(custom_preset)
144
+
145
+ self.preset = base_preset.bake(self)
134
146
 
135
147
  # scan name
136
- if preset.scan_name is None:
148
+ if self.preset.scan_name is None:
137
149
  tries = 0
138
150
  while 1:
139
151
  if tries > 5:
@@ -148,12 +160,16 @@ class Scanner:
148
160
  break
149
161
  tries += 1
150
162
  else:
151
- scan_name = str(preset.scan_name)
152
- self.name = scan_name
163
+ scan_name = str(self.preset.scan_name)
164
+ self.name = scan_name.replace("/", "_")
165
+
166
+ # make sure the preset has a description
167
+ if not self.preset.description:
168
+ self.preset.description = self.name
153
169
 
154
170
  # scan output dir
155
- if preset.output_dir is not None:
156
- self.home = Path(preset.output_dir).resolve() / self.name
171
+ if self.preset.output_dir is not None:
172
+ self.home = Path(self.preset.output_dir).resolve() / self.name
157
173
  else:
158
174
  self.home = self.preset.bbot_home / "scans" / self.name
159
175
 
@@ -198,8 +214,8 @@ class Scanner:
198
214
  )
199
215
 
200
216
  # url file extensions
201
- self.url_extension_blacklist = set(e.lower() for e in self.config.get("url_extension_blacklist", []))
202
- self.url_extension_httpx_only = set(e.lower() for e in self.config.get("url_extension_httpx_only", []))
217
+ self.url_extension_blacklist = {e.lower() for e in self.config.get("url_extension_blacklist", [])}
218
+ self.url_extension_httpx_only = {e.lower() for e in self.config.get("url_extension_httpx_only", [])}
203
219
 
204
220
  # url querystring behavior
205
221
  self.url_querystring_remove = self.config.get("url_querystring_remove", True)
@@ -232,6 +248,8 @@ class Scanner:
232
248
  self._dns_strings = None
233
249
  self._dns_regexes = None
234
250
  self._dns_regexes_yara = None
251
+ self._dns_yara_rules_uncompiled = None
252
+ self._dns_yara_rules = None
235
253
 
236
254
  self.__log_handlers = None
237
255
  self._log_handler_backup = []
@@ -241,6 +259,9 @@ class Scanner:
241
259
  Creates the scan's output folder, loads its modules, and calls their .setup() methods.
242
260
  """
243
261
 
262
+ # update the master PID
263
+ SHARED_INTERPRETER_STATE.update_scan_pid()
264
+
244
265
  self.helpers.mkdir(self.home)
245
266
  if not self._prepped:
246
267
  # save scan preset
@@ -248,7 +269,7 @@ class Scanner:
248
269
  f.write(self.preset.to_yaml())
249
270
 
250
271
  # log scan overview
251
- start_msg = f"Scan with {len(self.preset.scan_modules):,} modules seeded with {len(self.target):,} targets"
272
+ start_msg = f"Scan seeded with {len(self.seeds):,} targets"
252
273
  details = []
253
274
  if self.whitelist != self.target:
254
275
  details.append(f"{len(self.whitelist):,} in whitelist")
@@ -272,7 +293,9 @@ class Scanner:
272
293
  self.debug(
273
294
  f"Setting intercept module {intercept_module.name}._incoming_event_queue to previous intercept module {prev_intercept_module.name}.outgoing_event_queue"
274
295
  )
275
- intercept_module._incoming_event_queue = prev_intercept_module.outgoing_event_queue
296
+ interqueue = asyncio.Queue()
297
+ intercept_module._incoming_event_queue = interqueue
298
+ prev_intercept_module._outgoing_event_queue = interqueue
276
299
 
277
300
  # abort if there are no output modules
278
301
  num_output_modules = len([m for m in self.modules.values() if m._type == "output"])
@@ -304,18 +327,18 @@ class Scanner:
304
327
 
305
328
  async def async_start(self):
306
329
  """ """
307
- failed = True
308
- scan_start_time = datetime.now()
330
+ self.start_time = datetime.now()
331
+ self.root_event.data["started_at"] = self.start_time.isoformat()
309
332
  try:
310
333
  await self._prep()
311
334
 
312
335
  self._start_log_handlers()
313
- self.trace(f'Ran BBOT {__version__} at {scan_start_time}, command: {" ".join(sys.argv)}')
336
+ self.trace(f'Ran BBOT {__version__} at {self.start_time}, command: {" ".join(sys.argv)}')
314
337
  self.trace(f"Target: {self.preset.target.json}")
315
338
  self.trace(f"Preset: {self.preset.to_dict(redact_secrets=True)}")
316
339
 
317
340
  if not self.target:
318
- self.warning(f"No scan targets specified")
341
+ self.warning("No scan targets specified")
319
342
 
320
343
  # start status ticker
321
344
  self.ticker_task = asyncio.create_task(
@@ -325,7 +348,7 @@ class Scanner:
325
348
  self.status = "STARTING"
326
349
 
327
350
  if not self.modules:
328
- self.error(f"No modules loaded")
351
+ self.error("No modules loaded")
329
352
  self.status = "FAILED"
330
353
  return
331
354
  else:
@@ -339,7 +362,8 @@ class Scanner:
339
362
 
340
363
  # distribute seed events
341
364
  self.init_events_task = asyncio.create_task(
342
- self.ingress_module.init_events(self.target.events), name=f"{self.name}.ingress_module.init_events()"
365
+ self.ingress_module.init_events(self.target.seeds.events),
366
+ name=f"{self.name}.ingress_module.init_events()",
343
367
  )
344
368
 
345
369
  # main scan loop
@@ -361,16 +385,19 @@ class Scanner:
361
385
  if self._finished_init and self.modules_finished:
362
386
  new_activity = await self.finish()
363
387
  if not new_activity:
388
+ self._success = True
389
+ scan_finish_event = await self._mark_finished()
390
+ yield scan_finish_event
364
391
  break
365
392
 
366
393
  await asyncio.sleep(0.1)
367
394
 
368
- failed = False
395
+ self._success = True
369
396
 
370
397
  except BaseException as e:
371
398
  if self.helpers.in_exception_chain(e, (KeyboardInterrupt, asyncio.CancelledError)):
372
399
  self.stop()
373
- failed = False
400
+ self._success = True
374
401
  else:
375
402
  try:
376
403
  raise
@@ -394,26 +421,47 @@ class Scanner:
394
421
  await self._report()
395
422
  await self._cleanup()
396
423
 
397
- log_fn = self.hugesuccess
398
- if self.status == "ABORTING":
399
- self.status = "ABORTED"
400
- log_fn = self.hugewarning
401
- elif failed:
402
- self.status = "FAILED"
403
- log_fn = self.critical
404
- else:
405
- self.status = "FINISHED"
406
-
407
- scan_run_time = datetime.now() - scan_start_time
408
- scan_run_time = self.helpers.human_timedelta(scan_run_time)
409
- log_fn(f"Scan {self.name} completed in {scan_run_time} with status {self.status}")
410
-
411
424
  await self.dispatcher.on_finish(self)
412
425
 
413
426
  self._stop_log_handlers()
414
427
 
428
+ async def _mark_finished(self):
429
+ log_fn = self.hugesuccess
430
+ if self.status == "ABORTING":
431
+ status = "ABORTED"
432
+ log_fn = self.hugewarning
433
+ elif not self._success:
434
+ status = "FAILED"
435
+ log_fn = self.critical
436
+ else:
437
+ status = "FINISHED"
438
+
439
+ self.end_time = datetime.now()
440
+ self.duration = self.end_time - self.start_time
441
+ self.duration_seconds = self.duration.total_seconds()
442
+ self.duration_human = self.helpers.human_timedelta(self.duration)
443
+
444
+ status_message = f"Scan {self.name} completed in {self.duration_human} with status {status}"
445
+
446
+ scan_finish_event = self.finish_event(status_message, status)
447
+
448
+ # queue final scan event with output modules
449
+ output_modules = [m for m in self.modules.values() if m._type == "output" and m.name != "python"]
450
+ for m in output_modules:
451
+ await m.queue_event(scan_finish_event)
452
+ # wait until output modules are flushed
453
+ while 1:
454
+ modules_finished = all(m.finished for m in output_modules)
455
+ if modules_finished:
456
+ break
457
+ await asyncio.sleep(0.05)
458
+
459
+ self.status = status
460
+ log_fn(status_message)
461
+ return scan_finish_event
462
+
415
463
  def _start_modules(self):
416
- self.verbose(f"Starting module worker loops")
464
+ self.verbose("Starting module worker loops")
417
465
  for module in self.modules.values():
418
466
  module.start()
419
467
 
@@ -437,17 +485,17 @@ class Scanner:
437
485
  Soft-failed modules are not set to an error state but are also removed if `remove_failed` is True.
438
486
  """
439
487
  await self.load_modules()
440
- self.verbose(f"Setting up modules")
488
+ self.verbose("Setting up modules")
441
489
  succeeded = []
442
490
  hard_failed = []
443
491
  soft_failed = []
444
492
 
445
493
  async for task in self.helpers.as_completed([m._setup() for m in self.modules.values()]):
446
494
  module, status, msg = await task
447
- if status == True:
495
+ if status is True:
448
496
  self.debug(f"Setup succeeded for {module.name} ({msg})")
449
497
  succeeded.append(module.name)
450
- elif status == False:
498
+ elif status is False:
451
499
  self.warning(f"Setup hard-failed for {module.name}: {msg}")
452
500
  self.modules[module.name].set_error_state()
453
501
  hard_failed.append(module.name)
@@ -489,11 +537,11 @@ class Scanner:
489
537
  """
490
538
  if not self._modules_loaded:
491
539
  if not self.preset.modules:
492
- self.warning(f"No modules to load")
540
+ self.warning("No modules to load")
493
541
  return
494
542
 
495
543
  if not self.preset.scan_modules:
496
- self.warning(f"No scan modules to load")
544
+ self.warning("No scan modules to load")
497
545
 
498
546
  # install module dependencies
499
547
  succeeded, failed = await self.helpers.depsinstaller.install(*self.preset.modules)
@@ -637,7 +685,7 @@ class Scanner:
637
685
 
638
686
  if modules_errored:
639
687
  self.verbose(
640
- f'{self.name}: Modules errored: {len(modules_errored):,} ({", ".join([m for m in modules_errored])})'
688
+ f'{self.name}: Modules errored: {len(modules_errored):,} ({", ".join(list(modules_errored))})'
641
689
  )
642
690
 
643
691
  num_queued_events = self.num_queued_events
@@ -674,7 +722,7 @@ class Scanner:
674
722
  memory_usage = module.memory_usage
675
723
  module_memory_usage.append((module.name, memory_usage))
676
724
  module_memory_usage.sort(key=lambda x: x[-1], reverse=True)
677
- self.debug(f"MODULE MEMORY USAGE:")
725
+ self.debug("MODULE MEMORY USAGE:")
678
726
  for module_name, usage in module_memory_usage:
679
727
  self.debug(f" - {module_name}: {self.helpers.bytes_to_human(usage)}")
680
728
 
@@ -721,12 +769,12 @@ class Scanner:
721
769
  # Trigger .finished() on every module and start over
722
770
  log.info("Finishing scan")
723
771
  for module in self.modules.values():
724
- finished_event = self.make_event(f"FINISHED", "FINISHED", dummy=True, tags={module.name})
772
+ finished_event = self.make_event("FINISHED", "FINISHED", dummy=True, tags={module.name})
725
773
  await module.queue_event(finished_event)
726
774
  self.verbose("Completed finish()")
727
775
  return True
728
- # Return False if no new events were generated since last time
729
776
  self.verbose("Completed final finish()")
777
+ # Return False if no new events were generated since last time
730
778
  return False
731
779
 
732
780
  def _drain_queues(self):
@@ -849,6 +897,10 @@ class Scanner:
849
897
  def target(self):
850
898
  return self.preset.target
851
899
 
900
+ @property
901
+ def seeds(self):
902
+ return self.preset.seeds
903
+
852
904
  @property
853
905
  def whitelist(self):
854
906
  return self.preset.whitelist
@@ -945,12 +997,25 @@ class Scanner:
945
997
  }
946
998
  ```
947
999
  """
948
- root_event = self.make_event(data=self.json, event_type="SCAN", dummy=True)
1000
+ if self._root_event is None:
1001
+ self._root_event = self.make_root_event(f"Scan {self.name} started at {self.start_time}")
1002
+ self._root_event.data["status"] = self.status
1003
+ return self._root_event
1004
+
1005
+ def finish_event(self, context=None, status=None):
1006
+ if self._finish_event is None:
1007
+ if context is None or status is None:
1008
+ raise ValueError("Must specify context and status")
1009
+ self._finish_event = self.make_root_event(context)
1010
+ self._finish_event.data["status"] = status
1011
+ return self._finish_event
1012
+
1013
+ def make_root_event(self, context):
1014
+ root_event = self.make_event(data=self.json, event_type="SCAN", dummy=True, context=context)
949
1015
  root_event._id = self.id
950
1016
  root_event.scope_distance = 0
951
1017
  root_event.parent = root_event
952
1018
  root_event.module = self._make_dummy_module(name="TARGET", _type="TARGET")
953
- root_event.discovery_context = f"Scan {self.name} started at {root_event.timestamp}"
954
1019
  return root_event
955
1020
 
956
1021
  @property
@@ -959,14 +1024,13 @@ class Scanner:
959
1024
  A list of DNS hostname strings generated from the scan target
960
1025
  """
961
1026
  if self._dns_strings is None:
962
- dns_targets = set(t.host for t in self.target if t.host and isinstance(t.host, str))
963
- dns_whitelist = set(t.host for t in self.whitelist if t.host and isinstance(t.host, str))
964
- dns_targets.update(dns_whitelist)
965
- dns_targets = sorted(dns_targets, key=len)
966
- dns_targets_set = set()
1027
+ dns_whitelist = {t.host for t in self.whitelist if t.host and isinstance(t.host, str)}
1028
+ dns_whitelist = sorted(dns_whitelist, key=len)
1029
+ dns_whitelist_set = set()
967
1030
  dns_strings = []
968
- for t in dns_targets:
969
- if not any(x in dns_targets_set for x in self.helpers.domain_parents(t, include_self=True)):
1031
+ for t in dns_whitelist:
1032
+ if not any(x in dns_whitelist_set for x in self.helpers.domain_parents(t, include_self=True)):
1033
+ dns_whitelist_set.add(t)
970
1034
  dns_strings.append(t)
971
1035
  self._dns_strings = dns_strings
972
1036
  return self._dns_strings
@@ -1011,21 +1075,67 @@ class Scanner:
1011
1075
  Returns a list of DNS hostname regexes formatted specifically for compatibility with YARA rules.
1012
1076
  """
1013
1077
  if self._dns_regexes_yara is None:
1014
- self._dns_regexes_yara = self._generate_dns_regexes(r"(([a-z0-9-]+\.)+")
1078
+ self._dns_regexes_yara = self._generate_dns_regexes(r"(([a-z0-9-]+\.)*")
1015
1079
  return self._dns_regexes_yara
1016
1080
 
1081
+ @property
1082
+ def dns_yara_rules_uncompiled(self):
1083
+ if self._dns_yara_rules_uncompiled is None:
1084
+ regexes_component_list = []
1085
+ for i, r in enumerate(self.dns_regexes_yara):
1086
+ regexes_component_list.append(rf"$dns_name_{i} = /\b{r.pattern}/ nocase")
1087
+ if regexes_component_list:
1088
+ regexes_component = " ".join(regexes_component_list)
1089
+ self._dns_yara_rules_uncompiled = f'rule hostname_extraction {{meta: description = "matches DNS hostname pattern derived from target(s)" strings: {regexes_component} condition: any of them}}'
1090
+ return self._dns_yara_rules_uncompiled
1091
+
1092
+ async def dns_yara_rules(self):
1093
+ if self._dns_yara_rules is None:
1094
+ if self.dns_yara_rules_uncompiled is not None:
1095
+ import yara
1096
+
1097
+ self._dns_yara_rules = await self.helpers.run_in_executor(
1098
+ yara.compile, source=self.dns_yara_rules_uncompiled
1099
+ )
1100
+ return self._dns_yara_rules
1101
+
1102
+ async def extract_in_scope_hostnames(self, s):
1103
+ """
1104
+ Given a string, uses yara to extract hostnames matching scan targets
1105
+
1106
+ Examples:
1107
+ >>> await self.scan.extract_in_scope_hostnames("http://www.evilcorp.com")
1108
+ ... {"www.evilcorp.com"}
1109
+ """
1110
+ matches = set()
1111
+ dns_yara_rules = await self.dns_yara_rules()
1112
+ if dns_yara_rules is not None:
1113
+ for match in await self.helpers.run_in_executor(dns_yara_rules.match, data=s):
1114
+ for string in match.strings:
1115
+ for instance in string.instances:
1116
+ matches.add(str(instance))
1117
+ return matches
1118
+
1017
1119
  @property
1018
1120
  def json(self):
1019
1121
  """
1020
1122
  A dictionary representation of the scan including its name, ID, targets, whitelist, blacklist, and modules
1021
1123
  """
1022
- j = dict()
1124
+ j = {}
1023
1125
  for i in ("id", "name"):
1024
1126
  v = getattr(self, i, "")
1025
1127
  if v:
1026
1128
  j.update({i: v})
1027
1129
  j["target"] = self.preset.target.json
1028
1130
  j["preset"] = self.preset.to_dict(redact_secrets=True)
1131
+ if self.start_time is not None:
1132
+ j["started_at"] = self.start_time.isoformat()
1133
+ if self.end_time is not None:
1134
+ j["finished_at"] = self.end_time.isoformat()
1135
+ if self.duration is not None:
1136
+ j["duration_seconds"] = self.duration_seconds
1137
+ if self.duration_human is not None:
1138
+ j["duration"] = self.duration_human
1029
1139
  return j
1030
1140
 
1031
1141
  def debug(self, *args, trace=False, **kwargs):
@@ -1181,7 +1291,7 @@ class Scanner:
1181
1291
  context = f"{context.__qualname__}()"
1182
1292
  filename, lineno, funcname = self.helpers.get_traceback_details(e)
1183
1293
  if self.helpers.in_exception_chain(e, (KeyboardInterrupt,)):
1184
- log.debug(f"Interrupted")
1294
+ log.debug("Interrupted")
1185
1295
  self.stop()
1186
1296
  elif isinstance(e, BrokenPipeError):
1187
1297
  log.debug(f"BrokenPipeError in {filename}:{lineno}:{funcname}(): {e}")