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
@@ -1,23 +1,13 @@
1
1
  import ipaddress
2
2
  from contextlib import suppress
3
- from cachetools import LFUCache
4
3
 
5
4
  from bbot.errors import ValidationError
6
5
  from bbot.core.helpers.dns.engine import all_rdtypes
7
- from bbot.core.helpers.async_helpers import NamedLock
8
- from bbot.modules.base import InterceptModule, BaseModule
9
6
  from bbot.core.helpers.dns.helpers import extract_targets
7
+ from bbot.modules.base import BaseInterceptModule, BaseModule
10
8
 
11
9
 
12
- class DNSResolve(InterceptModule):
13
- """
14
- TODO:
15
- - scrap event cache in favor of the parent backtracking method
16
- - don't duplicate resolution on the same host
17
- - clean up wildcard checking to only happen once, and re-emit/abort if one is detected
18
- - same thing with main_host_event. we should never be processing two events - only one.
19
- """
20
-
10
+ class DNSResolve(BaseInterceptModule):
21
11
  watched_events = ["*"]
22
12
  _priority = 1
23
13
  scope_distance_modifier = None
@@ -26,9 +16,6 @@ class DNSResolve(InterceptModule):
26
16
  _name = "host"
27
17
  _type = "internal"
28
18
 
29
- def _outgoing_dedup_hash(self, event):
30
- return hash((event, self.name, event.always_emit))
31
-
32
19
  @property
33
20
  def module_threads(self):
34
21
  return self.dns_config.get("threads", 25)
@@ -40,32 +27,21 @@ class DNSResolve(InterceptModule):
40
27
  return None, "DNS resolution is disabled in the config"
41
28
 
42
29
  self.minimal = self.dns_config.get("minimal", False)
30
+ self.minimal_rdtypes = ("A", "AAAA", "CNAME")
31
+ if self.minimal:
32
+ self.non_minimal_rdtypes = ()
33
+ else:
34
+ self.non_minimal_rdtypes = tuple([t for t in all_rdtypes if t not in self.minimal_rdtypes])
43
35
  self.dns_search_distance = max(0, int(self.dns_config.get("search_distance", 1)))
44
36
  self._emit_raw_records = None
45
37
 
46
- # event resolution cache
47
- self._event_cache = LFUCache(maxsize=10000)
48
- self._event_cache_locks = NamedLock()
49
-
50
38
  self.host_module = self.HostModule(self.scan)
39
+ self.children_emitted = set()
40
+ self.children_emitted_raw = set()
41
+ self.hosts_resolved = set()
51
42
 
52
43
  return True
53
44
 
54
- @property
55
- def _dns_search_distance(self):
56
- return max(self.scan.scope_search_distance, self.dns_search_distance)
57
-
58
- @property
59
- def emit_raw_records(self):
60
- if self._emit_raw_records is None:
61
- watching_raw_records = any(
62
- ["RAW_DNS_RECORD" in m.get_watched_events() for m in self.scan.modules.values()]
63
- )
64
- omitted_event_types = self.scan.config.get("omit_event_types", [])
65
- omit_raw_records = "RAW_DNS_RECORD" in omitted_event_types
66
- self._emit_raw_records = watching_raw_records or not omit_raw_records
67
- return self._emit_raw_records
68
-
69
45
  async def filter_event(self, event):
70
46
  if (not event.host) or (event.type in ("IP_RANGE",)):
71
47
  return False, "event does not have host attribute"
@@ -73,246 +49,232 @@ class DNSResolve(InterceptModule):
73
49
 
74
50
  async def handle_event(self, event, **kwargs):
75
51
  event_is_ip = self.helpers.is_ip(event.host)
