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
@@ -7,7 +7,6 @@ import traceback
7
7
  from cachetools import LRUCache
8
8
  from contextlib import suppress
9
9
 
10
- from bbot.errors import DNSWildcardBreak
11
10
  from bbot.core.engine import EngineServer
12
11
  from bbot.core.helpers.async_helpers import NamedLock
13
12
  from bbot.core.helpers.dns.helpers import extract_targets
@@ -16,7 +15,6 @@ from bbot.core.helpers.misc import (
16
15
  rand_string,
17
16
  parent_domain,
18
17
  domain_parents,
19
- clean_dns_record,
20
18
  )
21
19
 
22
20
 
@@ -26,7 +24,6 @@ all_rdtypes = ["A", "AAAA", "SRV", "MX", "NS", "SOA", "CNAME", "TXT"]
26
24
 
27
25
 
28
26
  class DNSEngine(EngineServer):
29
-
30
27
  CMDS = {
31
28
  0: "resolve",
32
29
  1: "resolve_raw",
@@ -57,7 +54,7 @@ class DNSEngine(EngineServer):
57
54
  dns_omit_queries = self.dns_config.get("omit_queries", None)
58
55
  if not dns_omit_queries:
59
56
  dns_omit_queries = []
60
- self.dns_omit_queries = dict()
57
+ self.dns_omit_queries = {}
61
58
  for d in dns_omit_queries:
62
59
  d = d.split(":")
63
60
  if len(d) == 2:
@@ -75,7 +72,7 @@ class DNSEngine(EngineServer):
75
72
  self.wildcard_ignore = []
76
73
  self.wildcard_ignore = tuple([str(d).strip().lower() for d in self.wildcard_ignore])
77
74
  self.wildcard_tests = self.dns_config.get("wildcard_tests", 5)
78
- self._wildcard_cache = dict()
75
+ self._wildcard_cache = {}
79
76
  # since wildcard detection takes some time, This is to prevent multiple
80
77
  # modules from kicking off wildcard detection for the same domain at the same time
81
78
  self._wildcard_lock = NamedLock()
@@ -85,7 +82,7 @@ class DNSEngine(EngineServer):
85
82
  self._last_connectivity_warning = time.time()
86
83
  # keeps track of warnings issued for wildcard detection to prevent duplicate warnings
87
84
  self._dns_warnings = set()
88
- self._errors = dict()
85
+ self._errors = {}
89
86
  self._debug = self.dns_config.get("debug", False)
90
87
  self._dns_cache = LRUCache(maxsize=10000)
91
88
 
@@ -208,8 +205,8 @@ class DNSEngine(EngineServer):
208
205
  retries = kwargs.pop("retries", self.retries)
209
206
  use_cache = kwargs.pop("use_cache", True)
210
207
  tries_left = int(retries) + 1
211
- parent_hash = hash(f"{parent}:{rdtype}")
212
- dns_cache_hash = hash(f"{query}:{rdtype}")
208
+ parent_hash = hash((parent, rdtype))
209
+ dns_cache_hash = hash((query, rdtype))
213
210
  while tries_left > 0:
214
211
  try:
215
212
  if use_cache:
@@ -297,7 +294,7 @@ class DNSEngine(EngineServer):
297
294
  tries_left = int(retries) + 1
298
295
  results = []
299
296
  errors = []
300
- dns_cache_hash = hash(f"{query}:PTR")
297
+ dns_cache_hash = hash((query, "PTR"))
301
298
  while tries_left > 0:
302
299
  try:
303
300
  if use_cache:
@@ -361,8 +358,7 @@ class DNSEngine(EngineServer):
361
358
  ):
362
359
  query = args[0]
363
360
  rdtype = kwargs["type"]
364
- for answer in answers:
365
- yield ((query, rdtype), (answer, errors))
361
+ yield ((query, rdtype), (answers, errors))
366
362
 
367
363
  async def _catch(self, callback, *args, **kwargs):
