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.
- bbot/__init__.py +1 -1
- bbot/cli.py +3 -7
- bbot/core/config/files.py +0 -1
- bbot/core/config/logger.py +34 -4
- bbot/core/core.py +21 -6
- bbot/core/engine.py +9 -8
- bbot/core/event/base.py +162 -63
- bbot/core/helpers/bloom.py +10 -3
- bbot/core/helpers/command.py +9 -8
- bbot/core/helpers/depsinstaller/installer.py +89 -32
- bbot/core/helpers/depsinstaller/sudo_askpass.py +38 -2
- bbot/core/helpers/diff.py +10 -10
- bbot/core/helpers/dns/brute.py +18 -14
- bbot/core/helpers/dns/dns.py +16 -15
- bbot/core/helpers/dns/engine.py +159 -132
- bbot/core/helpers/dns/helpers.py +2 -2
- bbot/core/helpers/dns/mock.py +26 -8
- bbot/core/helpers/files.py +1 -1
- bbot/core/helpers/helper.py +7 -4
- bbot/core/helpers/interactsh.py +3 -3
- bbot/core/helpers/libmagic.py +65 -0
- bbot/core/helpers/misc.py +65 -22
- bbot/core/helpers/names_generator.py +17 -3
- bbot/core/helpers/process.py +0 -20
- bbot/core/helpers/regex.py +1 -1
- bbot/core/helpers/regexes.py +12 -6
- bbot/core/helpers/validators.py +1 -2
- bbot/core/helpers/web/client.py +1 -1
- bbot/core/helpers/web/engine.py +18 -13
- bbot/core/helpers/web/web.py +25 -116
- bbot/core/helpers/wordcloud.py +5 -5
- bbot/core/modules.py +36 -27
- bbot/core/multiprocess.py +58 -0
- bbot/core/shared_deps.py +46 -3
- bbot/db/sql/models.py +147 -0
- bbot/defaults.yml +15 -10
- bbot/errors.py +0 -8
- bbot/modules/anubisdb.py +2 -2
- bbot/modules/apkpure.py +63 -0
- bbot/modules/azure_tenant.py +2 -2
- bbot/modules/baddns.py +35 -19
- bbot/modules/baddns_direct.py +92 -0
- bbot/modules/baddns_zone.py +3 -8
- bbot/modules/badsecrets.py +4 -3
- bbot/modules/base.py +195 -51
- bbot/modules/bevigil.py +7 -7
- bbot/modules/binaryedge.py +7 -4
- bbot/modules/bufferoverrun.py +47 -0
- bbot/modules/builtwith.py +6 -10
- bbot/modules/bypass403.py +5 -5
- bbot/modules/c99.py +10 -7
- bbot/modules/censys.py +9 -13
- bbot/modules/certspotter.py +5 -3
- bbot/modules/chaos.py +9 -7
- bbot/modules/code_repository.py +1 -0
- bbot/modules/columbus.py +3 -3
- bbot/modules/crt.py +5 -3
- bbot/modules/deadly/dastardly.py +1 -1
- bbot/modules/deadly/ffuf.py +9 -9
- bbot/modules/deadly/nuclei.py +3 -3
- bbot/modules/deadly/vhost.py +4 -3
- bbot/modules/dehashed.py +1 -1
- bbot/modules/digitorus.py +1 -1
- bbot/modules/dnsbimi.py +145 -0
- bbot/modules/dnscaa.py +3 -3
- bbot/modules/dnsdumpster.py +4 -4
- bbot/modules/dnstlsrpt.py +144 -0
- bbot/modules/docker_pull.py +7 -5
- bbot/modules/dockerhub.py +2 -2
- bbot/modules/dotnetnuke.py +18 -19
- bbot/modules/emailformat.py +1 -1
- bbot/modules/extractous.py +122 -0
- bbot/modules/filedownload.py +9 -7
- bbot/modules/fullhunt.py +7 -4
- bbot/modules/generic_ssrf.py +5 -5
- bbot/modules/github_codesearch.py +3 -2
- bbot/modules/github_org.py +4 -4
- bbot/modules/github_workflows.py +4 -4
- bbot/modules/gitlab.py +2 -5
- bbot/modules/google_playstore.py +93 -0
- bbot/modules/gowitness.py +48 -50
- bbot/modules/hackertarget.py +5 -3
- bbot/modules/host_header.py +5 -5
- bbot/modules/httpx.py +1 -4
- bbot/modules/hunterio.py +3 -9
- bbot/modules/iis_shortnames.py +19 -30
- bbot/modules/internal/cloudcheck.py +27 -12
- bbot/modules/internal/dnsresolve.py +250 -276
- bbot/modules/internal/excavate.py +100 -64
- bbot/modules/internal/speculate.py +42 -33
- bbot/modules/internetdb.py +4 -2
- bbot/modules/ip2location.py +3 -5
- bbot/modules/ipneighbor.py +1 -1
- bbot/modules/ipstack.py +3 -8
- bbot/modules/jadx.py +87 -0
- bbot/modules/leakix.py +11 -10
- bbot/modules/myssl.py +2 -2
- bbot/modules/newsletters.py +2 -2
- bbot/modules/otx.py +5 -3
- bbot/modules/output/asset_inventory.py +7 -7
- bbot/modules/output/base.py +1 -1
- bbot/modules/output/csv.py +1 -2
- bbot/modules/output/http.py +20 -14
- bbot/modules/output/mysql.py +51 -0
- bbot/modules/output/neo4j.py +7 -2
- bbot/modules/output/postgres.py +49 -0
- bbot/modules/output/slack.py +0 -1
- bbot/modules/output/sqlite.py +29 -0
- bbot/modules/output/stdout.py +2 -2
- bbot/modules/output/teams.py +107 -6
- bbot/modules/paramminer_headers.py +5 -8
- bbot/modules/passivetotal.py +13 -13
- bbot/modules/portscan.py +32 -6
- bbot/modules/postman.py +50 -126
- bbot/modules/postman_download.py +220 -0
- bbot/modules/rapiddns.py +3 -8
- bbot/modules/report/asn.py +11 -11
- bbot/modules/robots.py +3 -3
- bbot/modules/securitytrails.py +7 -10
- bbot/modules/securitytxt.py +128 -0
- bbot/modules/shodan_dns.py +7 -9
- bbot/modules/sitedossier.py +1 -1
- bbot/modules/skymem.py +2 -2
- bbot/modules/social.py +2 -1
- bbot/modules/subdomaincenter.py +1 -1
- bbot/modules/subdomainradar.py +160 -0
- bbot/modules/telerik.py +8 -8
- bbot/modules/templates/bucket.py +1 -1
- bbot/modules/templates/github.py +22 -14
- bbot/modules/templates/postman.py +21 -0
- bbot/modules/templates/shodan.py +14 -13
- bbot/modules/templates/sql.py +95 -0
- bbot/modules/templates/subdomain_enum.py +53 -17
- bbot/modules/templates/webhook.py +2 -4
- bbot/modules/trickest.py +8 -37
- bbot/modules/trufflehog.py +18 -3
- bbot/modules/url_manipulation.py +3 -3
- bbot/modules/urlscan.py +1 -1
- bbot/modules/viewdns.py +1 -1
- bbot/modules/virustotal.py +8 -30
- bbot/modules/wafw00f.py +1 -1
- bbot/modules/wayback.py +1 -1
- bbot/modules/wpscan.py +17 -11
- bbot/modules/zoomeye.py +11 -6
- bbot/presets/baddns-thorough.yml +12 -0
- bbot/presets/fast.yml +16 -0
- bbot/presets/kitchen-sink.yml +1 -0
- bbot/presets/spider.yml +4 -0
- bbot/presets/subdomain-enum.yml +7 -7
- bbot/scanner/manager.py +5 -16
- bbot/scanner/preset/args.py +44 -26
- bbot/scanner/preset/environ.py +7 -2
- bbot/scanner/preset/path.py +7 -4
- bbot/scanner/preset/preset.py +36 -23
- bbot/scanner/scanner.py +176 -63
- bbot/scanner/target.py +236 -434
- bbot/scripts/docs.py +1 -1
- bbot/test/bbot_fixtures.py +22 -3
- bbot/test/conftest.py +132 -100
- bbot/test/fastapi_test.py +17 -0
- bbot/test/owasp_mastg.apk +0 -0
- bbot/test/run_tests.sh +4 -4
- bbot/test/test.conf +2 -0
- bbot/test/test_step_1/test_bbot_fastapi.py +82 -0
- bbot/test/test_step_1/test_bloom_filter.py +2 -0
- bbot/test/test_step_1/test_cli.py +138 -64
- bbot/test/test_step_1/test_dns.py +392 -70
- bbot/test/test_step_1/test_engine.py +17 -17
- bbot/test/test_step_1/test_events.py +203 -37
- bbot/test/test_step_1/test_helpers.py +64 -28
- bbot/test/test_step_1/test_manager_deduplication.py +1 -1
- bbot/test/test_step_1/test_manager_scope_accuracy.py +336 -338
- bbot/test/test_step_1/test_modules_basic.py +69 -71
- bbot/test/test_step_1/test_presets.py +184 -96
- bbot/test/test_step_1/test_python_api.py +7 -2
- bbot/test/test_step_1/test_regexes.py +35 -5
- bbot/test/test_step_1/test_scan.py +39 -5
- bbot/test/test_step_1/test_scope.py +5 -4
- bbot/test/test_step_1/test_target.py +243 -145
- bbot/test/test_step_1/test_web.py +48 -10
- bbot/test/test_step_2/module_tests/base.py +17 -20
- bbot/test/test_step_2/module_tests/test_module_anubisdb.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_apkpure.py +71 -0
- bbot/test/test_step_2/module_tests/test_module_asset_inventory.py +0 -1
- bbot/test/test_step_2/module_tests/test_module_azure_realm.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_baddns.py +6 -6
- bbot/test/test_step_2/module_tests/test_module_baddns_direct.py +62 -0
- bbot/test/test_step_2/module_tests/test_module_bevigil.py +29 -2
- bbot/test/test_step_2/module_tests/test_module_binaryedge.py +4 -2
- bbot/test/test_step_2/module_tests/test_module_bucket_amazon.py +2 -2
- bbot/test/test_step_2/module_tests/test_module_bucket_azure.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_bufferoverrun.py +35 -0
- bbot/test/test_step_2/module_tests/test_module_builtwith.py +2 -2
- bbot/test/test_step_2/module_tests/test_module_bypass403.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_c99.py +126 -0
- bbot/test/test_step_2/module_tests/test_module_censys.py +4 -1
- bbot/test/test_step_2/module_tests/test_module_cloudcheck.py +4 -0
- bbot/test/test_step_2/module_tests/test_module_code_repository.py +11 -1
- bbot/test/test_step_2/module_tests/test_module_columbus.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_credshed.py +3 -3
- bbot/test/test_step_2/module_tests/test_module_dastardly.py +2 -1
- bbot/test/test_step_2/module_tests/test_module_dehashed.py +2 -2
- bbot/test/test_step_2/module_tests/test_module_digitorus.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_discord.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_dnsbimi.py +103 -0
- bbot/test/test_step_2/module_tests/test_module_dnsbrute.py +9 -10
- bbot/test/test_step_2/module_tests/test_module_dnsbrute_mutations.py +1 -2
- bbot/test/test_step_2/module_tests/test_module_dnscommonsrv.py +1 -2
- bbot/test/test_step_2/module_tests/test_module_dnsdumpster.py +4 -4
- bbot/test/test_step_2/module_tests/test_module_dnstlsrpt.py +64 -0
- bbot/test/test_step_2/module_tests/test_module_dotnetnuke.py +0 -8
- bbot/test/test_step_2/module_tests/test_module_excavate.py +17 -37
- bbot/test/test_step_2/module_tests/test_module_extractous.py +54 -0
- bbot/test/test_step_2/module_tests/test_module_ffuf_shortnames.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_filedownload.py +14 -14
- bbot/test/test_step_2/module_tests/test_module_git_clone.py +2 -2
- bbot/test/test_step_2/module_tests/test_module_github_org.py +19 -8
- bbot/test/test_step_2/module_tests/test_module_github_workflows.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_gitlab.py +9 -4
- bbot/test/test_step_2/module_tests/test_module_google_playstore.py +83 -0
- bbot/test/test_step_2/module_tests/test_module_gowitness.py +4 -4
- bbot/test/test_step_2/module_tests/test_module_host_header.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_http.py +4 -4
- bbot/test/test_step_2/module_tests/test_module_httpx.py +10 -8
- bbot/test/test_step_2/module_tests/test_module_hunterio.py +68 -4
- bbot/test/test_step_2/module_tests/test_module_jadx.py +55 -0
- bbot/test/test_step_2/module_tests/test_module_json.py +24 -11
- bbot/test/test_step_2/module_tests/test_module_leakix.py +7 -3
- bbot/test/test_step_2/module_tests/test_module_mysql.py +76 -0
- bbot/test/test_step_2/module_tests/test_module_myssl.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_neo4j.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_newsletters.py +6 -6
- bbot/test/test_step_2/module_tests/test_module_ntlm.py +7 -7
- bbot/test/test_step_2/module_tests/test_module_oauth.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_otx.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_paramminer_cookies.py +1 -2
- bbot/test/test_step_2/module_tests/test_module_paramminer_getparams.py +0 -6
- bbot/test/test_step_2/module_tests/test_module_paramminer_headers.py +2 -9
- bbot/test/test_step_2/module_tests/test_module_passivetotal.py +3 -1
- bbot/test/test_step_2/module_tests/test_module_portscan.py +9 -8
- bbot/test/test_step_2/module_tests/test_module_postgres.py +74 -0
- bbot/test/test_step_2/module_tests/test_module_postman.py +84 -253
- bbot/test/test_step_2/module_tests/test_module_postman_download.py +439 -0
- bbot/test/test_step_2/module_tests/test_module_rapiddns.py +93 -1
- bbot/test/test_step_2/module_tests/test_module_securitytxt.py +50 -0
- bbot/test/test_step_2/module_tests/test_module_shodan_dns.py +20 -1
- bbot/test/test_step_2/module_tests/test_module_sitedossier.py +2 -2
- bbot/test/test_step_2/module_tests/test_module_smuggler.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_social.py +11 -1
- bbot/test/test_step_2/module_tests/test_module_speculate.py +2 -6
- bbot/test/test_step_2/module_tests/test_module_splunk.py +4 -4
- bbot/test/test_step_2/module_tests/test_module_sqlite.py +18 -0
- bbot/test/test_step_2/module_tests/test_module_sslcert.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_stdout.py +5 -3
- bbot/test/test_step_2/module_tests/test_module_subdomaincenter.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_subdomainradar.py +208 -0
- bbot/test/test_step_2/module_tests/test_module_subdomains.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_teams.py +8 -6
- bbot/test/test_step_2/module_tests/test_module_telerik.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_trufflehog.py +317 -11
- bbot/test/test_step_2/module_tests/test_module_wayback.py +1 -1
- bbot/test/test_step_2/template_tests/test_template_subdomain_enum.py +135 -0
- {bbot-2.0.1.4654rc0.dist-info → bbot-2.3.0.5397rc0.dist-info}/METADATA +48 -18
- bbot-2.3.0.5397rc0.dist-info/RECORD +421 -0
- {bbot-2.0.1.4654rc0.dist-info → bbot-2.3.0.5397rc0.dist-info}/WHEEL +1 -1
- bbot/modules/unstructured.py +0 -163
- bbot/test/test_step_2/module_tests/test_module_unstructured.py +0 -102
- bbot-2.0.1.4654rc0.dist-info/RECORD +0 -385
- {bbot-2.0.1.4654rc0.dist-info → bbot-2.3.0.5397rc0.dist-info}/LICENSE +0 -0
- {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(
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
main_host_event
|
|
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
|
|
168
|
-
return False,
|
|
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
|
|
171
|
-
if
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
)
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
for host in
|
|
239
|
-
#
|
|
240
|
-
if rdtype
|
|
241
|
-
|
|
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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
|
|
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
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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}")
|