76
- event_host = str(event.host)
77
- event_host_hash = hash(event_host)
78
-
79
- async with self._event_cache_locks.lock(event_host_hash):
80
- # first thing we do is check for wildcards
81
- if not event_is_ip:
82
- if event.scope_distance <= self.scan.scope_search_distance:
83
- await self.handle_wildcard_event(event)
84
-
85
- event_host = str(event.host)
86
- event_host_hash = hash(event_host)
87
-
88
- # we do DNS resolution inside a lock to make sure we don't duplicate work
89
- # once the resolution happens, it will be cached so it doesn't need to happen again
90
- async with self._event_cache_locks.lock(event_host_hash):
91
- try:
92
- # try to get from cache
93
- # the "main host event" is the original parent IP_ADDRESS or DNS_NAME
94
- main_host_event, dns_tags, event_whitelisted, event_blacklisted = self._event_cache[event_host_hash]
95
- # dns_tags, dns_children, event_whitelisted, event_blacklisted = self._event_cache[event_host_hash]
96
- except KeyError:
97
-
98
- main_host_event, dns_tags, event_whitelisted, event_blacklisted, raw_record_events = (
99
- await self.resolve_event(event)
100
- )
101
-
102
- # if we're not blacklisted and we haven't already done it, emit the main host event and all its raw records
103
- main_host_resolved = getattr(main_host_event, "_resolved", False)
104
- if not event_blacklisted and not main_host_resolved:
105
- if event_whitelisted:
106
- self.debug(
107
- f"Making {main_host_event} in-scope because it resolves to an in-scope resource (A/AAAA)"
108
- )
109
- main_host_event.scope_distance = 0
110
- await self.handle_wildcard_event(main_host_event)
111
-
112
- in_dns_scope = -1 < main_host_event.scope_distance < self._dns_search_distance
113
-
114
- if event != main_host_event:
115
- await self.emit_event(main_host_event)
116
- for raw_record_event in raw_record_events:
117
- await self.emit_event(raw_record_event)
118
-
119
- # kill runaway DNS chains
120
- dns_resolve_distance = getattr(event, "dns_resolve_distance", 0)
121
- if dns_resolve_distance >= self.helpers.dns.runaway_limit:
122
- self.debug(
123
- f"Skipping DNS children for {event} because their DNS resolve distances would be greater than the configured value for this scan ({self.helpers.dns.runaway_limit})"
124
- )
125
- main_host_event.dns_children = {}
126
-
127
- # emit DNS children
128
- if not self.minimal:
129
- in_dns_scope = -1 < event.scope_distance < self._dns_search_distance
130
- for rdtype, records in main_host_event.dns_children.items():
131
- module = self._make_dummy_module(rdtype)
132
- for record in records:
133
- parents = main_host_event.get_parents()
134
- for e in parents:
135
- e_is_host = e.type in ("DNS_NAME", "IP_ADDRESS")
136
- e_parent_matches = str(e.parent.host) == str(main_host_event.host)
137
- e_host_matches = str(e.data) == str(record)
138
- e_module_matches = str(e.module) == str(module)
139
- if e_is_host and e_parent_matches and e_host_matches and e_module_matches:
140
- self.trace(
141
- f"TRYING TO EMIT ALREADY-EMITTED {record}:{rdtype} CHILD OF {main_host_event}, parents: {parents}"
142
- )
143
- return
144
- try:
145
- child_event = self.scan.make_event(
146
- record, "DNS_NAME", module=module, parent=main_host_event
147
- )
148
- child_event.discovery_context = f"{rdtype} record for {event.host} contains {child_event.type}: {child_event.host}"
149
- # if it's a hostname and it's only one hop away, mark it as affiliate
150
- if child_event.type == "DNS_NAME" and child_event.scope_distance == 1:
151
- child_event.add_tag("affiliate")
152
- if in_dns_scope or self.preset.in_scope(child_event):
153
- self.debug(f"Queueing DNS child for {event}: {child_event}")
154
- await self.emit_event(child_event)
155
- except ValidationError as e:
156
- self.warning(
157
- f'Event validation failed for DNS child of {main_host_event}: "{record}" ({rdtype}): {e}'
158
- )
159
-
160
- # mark the host as resolved
161
- main_host_event._resolved = True
162
-
163
- # store results in cache
164
- self._event_cache[event_host_hash] = main_host_event, dns_tags, event_whitelisted, event_blacklisted
52
+ if event_is_ip:
53
+ minimal_rdtypes = ("PTR",)
54
+ non_minimal_rdtypes = ()
55
+ else:
56
+ minimal_rdtypes = self.minimal_rdtypes
57
+ non_minimal_rdtypes = self.non_minimal_rdtypes
58
+
59
+ # first, we find or create the main DNS_NAME or IP_ADDRESS associated with this event
60
+ main_host_event, whitelisted, blacklisted, new_event = self.get_dns_parent(event)
61
+ original_tags = set(event.tags)
62
+
63
+ # minimal resolution - first, we resolve A/AAAA records for scope purposes
64
+ if new_event or event is main_host_event:
65
+ await self.resolve_event(main_host_event, types=minimal_rdtypes)
66
+ # are any of its IPs whitelisted/blacklisted?
67
+ whitelisted, blacklisted = self.check_scope(main_host_event)
68
+ if whitelisted and event.scope_distance > 0:
69
+ self.debug(f"Making {main_host_event} in-scope because it resolves to an in-scope resource (A/AAAA)")
70
+ main_host_event.scope_distance = 0
165
71
 