368
364
  """
@@ -397,18 +393,21 @@ class DNSEngine(EngineServer):
397
393
  self.log.trace(traceback.format_exc())
398
394
  return []
399
395
 
400
- async def is_wildcard(self, query, ips=None, rdtype=None):
396
+ async def is_wildcard(self, query, rdtypes, raw_dns_records=None):
401
397
  """
402
398
  Use this method to check whether a *host* is a wildcard entry
403
399
 
404
400
  This can reliably tell the difference between a valid DNS record and a wildcard within a wildcard domain.
405
401
 
402
+ It works by making a bunch of random DNS queries to the parent domain, compiling a list of wildcard IPs,
403
+ then comparing those to the IPs of the host in question. If the host's IP matches the wildcard ones, it's a wildcard.
404
+
406
405
  If you want to know whether a domain is using wildcard DNS, use `is_wildcard_domain()` instead.
407
406
 
408
407
  Args:
409
408
  query (str): The hostname to check for a wildcard entry.
410
- ips (list, optional): List of IPs to compare against, typically obtained from a previous DNS resolution of the query.
411
- rdtype (str, optional): The DNS record type (e.g., "A", "AAAA") to consider during the check.
409
+ rdtypes (list): The DNS record type (e.g., "A", "AAAA") to consider during the check.
410
+ raw_dns_records (dict, optional): Dictionary of {rdtype: [answer1, answer2, ...], ...} containing raw dnspython answers for the query.
412
411
 
413
412
  Returns:
414
413
  dict: A dictionary indicating if the query is a wildcard for each checked DNS record type.
@@ -416,108 +415,115 @@ class DNSEngine(EngineServer):
416
415
  Values are tuples where the first element is a boolean indicating if the query is a wildcard,
417
416
  and the second element is the wildcard parent if it's a wildcard.
418
417
 
419
- Raises:
420
- ValueError: If only one of `ips` or `rdtype` is specified or if no valid IPs are specified.
421
-
422
418
  Examples:
423
- >>> is_wildcard("www.github.io")
424
- {"A": (True, "github.io"), "AAAA": (True, "github.io")}
419
+ >>> is_wildcard("www.github.io", rdtypes=["A", "AAAA", "MX"])
420
+ {"A": (True, "github.io"), "AAAA": (True, "github.io"), "MX": (False, "github.io")}
425
421
 
426
- >>> is_wildcard("www.evilcorp.com", ips=["93.184.216.34"], rdtype="A")
422
+ >>> is_wildcard("www.evilcorp.com", rdtypes=["A"])
427
423
  {"A": (False, "evilcorp.com")}
428
424
 
429
425
  Note:
430
426
  `is_wildcard` can be True, False, or None (indicating that wildcard detection was inconclusive)
