bbot 2.0.1.4720rc0__py3-none-any.whl → 2.3.0.5401rc0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of bbot might be problematic. Click here for more details.
- bbot/__init__.py +1 -1
- bbot/cli.py +3 -7
- bbot/core/config/files.py +0 -1
- bbot/core/config/logger.py +34 -4
- bbot/core/core.py +21 -4
- bbot/core/engine.py +9 -8
- bbot/core/event/base.py +131 -52
- bbot/core/helpers/bloom.py +10 -3
- bbot/core/helpers/command.py +8 -7
- bbot/core/helpers/depsinstaller/installer.py +31 -13
- bbot/core/helpers/diff.py +10 -10
- bbot/core/helpers/dns/brute.py +7 -4
- bbot/core/helpers/dns/dns.py +1 -2
- bbot/core/helpers/dns/engine.py +4 -6
- bbot/core/helpers/dns/helpers.py +2 -2
- bbot/core/helpers/dns/mock.py +0 -1
- bbot/core/helpers/files.py +1 -1
- bbot/core/helpers/helper.py +7 -4
- bbot/core/helpers/interactsh.py +3 -3
- bbot/core/helpers/libmagic.py +65 -0
- bbot/core/helpers/misc.py +65 -22
- bbot/core/helpers/names_generator.py +17 -3
- bbot/core/helpers/process.py +0 -20
- bbot/core/helpers/regex.py +1 -1
- bbot/core/helpers/regexes.py +12 -6
- bbot/core/helpers/validators.py +1 -2
- bbot/core/helpers/web/client.py +1 -1
- bbot/core/helpers/web/engine.py +1 -2
- bbot/core/helpers/web/web.py +4 -114
- bbot/core/helpers/wordcloud.py +5 -5
- bbot/core/modules.py +36 -27
- bbot/core/multiprocess.py +58 -0
- bbot/core/shared_deps.py +46 -3
- bbot/db/sql/models.py +147 -0
- bbot/defaults.yml +12 -10
- bbot/modules/anubisdb.py +2 -2
- bbot/modules/apkpure.py +63 -0
- bbot/modules/azure_tenant.py +2 -2
- bbot/modules/baddns.py +35 -19
- bbot/modules/baddns_direct.py +92 -0
- bbot/modules/baddns_zone.py +3 -8
- bbot/modules/badsecrets.py +4 -3
- bbot/modules/base.py +195 -51
- bbot/modules/bevigil.py +7 -7
- bbot/modules/binaryedge.py +7 -4
- bbot/modules/bufferoverrun.py +47 -0
- bbot/modules/builtwith.py +6 -10
- bbot/modules/bypass403.py +5 -5
- bbot/modules/c99.py +10 -7
- bbot/modules/censys.py +9 -13
- bbot/modules/certspotter.py +5 -3
- bbot/modules/chaos.py +9 -7
- bbot/modules/code_repository.py +1 -0
- bbot/modules/columbus.py +3 -3
- bbot/modules/crt.py +5 -3
- bbot/modules/deadly/dastardly.py +1 -1
- bbot/modules/deadly/ffuf.py +9 -9
- bbot/modules/deadly/nuclei.py +3 -3
- bbot/modules/deadly/vhost.py +4 -3
- bbot/modules/dehashed.py +1 -1
- bbot/modules/digitorus.py +1 -1
- bbot/modules/dnsbimi.py +145 -0
- bbot/modules/dnscaa.py +3 -3
- bbot/modules/dnsdumpster.py +4 -4
- bbot/modules/dnstlsrpt.py +144 -0
- bbot/modules/docker_pull.py +7 -5
- bbot/modules/dockerhub.py +2 -2
- bbot/modules/dotnetnuke.py +20 -21
- bbot/modules/emailformat.py +1 -1
- bbot/modules/extractous.py +122 -0
- bbot/modules/filedownload.py +9 -7
- bbot/modules/fullhunt.py +7 -4
- bbot/modules/generic_ssrf.py +5 -5
- bbot/modules/github_codesearch.py +3 -2
- bbot/modules/github_org.py +4 -4
- bbot/modules/github_workflows.py +4 -4
- bbot/modules/gitlab.py +2 -5
- bbot/modules/google_playstore.py +93 -0
- bbot/modules/gowitness.py +48 -50
- bbot/modules/hackertarget.py +5 -3
- bbot/modules/host_header.py +5 -5
- bbot/modules/httpx.py +1 -4
- bbot/modules/hunterio.py +3 -9
- bbot/modules/iis_shortnames.py +19 -30
- bbot/modules/internal/cloudcheck.py +29 -12
- bbot/modules/internal/dnsresolve.py +22 -22
- bbot/modules/internal/excavate.py +97 -59
- bbot/modules/internal/speculate.py +41 -32
- bbot/modules/internetdb.py +4 -2
- bbot/modules/ip2location.py +3 -5
- bbot/modules/ipneighbor.py +1 -1
- bbot/modules/ipstack.py +3 -8
- bbot/modules/jadx.py +87 -0
- bbot/modules/leakix.py +11 -10
- bbot/modules/myssl.py +2 -2
- bbot/modules/newsletters.py +2 -2
- bbot/modules/otx.py +5 -3
- bbot/modules/output/asset_inventory.py +7 -7
- bbot/modules/output/base.py +1 -1
- bbot/modules/output/csv.py +1 -1
- bbot/modules/output/http.py +20 -14
- bbot/modules/output/mysql.py +51 -0
- bbot/modules/output/neo4j.py +7 -2
- bbot/modules/output/postgres.py +49 -0
- bbot/modules/output/slack.py +0 -1
- bbot/modules/output/sqlite.py +29 -0
- bbot/modules/output/stdout.py +2 -2
- bbot/modules/output/teams.py +107 -6
- bbot/modules/paramminer_headers.py +8 -11
- bbot/modules/passivetotal.py +13 -13
- bbot/modules/portscan.py +32 -6
- bbot/modules/postman.py +50 -126
- bbot/modules/postman_download.py +220 -0
- bbot/modules/rapiddns.py +3 -8
- bbot/modules/report/asn.py +18 -11
- bbot/modules/robots.py +3 -3
- bbot/modules/securitytrails.py +7 -10
- bbot/modules/securitytxt.py +1 -1
- bbot/modules/shodan_dns.py +7 -9
- bbot/modules/sitedossier.py +1 -1
- bbot/modules/skymem.py +2 -2
- bbot/modules/social.py +2 -1
- bbot/modules/subdomaincenter.py +1 -1
- bbot/modules/subdomainradar.py +160 -0
- bbot/modules/telerik.py +8 -8
- bbot/modules/templates/bucket.py +1 -1
- bbot/modules/templates/github.py +22 -14
- bbot/modules/templates/postman.py +21 -0
- bbot/modules/templates/shodan.py +14 -13
- bbot/modules/templates/sql.py +95 -0
- bbot/modules/templates/subdomain_enum.py +51 -16
- bbot/modules/templates/webhook.py +2 -4
- bbot/modules/trickest.py +8 -37
- bbot/modules/trufflehog.py +10 -12
- bbot/modules/url_manipulation.py +3 -3
- bbot/modules/urlscan.py +1 -1
- bbot/modules/viewdns.py +1 -1
- bbot/modules/virustotal.py +8 -30
- bbot/modules/wafw00f.py +1 -1
- bbot/modules/wayback.py +1 -1
- bbot/modules/wpscan.py +17 -11
- bbot/modules/zoomeye.py +11 -6
- bbot/presets/baddns-thorough.yml +12 -0
- bbot/presets/fast.yml +16 -0
- bbot/presets/kitchen-sink.yml +1 -2
- bbot/presets/spider.yml +4 -0
- bbot/presets/subdomain-enum.yml +7 -7
- bbot/presets/web/dotnet-audit.yml +0 -1
- bbot/scanner/manager.py +5 -16
- bbot/scanner/preset/args.py +46 -26
- bbot/scanner/preset/environ.py +7 -2
- bbot/scanner/preset/path.py +7 -4
- bbot/scanner/preset/preset.py +36 -23
- bbot/scanner/scanner.py +172 -62
- bbot/scanner/target.py +236 -434
- bbot/scripts/docs.py +1 -1
- bbot/test/bbot_fixtures.py +13 -3
- bbot/test/conftest.py +132 -100
- bbot/test/fastapi_test.py +17 -0
- bbot/test/owasp_mastg.apk +0 -0
- bbot/test/run_tests.sh +4 -4
- bbot/test/test.conf +2 -0
- bbot/test/test_step_1/test__module__tests.py +0 -1
- bbot/test/test_step_1/test_bbot_fastapi.py +79 -0
- bbot/test/test_step_1/test_bloom_filter.py +2 -1
- bbot/test/test_step_1/test_cli.py +138 -64
- bbot/test/test_step_1/test_dns.py +61 -27
- bbot/test/test_step_1/test_engine.py +17 -19
- bbot/test/test_step_1/test_events.py +183 -30
- bbot/test/test_step_1/test_helpers.py +64 -29
- bbot/test/test_step_1/test_manager_deduplication.py +1 -1
- bbot/test/test_step_1/test_manager_scope_accuracy.py +333 -330
- bbot/test/test_step_1/test_modules_basic.py +68 -70
- bbot/test/test_step_1/test_presets.py +183 -100
- bbot/test/test_step_1/test_python_api.py +7 -2
- bbot/test/test_step_1/test_regexes.py +35 -5
- bbot/test/test_step_1/test_scan.py +39 -5
- bbot/test/test_step_1/test_scope.py +4 -3
- bbot/test/test_step_1/test_target.py +242 -145
- bbot/test/test_step_1/test_web.py +14 -10
- bbot/test/test_step_2/module_tests/base.py +15 -7
- bbot/test/test_step_2/module_tests/test_module_anubisdb.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_apkpure.py +71 -0
- bbot/test/test_step_2/module_tests/test_module_asset_inventory.py +0 -1
- bbot/test/test_step_2/module_tests/test_module_azure_realm.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_baddns.py +6 -6
- bbot/test/test_step_2/module_tests/test_module_baddns_direct.py +62 -0
- bbot/test/test_step_2/module_tests/test_module_bevigil.py +29 -2
- bbot/test/test_step_2/module_tests/test_module_binaryedge.py +4 -2
- bbot/test/test_step_2/module_tests/test_module_bucket_amazon.py +2 -2
- bbot/test/test_step_2/module_tests/test_module_bucket_azure.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_bufferoverrun.py +35 -0
- bbot/test/test_step_2/module_tests/test_module_builtwith.py +2 -2
- bbot/test/test_step_2/module_tests/test_module_bypass403.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_c99.py +126 -0
- bbot/test/test_step_2/module_tests/test_module_censys.py +4 -1
- bbot/test/test_step_2/module_tests/test_module_cloudcheck.py +4 -0
- bbot/test/test_step_2/module_tests/test_module_code_repository.py +11 -1
- bbot/test/test_step_2/module_tests/test_module_columbus.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_credshed.py +3 -3
- bbot/test/test_step_2/module_tests/test_module_dastardly.py +2 -1
- bbot/test/test_step_2/module_tests/test_module_dehashed.py +2 -2
- bbot/test/test_step_2/module_tests/test_module_digitorus.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_discord.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_dnsbimi.py +103 -0
- bbot/test/test_step_2/module_tests/test_module_dnsbrute.py +9 -10
- bbot/test/test_step_2/module_tests/test_module_dnsbrute_mutations.py +1 -2
- bbot/test/test_step_2/module_tests/test_module_dnscommonsrv.py +1 -2
- bbot/test/test_step_2/module_tests/test_module_dnsdumpster.py +4 -4
- bbot/test/test_step_2/module_tests/test_module_dnstlsrpt.py +64 -0
- bbot/test/test_step_2/module_tests/test_module_dotnetnuke.py +0 -8
- bbot/test/test_step_2/module_tests/test_module_excavate.py +28 -48
- bbot/test/test_step_2/module_tests/test_module_extractous.py +54 -0
- bbot/test/test_step_2/module_tests/test_module_ffuf_shortnames.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_filedownload.py +14 -14
- bbot/test/test_step_2/module_tests/test_module_git_clone.py +2 -2
- bbot/test/test_step_2/module_tests/test_module_github_org.py +19 -8
- bbot/test/test_step_2/module_tests/test_module_github_workflows.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_gitlab.py +9 -4
- bbot/test/test_step_2/module_tests/test_module_google_playstore.py +83 -0
- bbot/test/test_step_2/module_tests/test_module_gowitness.py +4 -6
- bbot/test/test_step_2/module_tests/test_module_host_header.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_http.py +4 -4
- bbot/test/test_step_2/module_tests/test_module_httpx.py +10 -8
- bbot/test/test_step_2/module_tests/test_module_hunterio.py +68 -4
- bbot/test/test_step_2/module_tests/test_module_jadx.py +55 -0
- bbot/test/test_step_2/module_tests/test_module_json.py +22 -9
- bbot/test/test_step_2/module_tests/test_module_leakix.py +7 -3
- bbot/test/test_step_2/module_tests/test_module_mysql.py +76 -0
- bbot/test/test_step_2/module_tests/test_module_myssl.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_neo4j.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_newsletters.py +16 -16
- bbot/test/test_step_2/module_tests/test_module_ntlm.py +8 -7
- bbot/test/test_step_2/module_tests/test_module_oauth.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_otx.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_paramminer_cookies.py +1 -2
- bbot/test/test_step_2/module_tests/test_module_paramminer_getparams.py +0 -6
- bbot/test/test_step_2/module_tests/test_module_paramminer_headers.py +2 -9
- bbot/test/test_step_2/module_tests/test_module_passivetotal.py +3 -1
- bbot/test/test_step_2/module_tests/test_module_pgp.py +2 -2
- bbot/test/test_step_2/module_tests/test_module_portscan.py +9 -8
- bbot/test/test_step_2/module_tests/test_module_postgres.py +74 -0
- bbot/test/test_step_2/module_tests/test_module_postman.py +84 -253
- bbot/test/test_step_2/module_tests/test_module_postman_download.py +439 -0
- bbot/test/test_step_2/module_tests/test_module_rapiddns.py +93 -1
- bbot/test/test_step_2/module_tests/test_module_shodan_dns.py +20 -1
- bbot/test/test_step_2/module_tests/test_module_sitedossier.py +2 -2
- bbot/test/test_step_2/module_tests/test_module_smuggler.py +14 -14
- bbot/test/test_step_2/module_tests/test_module_social.py +11 -1
- bbot/test/test_step_2/module_tests/test_module_speculate.py +4 -8
- bbot/test/test_step_2/module_tests/test_module_splunk.py +4 -4
- bbot/test/test_step_2/module_tests/test_module_sqlite.py +18 -0
- bbot/test/test_step_2/module_tests/test_module_sslcert.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_stdout.py +5 -3
- bbot/test/test_step_2/module_tests/test_module_subdomaincenter.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_subdomainradar.py +208 -0
- bbot/test/test_step_2/module_tests/test_module_subdomains.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_teams.py +8 -6
- bbot/test/test_step_2/module_tests/test_module_telerik.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_trufflehog.py +317 -14
- bbot/test/test_step_2/module_tests/test_module_viewdns.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_wayback.py +1 -1
- bbot/test/test_step_2/template_tests/test_template_subdomain_enum.py +2 -2
- bbot/wordlists/devops_mutations.txt +1 -1
- bbot/wordlists/ffuf_shortname_candidates.txt +1 -1
- bbot/wordlists/nameservers.txt +1 -1
- bbot/wordlists/paramminer_headers.txt +1 -1
- bbot/wordlists/paramminer_parameters.txt +1 -1
- bbot/wordlists/raft-small-extensions-lowercase_CLEANED.txt +1 -1
- bbot/wordlists/valid_url_schemes.txt +1 -1
- {bbot-2.0.1.4720rc0.dist-info → bbot-2.3.0.5401rc0.dist-info}/METADATA +48 -18
- bbot-2.3.0.5401rc0.dist-info/RECORD +421 -0
- {bbot-2.0.1.4720rc0.dist-info → bbot-2.3.0.5401rc0.dist-info}/WHEEL +1 -1
- bbot/modules/unstructured.py +0 -163
- bbot/test/test_step_2/module_tests/test_module_unstructured.py +0 -102
- bbot-2.0.1.4720rc0.dist-info/RECORD +0 -387
- {bbot-2.0.1.4720rc0.dist-info → bbot-2.3.0.5401rc0.dist-info}/LICENSE +0 -0
- {bbot-2.0.1.4720rc0.dist-info → bbot-2.3.0.5401rc0.dist-info}/entry_points.txt +0 -0
bbot/modules/base.py
CHANGED
|
@@ -63,7 +63,7 @@ class BaseModule:
|
|
|
63
63
|
|
|
64
64
|
batch_wait (int): Seconds to wait before force-submitting a batch. Default is 10.
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
api_failure_abort_threshold (int): Threshold for setting error state after failed HTTP requests (only takes effect when `api_request()` is used. Default is 5.
|
|
67
67
|
|
|
68
68
|
_preserve_graph (bool): When set to True, accept events that may be duplicates but are necessary for construction of complete graph. Typically only enabled for output modules that need to maintain full chains of events, e.g. `neo4j` and `json`. Default is False.
|
|
69
69
|
|
|
@@ -103,7 +103,13 @@ class BaseModule:
|
|
|
103
103
|
_module_threads = 1
|
|
104
104
|
_batch_size = 1
|
|
105
105
|
batch_wait = 10
|
|
106
|
-
|
|
106
|
+
|
|
107
|
+
# API retries, etc.
|
|
108
|
+
_api_retries = 2
|
|
109
|
+
# disable the module after this many failed attempts in a row
|
|
110
|
+
_api_failure_abort_threshold = 3
|
|
111
|
+
# sleep for this many seconds after being rate limited
|
|
112
|
+
_429_sleep_interval = 30
|
|
107
113
|
|
|
108
114
|
default_discovery_context = "{module} discovered {event.type}: {event.data}"
|
|
109
115
|
|
|
@@ -148,8 +154,10 @@ class BaseModule:
|
|
|
148
154
|
# string constant
|
|
149
155
|
self._custom_filter_criteria_msg = "it did not meet custom filter criteria"
|
|
150
156
|
|
|
151
|
-
|
|
152
|
-
|
|
157
|
+
self._api_keys = []
|
|
158
|
+
|
|
159
|
+
# track number of failures (for .api_request())
|
|
160
|
+
self._api_request_failures = 0
|
|
153
161
|
|
|
154
162
|
self._tasks = []
|
|
155
163
|
self._event_received = asyncio.Condition()
|
|
@@ -303,32 +311,76 @@ class BaseModule:
|
|
|
303
311
|
if self.auth_secret:
|
|
304
312
|
try:
|
|
305
313
|
await self.ping()
|
|
306
|
-
self.hugesuccess(
|
|
307
|
-
return True
|
|
314
|
+
self.hugesuccess("API is ready")
|
|
315
|
+
return True, ""
|
|
308
316
|
except Exception as e:
|
|
317
|
+
self.trace(traceback.format_exc())
|
|
309
318
|
return None, f"Error with API ({str(e).strip()})"
|
|
310
319
|
else:
|
|
311
320
|
return None, "No API key set"
|
|
312
321
|
|
|
313
|
-
|
|
322
|
+
@property
|
|
323
|
+
def api_key(self):
|
|
324
|
+
if self._api_keys:
|
|
325
|
+
return self._api_keys[0]
|
|
326
|
+
|
|
327
|
+
@api_key.setter
|
|
328
|
+
def api_key(self, api_keys):
|
|
329
|
+
if isinstance(api_keys, str):
|
|
330
|
+
api_keys = [api_keys]
|
|
331
|
+
self._api_keys = list(api_keys)
|
|
332
|
+
|
|
333
|
+
def cycle_api_key(self):
|
|
334
|
+
if len(self._api_keys) > 1:
|
|
335
|
+
self.verbose("Cycling API key")
|
|
336
|
+
self._api_keys.insert(0, self._api_keys.pop())
|
|
337
|
+
else:
|
|
338
|
+
self.debug("No extra API keys to cycle")
|
|
339
|
+
|
|
340
|
+
@property
|
|
341
|
+
def api_retries(self):
|
|
342
|
+
return max(self._api_retries + 1, len(self._api_keys))
|
|
343
|
+
|
|
344
|
+
@property
|
|
345
|
+
def api_failure_abort_threshold(self):
|
|
346
|
+
return (self.api_retries * self._api_failure_abort_threshold) + 1
|
|
347
|
+
|
|
348
|
+
async def ping(self, url=None):
|
|
314
349
|
"""Asynchronously checks the health of the configured API.
|
|
315
350
|
|
|
316
|
-
This method is used in conjunction with require_api_key() to verify that the API is not just configured, but also responsive.
|
|
351
|
+
This method is used in conjunction with require_api_key() to verify that the API is not just configured, but also responsive. It makes a test request to a known endpoint to validate the API's health.
|
|
317
352
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
resp_content = getattr(r, "text", "")
|
|
323
|
-
assert getattr(r, "status_code", 0) == 200, resp_content
|
|
353
|
+
The method uses the `ping_url` attribute if defined, or falls back to a provided URL. If neither is available, no request is made.
|
|
354
|
+
|
|
355
|
+
Args:
|
|
356
|
+
url (str, optional): A specific URL to use for the ping request. If not provided, the method will use the `ping_url` attribute.
|
|
324
357
|
|
|
325
358
|
Returns:
|
|
326
359
|
None
|
|
327
360
|
|
|
328
361
|
Raises:
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
362
|
+
ValueError: If the API response is not successful (status code != 200).
|
|
363
|
+
|
|
364
|
+
Example Usage:
|
|
365
|
+
To use this method, simply define the `ping_url` attribute in your module:
|
|
366
|
+
|
|
367
|
+
class MyModule(BaseModule):
|
|
368
|
+
ping_url = "https://api.example.com/ping"
|
|
369
|
+
|
|
370
|
+
Alternatively, you can override this method for more complex health checks:
|
|
371
|
+
|
|
372
|
+
async def ping(self):
|
|
373
|
+
r = await self.api_request(f"{self.base_url}/complex-health-check")
|
|
374
|
+
if r.status_code != 200 or r.json().get('status') != 'healthy':
|
|
375
|
+
raise ValueError(f"API unhealthy: {r.text}")
|
|
376
|
+
"""
|
|
377
|
+
if url is None:
|
|
378
|
+
url = getattr(self, "ping_url", "")
|
|
379
|
+
if url:
|
|
380
|
+
r = await self.api_request(url)
|
|
381
|
+
if getattr(r, "status_code", 0) != 200:
|
|
382
|
+
response_text = getattr(r, "text", "no response from server")
|
|
383
|
+
raise ValueError(response_text)
|
|
332
384
|
|
|
333
385
|
@property
|
|
334
386
|
def batch_size(self):
|
|
@@ -617,7 +669,7 @@ class BaseModule:
|
|
|
617
669
|
if self.incoming_event_queue is not False:
|
|
618
670
|
event = await self.incoming_event_queue.get()
|
|
619
671
|
else:
|
|
620
|
-
self.debug(
|
|
672
|
+
self.debug("Event queue is in bad state")
|
|
621
673
|
break
|
|
622
674
|
except asyncio.queues.QueueEmpty:
|
|
623
675
|
continue
|
|
@@ -648,7 +700,7 @@ class BaseModule:
|
|
|
648
700
|
else:
|
|
649
701
|
self.error(f"Critical failure in module {self.name}: {e}")
|
|
650
702
|
self.error(traceback.format_exc())
|
|
651
|
-
self.log.trace(
|
|
703
|
+
self.log.trace("Worker stopped")
|
|
652
704
|
|
|
653
705
|
@property
|
|
654
706
|
def max_scope_distance(self):
|
|
@@ -691,7 +743,7 @@ class BaseModule:
|
|
|
691
743
|
if event.type in ("FINISHED",):
|
|
692
744
|
return True, "its type is FINISHED"
|
|
693
745
|
if self.errored:
|
|
694
|
-
return False,
|
|
746
|
+
return False, "module is in error state"
|
|
695
747
|
# exclude non-watched types
|
|
696
748
|
if not any(t in self.get_watched_events() for t in ("*", event.type)):
|
|
697
749
|
return False, "its type is not in watched_events"
|
|
@@ -718,7 +770,7 @@ class BaseModule:
|
|
|
718
770
|
# check duplicates
|
|
719
771
|
is_incoming_duplicate, reason = self.is_incoming_duplicate(event, add=True)
|
|
720
772
|
if is_incoming_duplicate and not self.accept_dupes:
|
|
721
|
-
return False,
|
|
773
|
+
return False, "module has already seen it" + (f" ({reason})" if reason else "")
|
|
722
774
|
|
|
723
775
|
return acceptable, reason
|
|
724
776
|
|
|
@@ -811,7 +863,7 @@ class BaseModule:
|
|
|
811
863
|
"""
|
|
812
864
|
async with self._task_counter.count("queue_event()", _log=False):
|
|
813
865
|
if self.incoming_event_queue is False:
|
|
814
|
-
self.debug(
|
|
866
|
+
self.debug("Not in an acceptable state to queue incoming event")
|
|
815
867
|
return
|
|
816
868
|
acceptable, reason = self._event_precheck(event)
|
|
817
869
|
if not acceptable:
|
|
@@ -827,7 +879,7 @@ class BaseModule:
|
|
|
827
879
|
if event.type != "FINISHED":
|
|
828
880
|
self.scan._new_activity = True
|
|
829
881
|
except AttributeError:
|
|
830
|
-
self.debug(
|
|
882
|
+
self.debug("Not in an acceptable state to queue incoming event")
|
|
831
883
|
|
|
832
884
|
async def queue_outgoing_event(self, event, **kwargs):
|
|
833
885
|
"""
|
|
@@ -852,7 +904,7 @@ class BaseModule:
|
|
|
852
904
|
try:
|
|
853
905
|
await self.outgoing_event_queue.put((event, kwargs))
|
|
854
906
|
except AttributeError:
|
|
855
|
-
self.debug(
|
|
907
|
+
self.debug("Not in an acceptable state to queue outgoing event")
|
|
856
908
|
|
|
857
909
|
def set_error_state(self, message=None, clear_outgoing_queue=False, critical=False):
|
|
858
910
|
"""
|
|
@@ -887,7 +939,7 @@ class BaseModule:
|
|
|
887
939
|
self.errored = True
|
|
888
940
|
# clear incoming queue
|
|
889
941
|
if self.incoming_event_queue is not False:
|
|
890
|
-
self.debug(
|
|
942
|
+
self.debug("Emptying event_queue")
|
|
891
943
|
with suppress(asyncio.queues.QueueEmpty):
|
|
892
944
|
while 1:
|
|
893
945
|
self.incoming_event_queue.get_nowait()
|
|
@@ -932,8 +984,11 @@ class BaseModule:
|
|
|
932
984
|
def _outgoing_dedup_hash(self, event):
|
|
933
985
|
"""
|
|
934
986
|
Determines the criteria for what is considered to be a duplicate event if `suppress_dupes` is True.
|
|
987
|
+
|
|
988
|
+
We take into account the `internal` attribute we don't want an internal event (which isn't distributed to output modules)
|
|
989
|
+
to inadvertently suppress a non-internal event.
|
|
935
990
|
"""
|
|
936
|
-
return hash((event, self.name))
|
|
991
|
+
return hash((event, self.name, event.internal, event.always_emit))
|
|
937
992
|
|
|
938
993
|
def get_per_host_hash(self, event):
|
|
939
994
|
"""
|
|
@@ -1065,32 +1120,122 @@ class BaseModule:
|
|
|
1065
1120
|
async for line in self.helpers.run_live(*args, **kwargs):
|
|
1066
1121
|
yield line
|
|
1067
1122
|
|
|
1068
|
-
|
|
1069
|
-
"""
|
|
1123
|
+
def prepare_api_request(self, url, kwargs):
|
|
1124
|
+
"""
|
|
1125
|
+
Prepare an API request by adding the necessary authentication - header, bearer token, etc.
|
|
1126
|
+
"""
|
|
1127
|
+
if self.api_key:
|
|
1128
|
+
url = url.format(api_key=self.api_key)
|
|
1129
|
+
if "headers" not in kwargs:
|
|
1130
|
+
kwargs["headers"] = {}
|
|
1131
|
+
kwargs["headers"]["Authorization"] = f"Bearer {self.api_key}"
|
|
1132
|
+
return url, kwargs
|
|
1070
1133
|
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1134
|
+
async def api_request(self, *args, **kwargs):
|
|
1135
|
+
"""
|
|
1136
|
+
Makes an HTTP request while automatically:
|
|
1137
|
+
- avoiding rate limits (sleep/retry)
|
|
1138
|
+
- cycling API keys
|
|
1139
|
+
- cancelling after too many failed attempts
|
|
1140
|
+
"""
|
|
1141
|
+
url = args[0] if args else kwargs.pop("url", "")
|
|
1074
1142
|
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1143
|
+
# loop until we have a successful request
|
|
1144
|
+
for _ in range(self.api_retries):
|
|
1145
|
+
if "headers" not in kwargs:
|
|
1146
|
+
kwargs["headers"] = {}
|
|
1147
|
+
new_url, kwargs = self.prepare_api_request(url, kwargs)
|
|
1148
|
+
kwargs["url"] = new_url
|
|
1078
1149
|
|
|
1079
|
-
|
|
1080
|
-
|
|
1150
|
+
r = await self.helpers.request(**kwargs)
|
|
1151
|
+
success = False if r is None else r.is_success
|
|
1152
|
+
|
|
1153
|
+
if success:
|
|
1154
|
+
self._api_request_failures = 0
|
|
1155
|
+
else:
|
|
1156
|
+
status_code = getattr(r, "status_code", 0)
|
|
1157
|
+
response_text = getattr(r, "text", "")
|
|
1158
|
+
self.trace(f"API response to {url} failed with status code {status_code}: {response_text}")
|
|
1159
|
+
self._api_request_failures += 1
|
|
1160
|
+
if self._api_request_failures >= self.api_failure_abort_threshold:
|
|
1161
|
+
self.set_error_state(
|
|
1162
|
+
f"Setting error state due to {self._api_request_failures:,} failed HTTP requests"
|
|
1163
|
+
)
|
|
1164
|
+
else:
|
|
1165
|
+
# sleep for a bit if we're being rate limited
|
|
1166
|
+
if status_code == 429:
|
|
1167
|
+
self.verbose(
|
|
1168
|
+
f"Sleeping for {self._429_sleep_interval:,} seconds due to rate limit (HTTP status: 429)"
|
|
1169
|
+
)
|
|
1170
|
+
await asyncio.sleep(self._429_sleep_interval)
|
|
1171
|
+
elif self._api_keys:
|
|
1172
|
+
# if request failed, cycle API keys and try again
|
|
1173
|
+
self.cycle_api_key()
|
|
1174
|
+
continue
|
|
1175
|
+
break
|
|
1081
1176
|
|
|
1082
|
-
Raises:
|
|
1083
|
-
None: Sets the module to an error state when the failure threshold is reached.
|
|
1084
|
-
"""
|
|
1085
|
-
r = await self.helpers.request(*args, **kwargs)
|
|
1086
|
-
if r is None:
|
|
1087
|
-
self._request_failures += 1
|
|
1088
|
-
else:
|
|
1089
|
-
self._request_failures = 0
|
|
1090
|
-
if self._request_failures >= self.failed_request_abort_threshold:
|
|
1091
|
-
self.set_error_state(f"Setting error state due to {self._request_failures:,} failed HTTP requests")
|
|
1092
1177
|
return r
|
|
1093
1178
|
|
|
1179
|
+
async def api_page_iter(self, url, page_size=100, json=True, next_key=None, **requests_kwargs):
|
|
1180
|
+
"""
|
|
1181
|
+
An asynchronous generator function for iterating through paginated API data.
|
|
1182
|
+
|
|
1183
|
+
This function continuously makes requests to a specified API URL, incrementing the page number
|
|
1184
|
+
or applying a custom pagination function, and yields the received data one page at a time.
|
|
1185
|
+
It is well-suited for APIs that provide paginated results.
|
|
1186
|
+
|
|
1187
|
+
Args:
|
|
1188
|
+
url (str): The initial API URL. Can contain placeholders for 'page', 'page_size', and 'offset'.
|
|
1189
|
+
page_size (int, optional): The number of items per page. Defaults to 100.
|
|
1190
|
+
json (bool, optional): If True, attempts to deserialize the response content to a JSON object. Defaults to True.
|
|
1191
|
+
next_key (callable, optional): A function that takes the last page's data and returns the URL for the next page. Defaults to None.
|
|
1192
|
+
**requests_kwargs: Arbitrary keyword arguments that will be forwarded to the HTTP request function.
|
|
1193
|
+
|
|
1194
|
+
Yields:
|
|
1195
|
+
dict or httpx.Response: If 'json' is True, yields a dictionary containing the parsed JSON data. Otherwise, yields the raw HTTP response.
|
|
1196
|
+
|
|
1197
|
+
Note:
|
|
1198
|
+
The loop will continue indefinitely unless manually stopped. Make sure to break out of the loop once the last page has been received.
|
|
1199
|
+
|
|
1200
|
+
Examples:
|
|
1201
|
+
>>> agen = api_page_iter('https://api.example.com/data?page={page}&page_size={page_size}')
|
|
1202
|
+
>>> try:
|
|
1203
|
+
>>> async for page in agen:
|
|
1204
|
+
>>> subdomains = page["subdomains"]
|
|
1205
|
+
>>> self.hugesuccess(subdomains)
|
|
1206
|
+
>>> if not subdomains:
|
|
1207
|
+
>>> break
|
|
1208
|
+
>>> finally:
|
|
1209
|
+
>>> agen.aclose()
|
|
1210
|
+
"""
|
|
1211
|
+
page = 1
|
|
1212
|
+
offset = 0
|
|
1213
|
+
result = None
|
|
1214
|
+
while 1:
|
|
1215
|
+
if result and callable(next_key):
|
|
1216
|
+
try:
|
|
1217
|
+
new_url = next_key(result)
|
|
1218
|
+
except Exception as e:
|
|
1219
|
+
self.debug(f"Failed to extract next page of results from {url}: {e}")
|
|
1220
|
+
self.debug(traceback.format_exc())
|
|
1221
|
+
else:
|
|
1222
|
+
new_url = self.helpers.safe_format(url, page=page, page_size=page_size, offset=offset)
|
|
1223
|
+
result = await self.api_request(new_url, **requests_kwargs)
|
|
1224
|
+
if result is None:
|
|
1225
|
+
self.verbose(f"api_page_iter() got no response for {url}")
|
|
1226
|
+
break
|
|
1227
|
+
try:
|
|
1228
|
+
if json:
|
|
1229
|
+
result = result.json()
|
|
1230
|
+
yield result
|
|
1231
|
+
except Exception:
|
|
1232
|
+
self.warning(f'Error in api_page_iter() for url: "{new_url}"')
|
|
1233
|
+
self.trace(traceback.format_exc())
|
|
1234
|
+
break
|
|
1235
|
+
finally:
|
|
1236
|
+
offset += page_size
|
|
1237
|
+
page += 1
|
|
1238
|
+
|
|
1094
1239
|
@property
|
|
1095
1240
|
def preset(self):
|
|
1096
1241
|
return self.scan.preset
|
|
@@ -1417,7 +1562,7 @@ class BaseModule:
|
|
|
1417
1562
|
self.trace()
|
|
1418
1563
|
|
|
1419
1564
|
|
|
1420
|
-
class
|
|
1565
|
+
class BaseInterceptModule(BaseModule):
|
|
1421
1566
|
"""
|
|
1422
1567
|
An Intercept Module is a special type of high-priority module that gets early access to events.
|
|
1423
1568
|
|
|
@@ -1429,7 +1574,6 @@ class InterceptModule(BaseModule):
|
|
|
1429
1574
|
"""
|
|
1430
1575
|
|
|
1431
1576
|
accept_dupes = True
|
|
1432
|
-
suppress_dupes = False
|
|
1433
1577
|
_intercept = True
|
|
1434
1578
|
|
|
1435
1579
|
async def _worker(self):
|
|
@@ -1445,7 +1589,7 @@ class InterceptModule(BaseModule):
|
|
|
1445
1589
|
event = incoming
|
|
1446
1590
|
kwargs = {}
|
|
1447
1591
|
else:
|
|
1448
|
-
self.debug(
|
|
1592
|
+
self.debug("Event queue is in bad state")
|
|
1449
1593
|
break
|
|
1450
1594
|
except asyncio.queues.QueueEmpty:
|
|
1451
1595
|
await asyncio.sleep(0.1)
|
|
@@ -1500,7 +1644,7 @@ class InterceptModule(BaseModule):
|
|
|
1500
1644
|
else:
|
|
1501
1645
|
self.critical(f"Critical failure in intercept module {self.name}: {e}")
|
|
1502
1646
|
self.critical(traceback.format_exc())
|
|
1503
|
-
self.log.trace(
|
|
1647
|
+
self.log.trace("Worker stopped")
|
|
1504
1648
|
|
|
1505
1649
|
async def get_incoming_event(self):
|
|
1506
1650
|
"""
|
|
@@ -1531,7 +1675,7 @@ class InterceptModule(BaseModule):
|
|
|
1531
1675
|
try:
|
|
1532
1676
|
self.incoming_event_queue.put_nowait((event, kwargs))
|
|
1533
1677
|
except AttributeError:
|
|
1534
|
-
self.debug(
|
|
1678
|
+
self.debug("Not in an acceptable state to queue incoming event")
|
|
1535
1679
|
|
|
1536
1680
|
async def _event_postcheck(self, event):
|
|
1537
1681
|
return await self._event_postcheck_inner(event)
|
bbot/modules/bevigil.py
CHANGED
|
@@ -22,12 +22,12 @@ class bevigil(subdomain_enum_apikey):
|
|
|
22
22
|
|
|
23
23
|
async def setup(self):
|
|
24
24
|
self.api_key = self.config.get("api_key", "")
|
|
25
|
-
self.headers = {"X-Access-Token": self.api_key}
|
|
26
25
|
self.urls = self.config.get("urls", False)
|
|
27
26
|
return await super().setup()
|
|
28
27
|
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
def prepare_api_request(self, url, kwargs):
|
|
29
|
+
kwargs["headers"]["X-Access-Token"] = self.api_key
|
|
30
|
+
return url, kwargs
|
|
31
31
|
|
|
32
32
|
async def handle_event(self, event):
|
|
33
33
|
query = self.make_query(event)
|
|
@@ -54,20 +54,20 @@ class bevigil(subdomain_enum_apikey):
|
|
|
54
54
|
|
|
55
55
|
async def request_subdomains(self, query):
|
|
56
56
|
url = f"{self.base_url}/{self.helpers.quote(query)}/subdomains/"
|
|
57
|
-
return await self.
|
|
57
|
+
return await self.api_request(url)
|
|
58
58
|
|
|
59
59
|
async def request_urls(self, query):
|
|
60
60
|
url = f"{self.base_url}/{self.helpers.quote(query)}/urls/"
|
|
61
|
-
return await self.
|
|
61
|
+
return await self.api_request(url)
|
|
62
62
|
|
|
63
|
-
def parse_subdomains(self, r, query=None):
|
|
63
|
+
async def parse_subdomains(self, r, query=None):
|
|
64
64
|
results = set()
|
|
65
65
|
subdomains = r.json().get("subdomains")
|
|
66
66
|
if subdomains:
|
|
67
67
|
results.update(subdomains)
|
|
68
68
|
return results
|
|
69
69
|
|
|
70
|
-
def parse_urls(self, r, query=None):
|
|
70
|
+
async def parse_urls(self, r, query=None):
|
|
71
71
|
results = set()
|
|
72
72
|
urls = r.json().get("urls")
|
|
73
73
|
if urls:
|
bbot/modules/binaryedge.py
CHANGED
|
@@ -21,19 +21,22 @@ class binaryedge(subdomain_enum_apikey):
|
|
|
21
21
|
|
|
22
22
|
async def setup(self):
|
|
23
23
|
self.max_records = self.config.get("max_records", 1000)
|
|
24
|
-
self.headers = {"X-Key": self.config.get("api_key", "")}
|
|
25
24
|
return await super().setup()
|
|
26
25
|
|
|
26
|
+
def prepare_api_request(self, url, kwargs):
|
|
27
|
+
kwargs["headers"]["X-Key"] = self.api_key
|
|
28
|
+
return url, kwargs
|
|
29
|
+
|
|
27
30
|
async def ping(self):
|
|
28
31
|
url = f"{self.base_url}/user/subscription"
|
|
29
|
-
j = (await self.
|
|
32
|
+
j = (await self.api_request(url)).json()
|
|
30
33
|
assert j.get("requests_left", 0) > 0
|
|
31
34
|
|
|
32
35
|
async def request_url(self, query):
|
|
33
36
|
# todo: host query (certs + services)
|
|
34
37
|
url = f"{self.base_url}/query/domains/subdomain/{self.helpers.quote(query)}"
|
|
35
|
-
return await self.
|
|
38
|
+
return await self.api_request(url)
|
|
36
39
|
|
|
37
|
-
def parse_results(self, r, query):
|
|
40
|
+
async def parse_results(self, r, query):
|
|
38
41
|
j = r.json()
|
|
39
42
|
return j.get("events", [])
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from bbot.modules.templates.subdomain_enum import subdomain_enum_apikey
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class BufferOverrun(subdomain_enum_apikey):
|
|
5
|
+
watched_events = ["DNS_NAME"]
|
|
6
|
+
produced_events = ["DNS_NAME"]
|
|
7
|
+
flags = ["subdomain-enum", "passive", "safe"]
|
|
8
|
+
meta = {
|
|
9
|
+
"description": "Query BufferOverrun's TLS API for subdomains",
|
|
10
|
+
"created_date": "2024-10-23",
|
|
11
|
+
"author": "@TheTechromancer",
|
|
12
|
+
"auth_required": True,
|
|
13
|
+
}
|
|
14
|
+
options = {"api_key": "", "commercial": False}
|
|
15
|
+
options_desc = {"api_key": "BufferOverrun API key", "commercial": "Use commercial API"}
|
|
16
|
+
|
|
17
|
+
base_url = "https://tls.bufferover.run/dns"
|
|
18
|
+
commercial_base_url = "https://bufferover-run-tls.p.rapidapi.com/ipv4/dns"
|
|
19
|
+
|
|
20
|
+
async def setup(self):
|
|
21
|
+
self.commercial = self.config.get("commercial", False)
|
|
22
|
+
return await super().setup()
|
|
23
|
+
|
|
24
|
+
def prepare_api_request(self, url, kwargs):
|
|
25
|
+
if self.commercial:
|
|
26
|
+
kwargs["headers"]["x-rapidapi-host"] = "bufferover-run-tls.p.rapidapi.com"
|
|
27
|
+
kwargs["headers"]["x-rapidapi-key"] = self.api_key
|
|
28
|
+
else:
|
|
29
|
+
kwargs["headers"]["x-api-key"] = self.api_key
|
|
30
|
+
return url, kwargs
|
|
31
|
+
|
|
32
|
+
async def request_url(self, query):
|
|
33
|
+
url = f"{self.commercial_base_url if self.commercial else self.base_url}?q=.{query}"
|
|
34
|
+
return await self.api_request(url)
|
|
35
|
+
|
|
36
|
+
async def parse_results(self, r, query):
|
|
37
|
+
j = r.json()
|
|
38
|
+
subdomains_set = set()
|
|
39
|
+
if isinstance(j, dict):
|
|
40
|
+
results = j.get("Results", [])
|
|
41
|
+
for result in results:
|
|
42
|
+
parts = result.split(",")
|
|
43
|
+
if len(parts) > 4:
|
|
44
|
+
subdomain = parts[4].strip()
|
|
45
|
+
if subdomain and subdomain.endswith(f".{query}"):
|
|
46
|
+
subdomains_set.add(subdomain)
|
|
47
|
+
return subdomains_set
|
bbot/modules/builtwith.py
CHANGED
|
@@ -27,10 +27,6 @@ class builtwith(subdomain_enum_apikey):
|
|
|
27
27
|
options_desc = {"api_key": "Builtwith API key", "redirects": "Also look up inbound and outbound redirects"}
|
|
28
28
|
base_url = "https://api.builtwith.com"
|
|
29
29
|
|
|
30
|
-
async def ping(self):
|
|
31
|
-
# builtwith does not have a ping feature, so we skip it to save API credits
|
|
32
|
-
return
|
|
33
|
-
|
|
34
30
|
async def handle_event(self, event):
|
|
35
31
|
query = self.make_query(event)
|
|
36
32
|
# domains
|
|
@@ -59,14 +55,14 @@ class builtwith(subdomain_enum_apikey):
|
|
|
59
55
|
)
|
|
60
56
|
|
|
61
57
|
async def request_domains(self, query):
|
|
62
|
-
url = f"{self.base_url}/v20/api.json?KEY={
|
|
63
|
-
return await self.
|
|
58
|
+
url = f"{self.base_url}/v20/api.json?KEY={{api_key}}&LOOKUP={query}&NOMETA=yes&NOATTR=yes&HIDETEXT=yes&HIDEDL=yes"
|
|
59
|
+
return await self.api_request(url)
|
|
64
60
|
|
|
65
61
|
async def request_redirects(self, query):
|
|
66
|
-
url = f"{self.base_url}/redirect1/api.json?KEY={
|
|
67
|
-
return await self.
|
|
62
|
+
url = f"{self.base_url}/redirect1/api.json?KEY={{api_key}}&LOOKUP={query}"
|
|
63
|
+
return await self.api_request(url)
|
|
68
64
|
|
|
69
|
-
def parse_domains(self, r, query):
|
|
65
|
+
async def parse_domains(self, r, query):
|
|
70
66
|
"""
|
|
71
67
|
This method returns a set of subdomains.
|
|
72
68
|
Each subdomain is an "FQDN" that was reported in the "Detailed Technology Profile" page on builtwith.com
|
|
@@ -96,7 +92,7 @@ class builtwith(subdomain_enum_apikey):
|
|
|
96
92
|
self.verbose(f"No results for {query}: {error}")
|
|
97
93
|
return results_set
|
|
98
94
|
|
|
99
|
-
def parse_redirects(self, r, query):
|
|
95
|
+
async def parse_redirects(self, r, query):
|
|
100
96
|
"""
|
|
101
97
|
This method creates a set.
|
|
102
98
|
Each entry in the set is either an Inbound or Outbound Redirect reported in the "Redirect Profile" page on builtwith.com
|
bbot/modules/bypass403.py
CHANGED
|
@@ -92,7 +92,7 @@ class bypass403(BaseModule):
|
|
|
92
92
|
return None
|
|
93
93
|
|
|
94
94
|
sig = self.format_signature(sig, event)
|
|
95
|
-
if sig[2]
|
|
95
|
+
if sig[2] is not None:
|
|
96
96
|
headers = dict(sig[2])
|
|
97
97
|
else:
|
|
98
98
|
headers = None
|
|
@@ -106,13 +106,13 @@ class bypass403(BaseModule):
|
|
|
106
106
|
continue
|
|
107
107
|
|
|
108
108
|
# In some cases WAFs will respond with a 200 code which causes a false positive
|
|
109
|
-
if subject_response
|
|
109
|
+
if subject_response is not None:
|
|
110
110
|
for ws in waf_strings:
|
|
111
111
|
if ws in subject_response.text:
|
|
112
112
|
self.debug("Rejecting result based on presence of WAF string")
|
|
113
113
|
return
|
|
114
114
|
|
|
115
|
-
if match
|
|
115
|
+
if match is False:
|
|
116
116
|
if str(subject_response.status_code)[0] != "4":
|
|
117
117
|
if sig[2]:
|
|
118
118
|
added_header_tuple = next(iter(sig[2].items()))
|
|
@@ -165,13 +165,13 @@ class bypass403(BaseModule):
|
|
|
165
165
|
return False
|
|
166
166
|
|
|
167
167
|
def format_signature(self, sig, event):
|
|
168
|
-
if sig[3]
|
|
168
|
+
if sig[3] is True:
|
|
169
169
|
cleaned_path = event.parsed_url.path.strip("/")
|
|
170
170
|
else:
|
|
171
171
|
cleaned_path = event.parsed_url.path.lstrip("/")
|
|
172
172
|
kwargs = {"scheme": event.parsed_url.scheme, "netloc": event.parsed_url.netloc, "path": cleaned_path}
|
|
173
173
|
formatted_url = sig[1].format(**kwargs)
|
|
174
|
-
if sig[2]
|
|
174
|
+
if sig[2] is not None:
|
|
175
175
|
formatted_headers = {k: v.format(**kwargs) for k, v in sig[2].items()}
|
|
176
176
|
else:
|
|
177
177
|
formatted_headers = None
|
bbot/modules/c99.py
CHANGED
|
@@ -15,17 +15,19 @@ class c99(subdomain_enum_apikey):
|
|
|
15
15
|
options_desc = {"api_key": "c99.nl API key"}
|
|
16
16
|
|
|
17
17
|
base_url = "https://api.c99.nl"
|
|
18
|
+
ping_url = f"{base_url}/randomnumber?key={{api_key}}&between=1,100&json"
|
|
18
19
|
|
|
19
20
|
async def ping(self):
|
|
20
|
-
url = f"{self.base_url}/randomnumber?key={
|
|
21
|
-
response = await self.
|
|
22
|
-
assert response.json()["success"]
|
|
21
|
+
url = f"{self.base_url}/randomnumber?key={{api_key}}&between=1,100&json"
|
|
22
|
+
response = await self.api_request(url)
|
|
23
|
+
assert response.json()["success"] is True, getattr(response, "text", "no response from server")
|
|
23
24
|
|
|
24
25
|
async def request_url(self, query):
|
|
25
|
-
url = f"{self.base_url}/subdomainfinder?key={
|
|
26
|
-
return await self.
|
|
26
|
+
url = f"{self.base_url}/subdomainfinder?key={{api_key}}&domain={self.helpers.quote(query)}&json"
|
|
27
|
+
return await self.api_request(url)
|
|
27
28
|
|
|
28
|
-
def parse_results(self, r, query):
|
|
29
|
+
async def parse_results(self, r, query):
|
|
30
|
+
results = set()
|
|
29
31
|
j = r.json()
|
|
30
32
|
if isinstance(j, dict):
|
|
31
33
|
subdomains = j.get("subdomains", [])
|
|
@@ -33,4 +35,5 @@ class c99(subdomain_enum_apikey):
|
|
|
33
35
|
for s in subdomains:
|
|
34
36
|
subdomain = s.get("subdomain", "")
|
|
35
37
|
if subdomain:
|
|
36
|
-
|
|
38
|
+
results.add(subdomain)
|
|
39
|
+
return results
|