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