166
72
  # abort if the event resolves to something blacklisted
167
- if event_blacklisted:
168
- return False, f"it has a blacklisted DNS record"
73
+ if blacklisted:
74
+ return False, "it has a blacklisted DNS record"
75
+
76
+ if not event_is_ip:
77
+ # if the event is within our dns search distance, resolve the rest of our records
78
+ if main_host_event.scope_distance < self._dns_search_distance:
79
+ await self.resolve_event(main_host_event, types=non_minimal_rdtypes)
80
+ # check for wildcards if the event is within the scan's search distance
81
+ if new_event and main_host_event.scope_distance <= self.scan.scope_search_distance:
82
+ event_data_changed = await self.handle_wildcard_event(main_host_event)
83
+ if event_data_changed:
84
+ # since data has changed, we check again whether it's a duplicate
85
+ if self.scan.ingress_module.is_incoming_duplicate(event, add=True):
86
+ if not event._graph_important:
87
+ return False, "event was already emitted by its module"
88
+ else:
89
+ self.debug(
90
+ f"Event {event} was already emitted by its module, but it's graph-important so it gets a pass"
91
+ )
169
92
 
170
- # if the event resolves to an in-scope IP, set its scope distance to 0
171
- if event_whitelisted:
172
- self.debug(f"Making {event} in-scope because it resolves to an in-scope resource")
173
- event.scope_distance = 0
174
- await self.handle_wildcard_event(event)
93
+ # if there weren't any DNS children and it's not an IP address, tag as unresolved
94
+ if not main_host_event.raw_dns_records and not event_is_ip:
95
+ main_host_event.add_tag("unresolved")
96
+ main_host_event.type = "DNS_NAME_UNRESOLVED"
175
97
 
176
- # transfer resolved hosts
177
- event._resolved_hosts = main_host_event._resolved_hosts
98
+ # main_host_event.add_tag(f"resolve-distance-{main_host_event.dns_resolve_distance}")
178
99
 
179
- # If the event is unresolved, change its type to DNS_NAME_UNRESOLVED
180
- if event.type == "DNS_NAME" and "unresolved" in event.tags:
181
- event.type = "DNS_NAME_UNRESOLVED"
100
+ dns_tags = main_host_event.tags.difference(original_tags)
182
101
 
183
- async def resolve_event(self, event):
184
- dns_tags = set()
185
- event_whitelisted = False
186
- event_blacklisted = False
102
+ dns_resolve_distance = getattr(main_host_event, "dns_resolve_distance", 0)
103
+ runaway_dns = dns_resolve_distance >= self.helpers.dns.runaway_limit
104
+ if runaway_dns:
105
+ # kill runaway DNS chains
106
+ self.debug(
107
+ f"Skipping DNS children for {event} because their DNS resolve distances would be greater than the configured value for this scan ({self.helpers.dns.runaway_limit})"
108
+ )
109
+ main_host_event.add_tag(f"runaway-dns-{dns_resolve_distance}")
110
+ else:
111
+ # emit dns children
112
+ await self.emit_dns_children_raw(main_host_event, dns_tags)
113
+ if not self.minimal:
114
+ await self.emit_dns_children(main_host_event)
187
115
 
