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/helpers/dns/engine.py
CHANGED
|
@@ -7,7 +7,6 @@ import traceback
|
|
|
7
7
|
from cachetools import LRUCache
|
|
8
8
|
from contextlib import suppress
|
|
9
9
|
|
|
10
|
-
from bbot.errors import DNSWildcardBreak
|
|
11
10
|
from bbot.core.engine import EngineServer
|
|
12
11
|
from bbot.core.helpers.async_helpers import NamedLock
|
|
13
12
|
from bbot.core.helpers.dns.helpers import extract_targets
|
|
@@ -16,7 +15,6 @@ from bbot.core.helpers.misc import (
|
|
|
16
15
|
rand_string,
|
|
17
16
|
parent_domain,
|
|
18
17
|
domain_parents,
|
|
19
|
-
clean_dns_record,
|
|
20
18
|
)
|
|
21
19
|
|
|
22
20
|
|
|
@@ -26,7 +24,6 @@ all_rdtypes = ["A", "AAAA", "SRV", "MX", "NS", "SOA", "CNAME", "TXT"]
|
|
|
26
24
|
|
|
27
25
|
|
|
28
26
|
class DNSEngine(EngineServer):
|
|
29
|
-
|
|
30
27
|
CMDS = {
|
|
31
28
|
0: "resolve",
|
|
32
29
|
1: "resolve_raw",
|
|
@@ -57,7 +54,7 @@ class DNSEngine(EngineServer):
|
|
|
57
54
|
dns_omit_queries = self.dns_config.get("omit_queries", None)
|
|
58
55
|
if not dns_omit_queries:
|
|
59
56
|
dns_omit_queries = []
|
|
60
|
-
self.dns_omit_queries =
|
|
57
|
+
self.dns_omit_queries = {}
|
|
61
58
|
for d in dns_omit_queries:
|
|
62
59
|
d = d.split(":")
|
|
63
60
|
if len(d) == 2:
|
|
@@ -75,7 +72,7 @@ class DNSEngine(EngineServer):
|
|
|
75
72
|
self.wildcard_ignore = []
|
|
76
73
|
self.wildcard_ignore = tuple([str(d).strip().lower() for d in self.wildcard_ignore])
|
|
77
74
|
self.wildcard_tests = self.dns_config.get("wildcard_tests", 5)
|
|
78
|
-
self._wildcard_cache =
|
|
75
|
+
self._wildcard_cache = {}
|
|
79
76
|
# since wildcard detection takes some time, This is to prevent multiple
|
|
80
77
|
# modules from kicking off wildcard detection for the same domain at the same time
|
|
81
78
|
self._wildcard_lock = NamedLock()
|
|
@@ -85,7 +82,7 @@ class DNSEngine(EngineServer):
|
|
|
85
82
|
self._last_connectivity_warning = time.time()
|
|
86
83
|
# keeps track of warnings issued for wildcard detection to prevent duplicate warnings
|
|
87
84
|
self._dns_warnings = set()
|
|
88
|
-
self._errors =
|
|
85
|
+
self._errors = {}
|
|
89
86
|
self._debug = self.dns_config.get("debug", False)
|
|
90
87
|
self._dns_cache = LRUCache(maxsize=10000)
|
|
91
88
|
|
|
@@ -208,8 +205,8 @@ class DNSEngine(EngineServer):
|
|
|
208
205
|
retries = kwargs.pop("retries", self.retries)
|
|
209
206
|
use_cache = kwargs.pop("use_cache", True)
|
|
210
207
|
tries_left = int(retries) + 1
|
|
211
|
-
parent_hash = hash(
|
|
212
|
-
dns_cache_hash = hash(
|
|
208
|
+
parent_hash = hash((parent, rdtype))
|
|
209
|
+
dns_cache_hash = hash((query, rdtype))
|
|
213
210
|
while tries_left > 0:
|
|
214
211
|
try:
|
|
215
212
|
if use_cache:
|
|
@@ -297,7 +294,7 @@ class DNSEngine(EngineServer):
|
|
|
297
294
|
tries_left = int(retries) + 1
|
|
298
295
|
results = []
|
|
299
296
|
errors = []
|
|
300
|
-
dns_cache_hash = hash(
|
|
297
|
+
dns_cache_hash = hash((query, "PTR"))
|
|
301
298
|
while tries_left > 0:
|
|
302
299
|
try:
|
|
303
300
|
if use_cache:
|
|
@@ -361,8 +358,7 @@ class DNSEngine(EngineServer):
|
|
|
361
358
|
):
|
|
362
359
|
query = args[0]
|
|
363
360
|
rdtype = kwargs["type"]
|
|
364
|
-
|
|
365
|
-
yield ((query, rdtype), (answer, errors))
|
|
361
|
+
yield ((query, rdtype), (answers, errors))
|
|
366
362
|
|
|
367
363
|
async def _catch(self, callback, *args, **kwargs):
|
|
368
364
|
"""
|
|
@@ -397,18 +393,21 @@ class DNSEngine(EngineServer):
|
|
|
397
393
|
self.log.trace(traceback.format_exc())
|
|
398
394
|
return []
|
|
399
395
|
|
|
400
|
-
async def is_wildcard(self, query,
|
|
396
|
+
async def is_wildcard(self, query, rdtypes, raw_dns_records=None):
|
|
401
397
|
"""
|
|
402
398
|
Use this method to check whether a *host* is a wildcard entry
|
|
403
399
|
|
|
404
400
|
This can reliably tell the difference between a valid DNS record and a wildcard within a wildcard domain.
|
|
405
401
|
|
|
402
|
+
It works by making a bunch of random DNS queries to the parent domain, compiling a list of wildcard IPs,
|
|
403
|
+
then comparing those to the IPs of the host in question. If the host's IP matches the wildcard ones, it's a wildcard.
|
|
404
|
+
|
|
406
405
|
If you want to know whether a domain is using wildcard DNS, use `is_wildcard_domain()` instead.
|
|
407
406
|
|
|
408
407
|
Args:
|
|
409
408
|
query (str): The hostname to check for a wildcard entry.
|
|
410
|
-
|
|
411
|
-
|
|
409
|
+
rdtypes (list): The DNS record type (e.g., "A", "AAAA") to consider during the check.
|
|
410
|
+
raw_dns_records (dict, optional): Dictionary of {rdtype: [answer1, answer2, ...], ...} containing raw dnspython answers for the query.
|
|
412
411
|
|
|
413
412
|
Returns:
|
|
414
413
|
dict: A dictionary indicating if the query is a wildcard for each checked DNS record type.
|
|
@@ -416,108 +415,115 @@ class DNSEngine(EngineServer):
|
|
|
416
415
|
Values are tuples where the first element is a boolean indicating if the query is a wildcard,
|
|
417
416
|
and the second element is the wildcard parent if it's a wildcard.
|
|
418
417
|
|
|
419
|
-
Raises:
|
|
420
|
-
ValueError: If only one of `ips` or `rdtype` is specified or if no valid IPs are specified.
|
|
421
|
-
|
|
422
418
|
Examples:
|
|
423
|
-
>>> is_wildcard("www.github.io")
|
|
424
|
-
{"A": (True, "github.io"), "AAAA": (True, "github.io")}
|
|
419
|
+
>>> is_wildcard("www.github.io", rdtypes=["A", "AAAA", "MX"])
|
|
420
|
+
{"A": (True, "github.io"), "AAAA": (True, "github.io"), "MX": (False, "github.io")}
|
|
425
421
|
|
|
426
|
-
>>> is_wildcard("www.evilcorp.com",
|
|
422
|
+
>>> is_wildcard("www.evilcorp.com", rdtypes=["A"])
|
|
427
423
|
{"A": (False, "evilcorp.com")}
|
|
428
424
|
|
|
429
425
|
Note:
|
|
430
426
|
`is_wildcard` can be True, False, or None (indicating that wildcard detection was inconclusive)
|
|
431
427
|
"""
|
|
432
|
-
|
|
428
|
+
if isinstance(rdtypes, str):
|
|
429
|
+
rdtypes = [rdtypes]
|
|
433
430
|
|
|
434
|
-
|
|
435
|
-
parents = list(domain_parents(query))
|
|
431
|
+
result = {}
|
|
436
432
|
|
|
437
|
-
if
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
rdtypes_to_check = all_rdtypes
|
|
443
|
-
|
|
444
|
-
query_baseline = dict()
|
|
445
|
-
# if the caller hasn't already done the work of resolving the IPs
|
|
446
|
-
if ips is None:
|
|
447
|
-
# then resolve the query for all rdtypes
|
|
448
|
-
queries = [(query, t) for t in rdtypes_to_check]
|
|
449
|
-
async for (query, _rdtype), (answers, errors) in self.resolve_raw_batch(queries):
|
|
450
|
-
answers = extract_targets(answers)
|
|
433
|
+
# if the work of resolving hasn't been done yet, do it
|
|
434
|
+
if raw_dns_records is None:
|
|
435
|
+
raw_dns_records = {}
|
|
436
|
+
queries = [(query, rdtype) for rdtype in rdtypes]
|
|
437
|
+
async for (_, rdtype), (answers, errors) in self.resolve_raw_batch(queries):
|
|
451
438
|
if answers:
|
|
452
|
-
|
|
439
|
+
for answer in answers:
|
|
440
|
+
try:
|
|
441
|
+
raw_dns_records[rdtype].add(answer)
|
|
442
|
+
except KeyError:
|
|
443
|
+
raw_dns_records[rdtype] = {answer}
|
|
453
444
|
else:
|
|
454
445
|
if errors:
|
|
455
|
-
self.debug(f"Failed to resolve {query} ({
|
|
456
|
-
result[
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
446
|
+
self.debug(f"Failed to resolve {query} ({rdtype}) during wildcard detection")
|
|
447
|
+
result[rdtype] = ("ERROR", query)
|
|
448
|
+
|
|
449
|
+
# clean + process the raw records into a baseline
|
|
450
|
+
baseline = {}
|
|
451
|
+
baseline_raw = {}
|
|
452
|
+
for rdtype, answers in raw_dns_records.items():
|
|
453
|
+
for answer in answers:
|
|
454
|
+
text_answer = answer.to_text()
|
|
455
|
+
try:
|
|
456
|
+
baseline_raw[rdtype].add(text_answer)
|
|
457
|
+
except KeyError:
|
|
458
|
+
baseline_raw[rdtype] = {text_answer}
|
|
459
|
+
for _, host in extract_targets(answer):
|
|
460
|
+
try:
|
|
461
|
+
baseline[rdtype].add(host)
|
|
462
|
+
except KeyError:
|
|
463
|
+
baseline[rdtype] = {host}
|
|
464
|
+
|
|
465
|
+
# if it's unresolved, it's a big nope
|
|
466
|
+
if not raw_dns_records:
|
|
465
467
|
return result
|
|
466
468
|
|
|
467
469
|
# once we've resolved the base query and have IP addresses to work with
|
|
468
470
|
# we can compare the IPs to the ones we have on file for wildcards
|
|
469
471
|
|
|
472
|
+
# only bother to check the rdypes that actually resolve
|
|
473
|
+
rdtypes_to_check = set(raw_dns_records)
|
|
474
|
+
|
|
470
475
|
# for every parent domain, starting with the shortest
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
476
|
+
parents = list(domain_parents(query))
|
|
477
|
+
for parent in parents[::-1]:
|
|
478
|
+
# check if the parent domain is set up with wildcards
|
|
479
|
+
wildcard_results = await self.is_wildcard_domain(parent, rdtypes_to_check)
|
|
480
|
+
|
|
481
|
+
# for every rdtype
|
|
482
|
+
for rdtype in list(baseline_raw):
|
|
483
|
+
# skip if we already found a wildcard for this rdtype
|
|
484
|
+
if rdtype in result:
|
|
485
|
+
continue
|
|
486
|
+
|
|
487
|
+
# get our baseline IPs from above
|
|
488
|
+
_baseline = baseline.get(rdtype, set())
|
|
489
|
+
_baseline_raw = baseline_raw.get(rdtype, set())
|
|
490
|
+
|
|
491
|
+
wildcard_rdtypes = wildcard_results.get(parent, {})
|
|
492
|
+
wildcards = wildcard_rdtypes.get(rdtype, None)
|
|
493
|
+
if wildcards is None:
|
|
494
|
+
continue
|
|
495
|
+
wildcards, wildcard_raw = wildcards
|
|
496
|
+
|
|
497
|
+
if wildcard_raw:
|
|
498
|
+
# skip this rdtype from now on
|
|
499
|
+
rdtypes_to_check.remove(rdtype)
|
|
500
|
+
|
|
501
|
+
# check if any of our baseline IPs are in the wildcard results
|
|
502
|
+
is_wildcard = any(r in wildcards for r in _baseline)
|
|
503
|
+
is_wildcard_raw = any(r in wildcard_raw for r in _baseline_raw)
|
|
504
|
+
|
|
505
|
+
# if there are any matches, we have a wildcard
|
|
506
|
+
if is_wildcard or is_wildcard_raw:
|
|
507
|
+
result[rdtype] = (True, parent)
|
|
508
|
+
else:
|
|
509
|
+
# otherwise, it's still suspicious, because we had random stuff resolve at this level
|
|
510
|
+
result[rdtype] = ("POSSIBLE", parent)
|
|
511
|
+
|
|
512
|
+
# any rdtype that wasn't a wildcard, mark it as False
|
|
513
|
+
for rdtype, answers in baseline_raw.items():
|
|
514
|
+
if answers and rdtype not in result:
|
|
515
|
+
result[rdtype] = (False, query)
|
|
510
516
|
|
|
511
517
|
return result
|
|
512
518
|
|
|
513
|
-
async def is_wildcard_domain(self, domain,
|
|
519
|
+
async def is_wildcard_domain(self, domain, rdtypes):
|
|
514
520
|
"""
|
|
515
521
|
Check whether a given host or its children make use of wildcard DNS entries. Wildcard DNS can have
|
|
516
522
|
various implications, particularly in subdomain enumeration and subdomain takeovers.
|
|
517
523
|
|
|
518
524
|
Args:
|
|
519
525
|
domain (str): The domain to check for wildcard DNS entries.
|
|
520
|
-
|
|
526
|
+
rdtypes (list): Which DNS record types to check.
|
|
521
527
|
|
|
522
528
|
Returns:
|
|
523
529
|
dict: A dictionary where the keys are the parent domains that have wildcard DNS entries,
|
|
@@ -531,60 +537,74 @@ class DNSEngine(EngineServer):
|
|
|
531
537
|
>>> is_wildcard_domain("example.com")
|
|
532
538
|
{}
|
|
533
539
|
"""
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
540
|
+
if isinstance(rdtypes, str):
|
|
541
|
+
rdtypes = [rdtypes]
|
|
542
|
+
rdtypes = set(rdtypes)
|
|
537
543
|
|
|
544
|
+
wildcard_results = {}
|
|
538
545
|
# make a list of its parents
|
|
539
546
|
parents = list(domain_parents(domain, include_self=True))
|
|
540
547
|
# and check each of them, beginning with the highest parent (i.e. the root domain)
|
|
541
548
|
for i, host in enumerate(parents[::-1]):
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
async
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
+
host_results = {}
|
|
550
|
+
queries = [((host, rdtype), {}) for rdtype in rdtypes]
|
|
551
|
+
async for ((_, rdtype), _, _), (results, results_raw) in self.task_pool(
|
|
552
|
+
self._is_wildcard_zone, args_kwargs=queries
|
|
553
|
+
):
|
|
554
|
+
# if we hit a wildcard, we can skip this rdtype from now on
|
|
555
|
+
if results_raw:
|
|
556
|
+
rdtypes.remove(rdtype)
|
|
557
|
+
host_results[rdtype] = results, results_raw
|
|
558
|
+
|
|
559
|
+
if host_results:
|
|
560
|
+
wildcard_results[host] = host_results
|
|
561
|
+
|
|
562
|
+
return wildcard_results
|
|
563
|
+
|
|
564
|
+
async def _is_wildcard_zone(self, host, rdtype):
|
|
565
|
+
"""
|
|
566
|
+
Check whether a specific DNS zone+rdtype has a wildcard configuration
|
|
567
|
+
"""
|
|
568
|
+
rdtype = rdtype.upper()
|
|
549
569
|
|
|
550
|
-
|
|
570
|
+
# have we checked this host before?
|
|
571
|
+
host_hash = hash((host, rdtype))
|
|
572
|
+
async with self._wildcard_lock.lock(host_hash):
|
|
573
|
+
# if we've seen this host before
|
|
574
|
+
try:
|
|
575
|
+
wildcard_results, wildcard_results_raw = self._wildcard_cache[host_hash]
|
|
576
|
+
self.debug(f"Got {host}:{rdtype} from cache")
|
|
577
|
+
except KeyError:
|
|
578
|
+
wildcard_results = set()
|
|
579
|
+
wildcard_results_raw = set()
|
|
580
|
+
self.debug(f"Checking if {host}:{rdtype} is a wildcard")
|
|
551
581
|
|
|
552
582
|
# determine if this is a wildcard domain
|
|
553
|
-
|
|
554
583
|
# resolve a bunch of random subdomains of the same parent
|
|
555
|
-
is_wildcard = False
|
|
556
|
-
wildcard_results = dict()
|
|
557
|
-
|
|
558
584
|
rand_queries = []
|
|
559
|
-
for
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
rand_queries.append((rand_query, rdtype))
|
|
585
|
+
for _ in range(self.wildcard_tests):
|
|
586
|
+
rand_query = f"{rand_string(digits=False, length=10)}.{host}"
|
|
587
|
+
rand_queries.append((rand_query, rdtype))
|
|
563
588
|
|
|
564
589
|
async for (query, rdtype), (answers, errors) in self.resolve_raw_batch(rand_queries, use_cache=False):
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
rdtypes_to_check.remove(rdtype)
|
|
575
|
-
|
|
576
|
-
self._wildcard_cache.update({host_hash: wildcard_results})
|
|
577
|
-
wildcard_domain_results.update({host: wildcard_results})
|
|
578
|
-
if is_wildcard:
|
|
579
|
-
wildcard_rdtypes_str = ",".join(sorted([t.upper() for t, r in wildcard_results.items() if r]))
|
|
580
|
-
log_fn = self.log.verbose
|
|
581
|
-
if log_info:
|
|
582
|
-
log_fn = self.log.info
|
|
583
|
-
log_fn(f"Encountered domain with wildcard DNS ({wildcard_rdtypes_str}): {host}")
|
|
590
|
+
for answer in answers:
|
|
591
|
+
# consider both the raw record
|
|
592
|
+
wildcard_results_raw.add(answer.to_text())
|
|
593
|
+
# and all the extracted hosts
|
|
594
|
+
for _, t in extract_targets(answer):
|
|
595
|
+
wildcard_results.add(t)
|
|
596
|
+
|
|
597
|
+
if wildcard_results:
|
|
598
|
+
self.log.info(f"Encountered domain with wildcard DNS ({rdtype}): *.{host}")
|
|
584
599
|
else:
|
|
585
|
-
self.
|
|
600
|
+
self.debug(f"Finished checking {host}:{rdtype}, it is not a wildcard")
|
|
601
|
+
self._wildcard_cache[host_hash] = wildcard_results, wildcard_results_raw
|
|
586
602
|
|
|
587
|
-
return
|
|
603
|
+
return wildcard_results, wildcard_results_raw
|
|
604
|
+
|
|
605
|
+
async def _is_wildcard(self, query, rdtypes, dns_children):
|
|
606
|
+
if isinstance(rdtypes, str):
|
|
607
|
+
rdtypes = [rdtypes]
|
|
588
608
|
|
|
589
609
|
@property
|
|
590
610
|
def dns_connectivity_lock(self):
|
|
@@ -618,7 +638,7 @@ class DNSEngine(EngineServer):
|
|
|
618
638
|
self._last_dns_success = time.time()
|
|
619
639
|
return True
|
|
620
640
|
if time.time() - self._last_connectivity_warning > interval:
|
|
621
|
-
self.log.warning(
|
|
641
|
+
self.log.warning("DNS queries are failing, please check your internet connection")
|
|
622
642
|
self._last_connectivity_warning = time.time()
|
|
623
643
|
self._errors.clear()
|
|
624
644
|
return False
|
|
@@ -631,7 +651,14 @@ class DNSEngine(EngineServer):
|
|
|
631
651
|
def in_tests(self):
|
|
632
652
|
return os.getenv("BBOT_TESTING", "") == "True"
|
|
633
653
|
|
|
634
|
-
async def _mock_dns(self, mock_data):
|
|
654
|
+
async def _mock_dns(self, mock_data, custom_lookup_fn=None):
|
|
635
655
|
from .mock import MockResolver
|
|
636
656
|
|
|
637
|
-
|
|
657
|
+
def deserialize_function(func_source):
|
|
658
|
+
assert self.in_tests, "Can only mock when BBOT_TESTING=True"
|
|
659
|
+
if func_source is None:
|
|
660
|
+
return None
|
|
661
|
+
exec(func_source)
|
|
662
|
+
return locals()["custom_lookup"]
|
|
663
|
+
|
|
664
|
+
self.resolver = MockResolver(mock_data, custom_lookup_fn=deserialize_function(custom_lookup_fn))
|
bbot/core/helpers/dns/helpers.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
|
|
3
|
-
from bbot.core.helpers.regexes import
|
|
3
|
+
from bbot.core.helpers.regexes import dns_name_extraction_regex
|
|
4
4
|
from bbot.core.helpers.misc import clean_dns_record, smart_decode
|
|
5
5
|
|
|
6
6
|
log = logging.getLogger("bbot.core.helpers.dns")
|
|
@@ -198,7 +198,7 @@ def extract_targets(record):
|
|
|
198
198
|
elif rdtype == "TXT":
|
|
199
199
|
for s in record.strings:
|
|
200
200
|
s = smart_decode(s)
|
|
201
|
-
for match in
|
|
201
|
+
for match in dns_name_extraction_regex.finditer(s):
|
|
202
202
|
start, end = match.span()
|
|
203
203
|
host = s[start:end]
|
|
204
204
|
add_result(rdtype, host)
|
bbot/core/helpers/dns/mock.py
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import dns
|
|
2
|
+
import logging
|
|
2
3
|
|
|
4
|
+
log = logging.getLogger("bbot.core.helpers.dns.mock")
|
|
3
5
|
|
|
4
|
-
class MockResolver:
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
class MockResolver:
|
|
8
|
+
def __init__(self, mock_data=None, custom_lookup_fn=None):
|
|
7
9
|
self.mock_data = mock_data if mock_data else {}
|
|
10
|
+
self._custom_lookup_fn = custom_lookup_fn
|
|
8
11
|
self.nameservers = ["127.0.0.1"]
|
|
9
12
|
|
|
10
13
|
async def resolve_address(self, ipaddr, *args, **kwargs):
|
|
@@ -13,12 +16,22 @@ class MockResolver:
|
|
|
13
16
|
modified_kwargs["rdtype"] = "PTR"
|
|
14
17
|
return await self.resolve(str(dns.reversename.from_address(ipaddr)), *args, **modified_kwargs)
|
|
15
18
|
|
|
16
|
-
def
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if not
|
|
20
|
-
|
|
19
|
+
def _lookup(self, query, rdtype):
|
|
20
|
+
query = query.strip(".")
|
|
21
|
+
ret = []
|
|
22
|
+
if self._custom_lookup_fn is not None:
|
|
23
|
+
answers = self._custom_lookup_fn(query, rdtype)
|
|
24
|
+
if answers is not None:
|
|
25
|
+
ret.extend(list(answers))
|
|
26
|
+
answers = self.mock_data.get(query, {}).get(rdtype, [])
|
|
27
|
+
if answers:
|
|
28
|
+
ret.extend(list(answers))
|
|
29
|
+
if not ret:
|
|
30
|
+
raise dns.resolver.NXDOMAIN(f"No answer found for {query} {rdtype}")
|
|
31
|
+
return ret
|
|
21
32
|
|
|
33
|
+
def create_dns_response(self, query_name, answers, rdtype):
|
|
34
|
+
query_name = query_name.strip(".")
|
|
22
35
|
message_text = f"""id 1234
|
|
23
36
|
opcode QUERY
|
|
24
37
|
rcode NOERROR
|
|
@@ -27,10 +40,13 @@ flags QR AA RD
|
|
|
27
40
|
{query_name}. IN {rdtype}
|
|
28
41
|
;ANSWER"""
|
|
29
42
|
for answer in answers:
|
|
43
|
+
if answer == "":
|
|
44
|
+
answer = '""'
|
|
30
45
|
message_text += f"\n{query_name}. 1 IN {rdtype} {answer}"
|
|
31
46
|
|
|
32
47
|
message_text += "\n;AUTHORITY\n;ADDITIONAL\n"
|
|
33
48
|
message = dns.message.from_text(message_text)
|
|
49
|
+
# log.verbose(message_text)
|
|
34
50
|
return message
|
|
35
51
|
|
|
36
52
|
async def resolve(self, query_name, rdtype=None):
|
|
@@ -49,7 +65,9 @@ flags QR AA RD
|
|
|
49
65
|
raise dns.resolver.NXDOMAIN
|
|
50
66
|
|
|
51
67
|
try:
|
|
52
|
-
|
|
68
|
+
answers = self._lookup(query_name, rdtype)
|
|
69
|
+
log.verbose(f"Answers for {query_name}:{rdtype}: {answers}")
|
|
70
|
+
response = self.create_dns_response(query_name, answers, rdtype)
|
|
53
71
|
answer = dns.resolver.Answer(domain_name, rdtype_obj, dns.rdataclass.IN, response)
|
|
54
72
|
return answer
|
|
55
73
|
except dns.resolver.NXDOMAIN:
|
bbot/core/helpers/files.py
CHANGED
|
@@ -83,7 +83,7 @@ def _feed_pipe(self, pipe, content, text=True):
|
|
|
83
83
|
for c in content:
|
|
84
84
|
p.write(decode_fn(c) + newline)
|
|
85
85
|
except BrokenPipeError:
|
|
86
|
-
log.debug(
|
|
86
|
+
log.debug("Broken pipe in _feed_pipe()")
|
|
87
87
|
except ValueError:
|
|
88
88
|
log.debug(f"Error _feed_pipe(): {traceback.format_exc()}")
|
|
89
89
|
except KeyboardInterrupt:
|
bbot/core/helpers/helper.py
CHANGED
|
@@ -12,10 +12,11 @@ from .diff import HttpCompare
|
|
|
12
12
|
from .regex import RegexHelper
|
|
13
13
|
from .wordcloud import WordCloud
|
|
14
14
|
from .interactsh import Interactsh
|
|
15
|
-
from ...scanner.target import Target
|
|
16
15
|
from .depsinstaller import DepsInstaller
|
|
17
16
|
from .async_helpers import get_event_loop
|
|
18
17
|
|
|
18
|
+
from bbot.scanner.target import BaseTarget
|
|
19
|
+
|
|
19
20
|
log = logging.getLogger("bbot.core.helpers")
|
|
20
21
|
|
|
21
22
|
|
|
@@ -152,11 +153,13 @@ class ConfigAwareHelper:
|
|
|
152
153
|
return self.temp_dir / filename
|
|
153
154
|
|
|
154
155
|
def clean_old_scans(self):
|
|
155
|
-
_filter
|
|
156
|
+
def _filter(x):
|
|
157
|
+
return x.is_dir() and self.regexes.scan_name_regex.match(x.name)
|
|
158
|
+
|
|
156
159
|
self.clean_old(self.scans_dir, keep=self.keep_old_scans, filter=_filter)
|
|
157
160
|
|
|
158
|
-
def make_target(self, *
|
|
159
|
-
return
|
|
161
|
+
def make_target(self, *targets, **kwargs):
|
|
162
|
+
return BaseTarget(*targets, scan=self.scan, **kwargs)
|
|
160
163
|
|
|
161
164
|
@property
|
|
162
165
|
def config(self):
|
bbot/core/helpers/interactsh.py
CHANGED
|
@@ -155,7 +155,7 @@ class Interactsh:
|
|
|
155
155
|
break
|
|
156
156
|
|
|
157
157
|
if not self.server:
|
|
158
|
-
raise InteractshError(
|
|
158
|
+
raise InteractshError("Failed to register with an interactsh server")
|
|
159
159
|
|
|
160
160
|
log.info(
|
|
161
161
|
f"Successfully registered to interactsh server {self.server} with correlation_id {self.correlation_id} [{self.domain}]"
|
|
@@ -181,7 +181,7 @@ class Interactsh:
|
|
|
181
181
|
>>> await interactsh_client.deregister()
|
|
182
182
|
"""
|
|
183
183
|
if not self.server or not self.correlation_id or not self.secret:
|
|
184
|
-
raise InteractshError(
|
|
184
|
+
raise InteractshError("Missing required information to deregister")
|
|
185
185
|
|
|
186
186
|
headers = {}
|
|
187
187
|
if self.token:
|
|
@@ -226,7 +226,7 @@ class Interactsh:
|
|
|
226
226
|
]
|
|
227
227
|
"""
|
|
228
228
|
if not self.server or not self.correlation_id or not self.secret:
|
|
229
|
-
raise InteractshError(
|
|
229
|
+
raise InteractshError("Missing required information to poll")
|
|
230
230
|
|
|
231
231
|
headers = {}
|
|
232
232
|
if self.token:
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import puremagic
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def get_magic_info(file):
|
|
5
|
+
magic_detections = puremagic.magic_file(file)
|
|
6
|
+
if magic_detections:
|
|
7
|
+
magic_detections.sort(key=lambda x: x.confidence, reverse=True)
|
|
8
|
+
detection = magic_detections[0]
|
|
9
|
+
return detection.extension, detection.mime_type, detection.name, detection.confidence
|
|
10
|
+
return "", "", "", 0
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_compression(mime_type):
|
|
14
|
+
mime_type = mime_type.lower()
|
|
15
|
+
# from https://github.com/cdgriffith/puremagic/blob/master/puremagic/magic_data.json
|
|
16
|
+
compression_map = {
|
|
17
|
+
"application/arj": "arj", # ARJ archive
|
|
18
|
+
"application/binhex": "binhex", # BinHex encoded file
|
|
19
|
+
"application/epub+zip": "zip", # EPUB book (Zip archive)
|
|
20
|
+
"application/fictionbook2+zip": "zip", # FictionBook 2.0 (Zip)
|
|
21
|
+
"application/fictionbook3+zip": "zip", # FictionBook 3.0 (Zip)
|
|
22
|
+
"application/gzip": "gzip", # Gzip compressed file
|
|
23
|
+
"application/java-archive": "zip", # Java Archive (JAR)
|
|
24
|
+
"application/pak": "pak", # PAK archive
|
|
25
|
+
"application/vnd.android.package-archive": "zip", # Android package (APK)
|
|
26
|
+
"application/vnd.comicbook-rar": "rar", # Comic book archive (RAR)
|
|
27
|
+
"application/vnd.comicbook+zip": "zip", # Comic book archive (Zip)
|
|
28
|
+
"application/vnd.ms-cab-compressed": "cab", # Microsoft Cabinet archive
|
|
29
|
+
"application/vnd.palm": "palm", # Palm OS data
|
|
30
|
+
"application/vnd.rar": "rar", # RAR archive
|
|
31
|
+
"application/x-7z-compressed": "7z", # 7-Zip archive
|
|
32
|
+
"application/x-ace": "ace", # ACE archive
|
|
33
|
+
"application/x-alz": "alz", # ALZip archive
|
|
34
|
+
"application/x-arc": "arc", # ARC archive
|
|
35
|
+
"application/x-archive": "ar", # Unix archive
|
|
36
|
+
"application/x-bzip2": "bzip2", # Bzip2 compressed file
|
|
37
|
+
"application/x-compress": "compress", # Unix compress file
|
|
38
|
+
"application/x-cpio": "cpio", # CPIO archive
|
|
39
|
+
"application/x-gzip": "gzip", # Gzip compressed file
|
|
40
|
+
"application/x-itunes-ipa": "zip", # iOS application archive (IPA)
|
|
41
|
+
"application/x-java-pack200": "pack200", # Java Pack200 archive
|
|
42
|
+
"application/x-lha": "lha", # LHA archive
|
|
43
|
+
"application/x-lrzip": "lrzip", # Long Range ZIP
|
|
44
|
+
"application/x-lz4-compressed-tar": "lz4", # LZ4 compressed Tar archive
|
|
45
|
+
"application/x-lz4": "lz4", # LZ4 compressed file
|
|
46
|
+
"application/x-lzip": "lzip", # Lzip compressed file
|
|
47
|
+
"application/x-lzma": "lzma", # LZMA compressed file
|
|
48
|
+
"application/x-par2": "par2", # PAR2 recovery file
|
|
49
|
+
"application/x-qpress": "qpress", # Qpress archive
|
|
50
|
+
"application/x-rar-compressed": "rar", # RAR archive
|
|
51
|
+
"application/x-sit": "sit", # StuffIt archive
|
|
52
|
+
"application/x-stuffit": "sit", # StuffIt archive
|
|
53
|
+
"application/x-tar": "tar", # Tar archive
|
|
54
|
+
"application/x-tgz": "tgz", # Gzip compressed Tar archive
|
|
55
|
+
"application/x-webarchive": "zip", # Web archive (Zip)
|
|
56
|
+
"application/x-xar": "xar", # XAR archive
|
|
57
|
+
"application/x-xz": "xz", # XZ compressed file
|
|
58
|
+
"application/x-zip-compressed-fb2": "zip", # Zip archive (FB2)
|
|
59
|
+
"application/x-zoo": "zoo", # Zoo archive
|
|
60
|
+
"application/x-zstd-compressed-tar": "zstd", # Zstandard compressed Tar archive
|
|
61
|
+
"application/zip": "zip", # Zip archive
|
|
62
|
+
"application/zstd": "zstd", # Zstandard compressed file
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return compression_map.get(mime_type, "")
|