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
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# dnstlsrpt.py
|
|
2
|
+
#
|
|
3
|
+
# Checks for and parses common TLS-RPT TXT records, e.g. _smtp._tls.target.domain
|
|
4
|
+
#
|
|
5
|
+
# TLS-RPT policies may contain email addresses or URL's for reporting destinations, typically the email addresses are software processed inboxes, but they may also be to individual humans or team inboxes.
|
|
6
|
+
#
|
|
7
|
+
# The domain portion of any email address or URL is also passively checked and added as appropriate, for additional inspection by other modules.
|
|
8
|
+
#
|
|
9
|
+
# Example records,
|
|
10
|
+
# _smtp._tls.example.com TXT "v=TLSRPTv1;rua=https://tlsrpt.azurewebsites.net/report"
|
|
11
|
+
# _smtp._tls.example.net TXT "v=TLSRPTv1; rua=mailto:sts-reports@example.net;"
|
|
12
|
+
#
|
|
13
|
+
# TODO: extract %{UNIQUE_ID}% from hosted services as ORG_STUB ?
|
|
14
|
+
# e.g. %{UNIQUE_ID}%@tlsrpt.hosted.service.provider is usually a tenant specific ID.
|
|
15
|
+
# e.g. tlsrpt@%{UNIQUE_ID}%.hosted.service.provider is usually a tenant specific ID.
|
|
16
|
+
|
|
17
|
+
from bbot.modules.base import BaseModule
|
|
18
|
+
from bbot.core.helpers.dns.helpers import service_record
|
|
19
|
+
|
|
20
|
+
import re
|
|
21
|
+
|
|
22
|
+
from bbot.core.helpers.regexes import email_regex, url_regexes
|
|
23
|
+
|
|
24
|
+
_tlsrpt_regex = r"^v=(?P<v>TLSRPTv[0-9]+); *(?P<kvps>.*)$"
|
|
25
|
+
tlsrpt_regex = re.compile(_tlsrpt_regex, re.I)
|
|
26
|
+
|
|
27
|
+
_tlsrpt_kvp_regex = r"(?P<k>\w+)=(?P<v>[^;]+);*"
|
|
28
|
+
tlsrpt_kvp_regex = re.compile(_tlsrpt_kvp_regex)
|
|
29
|
+
|
|
30
|
+
_csul = r"(?P<uri>[^, ]+)"
|
|
31
|
+
csul = re.compile(_csul)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class dnstlsrpt(BaseModule):
|
|
35
|
+
watched_events = ["DNS_NAME"]
|
|
36
|
+
produced_events = ["EMAIL_ADDRESS", "URL_UNVERIFIED", "RAW_DNS_RECORD"]
|
|
37
|
+
flags = ["subdomain-enum", "cloud-enum", "email-enum", "passive", "safe"]
|
|
38
|
+
meta = {
|
|
39
|
+
"description": "Check for TLS-RPT records",
|
|
40
|
+
"author": "@colin-stubbs",
|
|
41
|
+
"created_date": "2024-07-26",
|
|
42
|
+
}
|
|
43
|
+
options = {
|
|
44
|
+
"emit_emails": True,
|
|
45
|
+
"emit_raw_dns_records": False,
|
|
46
|
+
"emit_urls": True,
|
|
47
|
+
"emit_vulnerabilities": True,
|
|
48
|
+
}
|
|
49
|
+
options_desc = {
|
|
50
|
+
"emit_emails": "Emit EMAIL_ADDRESS events",
|
|
51
|
+
"emit_raw_dns_records": "Emit RAW_DNS_RECORD events",
|
|
52
|
+
"emit_urls": "Emit URL_UNVERIFIED events",
|
|
53
|
+
"emit_vulnerabilities": "Emit VULNERABILITY events",
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async def setup(self):
|
|
57
|
+
self.emit_emails = self.config.get("emit_emails", True)
|
|
58
|
+
self.emit_raw_dns_records = self.config.get("emit_raw_dns_records", False)
|
|
59
|
+
self.emit_urls = self.config.get("emit_urls", True)
|
|
60
|
+
self.emit_vulnerabilities = self.config.get("emit_vulnerabilities", True)
|
|
61
|
+
return await super().setup()
|
|
62
|
+
|
|
63
|
+
def _incoming_dedup_hash(self, event):
|
|
64
|
+
# dedupe by parent
|
|
65
|
+
parent_domain = self.helpers.parent_domain(event.data)
|
|
66
|
+
return hash(parent_domain), "already processed parent domain"
|
|
67
|
+
|
|
68
|
+
async def filter_event(self, event):
|
|
69
|
+
if "_wildcard" in str(event.host).split("."):
|
|
70
|
+
return False, "event is wildcard"
|
|
71
|
+
|
|
72
|
+
# there's no value in inspecting service records
|
|
73
|
+
if service_record(event.host) is True:
|
|
74
|
+
return False, "service record detected"
|
|
75
|
+
|
|
76
|
+
return True
|
|
77
|
+
|
|
78
|
+
async def handle_event(self, event):
|
|
79
|
+
rdtype = "TXT"
|
|
80
|
+
tags = ["tlsrpt-record"]
|
|
81
|
+
hostname = f"_smtp._tls.{event.host}"
|
|
82
|
+
|
|
83
|
+
r = await self.helpers.resolve_raw(hostname, type=rdtype)
|
|
84
|
+
|
|
85
|
+
if r:
|
|
86
|
+
raw_results, errors = r
|
|
87
|
+
for answer in raw_results:
|
|
88
|
+
if self.emit_raw_dns_records:
|
|
89
|
+
await self.emit_event(
|
|
90
|
+
{"host": hostname, "type": rdtype, "answer": answer.to_text()},
|
|
91
|
+
"RAW_DNS_RECORD",
|
|
92
|
+
parent=event,
|
|
93
|
+
tags=tags.append(f"{rdtype.lower()}-record"),
|
|
94
|
+
context=f"{rdtype} lookup on {hostname} produced {{event.type}}",
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# we need to fix TXT data that may have been split across two different rdata's
|
|
98
|
+
# e.g. we will get a single string, but within that string we may have two parts such as:
|
|
99
|
+
# answer = '"part 1 that was really long" "part 2 that did not fit in part 1"'
|
|
100
|
+
# NOTE: the leading and trailing double quotes are essential as part of a raw DNS TXT record, or another record type that contains a free form text string as a component.
|
|
101
|
+
s = answer.to_text().strip('"').replace('" "', "")
|
|
102
|
+
|
|
103
|
+
# validate TLSRPT record, tag appropriately
|
|
104
|
+
tlsrpt_match = tlsrpt_regex.search(s)
|
|
105
|
+
|
|
106
|
+
if (
|
|
107
|
+
tlsrpt_match
|
|
108
|
+
and tlsrpt_match.group("v")
|
|
109
|
+
and tlsrpt_match.group("kvps")
|
|
110
|
+
and tlsrpt_match.group("kvps") != ""
|
|
111
|
+
):
|
|
112
|
+
for kvp_match in tlsrpt_kvp_regex.finditer(tlsrpt_match.group("kvps")):
|
|
113
|
+
key = kvp_match.group("k").lower()
|
|
114
|
+
|
|
115
|
+
if key == "rua":
|
|
116
|
+
for csul_match in csul.finditer(kvp_match.group("v")):
|
|
117
|
+
if csul_match.group("uri"):
|
|
118
|
+
for match in email_regex.finditer(csul_match.group("uri")):
|
|
119
|
+
start, end = match.span()
|
|
120
|
+
email = csul_match.group("uri")[start:end]
|
|
121
|
+
|
|
122
|
+
if self.emit_emails:
|
|
123
|
+
await self.emit_event(
|
|
124
|
+
email,
|
|
125
|
+
"EMAIL_ADDRESS",
|
|
126
|
+
tags=tags.append(f"tlsrpt-record-{key}"),
|
|
127
|
+
parent=event,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
for url_regex in url_regexes:
|
|
131
|
+
for match in url_regex.finditer(csul_match.group("uri")):
|
|
132
|
+
start, end = match.span()
|
|
133
|
+
url = csul_match.group("uri")[start:end]
|
|
134
|
+
|
|
135
|
+
if self.emit_urls:
|
|
136
|
+
await self.emit_event(
|
|
137
|
+
url,
|
|
138
|
+
"URL_UNVERIFIED",
|
|
139
|
+
tags=tags.append(f"tlsrpt-record-{key}"),
|
|
140
|
+
parent=event,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# EOF
|
bbot/modules/docker_pull.py
CHANGED
|
@@ -86,12 +86,12 @@ class docker_pull(BaseModule):
|
|
|
86
86
|
service = www_authenticate_headers.split('service="')[1].split('"')[0]
|
|
87
87
|
scope = www_authenticate_headers.split('scope="')[1].split('"')[0]
|
|
88
88
|
except (KeyError, IndexError):
|
|
89
|
-
self.log.
|
|
89
|
+
self.log.warning(f"Could not obtain realm, service or scope from {url}")
|
|
90
90
|
break
|
|
91
91
|
auth_url = f"{realm}?service={service}&scope={scope}"
|
|
92
92
|
auth_response = await self.helpers.request(auth_url)
|
|
93
93
|
if not auth_response:
|
|
94
|
-
self.log.
|
|
94
|
+
self.log.warning(f"Could not obtain token from {auth_url}")
|
|
95
95
|
break
|
|
96
96
|
auth_json = auth_response.json()
|
|
97
97
|
token = auth_json["token"]
|
|
@@ -102,7 +102,8 @@ class docker_pull(BaseModule):
|
|
|
102
102
|
url = f"{registry}/v2/{repository}/tags/list"
|
|
103
103
|
r = await self.docker_api_request(url)
|
|
104
104
|
if r is None or r.status_code != 200:
|
|
105
|
-
self.log.warning(f"Could not retrieve all tags for {repository}
|
|
105
|
+
self.log.warning(f"Could not retrieve all tags for {repository} assuming tag:latest only.")
|
|
106
|
+
self.log.debug(f"Response: {r}")
|
|
106
107
|
return ["latest"]
|
|
107
108
|
try:
|
|
108
109
|
tags = r.json().get("tags", ["latest"])
|
|
@@ -115,14 +116,15 @@ class docker_pull(BaseModule):
|
|
|
115
116
|
else:
|
|
116
117
|
return [tags[-1]]
|
|
117
118
|
except (KeyError, IndexError):
|
|
118
|
-
self.log.
|
|
119
|
+
self.log.warning(f"Could not retrieve tags for {repository}.")
|
|
119
120
|
return ["latest"]
|
|
120
121
|
|
|
121
122
|
async def get_manifest(self, registry, repository, tag):
|
|
122
123
|
url = f"{registry}/v2/{repository}/manifests/{tag}"
|
|
123
124
|
r = await self.docker_api_request(url)
|
|
124
125
|
if r is None or r.status_code != 200:
|
|
125
|
-
self.log.
|
|
126
|
+
self.log.warning(f"Could not retrieve manifest for {repository}:{tag}.")
|
|
127
|
+
self.log.debug(f"Response: {r}")
|
|
126
128
|
return {}
|
|
127
129
|
response_json = r.json()
|
|
128
130
|
if response_json.get("manifests", []):
|
bbot/modules/dockerhub.py
CHANGED
|
@@ -31,7 +31,7 @@ class dockerhub(BaseModule):
|
|
|
31
31
|
async def handle_org_stub(self, event):
|
|
32
32
|
profile_name = event.data
|
|
33
33
|
# docker usernames are case sensitive, so if there are capitalizations we also try a lowercase variation
|
|
34
|
-
profiles_to_check =
|
|
34
|
+
profiles_to_check = {profile_name, profile_name.lower()}
|
|
35
35
|
for p in profiles_to_check:
|
|
36
36
|
api_url = f"{self.api_url}/users/{p}"
|
|
37
37
|
api_result = await self.helpers.request(api_url, follow_redirects=True)
|
|
@@ -64,7 +64,7 @@ class dockerhub(BaseModule):
|
|
|
64
64
|
async def get_repos(self, username):
|
|
65
65
|
repos = []
|
|
66
66
|
url = f"{self.api_url}/repositories/{username}?page_size=25&page=" + "{page}"
|
|
67
|
-
agen = self.
|
|
67
|
+
agen = self.api_page_iter(url, json=False)
|
|
68
68
|
try:
|
|
69
69
|
async for r in agen:
|
|
70
70
|
if r is None:
|
bbot/modules/dotnetnuke.py
CHANGED
|
@@ -31,8 +31,7 @@ class dotnetnuke(BaseModule):
|
|
|
31
31
|
self.interactsh_subdomain_tags = {}
|
|
32
32
|
self.interactsh_instance = None
|
|
33
33
|
|
|
34
|
-
if self.scan.config.get("interactsh_disable", False)
|
|
35
|
-
|
|
34
|
+
if self.scan.config.get("interactsh_disable", False) is False:
|
|
36
35
|
try:
|
|
37
36
|
self.interactsh_instance = self.helpers.interactsh()
|
|
38
37
|
self.interactsh_domain = await self.interactsh_instance.register(callback=self.interactsh_callback)
|
|
@@ -94,7 +93,7 @@ class dotnetnuke(BaseModule):
|
|
|
94
93
|
detected = True
|
|
95
94
|
break
|
|
96
95
|
|
|
97
|
-
if detected
|
|
96
|
+
if detected is True:
|
|
98
97
|
# DNNPersonalization Deserialization Detection
|
|
99
98
|
for probe_url in [f'{event.data["url"]}/__', f'{event.data["url"]}/', f'{event.data["url"]}']:
|
|
100
99
|
result = await self.helpers.request(probe_url, cookies=self.exploit_probe)
|
|
@@ -114,7 +113,6 @@ class dotnetnuke(BaseModule):
|
|
|
114
113
|
)
|
|
115
114
|
|
|
116
115
|
if "endpoint" not in event.tags:
|
|
117
|
-
|
|
118
116
|
# NewsArticlesSlider ImageHandler.ashx File Read
|
|
119
117
|
result = await self.helpers.request(
|
|
120
118
|
f'{event.data["url"]}/DesktopModules/dnnUI_NewsArticlesSlider/ImageHandler.ashx?img=~/web.config'
|
|
@@ -155,24 +153,25 @@ class dotnetnuke(BaseModule):
|
|
|
155
153
|
|
|
156
154
|
# InstallWizard SuperUser Privilege Escalation
|
|
157
155
|
result = await self.helpers.request(f'{event.data["url"]}/Install/InstallWizard.aspx')
|
|
158
|
-
if result
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if result_confirm.status_code == 500:
|
|
163
|
-
description = "DotNetNuke InstallWizard SuperUser Privilege Escalation"
|
|
164
|
-
await self.emit_event(
|
|
165
|
-
{
|
|
166
|
-
"severity": "CRITICAL",
|
|
167
|
-
"description": description,
|
|
168
|
-
"host": str(event.host),
|
|
169
|
-
"url": f'{event.data["url"]}/Install/InstallWizard.aspx',
|
|
170
|
-
},
|
|
171
|
-
"VULNERABILITY",
|
|
172
|
-
event,
|
|
173
|
-
context=f'{{module}} scanned {event.data["url"]} and found critical {{event.type}}: {description}',
|
|
156
|
+
if result:
|
|
157
|
+
if result.status_code == 200:
|
|
158
|
+
result_confirm = await self.helpers.request(
|
|
159
|
+
f'{event.data["url"]}/Install/InstallWizard.aspx?__viewstate=1'
|
|
174
160
|
)
|
|
175
|
-
|
|
161
|
+
if result_confirm.status_code == 500:
|
|
162
|
+
description = "DotNetNuke InstallWizard SuperUser Privilege Escalation"
|
|
163
|
+
await self.emit_event(
|
|
164
|
+
{
|
|
165
|
+
"severity": "CRITICAL",
|
|
166
|
+
"description": description,
|
|
167
|
+
"host": str(event.host),
|
|
168
|
+
"url": f'{event.data["url"]}/Install/InstallWizard.aspx',
|
|
169
|
+
},
|
|
170
|
+
"VULNERABILITY",
|
|
171
|
+
event,
|
|
172
|
+
context=f'{{module}} scanned {event.data["url"]} and found critical {{event.type}}: {description}',
|
|
173
|
+
)
|
|
174
|
+
return
|
|
176
175
|
|
|
177
176
|
# DNNImageHandler.ashx Blind SSRF
|
|
178
177
|
self.event_dict[event.data["url"]] = event
|
bbot/modules/emailformat.py
CHANGED
|
@@ -18,7 +18,7 @@ class emailformat(BaseModule):
|
|
|
18
18
|
async def handle_event(self, event):
|
|
19
19
|
_, query = self.helpers.split_domain(event.data)
|
|
20
20
|
url = f"{self.base_url}/d/{self.helpers.quote(query)}/"
|
|
21
|
-
r = await self.
|
|
21
|
+
r = await self.api_request(url)
|
|
22
22
|
if not r:
|
|
23
23
|
return
|
|
24
24
|
for email in await self.helpers.re.extract_emails(r.text):
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
from extractous import Extractor
|
|
2
|
+
|
|
3
|
+
from bbot.modules.base import BaseModule
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class extractous(BaseModule):
|
|
7
|
+
watched_events = ["FILESYSTEM"]
|
|
8
|
+
produced_events = ["RAW_TEXT"]
|
|
9
|
+
flags = ["passive", "safe"]
|
|
10
|
+
meta = {
|
|
11
|
+
"description": "Module to extract data from files",
|
|
12
|
+
"created_date": "2024-06-03",
|
|
13
|
+
"author": "@domwhewell-sage",
|
|
14
|
+
}
|
|
15
|
+
options = {
|
|
16
|
+
"extensions": [
|
|
17
|
+
"bak", # Backup File
|
|
18
|
+
"bash", # Bash Script or Configuration
|
|
19
|
+
"bashrc", # Bash Script or Configuration
|
|
20
|
+
"conf", # Configuration File
|
|
21
|
+
"cfg", # Configuration File
|
|
22
|
+
"crt", # Certificate File
|
|
23
|
+
"csv", # Comma Separated Values File
|
|
24
|
+
"db", # SQLite Database File
|
|
25
|
+
"sqlite", # SQLite Database File
|
|
26
|
+
"doc", # Microsoft Word Document (Old Format)
|
|
27
|
+
"docx", # Microsoft Word Document
|
|
28
|
+
"ica", # Citrix Independent Computing Architecture File
|
|
29
|
+
"indd", # Adobe InDesign Document
|
|
30
|
+
"ini", # Initialization File
|
|
31
|
+
"key", # Private Key File
|
|
32
|
+
"pub", # Public Key File
|
|
33
|
+
"log", # Log File
|
|
34
|
+
"markdown", # Markdown File
|
|
35
|
+
"md", # Markdown File
|
|
36
|
+
"odg", # OpenDocument Graphics (LibreOffice, OpenOffice)
|
|
37
|
+
"odp", # OpenDocument Presentation (LibreOffice, OpenOffice)
|
|
38
|
+
"ods", # OpenDocument Spreadsheet (LibreOffice, OpenOffice)
|
|
39
|
+
"odt", # OpenDocument Text (LibreOffice, OpenOffice)
|
|
40
|
+
"pdf", # Adobe Portable Document Format
|
|
41
|
+
"pem", # Privacy Enhanced Mail (SSL certificate)
|
|
42
|
+
"pps", # Microsoft PowerPoint Slideshow (Old Format)
|
|
43
|
+
"ppsx", # Microsoft PowerPoint Slideshow
|
|
44
|
+
"ppt", # Microsoft PowerPoint Presentation (Old Format)
|
|
45
|
+
"pptx", # Microsoft PowerPoint Presentation
|
|
46
|
+
"ps1", # PowerShell Script
|
|
47
|
+
"rdp", # Remote Desktop Protocol File
|
|
48
|
+
"sh", # Shell Script
|
|
49
|
+
"sql", # SQL Database Dump
|
|
50
|
+
"swp", # Swap File (temporary file, often Vim)
|
|
51
|
+
"sxw", # OpenOffice.org Writer document
|
|
52
|
+
"txt", # Plain Text Document
|
|
53
|
+
"vbs", # Visual Basic Script
|
|
54
|
+
"wpd", # WordPerfect Document
|
|
55
|
+
"xls", # Microsoft Excel Spreadsheet (Old Format)
|
|
56
|
+
"xlsx", # Microsoft Excel Spreadsheet
|
|
57
|
+
"xml", # eXtensible Markup Language File
|
|
58
|
+
"yml", # YAML Ain't Markup Language
|
|
59
|
+
"yaml", # YAML Ain't Markup Language
|
|
60
|
+
],
|
|
61
|
+
}
|
|
62
|
+
options_desc = {
|
|
63
|
+
"extensions": "File extensions to parse",
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
deps_pip = ["extractous"]
|
|
67
|
+
scope_distance_modifier = 1
|
|
68
|
+
|
|
69
|
+
async def setup(self):
|
|
70
|
+
self.extensions = list({e.lower().strip(".") for e in self.config.get("extensions", [])})
|
|
71
|
+
return True
|
|
72
|
+
|
|
73
|
+
async def filter_event(self, event):
|
|
74
|
+
if "file" in event.tags:
|
|
75
|
+
if not any(event.data["path"].endswith(f".{ext}") for ext in self.extensions):
|
|
76
|
+
return False, "File extension not in the allowed list"
|
|
77
|
+
else:
|
|
78
|
+
return False, "Event is not a file"
|
|
79
|
+
return True
|
|
80
|
+
|
|
81
|
+
async def handle_event(self, event):
|
|
82
|
+
file_path = event.data["path"]
|
|
83
|
+
content = await self.scan.helpers.run_in_executor_mp(extract_text, file_path)
|
|
84
|
+
if isinstance(content, tuple):
|
|
85
|
+
error, traceback = content
|
|
86
|
+
self.error(f"Error extracting text from {file_path}: {error}")
|
|
87
|
+
self.trace(traceback)
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
if content:
|
|
91
|
+
raw_text_event = self.make_event(
|
|
92
|
+
content,
|
|
93
|
+
"RAW_TEXT",
|
|
94
|
+
context=f"Extracted text from {file_path}",
|
|
95
|
+
parent=event,
|
|
96
|
+
)
|
|
97
|
+
await self.emit_event(raw_text_event)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def extract_text(file_path):
|
|
101
|
+
"""
|
|
102
|
+
extract_text Extracts plaintext from a document path using extractous.
|
|
103
|
+
|
|
104
|
+
:param file_path: The path of the file to extract text from.
|
|
105
|
+
:return: ASCII-encoded plaintext extracted from the document.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
extractor = Extractor()
|
|
110
|
+
reader, metadata = extractor.extract_file(str(file_path))
|
|
111
|
+
|
|
112
|
+
result = ""
|
|
113
|
+
buffer = reader.read(4096)
|
|
114
|
+
while len(buffer) > 0:
|
|
115
|
+
result += buffer.decode("utf-8")
|
|
116
|
+
buffer = reader.read(4096)
|
|
117
|
+
|
|
118
|
+
return result.strip()
|
|
119
|
+
except Exception as e:
|
|
120
|
+
import traceback
|
|
121
|
+
|
|
122
|
+
return (str(e), traceback.format_exc())
|
bbot/modules/filedownload.py
CHANGED
|
@@ -25,12 +25,12 @@ class filedownload(BaseModule):
|
|
|
25
25
|
"bak", # Backup File
|
|
26
26
|
"bash", # Bash Script or Configuration
|
|
27
27
|
"bashrc", # Bash Script or Configuration
|
|
28
|
-
"conf", # Configuration File
|
|
29
28
|
"cfg", # Configuration File
|
|
29
|
+
"conf", # Configuration File
|
|
30
30
|
"crt", # Certificate File
|
|
31
31
|
"csv", # Comma Separated Values File
|
|
32
32
|
"db", # SQLite Database File
|
|
33
|
-
"
|
|
33
|
+
"dll", # Windows Dynamic Link Library
|
|
34
34
|
"doc", # Microsoft Word Document (Old Format)
|
|
35
35
|
"docx", # Microsoft Word Document
|
|
36
36
|
"exe", # Windows PE executable
|
|
@@ -39,7 +39,6 @@ class filedownload(BaseModule):
|
|
|
39
39
|
"ini", # Initialization File
|
|
40
40
|
"jar", # Java Archive
|
|
41
41
|
"key", # Private Key File
|
|
42
|
-
"pub", # Public Key File
|
|
43
42
|
"log", # Log File
|
|
44
43
|
"markdown", # Markdown File
|
|
45
44
|
"md", # Markdown File
|
|
@@ -55,23 +54,26 @@ class filedownload(BaseModule):
|
|
|
55
54
|
"ppt", # Microsoft PowerPoint Presentation (Old Format)
|
|
56
55
|
"pptx", # Microsoft PowerPoint Presentation
|
|
57
56
|
"ps1", # PowerShell Script
|
|
57
|
+
"pub", # Public Key File
|
|
58
58
|
"raw", # Raw Image File Format
|
|
59
59
|
"rdp", # Remote Desktop Protocol File
|
|
60
60
|
"sh", # Shell Script
|
|
61
61
|
"sql", # SQL Database Dump
|
|
62
|
+
"sqlite", # SQLite Database File
|
|
62
63
|
"swp", # Swap File (temporary file, often Vim)
|
|
63
64
|
"sxw", # OpenOffice.org Writer document
|
|
64
|
-
"tar", # Tar Archive
|
|
65
65
|
"tar.gz", # Gzip-Compressed Tar Archive
|
|
66
|
-
"
|
|
66
|
+
"tar", # Tar Archive
|
|
67
67
|
"txt", # Plain Text Document
|
|
68
68
|
"vbs", # Visual Basic Script
|
|
69
|
+
"war", # Java Web Archive
|
|
69
70
|
"wpd", # WordPerfect Document
|
|
70
71
|
"xls", # Microsoft Excel Spreadsheet (Old Format)
|
|
71
72
|
"xlsx", # Microsoft Excel Spreadsheet
|
|
72
73
|
"xml", # eXtensible Markup Language File
|
|
73
|
-
"yml", # YAML Ain't Markup Language
|
|
74
74
|
"yaml", # YAML Ain't Markup Language
|
|
75
|
+
"yml", # YAML Ain't Markup Language
|
|
76
|
+
"zip", # Zip Archive
|
|
75
77
|
],
|
|
76
78
|
"max_filesize": "10MB",
|
|
77
79
|
"base_64_encoded_file": "false",
|
|
@@ -85,7 +87,7 @@ class filedownload(BaseModule):
|
|
|
85
87
|
scope_distance_modifier = 3
|
|
86
88
|
|
|
87
89
|
async def setup(self):
|
|
88
|
-
self.extensions = list(
|
|
90
|
+
self.extensions = list({e.lower().strip(".") for e in self.config.get("extensions", [])})
|
|
89
91
|
self.max_filesize = self.config.get("max_filesize", "10MB")
|
|
90
92
|
self.download_dir = self.scan.home / "filedownload"
|
|
91
93
|
self.helpers.mkdir(self.download_dir)
|
bbot/modules/fullhunt.py
CHANGED
|
@@ -18,19 +18,22 @@ class fullhunt(subdomain_enum_apikey):
|
|
|
18
18
|
|
|
19
19
|
async def setup(self):
|
|
20
20
|
self.api_key = self.config.get("api_key", "")
|
|
21
|
-
self.headers = {"x-api-key": self.api_key}
|
|
22
21
|
return await super().setup()
|
|
23
22
|
|
|
24
23
|
async def ping(self):
|
|
25
24
|
url = f"{self.base_url}/auth/status"
|
|
26
|
-
j = (await self.
|
|
25
|
+
j = (await self.api_request(url)).json()
|
|
27
26
|
remaining = j["user_credits"]["remaining_credits"]
|
|
28
27
|
assert remaining > 0, "No credits remaining"
|
|
29
28
|
|
|
29
|
+
def prepare_api_request(self, url, kwargs):
|
|
30
|
+
kwargs["headers"]["x-api-key"] = self.api_key
|
|
31
|
+
return url, kwargs
|
|
32
|
+
|
|
30
33
|
async def request_url(self, query):
|
|
31
34
|
url = f"{self.base_url}/domain/{self.helpers.quote(query)}/subdomains"
|
|
32
|
-
response = await self.
|
|
35
|
+
response = await self.api_request(url)
|
|
33
36
|
return response
|
|
34
37
|
|
|
35
|
-
def parse_results(self, r, query):
|
|
38
|
+
async def parse_results(self, r, query):
|
|
36
39
|
return r.json().get("hosts", [])
|
bbot/modules/generic_ssrf.py
CHANGED
|
@@ -137,10 +137,10 @@ class Generic_XXE(BaseSubmodule):
|
|
|
137
137
|
post_body = f"""<?xml version="1.0" encoding="ISO-8859-1"?>
|
|
138
138
|
<!DOCTYPE foo [
|
|
139
139
|
<!ELEMENT foo ANY >
|
|
140
|
-
<!ENTITY
|
|
140
|
+
<!ENTITY {rand_entity} SYSTEM "http://{subdomain_tag}.{self.parent_module.interactsh_domain}" >
|
|
141
141
|
]>
|
|
142
142
|
<foo>&{rand_entity};</foo>"""
|
|
143
|
-
test_url =
|
|
143
|
+
test_url = event.parsed_url.geturl()
|
|
144
144
|
r = await self.parent_module.helpers.curl(
|
|
145
145
|
url=test_url, method="POST", raw_body=post_body, headers={"Content-type": "application/xml"}
|
|
146
146
|
)
|
|
@@ -163,7 +163,7 @@ class generic_ssrf(BaseModule):
|
|
|
163
163
|
self.severity = None
|
|
164
164
|
self.generic_only = self.config.get("generic_only", False)
|
|
165
165
|
|
|
166
|
-
if self.scan.config.get("interactsh_disable", False)
|
|
166
|
+
if self.scan.config.get("interactsh_disable", False) is False:
|
|
167
167
|
try:
|
|
168
168
|
self.interactsh_instance = self.helpers.interactsh()
|
|
169
169
|
self.interactsh_domain = await self.interactsh_instance.register(callback=self.interactsh_callback)
|
|
@@ -216,7 +216,7 @@ class generic_ssrf(BaseModule):
|
|
|
216
216
|
self.debug("skipping result because subdomain tag was missing")
|
|
217
217
|
|
|
218
218
|
async def cleanup(self):
|
|
219
|
-
if self.scan.config.get("interactsh_disable", False)
|
|
219
|
+
if self.scan.config.get("interactsh_disable", False) is False:
|
|
220
220
|
try:
|
|
221
221
|
await self.interactsh_instance.deregister()
|
|
222
222
|
self.debug(
|
|
@@ -226,7 +226,7 @@ class generic_ssrf(BaseModule):
|
|
|
226
226
|
self.warning(f"Interactsh failure: {e}")
|
|
227
227
|
|
|
228
228
|
async def finish(self):
|
|
229
|
-
if self.scan.config.get("interactsh_disable", False)
|
|
229
|
+
if self.scan.config.get("interactsh_disable", False) is False:
|
|
230
230
|
await self.helpers.sleep(5)
|
|
231
231
|
try:
|
|
232
232
|
for r in await self.interactsh_instance.poll():
|
|
@@ -42,7 +42,7 @@ class github_codesearch(github, subdomain_enum):
|
|
|
42
42
|
async def query(self, query):
|
|
43
43
|
repos = {}
|
|
44
44
|
url = f"{self.base_url}/search/code?per_page=100&type=Code&q={self.helpers.quote(query)}&page=" + "{page}"
|
|
45
|
-
agen = self.
|
|
45
|
+
agen = self.api_page_iter(url, headers=self.headers, json=False)
|
|
46
46
|
num_results = 0
|
|
47
47
|
try:
|
|
48
48
|
async for r in agen:
|
|
@@ -50,9 +50,10 @@ class github_codesearch(github, subdomain_enum):
|
|
|
50
50
|
break
|
|
51
51
|
status_code = getattr(r, "status_code", 0)
|
|
52
52
|
if status_code == 429:
|
|
53
|
-
"Github is rate-limiting us (HTTP status: 429)"
|
|
53
|
+
self.info("Github is rate-limiting us (HTTP status: 429)")
|
|
54
54
|
break
|
|
55
55
|
if status_code != 200:
|
|
56
|
+
self.info(f"Unexpected response (HTTP status: {status_code})")
|
|
56
57
|
break
|
|
57
58
|
try:
|
|
58
59
|
j = r.json()
|
bbot/modules/github_org.py
CHANGED
|
@@ -104,7 +104,7 @@ class github_org(github):
|
|
|
104
104
|
async def query_org_repos(self, query):
|
|
105
105
|
repos = []
|
|
106
106
|
url = f"{self.base_url}/orgs/{self.helpers.quote(query)}/repos?per_page=100&page=" + "{page}"
|
|
107
|
-
agen = self.
|
|
107
|
+
agen = self.api_page_iter(url, json=False)
|
|
108
108
|
try:
|
|
109
109
|
async for r in agen:
|
|
110
110
|
if r is None:
|
|
@@ -132,7 +132,7 @@ class github_org(github):
|
|
|
132
132
|
async def query_org_members(self, query):
|
|
133
133
|
members = []
|
|
134
134
|
url = f"{self.base_url}/orgs/{self.helpers.quote(query)}/members?per_page=100&page=" + "{page}"
|
|
135
|
-
agen = self.
|
|
135
|
+
agen = self.api_page_iter(url, json=False)
|
|
136
136
|
try:
|
|
137
137
|
async for r in agen:
|
|
138
138
|
if r is None:
|
|
@@ -160,7 +160,7 @@ class github_org(github):
|
|
|
160
160
|
async def query_user_repos(self, query):
|
|
161
161
|
repos = []
|
|
162
162
|
url = f"{self.base_url}/users/{self.helpers.quote(query)}/repos?per_page=100&page=" + "{page}"
|
|
163
|
-
agen = self.
|
|
163
|
+
agen = self.api_page_iter(url, json=False)
|
|
164
164
|
try:
|
|
165
165
|
async for r in agen:
|
|
166
166
|
if r is None:
|
|
@@ -189,7 +189,7 @@ class github_org(github):
|
|
|
189
189
|
is_org = False
|
|
190
190
|
in_scope = False
|
|
191
191
|
url = f"{self.base_url}/orgs/{org}"
|
|
192
|
-
r = await self.
|
|
192
|
+
r = await self.api_request(url)
|
|
193
193
|
if r is None:
|
|
194
194
|
return is_org, in_scope
|
|
195
195
|
status_code = getattr(r, "status_code", 0)
|
bbot/modules/github_workflows.py
CHANGED
|
@@ -88,7 +88,7 @@ class github_workflows(github):
|
|
|
88
88
|
async def get_workflows(self, owner, repo):
|
|
89
89
|
workflows = []
|
|
90
90
|
url = f"{self.base_url}/repos/{owner}/{repo}/actions/workflows?per_page=100&page=" + "{page}"
|
|
91
|
-
agen = self.
|
|
91
|
+
agen = self.api_page_iter(url, json=False)
|
|
92
92
|
try:
|
|
93
93
|
async for r in agen:
|
|
94
94
|
if r is None:
|
|
@@ -115,7 +115,7 @@ class github_workflows(github):
|
|
|
115
115
|
async def get_workflow_runs(self, owner, repo, workflow_id):
|
|
116
116
|
runs = []
|
|
117
117
|
url = f"{self.base_url}/repos/{owner}/{repo}/actions/workflows/{workflow_id}/runs?status=success&per_page={self.num_logs}"
|
|
118
|
-
r = await self.
|
|
118
|
+
r = await self.api_request(url)
|
|
119
119
|
if r is None:
|
|
120
120
|
return runs
|
|
121
121
|
status_code = getattr(r, "status_code", 0)
|
|
@@ -166,7 +166,7 @@ class github_workflows(github):
|
|
|
166
166
|
main_logs = []
|
|
167
167
|
with zipfile.ZipFile(file_destination, "r") as logzip:
|
|
168
168
|
for name in logzip.namelist():
|
|
169
|
-
if fnmatch.fnmatch(name, "*.txt") and
|
|
169
|
+
if fnmatch.fnmatch(name, "*.txt") and "/" not in name:
|
|
170
170
|
logzip.extract(name, folder)
|
|
171
171
|
main_logs.append(folder / name)
|
|
172
172
|
return main_logs
|
|
@@ -176,7 +176,7 @@ class github_workflows(github):
|
|
|
176
176
|
async def get_run_artifacts(self, owner, repo, run_id):
|
|
177
177
|
artifacts = []
|
|
178
178
|
url = f"{self.base_url}/repos/{owner}/{repo}/actions/runs/{run_id}/artifacts"
|
|
179
|
-
r = await self.
|
|
179
|
+
r = await self.api_request(url)
|
|
180
180
|
if r is None:
|
|
181
181
|
return artifacts
|
|
182
182
|
status_code = getattr(r, "status_code", 0)
|
bbot/modules/gitlab.py
CHANGED
|
@@ -16,10 +16,7 @@ class gitlab(BaseModule):
|
|
|
16
16
|
scope_distance_modifier = 2
|
|
17
17
|
|
|
18
18
|
async def setup(self):
|
|
19
|
-
self.
|
|
20
|
-
self.api_key = self.config.get("api_key", "")
|
|
21
|
-
if self.api_key:
|
|
22
|
-
self.headers.update({"Authorization": f"Bearer {self.api_key}"})
|
|
19
|
+
await self.require_api_key()
|
|
23
20
|
return True
|
|
24
21
|
|
|
25
22
|
async def filter_event(self, event):
|
|
@@ -111,7 +108,7 @@ class gitlab(BaseModule):
|
|
|
111
108
|
await self.handle_namespace(group, event)
|
|
112
109
|
|
|
113
110
|
async def gitlab_json_request(self, url):
|
|
114
|
-
response = await self.
|
|
111
|
+
response = await self.api_request(url)
|
|
115
112
|
if response is not None:
|
|
116
113
|
try:
|
|
117
114
|
json = response.json()
|