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
bbot/scanner/target.py CHANGED
@@ -1,112 +1,251 @@
1
- import re
2
- import copy
3
1
  import logging
4
- import ipaddress
5
- import traceback
2
+ import regex as re
6
3
  from hashlib import sha1
7
- from contextlib import suppress
8
4
  from radixtarget import RadixTarget
5
+ from radixtarget.helpers import host_size_key
9
6
 
10
7
  from bbot.errors import *
11
- from bbot.modules.base import BaseModule
12
- from bbot.core.helpers.misc import make_ip_type
13
8
  from bbot.core.event import make_event, is_event
9
+ from bbot.core.helpers.misc import is_dns_name, is_ip
10
+
14
11
 
15
12
  log = logging.getLogger("bbot.core.target")
16
13
 
17
14
 
18
- class BBOTTarget:
15
+ def special_target_type(regex_pattern):
16
+ def decorator(func):
17
+ func._regex = re.compile(regex_pattern, re.IGNORECASE)
18
+ return func
19
+
20
+ return decorator
21
+
22
+
23
+ class BaseTarget(RadixTarget):
19
24
  """
20
- A convenient abstraction of a scan target that includes whitelisting and blacklisting
25
+ A collection of BBOT events that represent a scan target.
21
26
 
22
- Provides high-level functions like in_scope(), which includes both whitelist and blacklist checks.
27
+ Based on radixtarget, which allows extremely fast IP and DNS lookups.
28
+
29
+ This class is inherited by all three components of the BBOT target:
30
+ - Whitelist
31
+ - Blacklist
32
+ - Seeds
23
33
  """
24
34
 
25
- def __init__(self, *targets, whitelist=None, blacklist=None, strict_scope=False, scan=None):
26
- self.strict_scope = strict_scope
35
+ special_target_types = {
36
+ # regex-callback pairs for handling special target types
37
+ # these aren't defined explicitly; instead they are decorated with @special_target_type
38
+ # the function must return a list of events
39
+ }
40
+ tags = []
41
+
42
+ def __init__(self, *targets, scan=None, **kwargs):
27
43
  self.scan = scan
28
- if len(targets) > 0:
29
- log.verbose(f"Creating events from {len(targets):,} targets")
30
- self.seeds = Target(*targets, strict_scope=self.strict_scope, scan=scan)
31
- if whitelist is None:
32
- whitelist = set([e.host for e in self.seeds if e.host])
44
+ self.events = set()
45
+ self.inputs = set()
46
+ # Register decorated methods
47
+ for method in dir(self):
48
+ if callable(getattr(self, method, None)):
49
+ func = getattr(self, method)
50
+ if hasattr(func, "_regex"):
51
+ self.special_target_types[func._regex] = func
52
+
53
+ super().__init__(*targets, **kwargs)
54
+
55
+ def get(self, event, **kwargs):
56
+ """
57
+ Override default .get() to accept events
58
+ """
59
+ if is_event(event):
60
+ host = event.host
61
+ # save resources by checking if the event is an IP or DNS name
62
+ elif is_ip(event, include_network=True) or is_dns_name(event):
63
+ host = event
64
+ elif isinstance(event, str):
65
+ event = self.make_event(event)
66
+ host = event.host
33
67
  else:
