bbot 2.0.1.4720rc0__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 -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 +11 -9
- 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 +22 -20
- bbot/modules/internal/excavate.py +85 -48
- 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 +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 +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 -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 +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_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 +62 -25
- bbot/test/test_step_1/test_engine.py +17 -17
- bbot/test/test_step_1/test_events.py +183 -28
- 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 +333 -330
- bbot/test/test_step_1/test_modules_basic.py +68 -70
- 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 +4 -3
- bbot/test/test_step_1/test_target.py +243 -145
- bbot/test/test_step_1/test_web.py +14 -8
- 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 +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 +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 +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_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 -14
- 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-2.0.1.4720rc0.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.4720rc0.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.4720rc0.dist-info/RECORD +0 -387
- {bbot-2.0.1.4720rc0.dist-info → bbot-2.3.0.5397rc0.dist-info}/LICENSE +0 -0
- {bbot-2.0.1.4720rc0.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)
|
|
@@ -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}")
|