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
|
@@ -10,10 +10,11 @@ from pathlib import Path
|
|
|
10
10
|
from threading import Lock
|
|
11
11
|
from itertools import chain
|
|
12
12
|
from contextlib import suppress
|
|
13
|
+
from secrets import token_bytes
|
|
13
14
|
from ansible_runner.interface import run
|
|
14
15
|
from subprocess import CalledProcessError
|
|
15
16
|
|
|
16
|
-
from ..misc import can_sudo_without_password, os_platform
|
|
17
|
+
from ..misc import can_sudo_without_password, os_platform, rm_at_exit, get_python_constraints
|
|
17
18
|
|
|
18
19
|
log = logging.getLogger("bbot.core.helpers.depsinstaller")
|
|
19
20
|
|
|
@@ -29,14 +30,13 @@ class DepsInstaller:
|
|
|
29
30
|
http_timeout = self.web_config.get("http_timeout", 30)
|
|
30
31
|
os.environ["ANSIBLE_TIMEOUT"] = str(http_timeout)
|
|
31
32
|
|
|
33
|
+
# cache encrypted sudo pass
|
|
32
34
|
self.askpass_filename = "sudo_askpass.py"
|
|
35
|
+
self._sudo_password = None
|
|
36
|
+
self._sudo_cache_setup = False
|
|
37
|
+
self._setup_sudo_cache()
|
|
33
38
|
self._installed_sudo_askpass = False
|
|
34
|
-
|
|
35
|
-
if self._sudo_password is None:
|
|
36
|
-
if self.core.bbot_sudo_pass is not None:
|
|
37
|
-
self._sudo_password = self.core.bbot_sudo_pass
|
|
38
|
-
elif can_sudo_without_password():
|
|
39
|
-
self._sudo_password = ""
|
|
39
|
+
|
|
40
40
|
self.data_dir = self.parent_helper.cache_dir / "depsinstaller"
|
|
41
41
|
self.parent_helper.mkdir(self.data_dir)
|
|
42
42
|
self.setup_status_cache = self.data_dir / "setup_status.json"
|
|
@@ -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):
|
|
@@ -314,20 +323,27 @@ class DepsInstaller:
|
|
|
314
323
|
|
|
315
324
|
def ensure_root(self, message=""):
|
|
316
325
|
self._install_sudo_askpass()
|
|
326
|
+
# skip if we've already done this
|
|
327
|
+
if self._sudo_password is not None:
|
|
328
|
+
return
|
|
317
329
|
with self.ensure_root_lock:
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
330
|
+
# first check if the environment variable is set
|
|
331
|
+
_sudo_password = os.environ.get("BBOT_SUDO_PASS", None)
|
|
332
|
+
if _sudo_password is not None or os.geteuid() == 0 or can_sudo_without_password():
|
|
333
|
+
# if we're already root or we can sudo without a password, there's no need to prompt
|
|
334
|
+
return
|
|
335
|
+
|
|
336
|
+
if message:
|
|
337
|
+
log.warning(message)
|
|
338
|
+
while not self._sudo_password:
|
|
339
|
+
# sleep for a split second to flush previous log messages
|
|
340
|
+
sleep(0.1)
|
|
341
|
+
_sudo_password = getpass.getpass(prompt="[USER] Please enter sudo password: ")
|
|
342
|
+
if self.parent_helper.verify_sudo_password(_sudo_password):
|
|
343
|
+
log.success("Authentication successful")
|
|
344
|
+
self._sudo_password = _sudo_password
|
|
345
|
+
else:
|
|
346
|
+
log.warning("Incorrect password")
|
|
331
347
|
|
|
332
348
|
def install_core_deps(self):
|
|
333
349
|
to_install = set()
|
|
@@ -335,7 +351,16 @@ class DepsInstaller:
|
|
|
335
351
|
# ensure tldextract data is cached
|
|
336
352
|
self.parent_helper.tldextract("evilcorp.co.uk")
|
|
337
353
|
# command: package_name
|
|
338
|
-
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
|
+
}
|
|
339
364
|
for command, package_name in core_deps.items():
|
|
340
365
|
if not self.parent_helper.which(command):
|
|
341
366
|
to_install.add(package_name)
|
|
@@ -343,6 +368,38 @@ class DepsInstaller:
|
|
|
343
368
|
self.ensure_root()
|
|
344
369
|
self.apt_install(list(to_install))
|
|
345
370
|
|
|
371
|
+
def _setup_sudo_cache(self):
|
|
372
|
+
if not self._sudo_cache_setup:
|
|
373
|
+
self._sudo_cache_setup = True
|
|
374
|
+
# write temporary encryption key, to be deleted upon scan completion
|
|
375
|
+
self._sudo_temp_keyfile = self.parent_helper.temp_filename()
|
|
376
|
+
# remove it at exit
|
|
377
|
+
rm_at_exit(self._sudo_temp_keyfile)
|
|
378
|
+
# generate random 32-byte key
|
|
379
|
+
random_key = token_bytes(32)
|
|
380
|
+
# write key to file and set secure permissions
|
|
381
|
+
self._sudo_temp_keyfile.write_bytes(random_key)
|
|
382
|
+
self._sudo_temp_keyfile.chmod(0o600)
|
|
383
|
+
# export path to environment variable, for use in askpass script
|
|
384
|
+
os.environ["BBOT_SUDO_KEYFILE"] = str(self._sudo_temp_keyfile.resolve())
|
|
385
|
+
|
|
386
|
+
@property
|
|
387
|
+
def encrypted_sudo_pw(self):
|
|
388
|
+
if self._sudo_password is None:
|
|
389
|
+
return ""
|
|
390
|
+
return self._encrypt_sudo_pw(self._sudo_password)
|
|
391
|
+
|
|
392
|
+
def _encrypt_sudo_pw(self, pw):
|
|
393
|
+
from Crypto.Cipher import AES
|
|
394
|
+
from Crypto.Util.Padding import pad
|
|
395
|
+
|
|
396
|
+
key = self._sudo_temp_keyfile.read_bytes()
|
|
397
|
+
cipher = AES.new(key, AES.MODE_CBC)
|
|
398
|
+
ct_bytes = cipher.encrypt(pad(pw.encode(), AES.block_size))
|
|
399
|
+
iv = cipher.iv.hex()
|
|
400
|
+
ct = ct_bytes.hex()
|
|
401
|
+
return f"{iv}:{ct}"
|
|
402
|
+
|
|
346
403
|
def _install_sudo_askpass(self):
|
|
347
404
|
if not self._installed_sudo_askpass:
|
|
348
405
|
self._installed_sudo_askpass = True
|
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
2
|
import os
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from Crypto.Cipher import AES
|
|
6
|
+
from Crypto.Util.Padding import unpad
|
|
7
|
+
|
|
8
|
+
ENV_VAR_NAME = "BBOT_SUDO_PASS"
|
|
9
|
+
KEY_ENV_VAR_PATH = "BBOT_SUDO_KEYFILE"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def decrypt_password(encrypted_data, key):
|
|
13
|
+
iv, ciphertext = encrypted_data.split(":")
|
|
14
|
+
iv = bytes.fromhex(iv)
|
|
15
|
+
ct = bytes.fromhex(ciphertext)
|
|
16
|
+
cipher = AES.new(key, AES.MODE_CBC, iv)
|
|
17
|
+
pt = unpad(cipher.decrypt(ct), AES.block_size)
|
|
18
|
+
return pt.decode("utf-8")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def main():
|
|
22
|
+
encrypted_password = os.environ.get(ENV_VAR_NAME, "")
|
|
23
|
+
# remove variable from environment once we've got it
|
|
24
|
+
os.environ.pop(ENV_VAR_NAME, None)
|
|
25
|
+
encryption_keypath = Path(os.environ.get(KEY_ENV_VAR_PATH, ""))
|
|
26
|
+
|
|
27
|
+
if not encrypted_password or not encryption_keypath.is_file():
|
|
28
|
+
print("Error: Encrypted password or encryption key not found in environment variables.", file=sys.stderr)
|
|
29
|
+
sys.exit(1)
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
key = encryption_keypath.read_bytes()
|
|
33
|
+
decrypted_password = decrypt_password(encrypted_password, key)
|
|
34
|
+
print(decrypted_password, end="")
|
|
35
|
+
except Exception as e:
|
|
36
|
+
print(f'Error decrypting password "{encrypted_password}": {str(e)}', file=sys.stderr)
|
|
37
|
+
sys.exit(1)
|
|
38
|
+
|
|
4
39
|
|
|
5
|
-
|
|
40
|
+
if __name__ == "__main__":
|
|
41
|
+
main()
|
bbot/core/helpers/diff.py
CHANGED
|
@@ -94,14 +94,14 @@ class HttpCompare:
|
|
|
94
94
|
baseline_1_json = xmltodict.parse(baseline_1.text)
|
|
95
95
|
baseline_2_json = xmltodict.parse(baseline_2.text)
|
|
96
96
|
except ExpatError:
|
|
97
|
-
log.debug(f"
|
|
97
|
+
log.debug(f"Can't HTML parse for {self.baseline_url}. Switching to text parsing as a backup")
|
|
98
98
|
baseline_1_json = baseline_1.text.split("\n")
|
|
99
99
|
baseline_2_json = baseline_2.text.split("\n")
|
|
100
100
|
|
|
101
101
|
ddiff = DeepDiff(baseline_1_json, baseline_2_json, ignore_order=True, view="tree")
|
|
102
102
|
self.ddiff_filters = []
|
|
103
103
|
|
|
104
|
-
for k
|
|
104
|
+
for k in ddiff.keys():
|
|
105
105
|
for x in list(ddiff[k]):
|
|
106
106
|
log.debug(f"Added {k} filter for path: {x.path()}")
|
|
107
107
|
self.ddiff_filters.append(x.path())
|
|
@@ -140,7 +140,7 @@ class HttpCompare:
|
|
|
140
140
|
|
|
141
141
|
ddiff = DeepDiff(headers_1, headers_2, ignore_order=True, view="tree")
|
|
142
142
|
|
|
143
|
-
for k
|
|
143
|
+
for k in ddiff.keys():
|
|
144
144
|
for x in list(ddiff[k]):
|
|
145
145
|
try:
|
|
146
146
|
header_value = str(x).split("'")[1]
|
|
@@ -183,7 +183,7 @@ class HttpCompare:
|
|
|
183
183
|
|
|
184
184
|
await self._baseline()
|
|
185
185
|
|
|
186
|
-
if timeout
|
|
186
|
+
if timeout is None:
|
|
187
187
|
timeout = self.timeout
|
|
188
188
|
|
|
189
189
|
reflection = False
|
|
@@ -203,7 +203,7 @@ class HttpCompare:
|
|
|
203
203
|
)
|
|
204
204
|
|
|
205
205
|
if subject_response is None:
|
|
206
|
-
# this can be caused by a WAF not liking the header, so we really
|
|
206
|
+
# this can be caused by a WAF not liking the header, so we really aren't interested in it
|
|
207
207
|
return (True, "403", reflection, subject_response)
|
|
208
208
|
|
|
209
209
|
if check_reflection:
|
|
@@ -225,7 +225,7 @@ class HttpCompare:
|
|
|
225
225
|
subject_json = xmltodict.parse(subject_response.text)
|
|
226
226
|
|
|
227
227
|
except ExpatError:
|
|
228
|
-
log.debug(f"
|
|
228
|
+
log.debug(f"Can't HTML parse for {subject.split('?')[0]}. Switching to text parsing as a backup")
|
|
229
229
|
subject_json = subject_response.text.split("\n")
|
|
230
230
|
|
|
231
231
|
diff_reasons = []
|
|
@@ -238,11 +238,11 @@ class HttpCompare:
|
|
|
238
238
|
|
|
239
239
|
different_headers = self.compare_headers(self.baseline.headers, subject_response.headers)
|
|
240
240
|
if different_headers:
|
|
241
|
-
log.debug(
|
|
241
|
+
log.debug("headers were different, no match")
|
|
242
242
|
diff_reasons.append("header")
|
|
243
243
|
|
|
244
|
-
if self.compare_body(self.baseline_json, subject_json)
|
|
245
|
-
log.debug(
|
|
244
|
+
if self.compare_body(self.baseline_json, subject_json) is False:
|
|
245
|
+
log.debug("difference in HTML body, no match")
|
|
246
246
|
|
|
247
247
|
diff_reasons.append("body")
|
|
248
248
|
|
|
@@ -275,6 +275,6 @@ class HttpCompare:
|
|
|
275
275
|
)
|
|
276
276
|
|
|
277
277
|
# if a nonsense header "caused" a difference, we need to abort. We also need to abort if our canary was reflected
|
|
278
|
-
if match
|
|
278
|
+
if match is False or reflection is True:
|
|
279
279
|
return False
|
|
280
280
|
return True
|
bbot/core/helpers/dns/brute.py
CHANGED
|
@@ -15,15 +15,17 @@ class DNSBrute:
|
|
|
15
15
|
>>> results = await self.helpers.dns.brute(self, domain, subdomains)
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
_nameservers_url = (
|
|
19
19
|
"https://raw.githubusercontent.com/blacklanternsecurity/public-dns-servers/master/nameservers.txt"
|
|
20
20
|
)
|
|
21
21
|
|
|
22
22
|
def __init__(self, parent_helper):
|
|
23
23
|
self.parent_helper = parent_helper
|
|
24
24
|
self.log = logging.getLogger("bbot.helper.dns.brute")
|
|
25
|
+
self.dns_config = self.parent_helper.config.get("dns", {})
|
|
25
26
|
self.num_canaries = 100
|
|
26
|
-
self.max_resolvers = self.
|
|
27
|
+
self.max_resolvers = self.dns_config.get("brute_threads", 1000)
|
|
28
|
+
self.nameservers_url = self.dns_config.get("brute_nameservers", self._nameservers_url)
|
|
27
29
|
self.devops_mutations = list(self.parent_helper.word_cloud.devops_mutations)
|
|
28
30
|
self.digit_regex = self.parent_helper.re.compile(r"\d+")
|
|
29
31
|
self._resolver_file = None
|
|
@@ -39,18 +41,15 @@ class DNSBrute:
|
|
|
39
41
|
type = "A"
|
|
40
42
|
type = str(type).strip().upper()
|
|
41
43
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
f"Aborting massdns on {domain} because it's a wildcard domain ({','.join(domain_wildcard_rdtypes)})"
|
|
44
|
+
wildcard_domains = await self.parent_helper.dns.is_wildcard_domain(domain, (type, "CNAME"))
|
|
45
|
+
wildcard_rdtypes = set()
|
|
46
|
+
for domain, rdtypes in wildcard_domains.items():
|
|
47
|
+
wildcard_rdtypes.update(rdtypes)
|
|
48
|
+
if wildcard_domains:
|
|
49
|
+
self.log.hugewarning(
|
|
50
|
+
f"Aborting massdns on {domain} because it's a wildcard domain ({','.join(sorted(wildcard_rdtypes))})"
|
|
50
51
|
)
|
|
51
52
|
return []
|
|
52
|
-
else:
|
|
53
|
-
self.log.debug(f"{domain}: A is not in domain_wildcard_rdtypes:{domain_wildcard_rdtypes}")
|
|
54
53
|
|
|
55
54
|
canaries = self.gen_random_subdomains(self.num_canaries)
|
|
56
55
|
canaries_list = list(canaries)
|
|
@@ -148,10 +147,15 @@ class DNSBrute:
|
|
|
148
147
|
|
|
149
148
|
async def resolver_file(self):
|
|
150
149
|
if self._resolver_file is None:
|
|
151
|
-
self.
|
|
150
|
+
self._resolver_file_original = await self.parent_helper.wordlist(
|
|
152
151
|
self.nameservers_url,
|
|
153
152
|
cache_hrs=24 * 7,
|
|
154
153
|
)
|
|
154
|
+
nameservers = set(self.parent_helper.read_file(self._resolver_file_original))
|
|
155
|
+
nameservers.difference_update(self.parent_helper.dns.system_resolvers)
|
|
156
|
+
# exclude system nameservers from brute-force
|
|
157
|
+
# this helps prevent rate-limiting which might cause BBOT's main dns queries to fail
|
|
158
|
+
self._resolver_file = self.parent_helper.tempfile(nameservers, pipe=False)
|
|
155
159
|
return self._resolver_file
|
|
156
160
|
|
|
157
161
|
def gen_random_subdomains(self, n=50):
|
|
@@ -160,7 +164,7 @@ class DNSBrute:
|
|
|
160
164
|
for i in range(0, max(0, n - 5)):
|
|
161
165
|
d = delimiters[i % len(delimiters)]
|
|
162
166
|
l = lengths[i % len(lengths)]
|
|
163
|
-
segments =
|
|
167
|
+
segments = [random.choice(self.devops_mutations) for _ in range(l)]
|
|
164
168
|
segments.append(self.parent_helper.rand_string(length=8, digits=False))
|
|
165
169
|
subdomain = d.join(segments)
|
|
166
170
|
yield subdomain
|
bbot/core/helpers/dns/dns.py
CHANGED
|
@@ -16,7 +16,6 @@ log = logging.getLogger("bbot.core.helpers.dns")
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class DNSHelper(EngineClient):
|
|
19
|
-
|
|
20
19
|
SERVER_CLASS = DNSEngine
|
|
21
20
|
ERROR_CLASS = DNSError
|
|
22
21
|
|
|
@@ -66,7 +65,7 @@ class DNSHelper(EngineClient):
|
|
|
66
65
|
self.resolver.timeout = self.timeout
|
|
67
66
|
self.resolver.lifetime = self.timeout
|
|
68
67
|
|
|
69
|
-
self.runaway_limit = self.
|
|
68
|
+
self.runaway_limit = self.dns_config.get("runaway_limit", 5)
|
|
70
69
|
|
|
71
70
|
# wildcard handling
|
|
72
71
|
self.wildcard_disable = self.dns_config.get("wildcard_disable", False)
|
|
@@ -117,8 +116,11 @@ class DNSHelper(EngineClient):
|
|
|
117
116
|
self._brute = DNSBrute(self.parent_helper)
|
|
118
117
|
return self._brute
|
|
119
118
|
|
|
120
|
-
@async_cachedmethod(
|
|
121
|
-
|
|
119
|
+
@async_cachedmethod(
|
|
120
|
+
lambda self: self._is_wildcard_cache,
|
|
121
|
+
key=lambda query, rdtypes, raw_dns_records: (query, tuple(sorted(rdtypes)), bool(raw_dns_records)),
|
|
122
|
+
)
|
|
123
|
+
async def is_wildcard(self, query, rdtypes, raw_dns_records=None):
|
|
122
124
|
"""
|
|
123
125
|
Use this method to check whether a *host* is a wildcard entry
|
|
124
126
|
|
|
@@ -150,9 +152,6 @@ class DNSHelper(EngineClient):
|
|
|
150
152
|
Note:
|
|
151
153
|
`is_wildcard` can be True, False, or None (indicating that wildcard detection was inconclusive)
|
|
152
154
|
"""
|
|
153
|
-
if [ips, rdtype].count(None) == 1:
|
|
154
|
-
raise ValueError("Both ips and rdtype must be specified")
|
|
155
|
-
|
|
156
155
|
query = self._wildcard_prevalidation(query)
|
|
157
156
|
if not query:
|
|
158
157
|
return {}
|
|
@@ -161,15 +160,17 @@ class DNSHelper(EngineClient):
|
|
|
161
160
|
if is_domain(query):
|
|
162
161
|
return {}
|
|
163
162
|
|
|
164
|
-
return await self.run_and_return("is_wildcard", query=query,
|
|
163
|
+
return await self.run_and_return("is_wildcard", query=query, rdtypes=rdtypes, raw_dns_records=raw_dns_records)
|
|
165
164
|
|
|
166
|
-
@async_cachedmethod(
|
|
167
|
-
|
|
165
|
+
@async_cachedmethod(
|
|
166
|
+
lambda self: self._is_wildcard_domain_cache, key=lambda domain, rdtypes: (domain, tuple(sorted(rdtypes)))
|
|
167
|
+
)
|
|
168
|
+
async def is_wildcard_domain(self, domain, rdtypes):
|
|
168
169
|
domain = self._wildcard_prevalidation(domain)
|
|
169
170
|
if not domain:
|
|
170
171
|
return {}
|
|
171
172
|
|
|
172
|
-
return await self.run_and_return("is_wildcard_domain", domain=domain,
|
|
173
|
+
return await self.run_and_return("is_wildcard_domain", domain=domain, rdtypes=rdtypes)
|
|
173
174
|
|
|
174
175
|
def _wildcard_prevalidation(self, host):
|
|
175
176
|
if self.wildcard_disable:
|
|
@@ -177,7 +178,7 @@ class DNSHelper(EngineClient):
|
|
|
177
178
|
|
|
178
179
|
host = clean_dns_record(host)
|
|
179
180
|
# skip check if it's an IP or a plain hostname
|
|
180
|
-
if is_ip(host) or
|
|
181
|
+
if is_ip(host) or "." not in host:
|
|
181
182
|
return False
|
|
182
183
|
|
|
183
184
|
# skip if query isn't a dns name
|
|
@@ -192,8 +193,8 @@ class DNSHelper(EngineClient):
|
|
|
192
193
|
|
|
193
194
|
return host
|
|
194
195
|
|
|
195
|
-
async def _mock_dns(self, mock_data):
|
|
196
|
+
async def _mock_dns(self, mock_data, custom_lookup_fn=None):
|
|
196
197
|
from .mock import MockResolver
|
|
197
198
|
|
|
198
|
-
self.resolver = MockResolver(mock_data)
|
|
199
|
-
await self.run_and_return("_mock_dns", mock_data=mock_data)
|
|
199
|
+
self.resolver = MockResolver(mock_data, custom_lookup_fn=custom_lookup_fn)
|
|
200
|
+
await self.run_and_return("_mock_dns", mock_data=mock_data, custom_lookup_fn=custom_lookup_fn)
|