34
- log.verbose(f"Creating events from {len(whitelist):,} whitelist entries")
35
- self.whitelist = Target(*whitelist, strict_scope=self.strict_scope, scan=scan, acl_mode=True)
36
- if blacklist is None:
37
- blacklist = []
38
- if blacklist:
39
- log.verbose(f"Creating events from {len(blacklist):,} blacklist entries")
40
- self.blacklist = Target(*blacklist, scan=scan, acl_mode=True)
41
- self._hash = None
68
+ raise ValueError(f"Invalid host/event: {event} ({type(event)})")
69
+ if not host:
70
+ if kwargs.get("raise_error", False):
71
+ raise KeyError(f"Host not found: '{event}'")
72
+ return None
73
+ results = super().get(host, **kwargs)
74
+ return results
75
+
76
+ def make_event(self, *args, **kwargs):
77
+ # if it's already an event, return it
78
+ if args and is_event(args[0]):
79
+ return args[0]
80
+ # otherwise make a new one
81
+ if "tags" not in kwargs:
82
+ kwargs["tags"] = set()
83
+ kwargs["tags"].update(self.tags)
84
+ return make_event(*args, dummy=True, scan=self.scan, **kwargs)
85
+
86
+ def add(self, targets):
87
+ if not isinstance(targets, (list, set, tuple)):
88
+ targets = [targets]
89
+ events = set()
90
+ for target in targets:
91
+ _events = []
92
+ special_target_type, _events = self.check_special_target_types(str(target))
93
+ if special_target_type:
94
+ self.inputs.add(str(target))
95
+ else:
96
+ event = self.make_event(target)
97
+ if event:
98
+ _events = [event]
99
+ for event in _events:
100
+ self.inputs.add(event.data)
101
+ events.add(event)
102
+
103
+ # sort by host size to ensure consistency
104
+ events = sorted(events, key=lambda e: (0 if not e.host else host_size_key(e.host)))
105
+ for event in events:
106
+ self.events.add(event)
107
+ self._add(event.host, data=event)
108
+
109
+ def check_special_target_types(self, target):
110
+ for regex, callback in self.special_target_types.items():
111
+ match = regex.match(target)
112
+ if match:
113
+ return True, callback(match)
114
+ return False, []
115
+
116
+ def __iter__(self):
117
+ yield from self.events
42
118
 
43
- def add(self, *args, **kwargs):
44
- self.seeds.add(*args, **kwargs)
45
- self._hash = None
46
119
 
47
- def get(self, host):
48
- return self.seeds.get(host)
120
+ class ScanSeeds(BaseTarget):
121
+ """
122
+ Initial events used to seed a scan.
49
123
 
50
- def get_host(self, host):
51
- return self.seeds.get(host)
124
+ These are the targets specified by the user, e.g. via `-t` on the CLI.
125
+ """
52
126
 
53
- def __iter__(self):
54
- return iter(self.seeds)
127
+ tags = ["target"]
128
+
129
+ @special_target_type(r"^(?:ORG|ORG_STUB):(.*)")
130
+ def handle_org_stub(self, match):
131
+ org_stub_event = self.make_event(match.group(1), event_type="ORG_STUB")
132
+ if org_stub_event:
133
+ return [org_stub_event]
134
+ return []
135
+
136
+ @special_target_type(r"^(?:USER|USERNAME):(.*)")
137
+ def handle_username(self, match):
138
+ username_event = self.make_event(match.group(1), event_type="USERNAME")
139
+ if username_event:
140
+ return [username_event]
141
+ return []
142
+
143
+ def get(self, event, single=True, **kwargs):
144
+ results = super().get(event, **kwargs)
145
+ if results and single:
146
+ return next(iter(results))
147
+ return results
148
+
149
+ def _add(self, host, data):
150
+ """
151
+ Overrides the base method to enable having multiple events for the same host.
55
152
 
56
- def __len__(self):
57
- return len(self.seeds)
153
+ The "data" attribute of the node is now a set of events.
154
+ """
155
+ if host:
156
+ try:
157
+ event_set = self.get(host, raise_error=True, single=False)
158
+ event_set.add(data)
159
+ except KeyError:
160
+ event_set = {data}
161
+ super()._add(host, data=event_set)
58
162
 
59
- def __contains__(self, other):
60
- if isinstance(other, self.__class__):
61
- other = other.seeds
62
- return other in self.seeds
163
+ def _hash_value(self):
164
+ # seeds get hashed by event data
165
+ return sorted(str(e.data).encode() for e in self.events)
63
166
 
64
- def __bool__(self):
65
- return bool(self.seeds)
66
167
 
