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
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)
|
|
@@ -231,6 +247,9 @@ class Scanner:
|
|
|
231
247
|
|
|
232
248
|
self._dns_strings = None
|
|
233
249
|
self._dns_regexes = None
|
|
250
|
+
self._dns_regexes_yara = None
|
|
251
|
+
self._dns_yara_rules_uncompiled = None
|
|
252
|
+
self._dns_yara_rules = None
|
|
234
253
|
|
|
235
254
|
self.__log_handlers = None
|
|
236
255
|
self._log_handler_backup = []
|
|
@@ -240,6 +259,9 @@ class Scanner:
|
|
|
240
259
|
Creates the scan's output folder, loads its modules, and calls their .setup() methods.
|
|
241
260
|
"""
|
|
242
261
|
|
|
262
|
+
# update the master PID
|
|
263
|
+
SHARED_INTERPRETER_STATE.update_scan_pid()
|
|
264
|
+
|
|
243
265
|
self.helpers.mkdir(self.home)
|
|
244
266
|
if not self._prepped:
|
|
245
267
|
# save scan preset
|
|
@@ -247,7 +269,7 @@ class Scanner:
|
|
|
247
269
|
f.write(self.preset.to_yaml())
|
|
248
270
|
|
|
249
271
|
# log scan overview
|
|
250
|
-
start_msg = f"Scan
|
|
272
|
+
start_msg = f"Scan seeded with {len(self.seeds):,} targets"
|
|
251
273
|
details = []
|
|
252
274
|
if self.whitelist != self.target:
|
|
253
275
|
details.append(f"{len(self.whitelist):,} in whitelist")
|
|
@@ -271,7 +293,9 @@ class Scanner:
|
|
|
271
293
|
self.debug(
|
|
272
294
|
f"Setting intercept module {intercept_module.name}._incoming_event_queue to previous intercept module {prev_intercept_module.name}.outgoing_event_queue"
|
|
273
295
|
)
|
|
274
|
-
|
|
296
|
+
interqueue = asyncio.Queue()
|
|
297
|
+
intercept_module._incoming_event_queue = interqueue
|
|
298
|
+
prev_intercept_module._outgoing_event_queue = interqueue
|
|
275
299
|
|
|
276
300
|
# abort if there are no output modules
|
|
277
301
|
num_output_modules = len([m for m in self.modules.values() if m._type == "output"])
|
|
@@ -303,18 +327,18 @@ class Scanner:
|
|
|
303
327
|
|
|
304
328
|
async def async_start(self):
|
|
305
329
|
""" """
|
|
306
|
-
|
|
307
|
-
|
|
330
|
+
self.start_time = datetime.now()
|
|
331
|
+
self.root_event.data["started_at"] = self.start_time.isoformat()
|
|
308
332
|
try:
|
|
309
333
|
await self._prep()
|
|
310
334
|
|
|
311
335
|
self._start_log_handlers()
|
|
312
|
-
self.trace(f'Ran BBOT {__version__} at {
|
|
336
|
+
self.trace(f'Ran BBOT {__version__} at {self.start_time}, command: {" ".join(sys.argv)}')
|
|
313
337
|
self.trace(f"Target: {self.preset.target.json}")
|
|
314
338
|
self.trace(f"Preset: {self.preset.to_dict(redact_secrets=True)}")
|
|
315
339
|
|
|
316
340
|
if not self.target:
|
|
317
|
-
self.warning(
|
|
341
|
+
self.warning("No scan targets specified")
|
|
318
342
|
|
|
319
343
|
# start status ticker
|
|
320
344
|
self.ticker_task = asyncio.create_task(
|
|
@@ -324,7 +348,7 @@ class Scanner:
|
|
|
324
348
|
self.status = "STARTING"
|
|
325
349
|
|
|
326
350
|
if not self.modules:
|
|
327
|
-
self.error(
|
|
351
|
+
self.error("No modules loaded")
|
|
328
352
|
self.status = "FAILED"
|
|
329
353
|
return
|
|
330
354
|
else:
|
|
@@ -338,7 +362,8 @@ class Scanner:
|
|
|
338
362
|
|
|
339
363
|
# distribute seed events
|
|
340
364
|
self.init_events_task = asyncio.create_task(
|
|
341
|
-
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()",
|
|
342
367
|
)
|
|
343
368
|
|
|
344
369
|
# main scan loop
|
|
@@ -360,16 +385,19 @@ class Scanner:
|
|
|
360
385
|
if self._finished_init and self.modules_finished:
|
|
361
386
|
new_activity = await self.finish()
|
|
362
387
|
if not new_activity:
|
|
388
|
+
self._success = True
|
|
389
|
+
scan_finish_event = await self._mark_finished()
|
|
390
|
+
yield scan_finish_event
|
|
363
391
|
break
|
|
364
392
|
|
|
365
393
|
await asyncio.sleep(0.1)
|
|
366
394
|
|
|
367
|
-
|
|
395
|
+
self._success = True
|
|
368
396
|
|
|
369
397
|
except BaseException as e:
|
|
370
398
|
if self.helpers.in_exception_chain(e, (KeyboardInterrupt, asyncio.CancelledError)):
|
|
371
399
|
self.stop()
|
|
372
|
-
|
|
400
|
+
self._success = True
|
|
373
401
|
else:
|
|
374
402
|
try:
|
|
375
403
|
raise
|
|
@@ -393,26 +421,47 @@ class Scanner:
|
|
|
393
421
|
await self._report()
|
|
394
422
|
await self._cleanup()
|
|
395
423
|
|
|
396
|
-
log_fn = self.hugesuccess
|
|
397
|
-
if self.status == "ABORTING":
|
|
398
|
-
self.status = "ABORTED"
|
|
399
|
-
log_fn = self.hugewarning
|
|
400
|
-
elif failed:
|
|
401
|
-
self.status = "FAILED"
|
|
402
|
-
log_fn = self.critical
|
|
403
|
-
else:
|
|
404
|
-
self.status = "FINISHED"
|
|
405
|
-
|
|
406
|
-
scan_run_time = datetime.now() - scan_start_time
|
|
407
|
-
scan_run_time = self.helpers.human_timedelta(scan_run_time)
|
|
408
|
-
log_fn(f"Scan {self.name} completed in {scan_run_time} with status {self.status}")
|
|
409
|
-
|
|
410
424
|
await self.dispatcher.on_finish(self)
|
|
411
425
|
|
|
412
426
|
self._stop_log_handlers()
|
|
413
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
|
+
|
|
414
463
|
def _start_modules(self):
|
|
415
|
-
self.verbose(
|
|
464
|
+
self.verbose("Starting module worker loops")
|
|
416
465
|
for module in self.modules.values():
|
|
417
466
|
module.start()
|
|
418
467
|
|
|
@@ -436,17 +485,17 @@ class Scanner:
|
|
|
436
485
|
Soft-failed modules are not set to an error state but are also removed if `remove_failed` is True.
|
|
437
486
|
"""
|
|
438
487
|
await self.load_modules()
|
|
439
|
-
self.verbose(
|
|
488
|
+
self.verbose("Setting up modules")
|
|
440
489
|
succeeded = []
|
|
441
490
|
hard_failed = []
|
|
442
491
|
soft_failed = []
|
|
443
492
|
|
|
444
493
|
async for task in self.helpers.as_completed([m._setup() for m in self.modules.values()]):
|
|
445
494
|
module, status, msg = await task
|
|
446
|
-
if status
|
|
495
|
+
if status is True:
|
|
447
496
|
self.debug(f"Setup succeeded for {module.name} ({msg})")
|
|
448
497
|
succeeded.append(module.name)
|
|
449
|
-
elif status
|
|
498
|
+
elif status is False:
|
|
450
499
|
self.warning(f"Setup hard-failed for {module.name}: {msg}")
|
|
451
500
|
self.modules[module.name].set_error_state()
|
|
452
501
|
hard_failed.append(module.name)
|
|
@@ -488,11 +537,11 @@ class Scanner:
|
|
|
488
537
|
"""
|
|
489
538
|
if not self._modules_loaded:
|
|
490
539
|
if not self.preset.modules:
|
|
491
|
-
self.warning(
|
|
540
|
+
self.warning("No modules to load")
|
|
492
541
|
return
|
|
493
542
|
|
|
494
543
|
if not self.preset.scan_modules:
|
|
495
|
-
self.warning(
|
|
544
|
+
self.warning("No scan modules to load")
|
|
496
545
|
|
|
497
546
|
# install module dependencies
|
|
498
547
|
succeeded, failed = await self.helpers.depsinstaller.install(*self.preset.modules)
|
|
@@ -636,7 +685,7 @@ class Scanner:
|
|
|
636
685
|
|
|
637
686
|
if modules_errored:
|
|
638
687
|
self.verbose(
|
|
639
|
-
f'{self.name}: Modules errored: {len(modules_errored):,} ({", ".join(
|
|
688
|
+
f'{self.name}: Modules errored: {len(modules_errored):,} ({", ".join(list(modules_errored))})'
|
|
640
689
|
)
|
|
641
690
|
|
|
642
691
|
num_queued_events = self.num_queued_events
|
|
@@ -673,7 +722,7 @@ class Scanner:
|
|
|
673
722
|
memory_usage = module.memory_usage
|
|
674
723
|
module_memory_usage.append((module.name, memory_usage))
|
|
675
724
|
module_memory_usage.sort(key=lambda x: x[-1], reverse=True)
|
|
676
|
-
self.debug(
|
|
725
|
+
self.debug("MODULE MEMORY USAGE:")
|
|
677
726
|
for module_name, usage in module_memory_usage:
|
|
678
727
|
self.debug(f" - {module_name}: {self.helpers.bytes_to_human(usage)}")
|
|
679
728
|
|
|
@@ -720,12 +769,12 @@ class Scanner:
|
|
|
720
769
|
# Trigger .finished() on every module and start over
|
|
721
770
|
log.info("Finishing scan")
|
|
722
771
|
for module in self.modules.values():
|
|
723
|
-
finished_event = self.make_event(
|
|
772
|
+
finished_event = self.make_event("FINISHED", "FINISHED", dummy=True, tags={module.name})
|
|
724
773
|
await module.queue_event(finished_event)
|
|
725
774
|
self.verbose("Completed finish()")
|
|
726
775
|
return True
|
|
727
|
-
# Return False if no new events were generated since last time
|
|
728
776
|
self.verbose("Completed final finish()")
|
|
777
|
+
# Return False if no new events were generated since last time
|
|
729
778
|
return False
|
|
730
779
|
|
|
731
780
|
def _drain_queues(self):
|
|
@@ -848,6 +897,10 @@ class Scanner:
|
|
|
848
897
|
def target(self):
|
|
849
898
|
return self.preset.target
|
|
850
899
|
|
|
900
|
+
@property
|
|
901
|
+
def seeds(self):
|
|
902
|
+
return self.preset.seeds
|
|
903
|
+
|
|
851
904
|
@property
|
|
852
905
|
def whitelist(self):
|
|
853
906
|
return self.preset.whitelist
|
|
@@ -944,12 +997,25 @@ class Scanner:
|
|
|
944
997
|
}
|
|
945
998
|
```
|
|
946
999
|
"""
|
|
947
|
-
|
|
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)
|
|
948
1015
|
root_event._id = self.id
|
|
949
1016
|
root_event.scope_distance = 0
|
|
950
1017
|
root_event.parent = root_event
|
|
951
1018
|
root_event.module = self._make_dummy_module(name="TARGET", _type="TARGET")
|
|
952
|
-
root_event.discovery_context = f"Scan {self.name} started at {root_event.timestamp}"
|
|
953
1019
|
return root_event
|
|
954
1020
|
|
|
955
1021
|
@property
|
|
@@ -958,14 +1024,13 @@ class Scanner:
|
|
|
958
1024
|
A list of DNS hostname strings generated from the scan target
|
|
959
1025
|
"""
|
|
960
1026
|
if self._dns_strings is None:
|
|
961
|
-
|
|
962
|
-
dns_whitelist =
|
|
963
|
-
|
|
964
|
-
dns_targets = sorted(dns_targets, key=len)
|
|
965
|
-
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()
|
|
966
1030
|
dns_strings = []
|
|
967
|
-
for t in
|
|
968
|
-
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)
|
|
969
1034
|
dns_strings.append(t)
|
|
970
1035
|
self._dns_strings = dns_strings
|
|
971
1036
|
return self._dns_strings
|
|
@@ -1000,7 +1065,7 @@ class Scanner:
|
|
|
1000
1065
|
... for match in regex.finditer(response.text):
|
|
1001
1066
|
... hostname = match.group().lower()
|
|
1002
1067
|
"""
|
|
1003
|
-
if
|
|
1068
|
+
if self._dns_regexes is None:
|
|
1004
1069
|
self._dns_regexes = self._generate_dns_regexes(r"((?:(?:[\w-]+)\.)+")
|
|
1005
1070
|
return self._dns_regexes
|
|
1006
1071
|
|
|
@@ -1009,20 +1074,68 @@ class Scanner:
|
|
|
1009
1074
|
"""
|
|
1010
1075
|
Returns a list of DNS hostname regexes formatted specifically for compatibility with YARA rules.
|
|
1011
1076
|
"""
|
|
1012
|
-
|
|
1077
|
+
if self._dns_regexes_yara is None:
|
|
1078
|
+
self._dns_regexes_yara = self._generate_dns_regexes(r"(([a-z0-9-]+\.)*")
|
|
1079
|
+
return self._dns_regexes_yara
|
|
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
|
|
1013
1118
|
|
|
1014
1119
|
@property
|
|
1015
1120
|
def json(self):
|
|
1016
1121
|
"""
|
|
1017
1122
|
A dictionary representation of the scan including its name, ID, targets, whitelist, blacklist, and modules
|
|
1018
1123
|
"""
|
|
1019
|
-
j =
|
|
1124
|
+
j = {}
|
|
1020
1125
|
for i in ("id", "name"):
|
|
1021
1126
|
v = getattr(self, i, "")
|
|
1022
1127
|
if v:
|
|
1023
1128
|
j.update({i: v})
|
|
1024
1129
|
j["target"] = self.preset.target.json
|
|
1025
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
|
|
1026
1139
|
return j
|
|
1027
1140
|
|
|
1028
1141
|
def debug(self, *args, trace=False, **kwargs):
|
|
@@ -1178,7 +1291,7 @@ class Scanner:
|
|
|
1178
1291
|
context = f"{context.__qualname__}()"
|
|
1179
1292
|
filename, lineno, funcname = self.helpers.get_traceback_details(e)
|
|
1180
1293
|
if self.helpers.in_exception_chain(e, (KeyboardInterrupt,)):
|
|
1181
|
-
log.debug(
|
|
1294
|
+
log.debug("Interrupted")
|
|
1182
1295
|
self.stop()
|
|
1183
1296
|
elif isinstance(e, BrokenPipeError):
|
|
1184
1297
|
log.debug(f"BrokenPipeError in {filename}:{lineno}:{funcname}(): {e}")
|