188
- main_host_event = self.get_dns_parent(event)
189
- event_host = str(event.host)
190
- event_is_ip = self.helpers.is_ip(event.host)
116
+ # emit the main DNS_NAME or IP_ADDRESS
117
+ if (
118
+ new_event
119
+ and event is not main_host_event
120
+ and main_host_event.scope_distance <= self._dns_search_distance
121
+ ):
122
+ await self.emit_event(main_host_event)
191
123
 
192
- rdtypes_to_resolve = ()
193
- if event_is_ip:
194
- if not self.minimal:
195
- rdtypes_to_resolve = ("PTR",)
196
- else:
197
- if self.minimal:
198
- rdtypes_to_resolve = ("A", "AAAA", "CNAME")
199
- else:
200
- rdtypes_to_resolve = all_rdtypes
124
+ # transfer scope distance to event
125
+ event.scope_distance = main_host_event.scope_distance
126
+ event._resolved_hosts = main_host_event.resolved_hosts
201
127
 
202
- # if missing from cache, do DNS resolution
203
- queries = [(event_host, rdtype) for rdtype in rdtypes_to_resolve]
204
- error_rdtypes = []
205
- raw_record_events = []
206
- async for (query, rdtype), (answer, errors) in self.helpers.dns.resolve_raw_batch(queries):
207
- if self.emit_raw_records and rdtype not in ("A", "AAAA", "CNAME", "PTR"):
208
- raw_record_event = self.make_event(
209
- {"host": str(event_host), "type": rdtype, "answer": answer.to_text()},
210
- "RAW_DNS_RECORD",
211
- parent=main_host_event,
212
- tags=[f"{rdtype.lower()}-record"],
213
- context=f"{rdtype} lookup on {{event.parent.host}} produced {{event.type}}",
214
- )
215
- raw_record_events.append(raw_record_event)
216
- if errors:
217
- error_rdtypes.append(rdtype)
218
- dns_tags.add(f"{rdtype.lower()}-record")
219
- for _rdtype, host in extract_targets(answer):
128
+ async def handle_wildcard_event(self, event):
129
+ rdtypes = tuple(event.raw_dns_records)
130
+ wildcard_rdtypes = await self.helpers.is_wildcard(
131
+ event.host, rdtypes=rdtypes, raw_dns_records=event.raw_dns_records
132
+ )
133
+ for rdtype, (is_wildcard, wildcard_host) in wildcard_rdtypes.items():
134
+ if is_wildcard is False:
135
+ continue
136
+ elif is_wildcard is True:
137
+ event.add_tag("wildcard")
138
+ wildcard_tag = "wildcard"
139
+ else:
140
+ event.add_tag(f"wildcard-{is_wildcard}")
141
+ wildcard_tag = f"wildcard-{is_wildcard}"
142
+ event.add_tag(f"{rdtype}-{wildcard_tag}")
143
+
144
+ # wildcard event modification (www.evilcorp.com --> _wildcard.evilcorp.com)
145
+ if wildcard_rdtypes and "target" not in event.tags:
146
+ # these are the rdtypes that have wildcards
147
+ wildcard_rdtypes_set = set(wildcard_rdtypes)
148
+ # consider the event a full wildcard if all its records are wildcards
149
+ event_is_wildcard = False
150
+ if wildcard_rdtypes_set:
151
+ event_is_wildcard = all(r[0] is True for r in wildcard_rdtypes.values())
152
+
153
+ if event_is_wildcard:
154
+ if event.type in ("DNS_NAME",) and "_wildcard" not in event.data.split("."):
155
+ wildcard_parent = self.helpers.parent_domain(event.host)
156
+ for rdtype, (_is_wildcard, _parent_domain) in wildcard_rdtypes.items():
157
+ if _is_wildcard:
158
+ wildcard_parent = _parent_domain
159
+ break
160
+ wildcard_data = f"_wildcard.{wildcard_parent}"
161
+ if wildcard_data != event.data:
162
+ self.debug(f'Wildcard detected, changing event.data "{event.data}" --> "{wildcard_data}"')
163
+ event.data = wildcard_data
164
+ return True
165
+ return False
166
+
167
+ async def emit_dns_children(self, event):
168
+ for rdtype, children in event.dns_children.items():
169
+ module = self._make_dummy_module(rdtype)
170
+ for child_host in children:
220
171
  try:
