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/core/event/base.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import io
|
|
2
2
|
import re
|
|
3
|
+
import uuid
|
|
3
4
|
import json
|
|
4
5
|
import base64
|
|
5
6
|
import logging
|
|
@@ -24,6 +25,7 @@ from bbot.core.helpers import (
|
|
|
24
25
|
is_domain,
|
|
25
26
|
is_subdomain,
|
|
26
27
|
is_ip,
|
|
28
|
+
is_ip_type,
|
|
27
29
|
is_ptr,
|
|
28
30
|
is_uri,
|
|
29
31
|
url_depth,
|
|
@@ -58,9 +60,10 @@ class BaseEvent:
|
|
|
58
60
|
|
|
59
61
|
Attributes:
|
|
60
62
|
type (str): Specifies the type of the event, e.g., `IP_ADDRESS`, `DNS_NAME`.
|
|
61
|
-
id (str):
|
|
63
|
+
id (str): An identifier for the event (event type + sha1 hash of data). NOT universally unique.
|
|
64
|
+
uuid (UUID): A universally unique identifier for the event.
|
|
62
65
|
data (str or dict): The main data for the event, e.g., a URL or IP address.
|
|
63
|
-
data_graph (str): Representation of `self.data` for
|
|
66
|
+
data_graph (str): Representation of `self.data` for graph nodes (e.g. Neo4j).
|
|
64
67
|
data_human (str): Representation of `self.data` for human output.
|
|
65
68
|
data_id (str): Representation of `self.data` used to calculate the event's ID (and ultimately its hash, which is used for deduplication)
|
|
66
69
|
data_json (str): Representation of `self.data` to be used in JSON serialization.
|
|
@@ -75,6 +78,7 @@ class BaseEvent:
|
|
|
75
78
|
resolved_hosts (list of str): List of hosts to which the event data resolves, applicable for URLs and DNS names.
|
|
76
79
|
parent (BaseEvent): The parent event that led to the discovery of this event.
|
|
77
80
|
parent_id (str): The `id` attribute of the parent event.
|
|
81
|
+
parent_uuid (str): The `uuid` attribute of the parent event.
|
|
78
82
|
tags (set of str): Descriptive tags for the event, e.g., `mx-record`, `in-scope`.
|
|
79
83
|
module (BaseModule): The module that discovered the event.
|
|
80
84
|
module_sequence (str): The sequence of modules that participated in the discovery.
|
|
@@ -154,7 +158,7 @@ class BaseEvent:
|
|
|
154
158
|
Raises:
|
|
155
159
|
ValidationError: If either `scan` or `parent` are not specified and `_dummy` is False.
|
|
156
160
|
"""
|
|
157
|
-
|
|
161
|
+
self._uuid = uuid.uuid4()
|
|
158
162
|
self._id = None
|
|
159
163
|
self._hash = None
|
|
160
164
|
self._data = None
|
|
@@ -166,11 +170,13 @@ class BaseEvent:
|
|
|
166
170
|
self._parent = None
|
|
167
171
|
self._priority = None
|
|
168
172
|
self._parent_id = None
|
|
173
|
+
self._parent_uuid = None
|
|
169
174
|
self._host_original = None
|
|
170
175
|
self._scope_distance = None
|
|
171
176
|
self._module_priority = None
|
|
172
177
|
self._resolved_hosts = set()
|
|
173
|
-
self.dns_children =
|
|
178
|
+
self.dns_children = {}
|
|
179
|
+
self.raw_dns_records = {}
|
|
174
180
|
self._discovery_context = ""
|
|
175
181
|
self._discovery_context_regex = re.compile(r"\{(?:event|module)[^}]*\}")
|
|
176
182
|
self.web_spider_distance = 0
|
|
@@ -197,7 +203,7 @@ class BaseEvent:
|
|
|
197
203
|
# self.scan holds the instantiated scan object (for helpers, etc.)
|
|
198
204
|
self.scan = scan
|
|
199
205
|
if (not self.scan) and (not self._dummy):
|
|
200
|
-
raise ValidationError(
|
|
206
|
+
raise ValidationError("Must specify scan")
|
|
201
207
|
# self.scans holds a list of scan IDs from scans that encountered this event
|
|
202
208
|
self.scans = []
|
|
203
209
|
if scans is not None:
|
|
@@ -216,7 +222,7 @@ class BaseEvent:
|
|
|
216
222
|
|
|
217
223
|
self.parent = parent
|
|
218
224
|
if (not self.parent) and (not self._dummy):
|
|
219
|
-
raise ValidationError(
|
|
225
|
+
raise ValidationError("Must specify event parent")
|
|
220
226
|
|
|
221
227
|
if tags is not None:
|
|
222
228
|
for tag in tags:
|
|
@@ -295,9 +301,9 @@ class BaseEvent:
|
|
|
295
301
|
The purpose of internal events is to enable speculative/explorative discovery without cluttering
|
|
296
302
|
the console with irrelevant or uninteresting events.
|
|
297
303
|
"""
|
|
298
|
-
if not
|
|
304
|
+
if value not in (True, False):
|
|
299
305
|
raise ValueError(f'"internal" must be boolean, not {type(value)}')
|
|
300
|
-
if value
|
|
306
|
+
if value is True:
|
|
301
307
|
self.add_tag("internal")
|
|
302
308
|
else:
|
|
303
309
|
self.remove_tag("internal")
|
|
@@ -335,6 +341,21 @@ class BaseEvent:
|
|
|
335
341
|
return self.host
|
|
336
342
|
return self._host_original
|
|
337
343
|
|
|
344
|
+
@property
|
|
345
|
+
def host_filterable(self):
|
|
346
|
+
"""
|
|
347
|
+
A string version of the event that's used for regex-based blacklisting.
|
|
348
|
+
|
|
349
|
+
For example, the user can specify "REGEX:.*.evilcorp.com" in their blacklist, and this regex
|
|
350
|
+
will be applied against this property.
|
|
351
|
+
"""
|
|
352
|
+
parsed_url = getattr(self, "parsed_url", None)
|
|
353
|
+
if parsed_url is not None:
|
|
354
|
+
return parsed_url.geturl()
|
|
355
|
+
if self.host is not None:
|
|
356
|
+
return str(self.host)
|
|
357
|
+
return ""
|
|
358
|
+
|
|
338
359
|
@property
|
|
339
360
|
def port(self):
|
|
340
361
|
self.host
|
|
@@ -347,6 +368,12 @@ class BaseEvent:
|
|
|
347
368
|
return 80
|
|
348
369
|
return self._port
|
|
349
370
|
|
|
371
|
+
@property
|
|
372
|
+
def netloc(self):
|
|
373
|
+
if self.host and is_ip_type(self.host, network=False):
|
|
374
|
+
return make_netloc(self.host, self.port)
|
|
375
|
+
return None
|
|
376
|
+
|
|
350
377
|
@property
|
|
351
378
|
def host_stem(self):
|
|
352
379
|
"""
|
|
@@ -379,10 +406,20 @@ class BaseEvent:
|
|
|
379
406
|
"""
|
|
380
407
|
This event's full discovery context, including those of all its parents
|
|
381
408
|
"""
|
|
382
|
-
|
|
409
|
+
discovery_path = []
|
|
410
|
+
if self.parent is not None and self.parent is not self:
|
|
411
|
+
discovery_path = self.parent.discovery_path
|
|
412
|
+
return discovery_path + [self.discovery_context]
|
|
413
|
+
|
|
414
|
+
@property
|
|
415
|
+
def parent_chain(self):
|
|
416
|
+
"""
|
|
417
|
+
This event's full discovery context, including those of all its parents
|
|
418
|
+
"""
|
|
419
|
+
parent_chain = []
|
|
383
420
|
if self.parent is not None and self.parent is not self:
|
|
384
|
-
|
|
385
|
-
return
|
|
421
|
+
parent_chain = self.parent.parent_chain
|
|
422
|
+
return parent_chain + [str(self.uuid)]
|
|
386
423
|
|
|
387
424
|
@property
|
|
388
425
|
def words(self):
|
|
@@ -425,11 +462,6 @@ class BaseEvent:
|
|
|
425
462
|
no_host_information = not bool(self.host)
|
|
426
463
|
return self._always_emit or always_emit_tags or no_host_information
|
|
427
464
|
|
|
428
|
-
@property
|
|
429
|
-
def quick_emit(self):
|
|
430
|
-
no_host_information = not bool(self.host)
|
|
431
|
-
return self._quick_emit or no_host_information
|
|
432
|
-
|
|
433
465
|
@property
|
|
434
466
|
def id(self):
|
|
435
467
|
"""
|
|
@@ -439,6 +471,13 @@ class BaseEvent:
|
|
|
439
471
|
self._id = f"{self.type}:{self.data_hash.hex()}"
|
|
440
472
|
return self._id
|
|
441
473
|
|
|
474
|
+
@property
|
|
475
|
+
def uuid(self):
|
|
476
|
+
"""
|
|
477
|
+
A universally unique identifier for the event
|
|
478
|
+
"""
|
|
479
|
+
return f"{self.type}:{self._uuid}"
|
|
480
|
+
|
|
442
481
|
@property
|
|
443
482
|
def data_hash(self):
|
|
444
483
|
"""
|
|
@@ -479,12 +518,13 @@ class BaseEvent:
|
|
|
479
518
|
for t in list(self.tags):
|
|
480
519
|
if t.startswith("distance-"):
|
|
481
520
|
self.remove_tag(t)
|
|
482
|
-
if
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
521
|
+
if self.host:
|
|
522
|
+
if scope_distance == 0:
|
|
523
|
+
self.add_tag("in-scope")
|
|
524
|
+
self.remove_tag("affiliate")
|
|
525
|
+
else:
|
|
526
|
+
self.remove_tag("in-scope")
|
|
527
|
+
self.add_tag(f"distance-{new_scope_distance}")
|
|
488
528
|
self._scope_distance = new_scope_distance
|
|
489
529
|
# apply recursively to parent events
|
|
490
530
|
parent_scope_distance = getattr(self.parent, "scope_distance", None)
|
|
@@ -556,6 +596,13 @@ class BaseEvent:
|
|
|
556
596
|
return parent_id
|
|
557
597
|
return self._parent_id
|
|
558
598
|
|
|
599
|
+
@property
|
|
600
|
+
def parent_uuid(self):
|
|
601
|
+
parent_uuid = getattr(self.get_parent(), "uuid", None)
|
|
602
|
+
if parent_uuid is not None:
|
|
603
|
+
return parent_uuid
|
|
604
|
+
return self._parent_uuid
|
|
605
|
+
|
|
559
606
|
@property
|
|
560
607
|
def validators(self):
|
|
561
608
|
"""
|
|
@@ -722,12 +769,12 @@ class BaseEvent:
|
|
|
722
769
|
Returns:
|
|
723
770
|
dict: JSON-serializable dictionary representation of the event object.
|
|
724
771
|
"""
|
|
772
|
+
j = {}
|
|
725
773
|
# type, ID, scope description
|
|
726
|
-
|
|
727
|
-
for i in ("type", "id", "scope_description"):
|
|
774
|
+
for i in ("type", "id", "uuid", "scope_description", "netloc"):
|
|
728
775
|
v = getattr(self, i, "")
|
|
729
776
|
if v:
|
|
730
|
-
j.update({i: v})
|
|
777
|
+
j.update({i: str(v)})
|
|
731
778
|
# event data
|
|
732
779
|
data_attr = getattr(self, f"data_{mode}", None)
|
|
733
780
|
if data_attr is not None:
|
|
@@ -743,6 +790,8 @@ class BaseEvent:
|
|
|
743
790
|
j["host"] = str(self.host)
|
|
744
791
|
j["resolved_hosts"] = sorted(str(h) for h in self.resolved_hosts)
|
|
745
792
|
j["dns_children"] = {k: list(v) for k, v in self.dns_children.items()}
|
|
793
|
+
if isinstance(self.port, int):
|
|
794
|
+
j["port"] = self.port
|
|
746
795
|
# web spider distance
|
|
747
796
|
web_spider_distance = getattr(self, "web_spider_distance", None)
|
|
748
797
|
if web_spider_distance is not None:
|
|
@@ -758,6 +807,9 @@ class BaseEvent:
|
|
|
758
807
|
parent_id = self.parent_id
|
|
759
808
|
if parent_id:
|
|
760
809
|
j["parent"] = parent_id
|
|
810
|
+
parent_uuid = self.parent_uuid
|
|
811
|
+
if parent_uuid:
|
|
812
|
+
j["parent_uuid"] = parent_uuid
|
|
761
813
|
# tags
|
|
762
814
|
if self.tags:
|
|
763
815
|
j.update({"tags": list(self.tags)})
|
|
@@ -770,6 +822,7 @@ class BaseEvent:
|
|
|
770
822
|
# discovery context
|
|
771
823
|
j["discovery_context"] = self.discovery_context
|
|
772
824
|
j["discovery_path"] = self.discovery_path
|
|
825
|
+
j["parent_chain"] = self.parent_chain
|
|
773
826
|
|
|
774
827
|
# normalize non-primitive python objects
|
|
775
828
|
for k, v in list(j.items()):
|
|
@@ -909,6 +962,10 @@ class SCAN(BaseEvent):
|
|
|
909
962
|
def discovery_path(self):
|
|
910
963
|
return []
|
|
911
964
|
|
|
965
|
+
@property
|
|
966
|
+
def parent_chain(self):
|
|
967
|
+
return []
|
|
968
|
+
|
|
912
969
|
|
|
913
970
|
class FINISHED(BaseEvent):
|
|
914
971
|
"""
|
|
@@ -956,18 +1013,20 @@ class ClosestHostEvent(DictHostEvent):
|
|
|
956
1013
|
if not self.host:
|
|
957
1014
|
for parent in self.get_parents(include_self=True):
|
|
958
1015
|
# inherit closest URL
|
|
959
|
-
if
|
|
1016
|
+
if "url" not in self.data:
|
|
960
1017
|
parent_url = getattr(parent, "parsed_url", None)
|
|
961
1018
|
if parent_url is not None:
|
|
962
1019
|
self.data["url"] = parent_url.geturl()
|
|
963
1020
|
# inherit closest path
|
|
964
|
-
if
|
|
1021
|
+
if "path" not in self.data and isinstance(parent.data, dict) and not parent.type == "HTTP_RESPONSE":
|
|
965
1022
|
parent_path = parent.data.get("path", None)
|
|
966
1023
|
if parent_path is not None:
|
|
967
1024
|
self.data["path"] = parent_path
|
|
968
1025
|
# inherit closest host
|
|
969
1026
|
if parent.host:
|
|
970
1027
|
self.data["host"] = str(parent.host)
|
|
1028
|
+
# we do this to refresh the hash
|
|
1029
|
+
self.data = self.data
|
|
971
1030
|
break
|
|
972
1031
|
# die if we still haven't found a host
|
|
973
1032
|
if not self.host:
|
|
@@ -977,20 +1036,21 @@ class ClosestHostEvent(DictHostEvent):
|
|
|
977
1036
|
class DictPathEvent(DictEvent):
|
|
978
1037
|
def sanitize_data(self, data):
|
|
979
1038
|
new_data = dict(data)
|
|
1039
|
+
new_data["path"] = str(new_data["path"])
|
|
980
1040
|
file_blobs = getattr(self.scan, "_file_blobs", False)
|
|
981
1041
|
folder_blobs = getattr(self.scan, "_folder_blobs", False)
|
|
982
1042
|
blob = None
|
|
983
1043
|
try:
|
|
984
|
-
|
|
985
|
-
if
|
|
1044
|
+
self._data_path = Path(data["path"])
|
|
1045
|
+
if self._data_path.is_file():
|
|
986
1046
|
self.add_tag("file")
|
|
987
1047
|
if file_blobs:
|
|
988
|
-
with open(
|
|
1048
|
+
with open(self._data_path, "rb") as file:
|
|
989
1049
|
blob = file.read()
|
|
990
|
-
elif
|
|
1050
|
+
elif self._data_path.is_dir():
|
|
991
1051
|
self.add_tag("folder")
|
|
992
1052
|
if folder_blobs:
|
|
993
|
-
blob = self._tar_directory(
|
|
1053
|
+
blob = self._tar_directory(self._data_path)
|
|
994
1054
|
except KeyError:
|
|
995
1055
|
pass
|
|
996
1056
|
if blob:
|
|
@@ -1053,13 +1113,23 @@ class DnsEvent(BaseEvent):
|
|
|
1053
1113
|
if parent_module_type == "DNS":
|
|
1054
1114
|
self.dns_resolve_distance += 1
|
|
1055
1115
|
# self.add_tag(f"resolve-distance-{self.dns_resolve_distance}")
|
|
1116
|
+
# tag subdomain / domain
|
|
1117
|
+
if is_subdomain(self.host):
|
|
1118
|
+
self.add_tag("subdomain")
|
|
1119
|
+
elif is_domain(self.host):
|
|
1120
|
+
self.add_tag("domain")
|
|
1121
|
+
# tag private IP
|
|
1122
|
+
try:
|
|
1123
|
+
if self.host.is_private:
|
|
1124
|
+
self.add_tag("private-ip")
|
|
1125
|
+
except AttributeError:
|
|
1126
|
+
pass
|
|
1056
1127
|
|
|
1057
1128
|
|
|
1058
1129
|
class IP_RANGE(DnsEvent):
|
|
1059
1130
|
def __init__(self, *args, **kwargs):
|
|
1060
1131
|
super().__init__(*args, **kwargs)
|
|
1061
|
-
|
|
1062
|
-
self.add_tag(f"ipv{net.version}")
|
|
1132
|
+
self.add_tag(f"ipv{self.host.version}")
|
|
1063
1133
|
|
|
1064
1134
|
def sanitize_data(self, data):
|
|
1065
1135
|
return str(ipaddress.ip_network(str(data), strict=False))
|
|
@@ -1069,13 +1139,6 @@ class IP_RANGE(DnsEvent):
|
|
|
1069
1139
|
|
|
1070
1140
|
|
|
1071
1141
|
class DNS_NAME(DnsEvent):
|
|
1072
|
-
def __init__(self, *args, **kwargs):
|
|
1073
|
-
super().__init__(*args, **kwargs)
|
|
1074
|
-
if is_subdomain(self.data):
|
|
1075
|
-
self.add_tag("subdomain")
|
|
1076
|
-
elif is_domain(self.data):
|
|
1077
|
-
self.add_tag("domain")
|
|
1078
|
-
|
|
1079
1142
|
def sanitize_data(self, data):
|
|
1080
1143
|
return validators.validate_host(data)
|
|
1081
1144
|
|
|
@@ -1117,7 +1180,6 @@ class URL_UNVERIFIED(BaseEvent):
|
|
|
1117
1180
|
self.num_redirects = getattr(self.parent, "num_redirects", 0)
|
|
1118
1181
|
|
|
1119
1182
|
def _data_id(self):
|
|
1120
|
-
|
|
1121
1183
|
data = super()._data_id()
|
|
1122
1184
|
|
|
1123
1185
|
# remove the querystring for URL/URL_UNVERIFIED events, because we will conditionally add it back in (based on settings)
|
|
@@ -1165,7 +1227,7 @@ class URL_UNVERIFIED(BaseEvent):
|
|
|
1165
1227
|
|
|
1166
1228
|
def add_tag(self, tag):
|
|
1167
1229
|
host_same_as_parent = self.parent and self.host == self.parent.host
|
|
1168
|
-
if tag == "spider-danger" and host_same_as_parent and
|
|
1230
|
+
if tag == "spider-danger" and host_same_as_parent and "spider-danger" not in self.tags:
|
|
1169
1231
|
# increment the web spider distance
|
|
1170
1232
|
if self.type == "URL_UNVERIFIED":
|
|
1171
1233
|
self.web_spider_distance += 1
|
|
@@ -1187,7 +1249,7 @@ class URL_UNVERIFIED(BaseEvent):
|
|
|
1187
1249
|
|
|
1188
1250
|
def _words(self):
|
|
1189
1251
|
first_elem = self.parsed_url.path.lstrip("/").split("/")[0]
|
|
1190
|
-
if
|
|
1252
|
+
if "." not in first_elem:
|
|
1191
1253
|
return extract_words(first_elem)
|
|
1192
1254
|
return set()
|
|
1193
1255
|
|
|
@@ -1204,7 +1266,6 @@ class URL_UNVERIFIED(BaseEvent):
|
|
|
1204
1266
|
|
|
1205
1267
|
|
|
1206
1268
|
class URL(URL_UNVERIFIED):
|
|
1207
|
-
|
|
1208
1269
|
def __init__(self, *args, **kwargs):
|
|
1209
1270
|
super().__init__(*args, **kwargs)
|
|
1210
1271
|
|
|
@@ -1216,7 +1277,7 @@ class URL(URL_UNVERIFIED):
|
|
|
1216
1277
|
@property
|
|
1217
1278
|
def resolved_hosts(self):
|
|
1218
1279
|
# TODO: remove this when we rip out httpx
|
|
1219
|
-
return
|
|
1280
|
+
return {".".join(i.split("-")[1:]) for i in self.tags if i.startswith("ip-")}
|
|
1220
1281
|
|
|
1221
1282
|
@property
|
|
1222
1283
|
def pretty_string(self):
|
|
@@ -1246,7 +1307,6 @@ class URL_HINT(URL_UNVERIFIED):
|
|
|
1246
1307
|
|
|
1247
1308
|
|
|
1248
1309
|
class WEB_PARAMETER(DictHostEvent):
|
|
1249
|
-
|
|
1250
1310
|
def _data_id(self):
|
|
1251
1311
|
# dedupe by url:name:param_type
|
|
1252
1312
|
url = self.data.get("url", "")
|
|
@@ -1495,14 +1555,39 @@ class WAF(DictHostEvent):
|
|
|
1495
1555
|
|
|
1496
1556
|
|
|
1497
1557
|
class FILESYSTEM(DictPathEvent):
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1558
|
+
def __init__(self, *args, **kwargs):
|
|
1559
|
+
super().__init__(*args, **kwargs)
|
|
1560
|
+
if self._data_path.is_file():
|
|
1561
|
+
# detect type of file content using magic
|
|
1562
|
+
from bbot.core.helpers.libmagic import get_magic_info, get_compression
|
|
1563
|
+
|
|
1564
|
+
extension, mime_type, description, confidence = get_magic_info(self.data["path"])
|
|
1565
|
+
self.data["magic_extension"] = extension
|
|
1566
|
+
self.data["magic_mime_type"] = mime_type
|
|
1567
|
+
self.data["magic_description"] = description
|
|
1568
|
+
self.data["magic_confidence"] = confidence
|
|
1569
|
+
# detection compression
|
|
1570
|
+
compression = get_compression(mime_type)
|
|
1571
|
+
if compression:
|
|
1572
|
+
self.add_tag("compressed")
|
|
1573
|
+
self.add_tag(f"{compression}-archive")
|
|
1574
|
+
self.data["compression"] = compression
|
|
1575
|
+
# refresh hash
|
|
1576
|
+
self.data = self.data
|
|
1577
|
+
|
|
1578
|
+
|
|
1579
|
+
class RAW_DNS_RECORD(DictHostEvent, DnsEvent):
|
|
1502
1580
|
# don't emit raw DNS records for affiliates
|
|
1503
1581
|
_always_emit_tags = ["target"]
|
|
1504
1582
|
|
|
1505
1583
|
|
|
1584
|
+
class MOBILE_APP(DictEvent):
|
|
1585
|
+
_always_emit = True
|
|
1586
|
+
|
|
1587
|
+
def _pretty_string(self):
|
|
1588
|
+
return self.data["url"]
|
|
1589
|
+
|
|
1590
|
+
|
|
1506
1591
|
def make_event(
|
|
1507
1592
|
data,
|
|
1508
1593
|
event_type=None,
|
|
@@ -1569,23 +1654,23 @@ def make_event(
|
|
|
1569
1654
|
tags = set(tags)
|
|
1570
1655
|
|
|
1571
1656
|
if is_event(data):
|
|
1572
|
-
|
|
1573
|
-
if scan is not None and not
|
|
1574
|
-
|
|
1575
|
-
if scans is not None and not
|
|
1576
|
-
|
|
1657
|
+
event = copy(data)
|
|
1658
|
+
if scan is not None and not event.scan:
|
|
1659
|
+
event.scan = scan
|
|
1660
|
+
if scans is not None and not event.scans:
|
|
1661
|
+
event.scans = scans
|
|
1577
1662
|
if module is not None:
|
|
1578
|
-
|
|
1663
|
+
event.module = module
|
|
1579
1664
|
if parent is not None:
|
|
1580
|
-
|
|
1665
|
+
event.parent = parent
|
|
1581
1666
|
if context is not None:
|
|
1582
|
-
|
|
1583
|
-
if internal
|
|
1584
|
-
|
|
1667
|
+
event.discovery_context = context
|
|
1668
|
+
if internal is True:
|
|
1669
|
+
event.internal = True
|
|
1585
1670
|
if tags:
|
|
1586
|
-
|
|
1671
|
+
event.tags = tags.union(event.tags)
|
|
1587
1672
|
event_type = data.type
|
|
1588
|
-
return
|
|
1673
|
+
return event
|
|
1589
1674
|
else:
|
|
1590
1675
|
if event_type is None:
|
|
1591
1676
|
event_type, data = get_event_type(data)
|
|
@@ -1615,6 +1700,13 @@ def make_event(
|
|
|
1615
1700
|
if event_type == "USERNAME" and validators.soft_validate(data, "email"):
|
|
1616
1701
|
event_type = "EMAIL_ADDRESS"
|
|
1617
1702
|
tags.add("affiliate")
|
|
1703
|
+
# Convert single-host IP_RANGE to IP_ADDRESS
|
|
1704
|
+
if event_type == "IP_RANGE":
|
|
1705
|
+
with suppress(Exception):
|
|
1706
|
+
net = ipaddress.ip_network(data, strict=False)
|
|
1707
|
+
if net.prefixlen == net.max_prefixlen:
|
|
1708
|
+
event_type = "IP_ADDRESS"
|
|
1709
|
+
data = net.network_address
|
|
1618
1710
|
|
|
1619
1711
|
event_class = globals().get(event_type, DefaultEvent)
|
|
1620
1712
|
|
|
@@ -1671,6 +1763,9 @@ def event_from_json(j, siem_friendly=False):
|
|
|
1671
1763
|
data = j["data"]
|
|
1672
1764
|
kwargs["data"] = data
|
|
1673
1765
|
event = make_event(**kwargs)
|
|
1766
|
+
event_uuid = j.get("uuid", None)
|
|
1767
|
+
if event_uuid is not None:
|
|
1768
|
+
event._uuid = uuid.UUID(event_uuid.split(":")[-1])
|
|
1674
1769
|
|
|
1675
1770
|
resolved_hosts = j.get("resolved_hosts", [])
|
|
1676
1771
|
event._resolved_hosts = set(resolved_hosts)
|
|
@@ -1680,6 +1775,10 @@ def event_from_json(j, siem_friendly=False):
|
|
|
1680
1775
|
parent_id = j.get("parent", None)
|
|
1681
1776
|
if parent_id is not None:
|
|
1682
1777
|
event._parent_id = parent_id
|
|
1778
|
+
parent_uuid = j.get("parent_uuid", None)
|
|
1779
|
+
if parent_uuid is not None:
|
|
1780
|
+
parent_type, parent_uuid = parent_uuid.split(":", 1)
|
|
1781
|
+
event._parent_uuid = parent_type + ":" + str(uuid.UUID(parent_uuid))
|
|
1683
1782
|
return event
|
|
1684
1783
|
except KeyError as e:
|
|
1685
1784
|
raise ValidationError(f"Event missing required field: {e}")
|
bbot/core/helpers/bloom.py
CHANGED
|
@@ -5,14 +5,14 @@ import mmap
|
|
|
5
5
|
|
|
6
6
|
class BloomFilter:
|
|
7
7
|
"""
|
|
8
|
-
Simple bloom filter implementation capable of
|
|
8
|
+
Simple bloom filter implementation capable of roughly 400K lookups/s.
|
|
9
9
|
|
|
10
10
|
BBOT uses bloom filters in scenarios like DNS brute-forcing, where it's useful to keep track
|
|
11
11
|
of which mutations have been tried so far.
|
|
12
12
|
|
|
13
13
|
A 100-megabyte bloom filter (800M bits) can store 10M entries with a .01% false-positive rate.
|
|
14
14
|
A python hash is 36 bytes. So if you wanted to store these in a set, this would take up
|
|
15
|
-
36 * 10M * 2 (key+value) == 720 megabytes. So we save
|
|
15
|
+
36 * 10M * 2 (key+value) == 720 megabytes. So we save roughly 7 times the space.
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
18
|
def __init__(self, size=8000000):
|
|
@@ -64,8 +64,15 @@ class BloomFilter:
|
|
|
64
64
|
hash = (hash * 0x01000193) % 2**32 # 16777619
|
|
65
65
|
return hash
|
|
66
66
|
|
|
67
|
-
def
|
|
67
|
+
def close(self):
|
|
68
|
+
"""Explicitly close the memory-mapped file."""
|
|
68
69
|
self.mmap_file.close()
|
|
69
70
|
|
|
71
|
+
def __del__(self):
|
|
72
|
+
try:
|
|
73
|
+
self.close()
|
|
74
|
+
except Exception:
|
|
75
|
+
pass
|
|
76
|
+
|
|
70
77
|
def __contains__(self, item):
|
|
71
78
|
return self.check(item)
|
bbot/core/helpers/command.py
CHANGED
|
@@ -210,9 +210,10 @@ async def _write_proc_line(proc, chunk):
|
|
|
210
210
|
return True
|
|
211
211
|
except Exception as e:
|
|
212
212
|
proc_args = [str(s) for s in getattr(proc, "args", [])]
|
|
213
|
-
command = " ".join(proc_args)
|
|
214
|
-
|
|
215
|
-
|
|
213
|
+
command = " ".join(proc_args).strip()
|
|
214
|
+
if command:
|
|
215
|
+
log.warning(f"Error writing line to stdin for command: {command}: {e}")
|
|
216
|
+
log.trace(traceback.format_exc())
|
|
216
217
|
return False
|
|
217
218
|
|
|
218
219
|
|
|
@@ -268,11 +269,11 @@ def _prepare_command_kwargs(self, command, kwargs):
|
|
|
268
269
|
(['sudo', '-E', '-A', 'LD_LIBRARY_PATH=...', 'PATH=...', 'ls', '-l'], {'limit': 104857600, 'stdout': -1, 'stderr': -1, 'env': environ(...)})
|
|
269
270
|
"""
|
|
270
271
|
# limit = 100MB (this is needed for cases like httpx that are sending large JSON blobs over stdout)
|
|
271
|
-
if
|
|
272
|
+
if "limit" not in kwargs:
|
|
272
273
|
kwargs["limit"] = 1024 * 1024 * 100
|
|
273
|
-
if
|
|
274
|
+
if "stdout" not in kwargs:
|
|
274
275
|
kwargs["stdout"] = asyncio.subprocess.PIPE
|
|
275
|
-
if
|
|
276
|
+
if "stderr" not in kwargs:
|
|
276
277
|
kwargs["stderr"] = asyncio.subprocess.PIPE
|
|
277
278
|
sudo = kwargs.pop("sudo", False)
|
|
278
279
|
|
|
@@ -285,7 +286,7 @@ def _prepare_command_kwargs(self, command, kwargs):
|
|
|
285
286
|
|
|
286
287
|
# use full path of binary, if not already specified
|
|
287
288
|
binary = command[0]
|
|
288
|
-
if
|
|
289
|
+
if "/" not in binary:
|
|
289
290
|
binary_full_path = which(binary)
|
|
290
291
|
if binary_full_path is None:
|
|
291
292
|
raise SubprocessError(f'Command "{binary}" was not found')
|
|
@@ -295,7 +296,7 @@ def _prepare_command_kwargs(self, command, kwargs):
|
|
|
295
296
|
if sudo and os.geteuid() != 0:
|
|
296
297
|
self.depsinstaller.ensure_root()
|
|
297
298
|
env["SUDO_ASKPASS"] = str((self.tools_dir / self.depsinstaller.askpass_filename).resolve())
|
|
298
|
-
env["BBOT_SUDO_PASS"] = self.depsinstaller.
|
|
299
|
+
env["BBOT_SUDO_PASS"] = self.depsinstaller.encrypted_sudo_pw
|
|
299
300
|
kwargs["env"] = env
|
|
300
301
|
|
|
301
302
|
PATH = os.environ.get("PATH", "")
|