431
427
  """
432
- result = {}
428
+ if isinstance(rdtypes, str):
429
+ rdtypes = [rdtypes]
433
430
 
434
- parent = parent_domain(query)
435
- parents = list(domain_parents(query))
431
+ result = {}
436
432
 
437
- if rdtype is not None:
438
- if isinstance(rdtype, str):
439
- rdtype = [rdtype]
440
- rdtypes_to_check = rdtype
441
- else:
442
- rdtypes_to_check = all_rdtypes
443
-
444
- query_baseline = dict()
445
- # if the caller hasn't already done the work of resolving the IPs
446
- if ips is None:
447
- # then resolve the query for all rdtypes
448
- queries = [(query, t) for t in rdtypes_to_check]
449
- async for (query, _rdtype), (answers, errors) in self.resolve_raw_batch(queries):
450
- answers = extract_targets(answers)
433
+ # if the work of resolving hasn't been done yet, do it
434
+ if raw_dns_records is None:
435
+ raw_dns_records = {}
436
+ queries = [(query, rdtype) for rdtype in rdtypes]
437
+ async for (_, rdtype), (answers, errors) in self.resolve_raw_batch(queries):
451
438
  if answers:
452
- query_baseline[_rdtype] = set([a[1] for a in answers])
439
+ for answer in answers:
440
+ try:
441
+ raw_dns_records[rdtype].add(answer)
442
+ except KeyError:
443
+ raw_dns_records[rdtype] = {answer}
453
444
  else:
454
445
  if errors:
455
- self.debug(f"Failed to resolve {query} ({_rdtype}) during wildcard detection")
456
- result[_rdtype] = (None, parent)
457
- continue
458
- else:
459
- # otherwise, we can skip all that
460
- cleaned_ips = set([clean_dns_record(ip) for ip in ips])
461
- if not cleaned_ips:
462
- raise ValueError("Valid IPs must be specified")
463
- query_baseline[rdtype] = cleaned_ips
464
- if not query_baseline:
446
+ self.debug(f"Failed to resolve {query} ({rdtype}) during wildcard detection")
447
+ result[rdtype] = ("ERROR", query)
448
+
449
+ # clean + process the raw records into a baseline
450
+ baseline = {}
451
+ baseline_raw = {}
452
+ for rdtype, answers in raw_dns_records.items():
453
+ for answer in answers:
454
+ text_answer = answer.to_text()
455
+ try:
456
+ baseline_raw[rdtype].add(text_answer)
457
+ except KeyError:
458
+ baseline_raw[rdtype] = {text_answer}
459
+ for _, host in extract_targets(answer):
460
+ try:
461
+ baseline[rdtype].add(host)
462
+ except KeyError:
463
+ baseline[rdtype] = {host}
464
+
465
+ # if it's unresolved, it's a big nope
466
+ if not raw_dns_records:
465
467
  return result
466
468
 
467
469
  # once we've resolved the base query and have IP addresses to work with
468
470
  # we can compare the IPs to the ones we have on file for wildcards
469
471
 
472
+ # only bother to check the rdypes that actually resolve
473
+ rdtypes_to_check = set(raw_dns_records)
474
+
470
475
  # for every parent domain, starting with the shortest
471
- try:
472
- for host in parents[::-1]:
473
- # make sure we've checked that domain for wildcards
474
- await self.is_wildcard_domain(host)
475
-
476
- # for every rdtype
477
- for _rdtype in list(query_baseline):
478
- # get the IPs from above
479
- query_ips = query_baseline.get(_rdtype, set())
480
- host_hash = hash(host)
481
-
482
- if host_hash in self._wildcard_cache:
483
- # then get its IPs from our wildcard cache
484
- wildcard_rdtypes = self._wildcard_cache[host_hash]
485
-
486
- # then check to see if our IPs match the wildcard ones
487
- if _rdtype in wildcard_rdtypes:
488
- wildcard_ips = wildcard_rdtypes[_rdtype]
489
- # if our IPs match the wildcard ones, then ladies and gentlemen we have a wildcard
490
- is_wildcard = any(r in wildcard_ips for r in query_ips)
491
-
492
- if is_wildcard and not result.get(_rdtype, (None, None))[0] is True:
493
- result[_rdtype] = (True, host)
494
-
495
- # if we've reached a point where the dns name is a complete wildcard, class can be dismissed early
496
- base_query_rdtypes = set(query_baseline)
497
- wildcard_rdtypes_set = set([k for k, v in result.items() if v[0] is True])
498
- if base_query_rdtypes and wildcard_rdtypes_set and base_query_rdtypes == wildcard_rdtypes_set:
499
- self.log.debug(
500
- f"Breaking from wildcard detection for {query} at {host} because base query rdtypes ({base_query_rdtypes}) == wildcard rdtypes ({wildcard_rdtypes_set})"
501
- )
502
- raise DNSWildcardBreak()
503
-
504
- except DNSWildcardBreak:
505
- pass
506
-
507
- for _rdtype, answers in query_baseline.items():
508
- if answers and _rdtype not in result:
509
- result[_rdtype] = (False, query)
476
+ parents = list(domain_parents(query))
477
+ for parent in parents[::-1]:
478
+ # check if the parent domain is set up with wildcards
479
+ wildcard_results = await self.is_wildcard_domain(parent, rdtypes_to_check)
480
+
481
+ # for every rdtype
482
+ for rdtype in list(baseline_raw):
483
+ # skip if we already found a wildcard for this rdtype
484
+ if rdtype in result:
485
+ continue
486
+
487
+ # get our baseline IPs from above
488
+ _baseline = baseline.get(rdtype, set())
489
+ _baseline_raw = baseline_raw.get(rdtype, set())
490
+
491
+ wildcard_rdtypes = wildcard_results.get(parent, {})
492
+ wildcards = wildcard_rdtypes.get(rdtype, None)
493
+ if wildcards is None:
494
+ continue
495
+ wildcards, wildcard_raw = wildcards
496
+
497
+ if wildcard_raw:
498
+ # skip this rdtype from now on
499
+ rdtypes_to_check.remove(rdtype)
500
+
501
+ # check if any of our baseline IPs are in the wildcard results
502
+ is_wildcard = any(r in wildcards for r in _baseline)
503
+ is_wildcard_raw = any(r in wildcard_raw for r in _baseline_raw)
504
+
505
+ # if there are any matches, we have a wildcard
506
+ if is_wildcard or is_wildcard_raw:
507
+ result[rdtype] = (True, parent)
508
+ else:
509
+ # otherwise, it's still suspicious, because we had random stuff resolve at this level
510
+ result[rdtype] = ("POSSIBLE", parent)
511
+
512
+ # any rdtype that wasn't a wildcard, mark it as False
513
+ for rdtype, answers in baseline_raw.items():
514
+ if answers and rdtype not in result:
515
+ result[rdtype] = (False, query)
510
516
 
511
517
  return result
512
518
 
513
- async def is_wildcard_domain(self, domain, log_info=False):
519
+ async def is_wildcard_domain(self, domain, rdtypes):
514
520
  """