221
- main_host_event.dns_children[_rdtype].add(host)
222
- except KeyError:
223
- main_host_event.dns_children[_rdtype] = {host}
172
+ child_event = self.scan.make_event(
173
+ child_host,
174
+ "DNS_NAME",
175
+ module=module,
176
+ parent=event,
177
+ context=f"{rdtype} record for {event.host} contains {{event.type}}: {{event.host}}",
178
+ )
179
+ except ValidationError as e:
180
+ self.warning(f'Event validation failed for DNS child of {event}: "{child_host}" ({rdtype}): {e}')
181
+ continue
224
182
 
225
- # if there were dns resolution errors, notify the user with tags
226
- for rdtype in error_rdtypes:
227
- if rdtype not in main_host_event.dns_children:
228
- dns_tags.add(f"{rdtype.lower()}-error")
183
+ child_hash = hash(f"{event.host}:{module}:{child_host}")
184
+ # if we haven't emitted this one before
185
+ if child_hash not in self.children_emitted:
186
+ # and it's either in-scope or inside our dns search distance
187
+ if self.preset.in_scope(child_host) or child_event.scope_distance <= self._dns_search_distance:
188
+ self.children_emitted.add(child_hash)
189
+ # if it's a hostname and it's only one hop away, mark it as affiliate
190
+ if child_event.type == "DNS_NAME" and child_event.scope_distance == 1:
191
+ child_event.add_tag("affiliate")
192
+ self.debug(f"Queueing DNS child for {event}: {child_event}")
193
+ await self.emit_event(child_event)
194
+
195
+ async def emit_dns_children_raw(self, event, dns_tags):
196
+ for rdtype, answers in event.raw_dns_records.items():
197
+ rdtype_lower = rdtype.lower()
198
+ tags = {t for t in dns_tags if rdtype_lower in t.split("-")}
199
+ if self.emit_raw_records and rdtype not in ("A", "AAAA", "CNAME", "PTR"):
200
+ for answer in answers:
201
+ text_answer = answer.to_text()
202
+ child_hash = hash(f"{event.host}:{rdtype}:{text_answer}")
203
+ if child_hash not in self.children_emitted_raw:
204
+ self.children_emitted_raw.add(child_hash)
205
+ await self.emit_event(
206
+ {"host": str(event.host), "type": rdtype, "answer": text_answer},
207
+ "RAW_DNS_RECORD",
208
+ parent=event,
209
+ tags=tags,
210
+ context=f"{rdtype} lookup on {{event.parent.host}} produced {{event.type}}",
211
+ )
229
212
 
230
- # if there weren't any DNS children and it's not an IP address, tag as unresolved
231
- if not main_host_event.dns_children and not event_is_ip:
232
- dns_tags.add("unresolved")
233
-
234
- # check DNS children against whitelists and blacklists
235
- for rdtype, children in main_host_event.dns_children.items():
236
- if event_blacklisted:
237
- break
238
- for host in children:
239
- # whitelisting / blacklisting based on resolved hosts
240
- if rdtype in ("A", "AAAA", "CNAME"):
241
- # having a CNAME to an in-scope resource doesn't make you in-scope
242
- if (not event_whitelisted) and rdtype != "CNAME":
213
+ def check_scope(self, event):
214
+ whitelisted = False
215
+ blacklisted = False
216
+ dns_children = getattr(event, "dns_children", {})
217
+ for rdtype in ("A", "AAAA", "CNAME"):
218
+ hosts = dns_children.get(rdtype, [])
219
+ # update resolved hosts
220
+ event.resolved_hosts.update(hosts)
221
+ for host in hosts:
222
+ # having a CNAME to an in-scope host doesn't make you in-scope
223
+ if rdtype != "CNAME":
224
+ if not whitelisted:
243
225
  with suppress(ValidationError):
244
226
  if self.scan.whitelisted(host):
