bbot 2.0.1.4720rc0__py3-none-any.whl → 2.3.0.5401rc0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of bbot might be problematic. Click here for more details.
- bbot/__init__.py +1 -1
- bbot/cli.py +3 -7
- bbot/core/config/files.py +0 -1
- bbot/core/config/logger.py +34 -4
- bbot/core/core.py +21 -4
- bbot/core/engine.py +9 -8
- bbot/core/event/base.py +131 -52
- bbot/core/helpers/bloom.py +10 -3
- bbot/core/helpers/command.py +8 -7
- bbot/core/helpers/depsinstaller/installer.py +31 -13
- bbot/core/helpers/diff.py +10 -10
- bbot/core/helpers/dns/brute.py +7 -4
- bbot/core/helpers/dns/dns.py +1 -2
- bbot/core/helpers/dns/engine.py +4 -6
- bbot/core/helpers/dns/helpers.py +2 -2
- bbot/core/helpers/dns/mock.py +0 -1
- bbot/core/helpers/files.py +1 -1
- bbot/core/helpers/helper.py +7 -4
- bbot/core/helpers/interactsh.py +3 -3
- bbot/core/helpers/libmagic.py +65 -0
- bbot/core/helpers/misc.py +65 -22
- bbot/core/helpers/names_generator.py +17 -3
- bbot/core/helpers/process.py +0 -20
- bbot/core/helpers/regex.py +1 -1
- bbot/core/helpers/regexes.py +12 -6
- bbot/core/helpers/validators.py +1 -2
- bbot/core/helpers/web/client.py +1 -1
- bbot/core/helpers/web/engine.py +1 -2
- bbot/core/helpers/web/web.py +4 -114
- bbot/core/helpers/wordcloud.py +5 -5
- bbot/core/modules.py +36 -27
- bbot/core/multiprocess.py +58 -0
- bbot/core/shared_deps.py +46 -3
- bbot/db/sql/models.py +147 -0
- bbot/defaults.yml +12 -10
- bbot/modules/anubisdb.py +2 -2
- bbot/modules/apkpure.py +63 -0
- bbot/modules/azure_tenant.py +2 -2
- bbot/modules/baddns.py +35 -19
- bbot/modules/baddns_direct.py +92 -0
- bbot/modules/baddns_zone.py +3 -8
- bbot/modules/badsecrets.py +4 -3
- bbot/modules/base.py +195 -51
- bbot/modules/bevigil.py +7 -7
- bbot/modules/binaryedge.py +7 -4
- bbot/modules/bufferoverrun.py +47 -0
- bbot/modules/builtwith.py +6 -10
- bbot/modules/bypass403.py +5 -5
- bbot/modules/c99.py +10 -7
- bbot/modules/censys.py +9 -13
- bbot/modules/certspotter.py +5 -3
- bbot/modules/chaos.py +9 -7
- bbot/modules/code_repository.py +1 -0
- bbot/modules/columbus.py +3 -3
- bbot/modules/crt.py +5 -3
- bbot/modules/deadly/dastardly.py +1 -1
- bbot/modules/deadly/ffuf.py +9 -9
- bbot/modules/deadly/nuclei.py +3 -3
- bbot/modules/deadly/vhost.py +4 -3
- bbot/modules/dehashed.py +1 -1
- bbot/modules/digitorus.py +1 -1
- bbot/modules/dnsbimi.py +145 -0
- bbot/modules/dnscaa.py +3 -3
- bbot/modules/dnsdumpster.py +4 -4
- bbot/modules/dnstlsrpt.py +144 -0
- bbot/modules/docker_pull.py +7 -5
- bbot/modules/dockerhub.py +2 -2
- bbot/modules/dotnetnuke.py +20 -21
- bbot/modules/emailformat.py +1 -1
- bbot/modules/extractous.py +122 -0
- bbot/modules/filedownload.py +9 -7
- bbot/modules/fullhunt.py +7 -4
- bbot/modules/generic_ssrf.py +5 -5
- bbot/modules/github_codesearch.py +3 -2
- bbot/modules/github_org.py +4 -4
- bbot/modules/github_workflows.py +4 -4
- bbot/modules/gitlab.py +2 -5
- bbot/modules/google_playstore.py +93 -0
- bbot/modules/gowitness.py +48 -50
- bbot/modules/hackertarget.py +5 -3
- bbot/modules/host_header.py +5 -5
- bbot/modules/httpx.py +1 -4
- bbot/modules/hunterio.py +3 -9
- bbot/modules/iis_shortnames.py +19 -30
- bbot/modules/internal/cloudcheck.py +29 -12
- bbot/modules/internal/dnsresolve.py +22 -22
- bbot/modules/internal/excavate.py +97 -59
- bbot/modules/internal/speculate.py +41 -32
- bbot/modules/internetdb.py +4 -2
- bbot/modules/ip2location.py +3 -5
- bbot/modules/ipneighbor.py +1 -1
- bbot/modules/ipstack.py +3 -8
- bbot/modules/jadx.py +87 -0
- bbot/modules/leakix.py +11 -10
- bbot/modules/myssl.py +2 -2
- bbot/modules/newsletters.py +2 -2
- bbot/modules/otx.py +5 -3
- bbot/modules/output/asset_inventory.py +7 -7
- bbot/modules/output/base.py +1 -1
- bbot/modules/output/csv.py +1 -1
- bbot/modules/output/http.py +20 -14
- bbot/modules/output/mysql.py +51 -0
- bbot/modules/output/neo4j.py +7 -2
- bbot/modules/output/postgres.py +49 -0
- bbot/modules/output/slack.py +0 -1
- bbot/modules/output/sqlite.py +29 -0
- bbot/modules/output/stdout.py +2 -2
- bbot/modules/output/teams.py +107 -6
- bbot/modules/paramminer_headers.py +8 -11
- bbot/modules/passivetotal.py +13 -13
- bbot/modules/portscan.py +32 -6
- bbot/modules/postman.py +50 -126
- bbot/modules/postman_download.py +220 -0
- bbot/modules/rapiddns.py +3 -8
- bbot/modules/report/asn.py +18 -11
- bbot/modules/robots.py +3 -3
- bbot/modules/securitytrails.py +7 -10
- bbot/modules/securitytxt.py +1 -1
- bbot/modules/shodan_dns.py +7 -9
- bbot/modules/sitedossier.py +1 -1
- bbot/modules/skymem.py +2 -2
- bbot/modules/social.py +2 -1
- bbot/modules/subdomaincenter.py +1 -1
- bbot/modules/subdomainradar.py +160 -0
- bbot/modules/telerik.py +8 -8
- bbot/modules/templates/bucket.py +1 -1
- bbot/modules/templates/github.py +22 -14
- bbot/modules/templates/postman.py +21 -0
- bbot/modules/templates/shodan.py +14 -13
- bbot/modules/templates/sql.py +95 -0
- bbot/modules/templates/subdomain_enum.py +51 -16
- bbot/modules/templates/webhook.py +2 -4
- bbot/modules/trickest.py +8 -37
- bbot/modules/trufflehog.py +10 -12
- bbot/modules/url_manipulation.py +3 -3
- bbot/modules/urlscan.py +1 -1
- bbot/modules/viewdns.py +1 -1
- bbot/modules/virustotal.py +8 -30
- bbot/modules/wafw00f.py +1 -1
- bbot/modules/wayback.py +1 -1
- bbot/modules/wpscan.py +17 -11
- bbot/modules/zoomeye.py +11 -6
- bbot/presets/baddns-thorough.yml +12 -0
- bbot/presets/fast.yml +16 -0
- bbot/presets/kitchen-sink.yml +1 -2
- bbot/presets/spider.yml +4 -0
- bbot/presets/subdomain-enum.yml +7 -7
- bbot/presets/web/dotnet-audit.yml +0 -1
- bbot/scanner/manager.py +5 -16
- bbot/scanner/preset/args.py +46 -26
- bbot/scanner/preset/environ.py +7 -2
- bbot/scanner/preset/path.py +7 -4
- bbot/scanner/preset/preset.py +36 -23
- bbot/scanner/scanner.py +172 -62
- bbot/scanner/target.py +236 -434
- bbot/scripts/docs.py +1 -1
- bbot/test/bbot_fixtures.py +13 -3
- bbot/test/conftest.py +132 -100
- bbot/test/fastapi_test.py +17 -0
- bbot/test/owasp_mastg.apk +0 -0
- bbot/test/run_tests.sh +4 -4
- bbot/test/test.conf +2 -0
- bbot/test/test_step_1/test__module__tests.py +0 -1
- bbot/test/test_step_1/test_bbot_fastapi.py +79 -0
- bbot/test/test_step_1/test_bloom_filter.py +2 -1
- bbot/test/test_step_1/test_cli.py +138 -64
- bbot/test/test_step_1/test_dns.py +61 -27
- bbot/test/test_step_1/test_engine.py +17 -19
- bbot/test/test_step_1/test_events.py +183 -30
- bbot/test/test_step_1/test_helpers.py +64 -29
- bbot/test/test_step_1/test_manager_deduplication.py +1 -1
- bbot/test/test_step_1/test_manager_scope_accuracy.py +333 -330
- bbot/test/test_step_1/test_modules_basic.py +68 -70
- bbot/test/test_step_1/test_presets.py +183 -100
- bbot/test/test_step_1/test_python_api.py +7 -2
- bbot/test/test_step_1/test_regexes.py +35 -5
- bbot/test/test_step_1/test_scan.py +39 -5
- bbot/test/test_step_1/test_scope.py +4 -3
- bbot/test/test_step_1/test_target.py +242 -145
- bbot/test/test_step_1/test_web.py +14 -10
- bbot/test/test_step_2/module_tests/base.py +15 -7
- bbot/test/test_step_2/module_tests/test_module_anubisdb.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_apkpure.py +71 -0
- bbot/test/test_step_2/module_tests/test_module_asset_inventory.py +0 -1
- bbot/test/test_step_2/module_tests/test_module_azure_realm.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_baddns.py +6 -6
- bbot/test/test_step_2/module_tests/test_module_baddns_direct.py +62 -0
- bbot/test/test_step_2/module_tests/test_module_bevigil.py +29 -2
- bbot/test/test_step_2/module_tests/test_module_binaryedge.py +4 -2
- bbot/test/test_step_2/module_tests/test_module_bucket_amazon.py +2 -2
- bbot/test/test_step_2/module_tests/test_module_bucket_azure.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_bufferoverrun.py +35 -0
- bbot/test/test_step_2/module_tests/test_module_builtwith.py +2 -2
- bbot/test/test_step_2/module_tests/test_module_bypass403.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_c99.py +126 -0
- bbot/test/test_step_2/module_tests/test_module_censys.py +4 -1
- bbot/test/test_step_2/module_tests/test_module_cloudcheck.py +4 -0
- bbot/test/test_step_2/module_tests/test_module_code_repository.py +11 -1
- bbot/test/test_step_2/module_tests/test_module_columbus.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_credshed.py +3 -3
- bbot/test/test_step_2/module_tests/test_module_dastardly.py +2 -1
- bbot/test/test_step_2/module_tests/test_module_dehashed.py +2 -2
- bbot/test/test_step_2/module_tests/test_module_digitorus.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_discord.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_dnsbimi.py +103 -0
- bbot/test/test_step_2/module_tests/test_module_dnsbrute.py +9 -10
- bbot/test/test_step_2/module_tests/test_module_dnsbrute_mutations.py +1 -2
- bbot/test/test_step_2/module_tests/test_module_dnscommonsrv.py +1 -2
- bbot/test/test_step_2/module_tests/test_module_dnsdumpster.py +4 -4
- bbot/test/test_step_2/module_tests/test_module_dnstlsrpt.py +64 -0
- bbot/test/test_step_2/module_tests/test_module_dotnetnuke.py +0 -8
- bbot/test/test_step_2/module_tests/test_module_excavate.py +28 -48
- bbot/test/test_step_2/module_tests/test_module_extractous.py +54 -0
- bbot/test/test_step_2/module_tests/test_module_ffuf_shortnames.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_filedownload.py +14 -14
- bbot/test/test_step_2/module_tests/test_module_git_clone.py +2 -2
- bbot/test/test_step_2/module_tests/test_module_github_org.py +19 -8
- bbot/test/test_step_2/module_tests/test_module_github_workflows.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_gitlab.py +9 -4
- bbot/test/test_step_2/module_tests/test_module_google_playstore.py +83 -0
- bbot/test/test_step_2/module_tests/test_module_gowitness.py +4 -6
- bbot/test/test_step_2/module_tests/test_module_host_header.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_http.py +4 -4
- bbot/test/test_step_2/module_tests/test_module_httpx.py +10 -8
- bbot/test/test_step_2/module_tests/test_module_hunterio.py +68 -4
- bbot/test/test_step_2/module_tests/test_module_jadx.py +55 -0
- bbot/test/test_step_2/module_tests/test_module_json.py +22 -9
- bbot/test/test_step_2/module_tests/test_module_leakix.py +7 -3
- bbot/test/test_step_2/module_tests/test_module_mysql.py +76 -0
- bbot/test/test_step_2/module_tests/test_module_myssl.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_neo4j.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_newsletters.py +16 -16
- bbot/test/test_step_2/module_tests/test_module_ntlm.py +8 -7
- bbot/test/test_step_2/module_tests/test_module_oauth.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_otx.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_paramminer_cookies.py +1 -2
- bbot/test/test_step_2/module_tests/test_module_paramminer_getparams.py +0 -6
- bbot/test/test_step_2/module_tests/test_module_paramminer_headers.py +2 -9
- bbot/test/test_step_2/module_tests/test_module_passivetotal.py +3 -1
- bbot/test/test_step_2/module_tests/test_module_pgp.py +2 -2
- bbot/test/test_step_2/module_tests/test_module_portscan.py +9 -8
- bbot/test/test_step_2/module_tests/test_module_postgres.py +74 -0
- bbot/test/test_step_2/module_tests/test_module_postman.py +84 -253
- bbot/test/test_step_2/module_tests/test_module_postman_download.py +439 -0
- bbot/test/test_step_2/module_tests/test_module_rapiddns.py +93 -1
- bbot/test/test_step_2/module_tests/test_module_shodan_dns.py +20 -1
- bbot/test/test_step_2/module_tests/test_module_sitedossier.py +2 -2
- bbot/test/test_step_2/module_tests/test_module_smuggler.py +14 -14
- bbot/test/test_step_2/module_tests/test_module_social.py +11 -1
- bbot/test/test_step_2/module_tests/test_module_speculate.py +4 -8
- bbot/test/test_step_2/module_tests/test_module_splunk.py +4 -4
- bbot/test/test_step_2/module_tests/test_module_sqlite.py +18 -0
- bbot/test/test_step_2/module_tests/test_module_sslcert.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_stdout.py +5 -3
- bbot/test/test_step_2/module_tests/test_module_subdomaincenter.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_subdomainradar.py +208 -0
- bbot/test/test_step_2/module_tests/test_module_subdomains.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_teams.py +8 -6
- bbot/test/test_step_2/module_tests/test_module_telerik.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_trufflehog.py +317 -14
- bbot/test/test_step_2/module_tests/test_module_viewdns.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_wayback.py +1 -1
- bbot/test/test_step_2/template_tests/test_template_subdomain_enum.py +2 -2
- bbot/wordlists/devops_mutations.txt +1 -1
- bbot/wordlists/ffuf_shortname_candidates.txt +1 -1
- bbot/wordlists/nameservers.txt +1 -1
- bbot/wordlists/paramminer_headers.txt +1 -1
- bbot/wordlists/paramminer_parameters.txt +1 -1
- bbot/wordlists/raft-small-extensions-lowercase_CLEANED.txt +1 -1
- bbot/wordlists/valid_url_schemes.txt +1 -1
- {bbot-2.0.1.4720rc0.dist-info → bbot-2.3.0.5401rc0.dist-info}/METADATA +48 -18
- bbot-2.3.0.5401rc0.dist-info/RECORD +421 -0
- {bbot-2.0.1.4720rc0.dist-info → bbot-2.3.0.5401rc0.dist-info}/WHEEL +1 -1
- bbot/modules/unstructured.py +0 -163
- bbot/test/test_step_2/module_tests/test_module_unstructured.py +0 -102
- bbot-2.0.1.4720rc0.dist-info/RECORD +0 -387
- {bbot-2.0.1.4720rc0.dist-info → bbot-2.3.0.5401rc0.dist-info}/LICENSE +0 -0
- {bbot-2.0.1.4720rc0.dist-info → bbot-2.3.0.5401rc0.dist-info}/entry_points.txt +0 -0
bbot/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,12 +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 =
|
|
174
|
-
self.raw_dns_records =
|
|
178
|
+
self.dns_children = {}
|
|
179
|
+
self.raw_dns_records = {}
|
|
175
180
|
self._discovery_context = ""
|
|
176
181
|
self._discovery_context_regex = re.compile(r"\{(?:event|module)[^}]*\}")
|
|
177
182
|
self.web_spider_distance = 0
|
|
@@ -198,7 +203,7 @@ class BaseEvent:
|
|
|
198
203
|
# self.scan holds the instantiated scan object (for helpers, etc.)
|
|
199
204
|
self.scan = scan
|
|
200
205
|
if (not self.scan) and (not self._dummy):
|
|
201
|
-
raise ValidationError(
|
|
206
|
+
raise ValidationError("Must specify scan")
|
|
202
207
|
# self.scans holds a list of scan IDs from scans that encountered this event
|
|
203
208
|
self.scans = []
|
|
204
209
|
if scans is not None:
|
|
@@ -217,7 +222,7 @@ class BaseEvent:
|
|
|
217
222
|
|
|
218
223
|
self.parent = parent
|
|
219
224
|
if (not self.parent) and (not self._dummy):
|
|
220
|
-
raise ValidationError(
|
|
225
|
+
raise ValidationError("Must specify event parent")
|
|
221
226
|
|
|
222
227
|
if tags is not None:
|
|
223
228
|
for tag in tags:
|
|
@@ -296,9 +301,9 @@ class BaseEvent:
|
|
|
296
301
|
The purpose of internal events is to enable speculative/explorative discovery without cluttering
|
|
297
302
|
the console with irrelevant or uninteresting events.
|
|
298
303
|
"""
|
|
299
|
-
if not
|
|
304
|
+
if value not in (True, False):
|
|
300
305
|
raise ValueError(f'"internal" must be boolean, not {type(value)}')
|
|
301
|
-
if value
|
|
306
|
+
if value is True:
|
|
302
307
|
self.add_tag("internal")
|
|
303
308
|
else:
|
|
304
309
|
self.remove_tag("internal")
|
|
@@ -336,6 +341,21 @@ class BaseEvent:
|
|
|
336
341
|
return self.host
|
|
337
342
|
return self._host_original
|
|
338
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
|
+
|
|
339
359
|
@property
|
|
340
360
|
def port(self):
|
|
341
361
|
self.host
|
|
@@ -348,6 +368,12 @@ class BaseEvent:
|
|
|
348
368
|
return 80
|
|
349
369
|
return self._port
|
|
350
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
|
+
|
|
351
377
|
@property
|
|
352
378
|
def host_stem(self):
|
|
353
379
|
"""
|
|
@@ -393,7 +419,7 @@ class BaseEvent:
|
|
|
393
419
|
parent_chain = []
|
|
394
420
|
if self.parent is not None and self.parent is not self:
|
|
395
421
|
parent_chain = self.parent.parent_chain
|
|
396
|
-
return parent_chain + [self.
|
|
422
|
+
return parent_chain + [str(self.uuid)]
|
|
397
423
|
|
|
398
424
|
@property
|
|
399
425
|
def words(self):
|
|
@@ -436,11 +462,6 @@ class BaseEvent:
|
|
|
436
462
|
no_host_information = not bool(self.host)
|
|
437
463
|
return self._always_emit or always_emit_tags or no_host_information
|
|
438
464
|
|
|
439
|
-
@property
|
|
440
|
-
def quick_emit(self):
|
|
441
|
-
no_host_information = not bool(self.host)
|
|
442
|
-
return self._quick_emit or no_host_information
|
|
443
|
-
|
|
444
465
|
@property
|
|
445
466
|
def id(self):
|
|
446
467
|
"""
|
|
@@ -450,6 +471,13 @@ class BaseEvent:
|
|
|
450
471
|
self._id = f"{self.type}:{self.data_hash.hex()}"
|
|
451
472
|
return self._id
|
|
452
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
|
+
|
|
453
481
|
@property
|
|
454
482
|
def data_hash(self):
|
|
455
483
|
"""
|
|
@@ -490,12 +518,13 @@ class BaseEvent:
|
|
|
490
518
|
for t in list(self.tags):
|
|
491
519
|
if t.startswith("distance-"):
|
|
492
520
|
self.remove_tag(t)
|
|
493
|
-
if
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
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}")
|
|
499
528
|
self._scope_distance = new_scope_distance
|
|
500
529
|
# apply recursively to parent events
|
|
501
530
|
parent_scope_distance = getattr(self.parent, "scope_distance", None)
|
|
@@ -567,6 +596,13 @@ class BaseEvent:
|
|
|
567
596
|
return parent_id
|
|
568
597
|
return self._parent_id
|
|
569
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
|
+
|
|
570
606
|
@property
|
|
571
607
|
def validators(self):
|
|
572
608
|
"""
|
|
@@ -733,12 +769,12 @@ class BaseEvent:
|
|
|
733
769
|
Returns:
|
|
734
770
|
dict: JSON-serializable dictionary representation of the event object.
|
|
735
771
|
"""
|
|
772
|
+
j = {}
|
|
736
773
|
# type, ID, scope description
|
|
737
|
-
|
|
738
|
-
for i in ("type", "id", "scope_description"):
|
|
774
|
+
for i in ("type", "id", "uuid", "scope_description", "netloc"):
|
|
739
775
|
v = getattr(self, i, "")
|
|
740
776
|
if v:
|
|
741
|
-
j.update({i: v})
|
|
777
|
+
j.update({i: str(v)})
|
|
742
778
|
# event data
|
|
743
779
|
data_attr = getattr(self, f"data_{mode}", None)
|
|
744
780
|
if data_attr is not None:
|
|
@@ -754,6 +790,8 @@ class BaseEvent:
|
|
|
754
790
|
j["host"] = str(self.host)
|
|
755
791
|
j["resolved_hosts"] = sorted(str(h) for h in self.resolved_hosts)
|
|
756
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
|
|
757
795
|
# web spider distance
|
|
758
796
|
web_spider_distance = getattr(self, "web_spider_distance", None)
|
|
759
797
|
if web_spider_distance is not None:
|
|
@@ -769,6 +807,9 @@ class BaseEvent:
|
|
|
769
807
|
parent_id = self.parent_id
|
|
770
808
|
if parent_id:
|
|
771
809
|
j["parent"] = parent_id
|
|
810
|
+
parent_uuid = self.parent_uuid
|
|
811
|
+
if parent_uuid:
|
|
812
|
+
j["parent_uuid"] = parent_uuid
|
|
772
813
|
# tags
|
|
773
814
|
if self.tags:
|
|
774
815
|
j.update({"tags": list(self.tags)})
|
|
@@ -972,18 +1013,20 @@ class ClosestHostEvent(DictHostEvent):
|
|
|
972
1013
|
if not self.host:
|
|
973
1014
|
for parent in self.get_parents(include_self=True):
|
|
974
1015
|
# inherit closest URL
|
|
975
|
-
if
|
|
1016
|
+
if "url" not in self.data:
|
|
976
1017
|
parent_url = getattr(parent, "parsed_url", None)
|
|
977
1018
|
if parent_url is not None:
|
|
978
1019
|
self.data["url"] = parent_url.geturl()
|
|
979
1020
|
# inherit closest path
|
|
980
|
-
if
|
|
1021
|
+
if "path" not in self.data and isinstance(parent.data, dict) and not parent.type == "HTTP_RESPONSE":
|
|
981
1022
|
parent_path = parent.data.get("path", None)
|
|
982
1023
|
if parent_path is not None:
|
|
983
1024
|
self.data["path"] = parent_path
|
|
984
1025
|
# inherit closest host
|
|
985
1026
|
if parent.host:
|
|
986
1027
|
self.data["host"] = str(parent.host)
|
|
1028
|
+
# we do this to refresh the hash
|
|
1029
|
+
self.data = self.data
|
|
987
1030
|
break
|
|
988
1031
|
# die if we still haven't found a host
|
|
989
1032
|
if not self.host:
|
|
@@ -993,20 +1036,21 @@ class ClosestHostEvent(DictHostEvent):
|
|
|
993
1036
|
class DictPathEvent(DictEvent):
|
|
994
1037
|
def sanitize_data(self, data):
|
|
995
1038
|
new_data = dict(data)
|
|
1039
|
+
new_data["path"] = str(new_data["path"])
|
|
996
1040
|
file_blobs = getattr(self.scan, "_file_blobs", False)
|
|
997
1041
|
folder_blobs = getattr(self.scan, "_folder_blobs", False)
|
|
998
1042
|
blob = None
|
|
999
1043
|
try:
|
|
1000
|
-
|
|
1001
|
-
if
|
|
1044
|
+
self._data_path = Path(data["path"])
|
|
1045
|
+
if self._data_path.is_file():
|
|
1002
1046
|
self.add_tag("file")
|
|
1003
1047
|
if file_blobs:
|
|
1004
|
-
with open(
|
|
1048
|
+
with open(self._data_path, "rb") as file:
|
|
1005
1049
|
blob = file.read()
|
|
1006
|
-
elif
|
|
1050
|
+
elif self._data_path.is_dir():
|
|
1007
1051
|
self.add_tag("folder")
|
|
1008
1052
|
if folder_blobs:
|
|
1009
|
-
blob = self._tar_directory(
|
|
1053
|
+
blob = self._tar_directory(self._data_path)
|
|
1010
1054
|
except KeyError:
|
|
1011
1055
|
pass
|
|
1012
1056
|
if blob:
|
|
@@ -1085,8 +1129,7 @@ class DnsEvent(BaseEvent):
|
|
|
1085
1129
|
class IP_RANGE(DnsEvent):
|
|
1086
1130
|
def __init__(self, *args, **kwargs):
|
|
1087
1131
|
super().__init__(*args, **kwargs)
|
|
1088
|
-
|
|
1089
|
-
self.add_tag(f"ipv{net.version}")
|
|
1132
|
+
self.add_tag(f"ipv{self.host.version}")
|
|
1090
1133
|
|
|
1091
1134
|
def sanitize_data(self, data):
|
|
1092
1135
|
return str(ipaddress.ip_network(str(data), strict=False))
|
|
@@ -1137,7 +1180,6 @@ class URL_UNVERIFIED(BaseEvent):
|
|
|
1137
1180
|
self.num_redirects = getattr(self.parent, "num_redirects", 0)
|
|
1138
1181
|
|
|
1139
1182
|
def _data_id(self):
|
|
1140
|
-
|
|
1141
1183
|
data = super()._data_id()
|
|
1142
1184
|
|
|
1143
1185
|
# remove the querystring for URL/URL_UNVERIFIED events, because we will conditionally add it back in (based on settings)
|
|
@@ -1185,7 +1227,7 @@ class URL_UNVERIFIED(BaseEvent):
|
|
|
1185
1227
|
|
|
1186
1228
|
def add_tag(self, tag):
|
|
1187
1229
|
host_same_as_parent = self.parent and self.host == self.parent.host
|
|
1188
|
-
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:
|
|
1189
1231
|
# increment the web spider distance
|
|
1190
1232
|
if self.type == "URL_UNVERIFIED":
|
|
1191
1233
|
self.web_spider_distance += 1
|
|
@@ -1207,7 +1249,7 @@ class URL_UNVERIFIED(BaseEvent):
|
|
|
1207
1249
|
|
|
1208
1250
|
def _words(self):
|
|
1209
1251
|
first_elem = self.parsed_url.path.lstrip("/").split("/")[0]
|
|
1210
|
-
if
|
|
1252
|
+
if "." not in first_elem:
|
|
1211
1253
|
return extract_words(first_elem)
|
|
1212
1254
|
return set()
|
|
1213
1255
|
|
|
@@ -1224,7 +1266,6 @@ class URL_UNVERIFIED(BaseEvent):
|
|
|
1224
1266
|
|
|
1225
1267
|
|
|
1226
1268
|
class URL(URL_UNVERIFIED):
|
|
1227
|
-
|
|
1228
1269
|
def __init__(self, *args, **kwargs):
|
|
1229
1270
|
super().__init__(*args, **kwargs)
|
|
1230
1271
|
|
|
@@ -1236,7 +1277,7 @@ class URL(URL_UNVERIFIED):
|
|
|
1236
1277
|
@property
|
|
1237
1278
|
def resolved_hosts(self):
|
|
1238
1279
|
# TODO: remove this when we rip out httpx
|
|
1239
|
-
return
|
|
1280
|
+
return {".".join(i.split("-")[1:]) for i in self.tags if i.startswith("ip-")}
|
|
1240
1281
|
|
|
1241
1282
|
@property
|
|
1242
1283
|
def pretty_string(self):
|
|
@@ -1266,7 +1307,6 @@ class URL_HINT(URL_UNVERIFIED):
|
|
|
1266
1307
|
|
|
1267
1308
|
|
|
1268
1309
|
class WEB_PARAMETER(DictHostEvent):
|
|
1269
|
-
|
|
1270
1310
|
def _data_id(self):
|
|
1271
1311
|
# dedupe by url:name:param_type
|
|
1272
1312
|
url = self.data.get("url", "")
|
|
@@ -1515,7 +1555,25 @@ class WAF(DictHostEvent):
|
|
|
1515
1555
|
|
|
1516
1556
|
|
|
1517
1557
|
class FILESYSTEM(DictPathEvent):
|
|
1518
|
-
|
|
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
|
|
1519
1577
|
|
|
1520
1578
|
|
|
1521
1579
|
class RAW_DNS_RECORD(DictHostEvent, DnsEvent):
|
|
@@ -1523,6 +1581,13 @@ class RAW_DNS_RECORD(DictHostEvent, DnsEvent):
|
|
|
1523
1581
|
_always_emit_tags = ["target"]
|
|
1524
1582
|
|
|
1525
1583
|
|
|
1584
|
+
class MOBILE_APP(DictEvent):
|
|
1585
|
+
_always_emit = True
|
|
1586
|
+
|
|
1587
|
+
def _pretty_string(self):
|
|
1588
|
+
return self.data["url"]
|
|
1589
|
+
|
|
1590
|
+
|
|
1526
1591
|
def make_event(
|
|
1527
1592
|
data,
|
|
1528
1593
|
event_type=None,
|
|
@@ -1589,23 +1654,23 @@ def make_event(
|
|
|
1589
1654
|
tags = set(tags)
|
|
1590
1655
|
|
|
1591
1656
|
if is_event(data):
|
|
1592
|
-
|
|
1593
|
-
if scan is not None and not
|
|
1594
|
-
|
|
1595
|
-
if scans is not None and not
|
|
1596
|
-
|
|
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
|
|
1597
1662
|
if module is not None:
|
|
1598
|
-
|
|
1663
|
+
event.module = module
|
|
1599
1664
|
if parent is not None:
|
|
1600
|
-
|
|
1665
|
+
event.parent = parent
|
|
1601
1666
|
if context is not None:
|
|
1602
|
-
|
|
1603
|
-
if internal
|
|
1604
|
-
|
|
1667
|
+
event.discovery_context = context
|
|
1668
|
+
if internal is True:
|
|
1669
|
+
event.internal = True
|
|
1605
1670
|
if tags:
|
|
1606
|
-
|
|
1671
|
+
event.tags = tags.union(event.tags)
|
|
1607
1672
|
event_type = data.type
|
|
1608
|
-
return
|
|
1673
|
+
return event
|
|
1609
1674
|
else:
|
|
1610
1675
|
if event_type is None:
|
|
1611
1676
|
event_type, data = get_event_type(data)
|
|
@@ -1635,6 +1700,13 @@ def make_event(
|
|
|
1635
1700
|
if event_type == "USERNAME" and validators.soft_validate(data, "email"):
|
|
1636
1701
|
event_type = "EMAIL_ADDRESS"
|
|
1637
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
|
|
1638
1710
|
|
|
1639
1711
|
event_class = globals().get(event_type, DefaultEvent)
|
|
1640
1712
|
|
|
@@ -1691,6 +1763,9 @@ def event_from_json(j, siem_friendly=False):
|
|
|
1691
1763
|
data = j["data"]
|
|
1692
1764
|
kwargs["data"] = data
|
|
1693
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])
|
|
1694
1769
|
|
|
1695
1770
|
resolved_hosts = j.get("resolved_hosts", [])
|
|
1696
1771
|
event._resolved_hosts = set(resolved_hosts)
|
|
@@ -1700,6 +1775,10 @@ def event_from_json(j, siem_friendly=False):
|
|
|
1700
1775
|
parent_id = j.get("parent", None)
|
|
1701
1776
|
if parent_id is not None:
|
|
1702
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))
|
|
1703
1782
|
return event
|
|
1704
1783
|
except KeyError as e:
|
|
1705
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')
|
|
@@ -14,7 +14,7 @@ from secrets import token_bytes
|
|
|
14
14
|
from ansible_runner.interface import run
|
|
15
15
|
from subprocess import CalledProcessError
|
|
16
16
|
|
|
17
|
-
from ..misc import can_sudo_without_password, os_platform, rm_at_exit
|
|
17
|
+
from ..misc import can_sudo_without_password, os_platform, rm_at_exit, get_python_constraints
|
|
18
18
|
|
|
19
19
|
log = logging.getLogger("bbot.core.helpers.depsinstaller")
|
|
20
20
|
|
|
@@ -44,7 +44,13 @@ class DepsInstaller:
|
|
|
44
44
|
self.parent_helper.mkdir(self.command_status)
|
|
45
45
|
self.setup_status = self.read_setup_status()
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
# make sure we're using a minimal git config
|
|
48
|
+
self.minimal_git_config = self.data_dir / "minimal_git.config"
|
|
49
|
+
self.minimal_git_config.touch()
|
|
50
|
+
os.environ["GIT_CONFIG_GLOBAL"] = str(self.minimal_git_config)
|
|
51
|
+
|
|
52
|
+
self.deps_config = self.parent_helper.config.get("deps", {})
|
|
53
|
+
self.deps_behavior = self.deps_config.get("behavior", "abort_on_failure").lower()
|
|
48
54
|
self.ansible_debug = self.core.logger.log_level <= logging.DEBUG
|
|
49
55
|
self.venv = ""
|
|
50
56
|
if sys.prefix != sys.base_prefix:
|
|
@@ -91,11 +97,11 @@ class DepsInstaller:
|
|
|
91
97
|
or self.deps_behavior == "force_install"
|
|
92
98
|
):
|
|
93
99
|
if not notified:
|
|
94
|
-
log.hugeinfo(
|
|
100
|
+
log.hugeinfo("Installing module dependencies. Please be patient, this may take a while.")
|
|
95
101
|
notified = True
|
|
96
102
|
log.verbose(f'Installing dependencies for module "{m}"')
|
|
97
103
|
# get sudo access if we need it
|
|
98
|
-
if preloaded.get("sudo", False)
|
|
104
|
+
if preloaded.get("sudo", False) is True:
|
|
99
105
|
self.ensure_root(f'Module "{m}" needs root privileges to install its dependencies.')
|
|
100
106
|
success = await self.install_module(m)
|
|
101
107
|
self.setup_status[module_hash] = success
|
|
@@ -153,7 +159,7 @@ class DepsInstaller:
|
|
|
153
159
|
deps_common = preloaded["deps"]["common"]
|
|
154
160
|
if deps_common:
|
|
155
161
|
for dep_common in deps_common:
|
|
156
|
-
if self.setup_status.get(dep_common, False)
|
|
162
|
+
if self.setup_status.get(dep_common, False) is True:
|
|
157
163
|
log.debug(
|
|
158
164
|
f'Skipping installation of dependency "{dep_common}" for module "{module}" since it is already installed'
|
|
159
165
|
)
|
|
@@ -171,10 +177,13 @@ class DepsInstaller:
|
|
|
171
177
|
|
|
172
178
|
command = [sys.executable, "-m", "pip", "install", "--upgrade"] + packages
|
|
173
179
|
|
|
174
|
-
if constraints
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
180
|
+
# if no custom constraints are provided, use the constraints of the currently installed version of bbot
|
|
181
|
+
if constraints is not None:
|
|
182
|
+
constraints = get_python_constraints()
|
|
183
|
+
|
|
184
|
+
constraints_tempfile = self.parent_helper.tempfile(constraints, pipe=False)
|
|
185
|
+
command.append("--constraint")
|
|
186
|
+
command.append(constraints_tempfile)
|
|
178
187
|
|
|
179
188
|
process = None
|
|
180
189
|
try:
|
|
@@ -235,7 +244,7 @@ class DepsInstaller:
|
|
|
235
244
|
if success:
|
|
236
245
|
log.info(f"Successfully ran {len(commands):,} shell commands")
|
|
237
246
|
else:
|
|
238
|
-
log.warning(
|
|
247
|
+
log.warning("Failed to run shell dependencies")
|
|
239
248
|
return success
|
|
240
249
|
|
|
241
250
|
def tasks(self, module, tasks):
|
|
@@ -248,7 +257,7 @@ class DepsInstaller:
|
|
|
248
257
|
return success
|
|
249
258
|
|
|
250
259
|
def ansible_run(self, tasks=None, module=None, args=None, ansible_args=None):
|
|
251
|
-
_ansible_args = {"ansible_connection": "local"}
|
|
260
|
+
_ansible_args = {"ansible_connection": "local", "ansible_python_interpreter": sys.executable}
|
|
252
261
|
if ansible_args is not None:
|
|
253
262
|
_ansible_args.update(ansible_args)
|
|
254
263
|
module_args = None
|
|
@@ -301,7 +310,7 @@ class DepsInstaller:
|
|
|
301
310
|
return success, err
|
|
302
311
|
|
|
303
312
|
def read_setup_status(self):
|
|
304
|
-
setup_status =
|
|
313
|
+
setup_status = {}
|
|
305
314
|
if self.setup_status_cache.is_file():
|
|
306
315
|
with open(self.setup_status_cache) as f:
|
|
307
316
|
with suppress(Exception):
|
|
@@ -342,7 +351,16 @@ class DepsInstaller:
|
|
|
342
351
|
# ensure tldextract data is cached
|
|
343
352
|
self.parent_helper.tldextract("evilcorp.co.uk")
|
|
344
353
|
# command: package_name
|
|
345
|
-
core_deps = {
|
|
354
|
+
core_deps = {
|
|
355
|
+
"unzip": "unzip",
|
|
356
|
+
"zipinfo": "unzip",
|
|
357
|
+
"curl": "curl",
|
|
358
|
+
"git": "git",
|
|
359
|
+
"make": "make",
|
|
360
|
+
"gcc": "gcc",
|
|
361
|
+
"bash": "bash",
|
|
362
|
+
"which": "which",
|
|
363
|
+
}
|
|
346
364
|
for command, package_name in core_deps.items():
|
|
347
365
|
if not self.parent_helper.which(command):
|
|
348
366
|
to_install.add(package_name)
|