515
521
  Check whether a given host or its children make use of wildcard DNS entries. Wildcard DNS can have
516
522
  various implications, particularly in subdomain enumeration and subdomain takeovers.
517
523
 
518
524
  Args:
519
525
  domain (str): The domain to check for wildcard DNS entries.
520
- log_info (bool, optional): Whether to log the result of the check. Defaults to False.
526
+ rdtypes (list): Which DNS record types to check.
521
527
 
522
528
  Returns:
523
529
  dict: A dictionary where the keys are the parent domains that have wildcard DNS entries,
@@ -531,60 +537,74 @@ class DNSEngine(EngineServer):
531
537
  >>> is_wildcard_domain("example.com")
532
538
  {}
533
539
  """
534
- wildcard_domain_results = {}
535
-
536
- rdtypes_to_check = set(all_rdtypes)
540
+ if isinstance(rdtypes, str):
541
+ rdtypes = [rdtypes]
542
+ rdtypes = set(rdtypes)
537
543
 
544
+ wildcard_results = {}
538
545
  # make a list of its parents
539
546
  parents = list(domain_parents(domain, include_self=True))
540
547
  # and check each of them, beginning with the highest parent (i.e. the root domain)
541
548
  for i, host in enumerate(parents[::-1]):
542
- # have we checked this host before?
543
- host_hash = hash(host)
544
- async with self._wildcard_lock.lock(host_hash):
545
- # if we've seen this host before
546
- if host_hash in self._wildcard_cache:
547
- wildcard_domain_results[host] = self._wildcard_cache[host_hash]
548
- continue
549
+ host_results = {}
550
+ queries = [((host, rdtype), {}) for rdtype in rdtypes]
551
+ async for ((_, rdtype), _, _), (results, results_raw) in self.task_pool(
552
+ self._is_wildcard_zone, args_kwargs=queries
553
+ ):
554
+ # if we hit a wildcard, we can skip this rdtype from now on
555
+ if results_raw:
556
+ rdtypes.remove(rdtype)
557
+ host_results[rdtype] = results, results_raw
558
+
559
+ if host_results:
560
+ wildcard_results[host] = host_results
561
+
562
+ return wildcard_results
563
+
564
+ async def _is_wildcard_zone(self, host, rdtype):
565
+ """
566
+ Check whether a specific DNS zone+rdtype has a wildcard configuration
567
+ """
568
+ rdtype = rdtype.upper()
549
569
 
550
- self.log.verbose(f"Checking if {host} is a wildcard")
570
+ # have we checked this host before?
571
+ host_hash = hash((host, rdtype))
572
+ async with self._wildcard_lock.lock(host_hash):
573
+ # if we've seen this host before
574
+ try:
575
+ wildcard_results, wildcard_results_raw = self._wildcard_cache[host_hash]
576
+ self.debug(f"Got {host}:{rdtype} from cache")
577
+ except KeyError:
578
+ wildcard_results = set()
579
+ wildcard_results_raw = set()
580
+ self.debug(f"Checking if {host}:{rdtype} is a wildcard")
551
581
 
552
582
  # determine if this is a wildcard domain
553
-
554
583
  # resolve a bunch of random subdomains of the same parent
555
- is_wildcard = False
556
- wildcard_results = dict()
557
-
558
584
  rand_queries = []
559
- for rdtype in rdtypes_to_check:
560
- for _ in range(self.wildcard_tests):
561
- rand_query = f"{rand_string(digits=False, length=10)}.{host}"
562
- rand_queries.append((rand_query, rdtype))
585
+ for _ in range(self.wildcard_tests):
586
+ rand_query = f"{rand_string(digits=False, length=10)}.{host}"
587
+ rand_queries.append((rand_query, rdtype))
563
588
 
564
589
  async for (query, rdtype), (answers, errors) in self.resolve_raw_batch(rand_queries, use_cache=False):
565
- answers = extract_targets(answers)
566
- if answers:
567
- is_wildcard = True
568
- if not rdtype in wildcard_results:
569
- wildcard_results[rdtype] = set()
570
- wildcard_results[rdtype].update(set(a[1] for a in answers))
571
- # we know this rdtype is a wildcard
572
- # so we don't need to check it anymore
573
- with suppress(KeyError):
574
- rdtypes_to_check.remove(rdtype)
575
-
576
- self._wildcard_cache.update({host_hash: wildcard_results})
577
- wildcard_domain_results.update({host: wildcard_results})
578
- if is_wildcard:
579
- wildcard_rdtypes_str = ",".join(sorted([t.upper() for t, r in wildcard_results.items() if r]))
580
- log_fn = self.log.verbose
581
- if log_info:
582
- log_fn = self.log.info
583
- log_fn(f"Encountered domain with wildcard DNS ({wildcard_rdtypes_str}): {host}")
590
+ for answer in answers:
591
+ # consider both the raw record
592
+ wildcard_results_raw.add(answer.to_text())
593
+ # and all the extracted hosts
594
+ for _, t in extract_targets(answer):
595
+ wildcard_results.add(t)
596
+
597
+ if wildcard_results:
598
+ self.log.info(f"Encountered domain with wildcard DNS ({rdtype}): *.{host}")
584
599
  else:
585
- self.log.verbose(f"Finished checking {host}, it is not a wildcard")
600
+ self.debug(f"Finished checking {host}:{rdtype}, it is not a wildcard")
601
+ self._wildcard_cache[host_hash] = wildcard_results, wildcard_results_raw
586
602
 
587
- return wildcard_domain_results
603
+ return wildcard_results, wildcard_results_raw
604
+
605
+ async def _is_wildcard(self, query, rdtypes, dns_children):
606
+ if isinstance(rdtypes, str):
607
+ rdtypes = [rdtypes]
588
608
 
589
609
  @property
590
610
  def dns_connectivity_lock(self):
@@ -618,7 +638,7 @@ class DNSEngine(EngineServer):
618
638
  self._last_dns_success = time.time()
619
639
  return True
620
640
  if time.time() - self._last_connectivity_warning > interval:
621
- self.log.warning(f"DNS queries are failing, please check your internet connection")
641
+ self.log.warning("DNS queries are failing, please check your internet connection")
622
642
  self._last_connectivity_warning = time.time()
623
643
  self._errors.clear()
624
644
  return False
@@ -631,7 +651,14 @@ class DNSEngine(EngineServer):
631
651
  def in_tests(self):
632
652
  return os.getenv("BBOT_TESTING", "") == "True"
633
653
 
634
- async def _mock_dns(self, mock_data):
654
+ async def _mock_dns(self, mock_data, custom_lookup_fn=None):
635
655
  from .mock import MockResolver
636
656
 
637
- self.resolver = MockResolver(mock_data)
657
+ def deserialize_function(func_source):
658
+ assert self.in_tests, "Can only mock when BBOT_TESTING=True"
659
+ if func_source is None:
660
+ return None
661
+ exec(func_source)
662
+ return locals()["custom_lookup"]
663
+
664
+ self.resolver = MockResolver(mock_data, custom_lookup_fn=deserialize_function(custom_lookup_fn))
@@ -1,6 +1,6 @@
1
1
  import logging
2
2
 
3
- from bbot.core.helpers.regexes import dns_name_regex
3
+ from bbot.core.helpers.regexes import dns_name_extraction_regex
4
4
  from bbot.core.helpers.misc import clean_dns_record, smart_decode
5
5
 
6
6
  log = logging.getLogger("bbot.core.helpers.dns")
@@ -198,7 +198,7 @@ def extract_targets(record):
198
198
  elif rdtype == "TXT":
199
199
  for s in record.strings:
200
200
  s = smart_decode(s)
201
- for match in dns_name_regex.finditer(s):
201
+ for match in dns_name_extraction_regex.finditer(s):
202
202
  start, end = match.span()
203
203
  host = s[start:end]
204
204
  add_result(rdtype, host)
@@ -1,10 +1,13 @@
1
1
  import dns
2
+ import logging
2
3
 
4
+ log = logging.getLogger("bbot.core.helpers.dns.mock")
3
5
 
4
- class MockResolver:
5
6
 
6
- def __init__(self, mock_data=None):
7
+ class MockResolver:
8
+ def __init__(self, mock_data=None, custom_lookup_fn=None):
7
9
  self.mock_data = mock_data if mock_data else {}
10
+ self._custom_lookup_fn = custom_lookup_fn
8
11
  self.nameservers = ["127.0.0.1"]
9
12
 
10
13
  async def resolve_address(self, ipaddr, *args, **kwargs):
@@ -13,12 +16,22 @@ class MockResolver:
13
16
  modified_kwargs["rdtype"] = "PTR"
14
17
  return await self.resolve(str(dns.reversename.from_address(ipaddr)), *args, **modified_kwargs)
15
18
 
16
- def create_dns_response(self, query_name, rdtype):
17
- query_name = query_name.strip(".")
18
- answers = self.mock_data.get(query_name, {}).get(rdtype, [])
19
- if not answers:
20
- raise dns.resolver.NXDOMAIN(f"No answer found for {query_name} {rdtype}")
19
+ def _lookup(self, query, rdtype):
20
+ query = query.strip(".")
21
+ ret = []
22
+ if self._custom_lookup_fn is not None:
23
+ answers = self._custom_lookup_fn(query, rdtype)
24
+ if answers is not None:
25
+ ret.extend(list(answers))
26
+ answers = self.mock_data.get(query, {}).get(rdtype, [])
27
+ if answers:
28
+ ret.extend(list(answers))
29
+ if not ret:
30
+ raise dns.resolver.NXDOMAIN(f"No answer found for {query} {rdtype}")
31
+ return ret
21
32
 
33
+ def create_dns_response(self, query_name, answers, rdtype):
34
+ query_name = query_name.strip(".")
22
35
  message_text = f"""id 1234