245
- event_whitelisted = True
246
- dns_tags.add(f"dns-whitelisted-{rdtype.lower()}")
247
- # CNAME to a blacklisted resource, means you're blacklisted
227
+ whitelisted = True
228
+ event.add_tag(f"dns-whitelisted-{rdtype}")
229
+ # but a CNAME to a blacklisted host means you're blacklisted
230
+ if not blacklisted:
248
231
  with suppress(ValidationError):
249
232
  if self.scan.blacklisted(host):
250
- dns_tags.add("blacklisted")
251
- dns_tags.add(f"dns-blacklisted-{rdtype.lower()}")
252
- event_blacklisted = True
253
- event_whitelisted = False
254
- break
255
-
256
- # check for private IPs
233
+ blacklisted = True
234
+ event.add_tag("blacklisted")
235
+ event.add_tag(f"dns-blacklisted-{rdtype}")
236
+ if blacklisted:
237
+ whitelisted = False
238
+ return whitelisted, blacklisted
239
+
240
+ async def resolve_event(self, event, types):
241
+ if not types:
242
+ return
243
+ event_host = str(event.host)
244
+ queries = [(event_host, rdtype) for rdtype in types]
245
+ dns_errors = {}
246
+ async for (query, rdtype), (answers, errors) in self.helpers.dns.resolve_raw_batch(queries):
247
+ # errors
248
+ try:
249
+ dns_errors[rdtype].update(errors)
250
+ except KeyError:
251
+ dns_errors[rdtype] = set(errors)
252
+ for answer in answers:
253
+ event.add_tag(f"{rdtype}-record")
254
+ # raw dnspython answers
257
255
  try:
258
- ip = ipaddress.ip_address(host)
259
- if ip.is_private:
260
- dns_tags.add("private-ip")
261
- except ValueError:
262
- continue
263
-
264
- # add DNS tags to main host
265
- for tag in dns_tags:
266
- main_host_event.add_tag(tag)
267
-
268
- # set resolved_hosts attribute
269
- for rdtype, children in main_host_event.dns_children.items():
270
- if rdtype in ("A", "AAAA", "CNAME"):
271
- for host in children:
272
- main_host_event._resolved_hosts.add(host)
273
-
274
- return main_host_event, dns_tags, event_whitelisted, event_blacklisted, raw_record_events
275
-
276
- async def handle_wildcard_event(self, event):
277
- self.debug(f"Entering handle_wildcard_event({event})")
278
- try:
279
- event_host = str(event.host)
280
- # check if the dns name itself is a wildcard entry
281
- wildcard_rdtypes = await self.helpers.is_wildcard(event_host)
282
- for rdtype, (is_wildcard, wildcard_host) in wildcard_rdtypes.items():
283
- if is_wildcard == False:
284
- continue
285
- elif is_wildcard == True:
286
- event.add_tag("wildcard")
287
- wildcard_tag = "wildcard"
288
- elif is_wildcard == None:
289
- wildcard_tag = "error"
290
-
291
- event.add_tag(f"{rdtype.lower()}-{wildcard_tag}")
292
-
293
- # wildcard event modification (www.evilcorp.com --> _wildcard.evilcorp.com)
294
- if wildcard_rdtypes and not "target" in event.tags:
295
- # these are the rdtypes that have wildcards
296
- wildcard_rdtypes_set = set(wildcard_rdtypes)
297
- # consider the event a full wildcard if all its records are wildcards
298
- event_is_wildcard = False
299
- if wildcard_rdtypes_set:
300
- event_is_wildcard = all(r[0] == True for r in wildcard_rdtypes.values())
301
-
302
- if event_is_wildcard:
303
- if event.type in ("DNS_NAME",) and not "_wildcard" in event.data.split("."):
304
- wildcard_parent = self.helpers.parent_domain(event_host)
305
- for rdtype, (_is_wildcard, _parent_domain) in wildcard_rdtypes.items():
306
- if _is_wildcard:
307
- wildcard_parent = _parent_domain
308
- break
309
- wildcard_data = f"_wildcard.{wildcard_parent}"
310
- if wildcard_data != event.data:
311
- self.debug(f'Wildcard detected, changing event.data "{event.data}" --> "{wildcard_data}"')
312
- event.data = wildcard_data
313
-
314
- finally:
315
- self.debug(f"Finished handle_wildcard_event({event})")
256
+ event.raw_dns_records[rdtype].add(answer)
257
+ except KeyError:
258
+ event.raw_dns_records[rdtype] = {answer}
259
+ # hosts
260
+ for _rdtype, host in extract_targets(answer):
261
+ try:
262
+ event.dns_children[_rdtype].add(host)
263
+ except KeyError:
264
+ event.dns_children[_rdtype] = {host}
265
+ # check for private IPs
266
+ try:
267
+ ip = ipaddress.ip_address(host)
268
+ if ip.is_private:
269
+ event.add_tag("private-ip")
270
+ except ValueError:
271
+ continue
272
+
273
+ # tag event with errors
274
+ for rdtype, errors in dns_errors.items():
275
+ # only consider it an error if there weren't any results for that rdtype
276
+ if errors and rdtype not in event.dns_children:
277
+ event.add_tag(f"{rdtype}-error")
316
278
 