67
- def __eq__(self, other):
68
- return self.hash == other.hash
168
+ class ACLTarget(BaseTarget):
169
+ def __init__(self, *args, **kwargs):
170
+ # ACL mode dedupes by host (and skips adding already-contained hosts) for efficiency
171
+ kwargs["acl_mode"] = True
172
+ super().__init__(*args, **kwargs)
69
173
 
70
- @property
71
- def hash(self):
72
- """
73
- A sha1 hash representing a BBOT target and all three of its components (seeds, whitelist, blacklist)
74
174
 
75
- This can be used to compare targets.
175
+ class ScanWhitelist(ACLTarget):
176
+ """
177
+ A collection of BBOT events that represent a scan's whitelist.
178
+ """
76
179
 
77
- Examples:
78
- >>> target1 = BBOTTarget("evilcorp.com", blacklist=["prod.evilcorp.com"], whitelist=["test.evilcorp.com"])
79
- >>> target2 = BBOTTarget("evilcorp.com", blacklist=["prod.evilcorp.com"], whitelist=["test.evilcorp.com"])
80
- >>> target3 = BBOTTarget("evilcorp.com", blacklist=["prod.evilcorp.com"])
81
- >>> target1 == target2
82
- True
83
- >>> target1 == target3
84
- False
85
- """
86
- if self._hash is None:
87
- # Create a new SHA-1 hash object
88
- sha1_hash = sha1()
89
- # Update the SHA-1 object with the hash values of each object
90
- for target_hash in [t.hash for t in (self.seeds, self.whitelist, self.blacklist)]:
91
- # Convert the hash value to bytes and update the SHA-1 object
92
- sha1_hash.update(target_hash)
93
- self._hash = sha1_hash.digest()
94
- return self._hash
180
+ pass
95
181
 
96
- @property
97
- def scope_hash(self):
98
- """
99
- A sha1 hash representing only the whitelist and blacklist
100
182
 
101
- This is used to record the scope of a scan.
183
+ class ScanBlacklist(ACLTarget):
184
+ """
185
+ A collection of BBOT events that represent a scan's blacklist.
186
+ """
187
+
188
+ def __init__(self, *args, **kwargs):
189
+ self.blacklist_regexes = set()
190
+ super().__init__(*args, **kwargs)
191
+
192
+ @special_target_type(r"^(?:RE|REGEX):(.*)")
193
+ def handle_regex(self, match):
194
+ pattern = match.group(1)
195
+ log.info(f"Blacklisting by custom regex: {pattern}")
196
+ blacklist_regex = re.compile(pattern, re.IGNORECASE)
197
+ self.blacklist_regexes.add(blacklist_regex)
198
+ return []
199
+
200
+ def get(self, event, **kwargs):
102
201
  """