23
36
  opcode QUERY
24
37
  rcode NOERROR
@@ -27,10 +40,13 @@ flags QR AA RD
27
40
  {query_name}. IN {rdtype}
28
41
  ;ANSWER"""
29
42
  for answer in answers:
43
+ if answer == "":
44
+ answer = '""'
30
45
  message_text += f"\n{query_name}. 1 IN {rdtype} {answer}"
31
46
 
32
47
  message_text += "\n;AUTHORITY\n;ADDITIONAL\n"
33
48
  message = dns.message.from_text(message_text)
49
+ # log.verbose(message_text)
34
50
  return message
35
51
 
36
52
  async def resolve(self, query_name, rdtype=None):
@@ -49,7 +65,9 @@ flags QR AA RD
49
65
  raise dns.resolver.NXDOMAIN
50
66
 
51
67
  try:
52
- response = self.create_dns_response(query_name, rdtype)
68
+ answers = self._lookup(query_name, rdtype)
69
+ log.verbose(f"Answers for {query_name}:{rdtype}: {answers}")
70
+ response = self.create_dns_response(query_name, answers, rdtype)
53
71
  answer = dns.resolver.Answer(domain_name, rdtype_obj, dns.rdataclass.IN, response)
54
72
  return answer
55
73
  except dns.resolver.NXDOMAIN:
@@ -83,7 +83,7 @@ def _feed_pipe(self, pipe, content, text=True):
83
83
  for c in content:
84
84
  p.write(decode_fn(c) + newline)
85
85
  except BrokenPipeError:
86
- log.debug(f"Broken pipe in _feed_pipe()")
86
+ log.debug("Broken pipe in _feed_pipe()")
87
87
  except ValueError:
88
88
  log.debug(f"Error _feed_pipe(): {traceback.format_exc()}")
89
89
  except KeyboardInterrupt:
@@ -12,10 +12,11 @@ from .diff import HttpCompare
12
12
  from .regex import RegexHelper
13
13
  from .wordcloud import WordCloud
14
14
  from .interactsh import Interactsh
15
- from ...scanner.target import Target
16
15
  from .depsinstaller import DepsInstaller
17
16
  from .async_helpers import get_event_loop
18
17
 
18
+ from bbot.scanner.target import BaseTarget
19
+
19
20
  log = logging.getLogger("bbot.core.helpers")
20
21
 
21
22
 
@@ -152,11 +153,13 @@ class ConfigAwareHelper:
152
153
  return self.temp_dir / filename
153
154
 
154
155
  def clean_old_scans(self):
155
- _filter = lambda x: x.is_dir() and self.regexes.scan_name_regex.match(x.name)
156
+ def _filter(x):
157
+ return x.is_dir() and self.regexes.scan_name_regex.match(x.name)
158
+
156
159
  self.clean_old(self.scans_dir, keep=self.keep_old_scans, filter=_filter)
157
160
 
158
- def make_target(self, *events, **kwargs):
159
- return Target(*events, **kwargs)
161
+ def make_target(self, *targets, **kwargs):
162
+ return BaseTarget(*targets, scan=self.scan, **kwargs)
160
163
 
161
164
  @property
162
165
  def config(self):
@@ -155,7 +155,7 @@ class Interactsh:
155
155
  break
156
156
 
157
157
  if not self.server:
158
- raise InteractshError(f"Failed to register with an interactsh server")
158
+ raise InteractshError("Failed to register with an interactsh server")
159
159
 
160
160
  log.info(
161
161
  f"Successfully registered to interactsh server {self.server} with correlation_id {self.correlation_id} [{self.domain}]"
@@ -181,7 +181,7 @@ class Interactsh:
181
181
  >>> await interactsh_client.deregister()
182
182
  """