317
279
  def get_dns_parent(self, event):
318
280
  """
@@ -320,19 +282,42 @@ class DNSResolve(InterceptModule):
320
282
  """
321
283
  for parent in event.get_parents(include_self=True):
322
284
  if parent.host == event.host and parent.type in ("IP_ADDRESS", "DNS_NAME", "DNS_NAME_UNRESOLVED"):
323
- return parent
285
+ blacklisted = any(t.startswith("dns-blacklisted-") for t in parent.tags)
286
+ whitelisted = any(t.startswith("dns-whitelisted-") for t in parent.tags)
287
+ new_event = parent is event
288
+ return parent, whitelisted, blacklisted, new_event
324
289
  tags = set()
325
290
  if "target" in event.tags:
326
291
  tags.add("target")
327
- return self.scan.make_event(
328
- event.host,
329
- "DNS_NAME",
330
- module=self.host_module,
331
- parent=event,
332
- context="{event.parent.type} has host {event.type}: {event.host}",
333
- tags=tags,
292
+ return (
293
+ self.scan.make_event(
294
+ event.host,
295
+ "DNS_NAME",
296
+ module=self.host_module,
297
+ parent=event,
298
+ context="{event.parent.type} has host {event.type}: {event.host}",
299
+ tags=tags,
300
+ ),
301
+ None,
302
+ None,
303
+ True,
334
304
  )
335
305
 
306
+ @property
307
+ def emit_raw_records(self):
308
+ if self._emit_raw_records is None:
309
+ watching_raw_records = any(
310
+ "RAW_DNS_RECORD" in m.get_watched_events() for m in self.scan.modules.values()
311
+ )
312
+ omitted_event_types = self.scan.config.get("omit_event_types", [])
313
+ omit_raw_records = "RAW_DNS_RECORD" in omitted_event_types
314
+ self._emit_raw_records = watching_raw_records or not omit_raw_records
315
+ return self._emit_raw_records
316
+
317
+ @property
318
+ def _dns_search_distance(self):
319
+ return max(self.scan.scope_search_distance, self.dns_search_distance)
320
+
336
321
  def _make_dummy_module(self, name):
337
322
  try:
338
323
  dummy_module = self.scan.dummy_modules[name]
@@ -342,14 +327,3 @@ class DNSResolve(InterceptModule):
342
327
  dummy_module.suppress_dupes = False
343
328
  self.scan.dummy_modules[name] = dummy_module
344
329
  return dummy_module
345
-
346
- def _dns_child_dedup_hash(self, parent_host, host, rdtype):
347
- # we deduplicate NS records by their parent domain
348
- # because otherwise every DNS_NAME has one, and it gets super messy
349
- if rdtype == "NS":
350
- _, parent_domain = self.helpers.split_domain(parent_host)
351
- return hash(f"{parent_domain}:{host}")
352
- return hash(f"{parent_host}:{host}:{rdtype}")
353
-
354
- def _main_outgoing_dedup_hash(self, event):
355
- return hash(f"{event.host}")