103
- # Create a new SHA-1 hash object
104
- sha1_hash = sha1()
105
- # Update the SHA-1 object with the hash values of each object
106
- for target_hash in [t.hash for t in (self.whitelist, self.blacklist)]:
107
- # Convert the hash value to bytes and update the SHA-1 object
108
- sha1_hash.update(target_hash)
109
- return sha1_hash.digest()
202
+ Here, for the blacklist, we modify this method to also consider any special regex patterns specified by the user
203
+ """
204
+ event = self.make_event(event)
205
+ # first, check event's host against blacklist
206
+ try:
207
+ event_result = super().get(event, raise_error=True)
208
+ except KeyError:
209
+ event_result = None
210
+ if event_result is not None:
211
+ return event_result
212
+ # next, check event's host against regexes
213
+ host_or_url = event.host_filterable
214
+ if host_or_url:
215
+ for regex in self.blacklist_regexes:
216
+ if regex.search(str(host_or_url)):
217
+ return event
218
+ if kwargs.get("raise_error", False):
219
+ raise KeyError(f"Host not found: '{event.data}'")
220
+ return None
221
+
222
+ def _hash_value(self):
223
+ # regexes are included in blacklist hash
224
+ regex_patterns = [str(r.pattern).encode() for r in self.blacklist_regexes]
225
+ hosts = [str(h).encode() for h in self.sorted_hosts]
226
+ return hosts + regex_patterns
227
+
228
+
229
+ class BBOTTarget:
230
+ """
231
+ A convenient abstraction of a scan target that contains three subtargets:
232
+ - seeds
233
+ - whitelist
234
+ - blacklist
235
+
236
+ Provides high-level functions like in_scope(), which includes both whitelist and blacklist checks.
237
+ """
238
+
239
+ def __init__(self, *seeds, whitelist=None, blacklist=None, strict_scope=False, scan=None):
240
+ self.scan = scan
241
+ self.strict_scope = strict_scope
242
+ self.seeds = ScanSeeds(*seeds, strict_dns_scope=strict_scope, scan=scan)
243
+ if whitelist is None:
244
+ whitelist = self.seeds.hosts
245
+ self.whitelist = ScanWhitelist(*whitelist, strict_dns_scope=strict_scope, scan=scan)
246
+ if blacklist is None:
247
+ blacklist = []
248
+ self.blacklist = ScanBlacklist(*blacklist, scan=scan)
110
249
 
111
250
  @property
112
251
  def json(self):
@@ -122,16 +261,20 @@ class BBOTTarget:
122
261
  "scope_hash": self.scope_hash.hex(),
123
262
  }
124
263
 
125
- def copy(self):
126
- self_copy = copy.copy(self)
127
- self_copy.seeds = self.seeds.copy()
128
- self_copy.whitelist = self.whitelist.copy()
129
- self_copy.blacklist = self.blacklist.copy()
130
- return self_copy
264
+ @property
265
+ def hash(self):
266
+ sha1_hash = sha1()
267
+ for target_hash in [t.hash for t in (self.seeds, self.whitelist, self.blacklist)]:
268
+ sha1_hash.update(target_hash)
269
+ return sha1_hash.digest()
131
270
 
132
271
  @property
133
- def events(self):
134
- return self.seeds.events
272
+ def scope_hash(self):
273
+ sha1_hash = sha1()
274
+ # Consider only the hash values of the whitelist and blacklist
275
+ for target_hash in [t.hash for t in (self.whitelist, self.blacklist)]:
276
+ sha1_hash.update(target_hash)
277
+ return sha1_hash.digest()
135
278
 
136
279
  def in_scope(self, host):
137
280
  """
@@ -167,8 +310,7 @@ class BBOTTarget:
167
310
  >>> preset.blacklisted("http://www.evilcorp.com")
168
311
  True
169
312
  """
170
- e = make_event(host, dummy=True)
171
- return e in self.blacklist
313
+ return host in self.blacklist
172
314
 
173
315
  def whitelisted(self, host):
174
316
  """
@@ -184,360 +326,20 @@ class BBOTTarget:
184
326
  >>> preset.whitelisted("http://www.evilcorp.com")
185
327
  True
186
328
  """
187
- e = make_event(host, dummy=True)
188
- whitelist = self.whitelist
189
- if whitelist is None:
190
- whitelist = self.seeds
191
- return e in whitelist
329
+ return host in self.whitelist
192
330
 
193
331
  @property
194
- def radix_only(self):
332
+ def minimal(self):
195
333
  """
196
334
  A slimmer, serializable version of the target designed for simple scope checks
197
335
 