183
183
  if not self.server or not self.correlation_id or not self.secret:
184
- raise InteractshError(f"Missing required information to deregister")
184
+ raise InteractshError("Missing required information to deregister")
185
185
 
186
186
  headers = {}
187
187
  if self.token:
@@ -226,7 +226,7 @@ class Interactsh:
226
226
  ]
227
227
  """
228
228
  if not self.server or not self.correlation_id or not self.secret:
229
- raise InteractshError(f"Missing required information to poll")
229
+ raise InteractshError("Missing required information to poll")
230
230
 
231
231
  headers = {}
232
232
  if self.token:
@@ -0,0 +1,65 @@
1
+ import puremagic
2
+
3
+
4
+ def get_magic_info(file):
5
+ magic_detections = puremagic.magic_file(file)
6
+ if magic_detections:
7
+ magic_detections.sort(key=lambda x: x.confidence, reverse=True)
8
+ detection = magic_detections[0]
9
+ return detection.extension, detection.mime_type, detection.name, detection.confidence
10
+ return "", "", "", 0
11
+
12
+
13
+ def get_compression(mime_type):
14
+ mime_type = mime_type.lower()
15
+ # from https://github.com/cdgriffith/puremagic/blob/master/puremagic/magic_data.json
16
+ compression_map = {
17
+ "application/arj": "arj", # ARJ archive
18
+ "application/binhex": "binhex", # BinHex encoded file
19
+ "application/epub+zip": "zip", # EPUB book (Zip archive)
20
+ "application/fictionbook2+zip": "zip", # FictionBook 2.0 (Zip)
21
+ "application/fictionbook3+zip": "zip", # FictionBook 3.0 (Zip)
22
+ "application/gzip": "gzip", # Gzip compressed file
23
+ "application/java-archive": "zip", # Java Archive (JAR)
24
+ "application/pak": "pak", # PAK archive
25
+ "application/vnd.android.package-archive": "zip", # Android package (APK)
26
+ "application/vnd.comicbook-rar": "rar", # Comic book archive (RAR)
27
+ "application/vnd.comicbook+zip": "zip", # Comic book archive (Zip)
28
+ "application/vnd.ms-cab-compressed": "cab", # Microsoft Cabinet archive
29
+ "application/vnd.palm": "palm", # Palm OS data
30
+ "application/vnd.rar": "rar", # RAR archive
31
+ "application/x-7z-compressed": "7z", # 7-Zip archive
32
+ "application/x-ace": "ace", # ACE archive
33
+ "application/x-alz": "alz", # ALZip archive
34
+ "application/x-arc": "arc", # ARC archive
35
+ "application/x-archive": "ar", # Unix archive
36
+ "application/x-bzip2": "bzip2", # Bzip2 compressed file
37
+ "application/x-compress": "compress", # Unix compress file
38
+ "application/x-cpio": "cpio", # CPIO archive
39
+ "application/x-gzip": "gzip", # Gzip compressed file
40
+ "application/x-itunes-ipa": "zip", # iOS application archive (IPA)
41
+ "application/x-java-pack200": "pack200", # Java Pack200 archive
42
+ "application/x-lha": "lha", # LHA archive
43
+ "application/x-lrzip": "lrzip", # Long Range ZIP
44
+ "application/x-lz4-compressed-tar": "lz4", # LZ4 compressed Tar archive
45
+ "application/x-lz4": "lz4", # LZ4 compressed file
46
+ "application/x-lzip": "lzip", # Lzip compressed file
47
+ "application/x-lzma": "lzma", # LZMA compressed file
48
+ "application/x-par2": "par2", # PAR2 recovery file
49
+ "application/x-qpress": "qpress", # Qpress archive
50
+ "application/x-rar-compressed": "rar", # RAR archive
51
+ "application/x-sit": "sit", # StuffIt archive
52
+ "application/x-stuffit": "sit", # StuffIt archive
53
+ "application/x-tar": "tar", # Tar archive
54
+ "application/x-tgz": "tgz", # Gzip compressed Tar archive
55
+ "application/x-webarchive": "zip", # Web archive (Zip)
56
+ "application/x-xar": "xar", # XAR archive
57
+ "application/x-xz": "xz", # XZ compressed file
58
+ "application/x-zip-compressed-fb2": "zip", # Zip archive (FB2)
59
+ "application/x-zoo": "zoo", # Zoo archive
60
+ "application/x-zstd-compressed-tar": "zstd", # Zstandard compressed Tar archive
61
+ "application/zip": "zip", # Zip archive
62
+ "application/zstd": "zstd", # Zstandard compressed file
63
+ }
64
+
65
+ return compression_map.get(mime_type, "")