198
- This version doesn't have the events, only their hosts.
336
+ This version doesn't have the events, only their hosts. This allows it to be passed across process boundaries.
199
337
  """
200
338
  return self.__class__(
201
- *[e.host for e in self.seeds if e.host],
202
- whitelist=None if self.whitelist is None else [e for e in self.whitelist],
203
- blacklist=[e for e in self.blacklist],
339
+ whitelist=self.whitelist.inputs,
340
+ blacklist=self.blacklist.inputs,
204
341
  strict_scope=self.strict_scope,
205
342
  )
206
343
 
207
-
208
- class Target:
209
- """
210
- A class representing a target. Can contain an unlimited number of hosts, IP or IP ranges, URLs, etc.
211
-
212
- Attributes:
213
- strict_scope (bool): Flag indicating whether to consider child domains in-scope.
214
- If set to True, only the exact hosts specified and not their children are considered part of the target.
215
-
216
- _radix (RadixTree): Radix tree for quick IP/DNS lookups.
217
- _events (set): Flat set of contained events.
218
-
219
- Examples:
220
- Basic usage
221
- >>> target = Target(scan, "evilcorp.com", "1.2.3.0/24")
222
- >>> len(target)
223
- 257
224
- >>> list(t.events)
225
- [
226
- DNS_NAME("evilcorp.com", module=TARGET, tags={'domain', 'distance-1', 'target'}),
227
- IP_RANGE("1.2.3.0/24", module=TARGET, tags={'ipv4', 'distance-1', 'target'})
228
- ]
229
- >>> "www.evilcorp.com" in target
230
- True
231
- >>> "1.2.3.4" in target
232
- True
233
- >>> "4.3.2.1" in target
234
- False
235
- >>> "https://admin.evilcorp.com" in target
236
- True
237
- >>> "bob@evilcorp.com" in target
238
- True
239
-
240
- Event correlation
241
- >>> target.get("www.evilcorp.com")
242
- DNS_NAME("evilcorp.com", module=TARGET, tags={'domain', 'distance-1', 'target'})
243
- >>> target.get("1.2.3.4")
244
- IP_RANGE("1.2.3.0/24", module=TARGET, tags={'ipv4', 'distance-1', 'target'})
245
-
246
- Target comparison
247
- >>> target2 = Targets(scan, "www.evilcorp.com")
248
- >>> target2 == target
249
- False
250
- >>> target2 in target
251
- True
252
- >>> target in target2
253
- False
254
-
255
- Notes:
256
- - Targets are only precise down to the individual host. Ports and protocols are not considered in scope calculations.
257
- - If you specify "https://evilcorp.com:8443" as a target, all of evilcorp.com (including subdomains and other ports and protocols) will be considered part of the target
258
- - If you do not want to include child subdomains, use `strict_scope=True`
259
- """
260
-
261
- def __init__(self, *targets, strict_scope=False, scan=None, acl_mode=False):
262
- """
263
- Initialize a Target object.
264
-
265
- Args:
266
- *targets: One or more targets (e.g., domain names, IP ranges) to be included in this Target.
267
- strict_scope (bool): Whether to consider subdomains of target domains in-scope
268
- scan (Scan): Reference to the Scan object that instantiated the Target.
269
- acl_mode (bool): Stricter deduplication for more efficient checks
270
-
271
- Notes:
272
- - If you are instantiating a target from within a BBOT module, use `self.helpers.make_target()` instead. (this removes the need to pass in a scan object.)
273
- - The strict_scope flag can be set to restrict scope calculation to only exactly-matching hosts and not their child subdomains.
274
- - Each target is processed and stored as an `Event` in the '_events' dictionary.
275
- """
276
- self.scan = scan
277
- self.strict_scope = strict_scope
278
- self.acl_mode = acl_mode
279
- self.special_event_types = {
280
- "ORG_STUB": re.compile(r"^(?:ORG|ORG_STUB):(.*)", re.IGNORECASE),
281
- "USERNAME": re.compile(r"^(?:USER|USERNAME):(.*)", re.IGNORECASE),
282
- }
283
- self._events = set()
284
- self._radix = RadixTarget()
285
-
286
- for target_event in self._make_events(targets):
287
- self._add_event(target_event)
288
-
289
- self._hash = None
290
-
291
- def add(self, t, event_type=None):
292
- """
293
- Add a target or merge events from another Target object into this Target.
294
-
295
- Args:
296
- t: The target to be added. It can be either a string, an event object, or another Target object.
297
-
298
- Attributes Modified:
299
- _events (dict): The dictionary is updated to include the new target's events.
300
-
301
- Examples:
302
- >>> target.add('example.com')
303
-
304
- Notes:
305
- - If `t` is of the same class as this Target, all its events are merged.
306
- - If `t` is an event, it is directly added to `_events`.
307
- """
308
- if not isinstance(t, (list, tuple, set)):
309
- t = [t]
310
- for single_target in t:
311
- if isinstance(single_target, self.__class__):
312
- for event in single_target.events:
313
- self._add_event(event)
314
- else:
315
- if is_event(single_target):
316
- event = single_target
317
- else:
318
- try:
319
- event = make_event(
320
- single_target, event_type=event_type, dummy=True, tags=["target"], scan=self.scan
321
- )
322
- except ValidationError as e:
323
- # allow commented lines
324
- if not str(t).startswith("#"):
325
- log.trace(traceback.format_exc())
326
- raise ValidationError(f'Could not add target "{t}": {e}')
327
- self._add_event(event)
328
-
329
- @property
330
- def events(self):
331
- """
332
- Returns all events in the target.
333
-
334
- Yields:
335
- Event object: One of the Event objects stored in the `_events` dictionary.
336
-
337
- Examples:
338
- >>> target = Target(scan, "example.com")
339
- >>> for event in target.events:
340
- ... print(event)
341
-
342
- Notes:
343
- - This property is read-only.
344
- """
345
- return self._events
346
-
347
- @property
348
- def hosts(self):
349
- return [e.host for e in self.events]
350
-
351
- def copy(self):
352
- """
353
- Creates and returns a copy of the Target object, including a shallow copy of the `_events` and `_radix` attributes.
354
-
355
- Returns:
356
- Target: A new Target object with the sameattributes as the original.
357
- A shallow copy of the `_events` dictionary is made.
358
-
359
- Examples:
360
- >>> original_target = Target(scan, "example.com")
361
- >>> copied_target = original_target.copy()
362
- >>> copied_target is original_target
363
- False
364
- >>> copied_target == original_target
365
- True
366
- >>> copied_target in original_target
367
- True
368
- >>> original_target in copied_target
369
- True
370
-
371
- Notes:
372
- - The `scan` object reference is kept intact in the copied Target object.
373
- """
374
- self_copy = self.__class__()
375
- self_copy._events = set(self._events)
376
- self_copy._radix = copy.copy(self._radix)
377
- return self_copy
378
-
379
- def get(self, host, single=True):
380
- """
381
- Gets the event associated with the specified host from the target's radix tree.
382
-
383
- Args:
384
- host (Event, Target, or str): The hostname, IP, URL, or event to look for.
385
- single (bool): Whether to return a single event. If False, return all events matching the host
386
-
387
- Returns:
388
- Event or None: Returns the Event object associated with the given host if it exists, otherwise returns None.
389
-
390
- Examples:
391
- >>> target = Target(scan, "evilcorp.com", "1.2.3.0/24")
392
- >>> target.get("www.evilcorp.com")
393
- DNS_NAME("evilcorp.com", module=TARGET, tags={'domain', 'distance-1', 'target'})
394
- >>> target.get("1.2.3.4")
395
- IP_RANGE("1.2.3.0/24", module=TARGET, tags={'ipv4', 'distance-1', 'target'})
396
-
397
- Notes:
398
- - The method returns the first event that matches the given host.
399
- - If `strict_scope` is False, it will also consider parent domains and IP ranges.
400
- """
401
- try:
402
- event = make_event(host, dummy=True)
403
- except ValidationError:
404
- return
405
- if event.host:
406
- return self.get_host(event.host, single=single)
407
-
408
- def get_host(self, host, single=True):
409
- """
410
- A more efficient version of .get() that only accepts hostnames and IP addresses
411
- """
412
- host = make_ip_type(host)
413
- with suppress(KeyError, StopIteration):
414
- result = self._radix.search(host)
415
- if result is not None:
416
- ret = set()
417
- for event in result:
418
- # if the result is a dns name and strict scope is enabled
419
- if isinstance(event.host, str) and self.strict_scope:
420
- # if the result doesn't exactly equal the host, abort
421
- if event.host != host:
422
- return
423
- if single:
424
- return event
425
- else:
426
- ret.add(event)
427
- if ret and not single:
428
- return ret
429
-
430
- def _sort_events(self, events):
431
- return sorted(events, key=lambda x: x._host_size)
432
-
433
- def _make_events(self, targets):
434
- events = []
435
- for target in targets:
436
- event_type = None
437
- for eventtype, regex in self.special_event_types.items():
438
- if isinstance(target, str):
439
- match = regex.match(target)
440
- if match:
441
- target = match.groups()[0]
442
- event_type = eventtype
443
- break
444
- events.append(make_event(target, event_type=event_type, dummy=True, scan=self.scan))
445
- return self._sort_events(events)
446
-
447
- def _add_event(self, event):
448
- skip = False
449
- if event.host:
450
- radix_data = self._radix.search(event.host)
451
- if self.acl_mode:
452
- # skip if the hostname/IP/subnet (or its parent) has already been added
453
- if radix_data is not None and not self.strict_scope:
454
- skip = True
455
- else:
456
- event_type = "IP_RANGE" if event.type == "IP_RANGE" else "DNS_NAME"
457
- event = make_event(event.host, event_type=event_type, dummy=True, scan=self.scan)
458
- if not skip:
459
- # if strict scope is enabled and it's not an exact host match, we add a whole new entry
460
- if radix_data is None or (self.strict_scope and event.host not in radix_data):
461
- radix_data = {event}
462
- self._radix.insert(event.host, radix_data)
463
- # otherwise, we add the event to the set
464
- else:
465
- radix_data.add(event)
466
- # clear hash
467
- self._hash = None
468
- elif self.acl_mode and not self.strict_scope:
469
- # skip if we're in ACL mode and there's no host
470
- skip = True
471
- if not skip:
472
- self._events.add(event)
473
-
474
- def _contains(self, other):
475
- if self.get(other) is not None:
476
- return True
477
- return False
478
-
479
- def __str__(self):
480
- return ",".join([str(e.data) for e in self.events][:5])
481
-
482
- def __iter__(self):
483
- yield from self.events
484
-
485
- def __contains__(self, other):
486
- # if "other" is a Target
487
- if isinstance(other, self.__class__):
488
- contained_in_self = [self._contains(e) for e in other.events]
489
- return all(contained_in_self)
490
- else:
491
- return self._contains(other)
492
-
493
- def __bool__(self):
494
- return bool(self._events)
495
-
496
344
  def __eq__(self, other):
497
345
  return self.hash == other.hash
498
-
499
- @property
500
- def hash(self):
501
- if self._hash is None:
502
- # Create a new SHA-1 hash object
503
- sha1_hash = sha1()
504
- # Update the SHA-1 object with the hash values of each object
505
- for event_type, event_hash in sorted([(e.type.encode(), e.data_hash) for e in self.events]):
506
- sha1_hash.update(event_type)
507
- sha1_hash.update(event_hash)
508
- if self.strict_scope:
509
- sha1_hash.update(b"\x00")
510
- self._hash = sha1_hash.digest()
511
- return self._hash
512
-
513
- def __len__(self):
514
- """
515
- Calculates and returns the total number of hosts within this target, not counting duplicate events.
516
-
517
- Returns:
518
- int: The total number of unique hosts present within the target's `_events`.
519
-
520
- Examples:
521
- >>> target = Target(scan, "evilcorp.com", "1.2.3.0/24")
522
- >>> len(target)
523
- 257
524
-
525
- Notes:
526
- - If a host is represented as an IP network, all individual IP addresses in that network are counted.
527
- - For other types of hosts, each unique event is counted as one.
528
- """
529
- num_hosts = 0
530
- for event in self._events:
531
- if isinstance(event.host, (ipaddress.IPv4Network, ipaddress.IPv6Network)):
532
- num_hosts += event.host.num_addresses
533
- else:
534
- num_hosts += 1
535
- return num_hosts
536
-
537
-
538
- class TargetDummyModule(BaseModule):
539
- _type = "TARGET"
540
- name = "TARGET"
541
-
542
- def __init__(self, scan):
543
- self.scan = scan