bbot 2.3.0.5370rc0__py3-none-any.whl → 2.3.0.5382rc0__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 +2 -6
- bbot/core/config/files.py +0 -1
- bbot/core/config/logger.py +1 -1
- bbot/core/core.py +1 -1
- bbot/core/event/base.py +13 -16
- bbot/core/helpers/command.py +4 -4
- bbot/core/helpers/depsinstaller/installer.py +5 -5
- bbot/core/helpers/diff.py +7 -7
- bbot/core/helpers/dns/brute.py +1 -1
- bbot/core/helpers/dns/dns.py +1 -2
- bbot/core/helpers/dns/engine.py +4 -6
- bbot/core/helpers/dns/mock.py +0 -1
- bbot/core/helpers/files.py +1 -1
- bbot/core/helpers/helper.py +3 -1
- bbot/core/helpers/interactsh.py +3 -3
- bbot/core/helpers/libmagic.py +0 -1
- bbot/core/helpers/misc.py +11 -11
- bbot/core/helpers/process.py +0 -2
- bbot/core/helpers/regex.py +1 -1
- bbot/core/helpers/regexes.py +3 -3
- 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 +2 -3
- bbot/core/helpers/wordcloud.py +5 -5
- bbot/core/modules.py +21 -22
- bbot/db/sql/models.py +0 -1
- bbot/modules/azure_tenant.py +2 -2
- bbot/modules/baddns.py +0 -2
- bbot/modules/baddns_direct.py +0 -1
- bbot/modules/base.py +16 -16
- bbot/modules/bypass403.py +5 -5
- bbot/modules/c99.py +1 -1
- bbot/modules/columbus.py +1 -1
- bbot/modules/deadly/ffuf.py +8 -8
- bbot/modules/deadly/nuclei.py +1 -1
- bbot/modules/deadly/vhost.py +3 -3
- bbot/modules/dnsbimi.py +1 -1
- bbot/modules/dnsdumpster.py +2 -2
- bbot/modules/dockerhub.py +1 -1
- bbot/modules/dotnetnuke.py +0 -2
- bbot/modules/extractous.py +1 -1
- bbot/modules/filedownload.py +1 -1
- bbot/modules/generic_ssrf.py +3 -3
- bbot/modules/github_workflows.py +1 -1
- bbot/modules/gowitness.py +7 -7
- bbot/modules/host_header.py +5 -5
- bbot/modules/httpx.py +1 -1
- bbot/modules/iis_shortnames.py +6 -6
- bbot/modules/internal/cloudcheck.py +5 -5
- bbot/modules/internal/dnsresolve.py +7 -7
- bbot/modules/internal/excavate.py +23 -26
- bbot/modules/internal/speculate.py +4 -4
- bbot/modules/ipneighbor.py +1 -1
- bbot/modules/jadx.py +1 -1
- bbot/modules/newsletters.py +2 -2
- bbot/modules/output/asset_inventory.py +6 -6
- bbot/modules/output/base.py +1 -1
- bbot/modules/output/csv.py +1 -1
- bbot/modules/output/stdout.py +2 -2
- bbot/modules/paramminer_headers.py +4 -7
- bbot/modules/portscan.py +3 -3
- bbot/modules/report/asn.py +11 -11
- bbot/modules/robots.py +3 -3
- bbot/modules/securitytxt.py +1 -1
- bbot/modules/sitedossier.py +1 -1
- bbot/modules/social.py +1 -1
- bbot/modules/subdomainradar.py +1 -1
- bbot/modules/telerik.py +7 -7
- bbot/modules/templates/bucket.py +1 -1
- bbot/modules/templates/github.py +1 -1
- bbot/modules/templates/shodan.py +1 -1
- bbot/modules/templates/subdomain_enum.py +1 -1
- bbot/modules/templates/webhook.py +1 -1
- bbot/modules/trufflehog.py +2 -2
- bbot/modules/url_manipulation.py +3 -3
- bbot/modules/urlscan.py +1 -1
- bbot/modules/viewdns.py +1 -1
- bbot/modules/wafw00f.py +1 -1
- bbot/scanner/preset/args.py +10 -11
- bbot/scanner/preset/environ.py +0 -1
- bbot/scanner/preset/preset.py +9 -9
- bbot/scanner/scanner.py +17 -17
- bbot/scanner/target.py +1 -1
- bbot/scripts/docs.py +1 -1
- bbot/test/bbot_fixtures.py +1 -1
- bbot/test/conftest.py +1 -1
- bbot/test/run_tests.sh +4 -4
- bbot/test/test_step_1/test_bbot_fastapi.py +2 -2
- bbot/test/test_step_1/test_cli.py +56 -56
- bbot/test/test_step_1/test_dns.py +15 -15
- bbot/test/test_step_1/test_engine.py +17 -17
- bbot/test/test_step_1/test_events.py +22 -22
- bbot/test/test_step_1/test_helpers.py +26 -26
- bbot/test/test_step_1/test_manager_scope_accuracy.py +306 -306
- bbot/test/test_step_1/test_modules_basic.py +52 -53
- bbot/test/test_step_1/test_presets.py +81 -81
- bbot/test/test_step_1/test_regexes.py +5 -5
- bbot/test/test_step_1/test_scan.py +4 -4
- bbot/test/test_step_1/test_target.py +25 -25
- bbot/test/test_step_1/test_web.py +5 -5
- bbot/test/test_step_2/module_tests/base.py +6 -6
- bbot/test/test_step_2/module_tests/test_module_anubisdb.py +1 -1
- 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 +2 -4
- bbot/test/test_step_2/module_tests/test_module_bevigil.py +4 -4
- bbot/test/test_step_2/module_tests/test_module_binaryedge.py +2 -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_builtwith.py +2 -2
- bbot/test/test_step_2/module_tests/test_module_c99.py +9 -9
- bbot/test/test_step_2/module_tests/test_module_columbus.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_credshed.py +2 -2
- bbot/test/test_step_2/module_tests/test_module_dehashed.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_digitorus.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_dnsbrute.py +8 -9
- bbot/test/test_step_2/module_tests/test_module_dnsbrute_mutations.py +0 -1
- bbot/test/test_step_2/module_tests/test_module_dnscommonsrv.py +0 -1
- bbot/test/test_step_2/module_tests/test_module_dnsdumpster.py +2 -2
- bbot/test/test_step_2/module_tests/test_module_dotnetnuke.py +0 -2
- bbot/test/test_step_2/module_tests/test_module_excavate.py +10 -30
- bbot/test/test_step_2/module_tests/test_module_extractous.py +9 -9
- 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_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 +7 -7
- bbot/test/test_step_2/module_tests/test_module_leakix.py +2 -2
- 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_portscan.py +3 -4
- bbot/test/test_step_2/module_tests/test_module_postgres.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_rapiddns.py +9 -9
- 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_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_subdomaincenter.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_subdomains.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_trufflehog.py +2 -2
- bbot/test/test_step_2/module_tests/test_module_wayback.py +1 -1
- {bbot-2.3.0.5370rc0.dist-info → bbot-2.3.0.5382rc0.dist-info}/METADATA +2 -2
- {bbot-2.3.0.5370rc0.dist-info → bbot-2.3.0.5382rc0.dist-info}/RECORD +157 -157
- {bbot-2.3.0.5370rc0.dist-info → bbot-2.3.0.5382rc0.dist-info}/LICENSE +0 -0
- {bbot-2.3.0.5370rc0.dist-info → bbot-2.3.0.5382rc0.dist-info}/WHEEL +0 -0
- {bbot-2.3.0.5370rc0.dist-info → bbot-2.3.0.5382rc0.dist-info}/entry_points.txt +0 -0
bbot/modules/gowitness.py
CHANGED
|
@@ -88,7 +88,7 @@ class gowitness(BaseModule):
|
|
|
88
88
|
self.screenshot_path = self.base_path / "screenshots"
|
|
89
89
|
self.command = self.construct_command()
|
|
90
90
|
self.prepped = False
|
|
91
|
-
self.screenshots_taken =
|
|
91
|
+
self.screenshots_taken = {}
|
|
92
92
|
self.connections_logged = set()
|
|
93
93
|
self.technologies_found = set()
|
|
94
94
|
return True
|
|
@@ -172,7 +172,7 @@ class gowitness(BaseModule):
|
|
|
172
172
|
|
|
173
173
|
# emit technologies
|
|
174
174
|
new_technologies = await self.get_new_technologies()
|
|
175
|
-
for
|
|
175
|
+
for row in new_technologies.values():
|
|
176
176
|
parent_id = row["url_id"]
|
|
177
177
|
parent_url = self.screenshots_taken[parent_id]
|
|
178
178
|
parent_event = event_dict[parent_url]
|
|
@@ -227,7 +227,7 @@ class gowitness(BaseModule):
|
|
|
227
227
|
return screenshots
|
|
228
228
|
|
|
229
229
|
async def get_new_network_logs(self):
|
|
230
|
-
network_logs =
|
|
230
|
+
network_logs = {}
|
|
231
231
|
if self.db_path.is_file():
|
|
232
232
|
async with aiosqlite.connect(str(self.db_path)) as con:
|
|
233
233
|
con.row_factory = aiosqlite.Row
|
|
@@ -241,7 +241,7 @@ class gowitness(BaseModule):
|
|
|
241
241
|
return network_logs
|
|
242
242
|
|
|
243
243
|
async def get_new_technologies(self):
|
|
244
|
-
technologies =
|
|
244
|
+
technologies = {}
|
|
245
245
|
if self.db_path.is_file():
|
|
246
246
|
async with aiosqlite.connect(str(self.db_path)) as con:
|
|
247
247
|
con.row_factory = aiosqlite.Row
|
|
@@ -264,8 +264,8 @@ class gowitness(BaseModule):
|
|
|
264
264
|
async def report(self):
|
|
265
265
|
if self.screenshots_taken:
|
|
266
266
|
self.success(f"{len(self.screenshots_taken):,} web screenshots captured. To view:")
|
|
267
|
-
self.success(
|
|
267
|
+
self.success(" - Start gowitness")
|
|
268
268
|
self.success(f" - cd {self.base_path} && ./gowitness server")
|
|
269
|
-
self.success(
|
|
269
|
+
self.success(" - Browse to http://localhost:7171")
|
|
270
270
|
else:
|
|
271
|
-
self.info(
|
|
271
|
+
self.info("No web screenshots captured")
|
bbot/modules/host_header.py
CHANGED
|
@@ -19,7 +19,7 @@ class host_header(BaseModule):
|
|
|
19
19
|
|
|
20
20
|
async def setup(self):
|
|
21
21
|
self.subdomain_tags = {}
|
|
22
|
-
if self.scan.config.get("interactsh_disable", False)
|
|
22
|
+
if self.scan.config.get("interactsh_disable", False) is False:
|
|
23
23
|
try:
|
|
24
24
|
self.interactsh_instance = self.helpers.interactsh()
|
|
25
25
|
self.domain = await self.interactsh_instance.register(callback=self.interactsh_callback)
|
|
@@ -60,7 +60,7 @@ class host_header(BaseModule):
|
|
|
60
60
|
self.debug("skipping results because subdomain tag was missing")
|
|
61
61
|
|
|
62
62
|
async def finish(self):
|
|
63
|
-
if self.scan.config.get("interactsh_disable", False)
|
|
63
|
+
if self.scan.config.get("interactsh_disable", False) is False:
|
|
64
64
|
await self.helpers.sleep(5)
|
|
65
65
|
try:
|
|
66
66
|
for r in await self.interactsh_instance.poll():
|
|
@@ -69,7 +69,7 @@ class host_header(BaseModule):
|
|
|
69
69
|
self.debug(f"Error in interact.sh: {e}")
|
|
70
70
|
|
|
71
71
|
async def cleanup(self):
|
|
72
|
-
if self.scan.config.get("interactsh_disable", False)
|
|
72
|
+
if self.scan.config.get("interactsh_disable", False) is False:
|
|
73
73
|
try:
|
|
74
74
|
await self.interactsh_instance.deregister()
|
|
75
75
|
self.debug(
|
|
@@ -84,7 +84,7 @@ class host_header(BaseModule):
|
|
|
84
84
|
|
|
85
85
|
added_cookies = {}
|
|
86
86
|
|
|
87
|
-
for
|
|
87
|
+
for header_values in event.data["header-dict"].values():
|
|
88
88
|
for header_value in header_values:
|
|
89
89
|
if header_value.lower() == "set-cookie":
|
|
90
90
|
header_split = header_value.split("=")
|
|
@@ -136,7 +136,7 @@ class host_header(BaseModule):
|
|
|
136
136
|
|
|
137
137
|
split_output = output.split("\n")
|
|
138
138
|
if " 4" in split_output:
|
|
139
|
-
description =
|
|
139
|
+
description = "Duplicate Host Header Tolerated"
|
|
140
140
|
await self.emit_event(
|
|
141
141
|
{
|
|
142
142
|
"host": str(event.host),
|
bbot/modules/httpx.py
CHANGED
bbot/modules/iis_shortnames.py
CHANGED
|
@@ -39,7 +39,7 @@ class iis_shortnames(BaseModule):
|
|
|
39
39
|
test_url = f"{target}*~1*/a.aspx"
|
|
40
40
|
|
|
41
41
|
for method in ["GET", "POST", "OPTIONS", "DEBUG", "HEAD", "TRACE"]:
|
|
42
|
-
kwargs =
|
|
42
|
+
kwargs = {"method": method, "allow_redirects": False, "timeout": 10}
|
|
43
43
|
confirmations = 0
|
|
44
44
|
iterations = 5 # one failed detection is tolerated, as long as its not the first run
|
|
45
45
|
while iterations > 0:
|
|
@@ -128,7 +128,7 @@ class iis_shortnames(BaseModule):
|
|
|
128
128
|
suffix = "/a.aspx"
|
|
129
129
|
|
|
130
130
|
urls_and_kwargs = []
|
|
131
|
-
kwargs =
|
|
131
|
+
kwargs = {"method": method, "allow_redirects": False, "retries": 2, "timeout": 10}
|
|
132
132
|
for c in valid_chars:
|
|
133
133
|
for file_part in ("stem", "ext"):
|
|
134
134
|
payload = encode_all(f"*{c}*~1*")
|
|
@@ -160,7 +160,7 @@ class iis_shortnames(BaseModule):
|
|
|
160
160
|
url_hint_list = []
|
|
161
161
|
found_results = False
|
|
162
162
|
|
|
163
|
-
cl = ext_char_list if extension_mode
|
|
163
|
+
cl = ext_char_list if extension_mode is True else char_list
|
|
164
164
|
|
|
165
165
|
urls_and_kwargs = []
|
|
166
166
|
|
|
@@ -169,7 +169,7 @@ class iis_shortnames(BaseModule):
|
|
|
169
169
|
wildcard = "*" if extension_mode else "*~1*"
|
|
170
170
|
payload = encode_all(f"{prefix}{c}{wildcard}")
|
|
171
171
|
url = f"{target}{payload}{suffix}"
|
|
172
|
-
kwargs =
|
|
172
|
+
kwargs = {"method": method}
|
|
173
173
|
urls_and_kwargs.append((url, kwargs, c))
|
|
174
174
|
|
|
175
175
|
async for url, kwargs, c, response in self.helpers.request_custom_batch(urls_and_kwargs):
|
|
@@ -209,7 +209,7 @@ class iis_shortnames(BaseModule):
|
|
|
209
209
|
extension_mode,
|
|
210
210
|
node_count=node_count,
|
|
211
211
|
)
|
|
212
|
-
if len(prefix) > 0 and found_results
|
|
212
|
+
if len(prefix) > 0 and found_results is False:
|
|
213
213
|
url_hint_list.append(f"{prefix}")
|
|
214
214
|
self.verbose(f"Found new (possibly partial) URL_HINT: {prefix} from node {target}")
|
|
215
215
|
return url_hint_list
|
|
@@ -234,7 +234,7 @@ class iis_shortnames(BaseModule):
|
|
|
234
234
|
{"severity": "LOW", "host": str(event.host), "url": normalized_url, "description": description},
|
|
235
235
|
"VULNERABILITY",
|
|
236
236
|
event,
|
|
237
|
-
context=
|
|
237
|
+
context="{module} detected low {event.type}: IIS shortname enumeration",
|
|
238
238
|
)
|
|
239
239
|
if not self.config.get("detect_only"):
|
|
240
240
|
for detection in detections:
|
|
@@ -15,7 +15,7 @@ class CloudCheck(BaseInterceptModule):
|
|
|
15
15
|
|
|
16
16
|
def make_dummy_modules(self):
|
|
17
17
|
self.dummy_modules = {}
|
|
18
|
-
for provider_name
|
|
18
|
+
for provider_name in self.helpers.cloud.providers.keys():
|
|
19
19
|
module = self.scan._make_dummy_module(f"cloud_{provider_name}", _type="scan")
|
|
20
20
|
module.default_discovery_context = "{module} derived {event.type}: {event.host}"
|
|
21
21
|
self.dummy_modules[provider_name] = module
|
|
@@ -56,9 +56,9 @@ class CloudCheck(BaseInterceptModule):
|
|
|
56
56
|
# loop through each provider
|
|
57
57
|
for provider in self.helpers.cloud.providers.values():
|
|
58
58
|
provider_name = provider.name.lower()
|
|
59
|
-
base_kwargs =
|
|
60
|
-
parent
|
|
61
|
-
|
|
59
|
+
base_kwargs = {
|
|
60
|
+
"parent": event, "tags": [f"{provider.provider_type}-{provider_name}"], "_provider": provider_name
|
|
61
|
+
}
|
|
62
62
|
# loop through the provider's regex signatures, if any
|
|
63
63
|
for event_type, sigs in provider.signatures.items():
|
|
64
64
|
if event_type != "STORAGE_BUCKET":
|
|
@@ -74,7 +74,7 @@ class CloudCheck(BaseInterceptModule):
|
|
|
74
74
|
if match:
|
|
75
75
|
matches.append(match.groups())
|
|
76
76
|
for match in matches:
|
|
77
|
-
if not
|
|
77
|
+
if match not in found:
|
|
78
78
|
found.add(match)
|
|
79
79
|
|
|
80
80
|
_kwargs = dict(base_kwargs)
|
|
@@ -131,9 +131,9 @@ class DNSResolve(BaseInterceptModule):
|
|
|
131
131
|
event.host, rdtypes=rdtypes, raw_dns_records=event.raw_dns_records
|
|
132
132
|
)
|
|
133
133
|
for rdtype, (is_wildcard, wildcard_host) in wildcard_rdtypes.items():
|
|
134
|
-
if is_wildcard
|
|
134
|
+
if is_wildcard is False:
|
|
135
135
|
continue
|
|
136
|
-
elif is_wildcard
|
|
136
|
+
elif is_wildcard is True:
|
|
137
137
|
event.add_tag("wildcard")
|
|
138
138
|
wildcard_tag = "wildcard"
|
|
139
139
|
else:
|
|
@@ -142,16 +142,16 @@ class DNSResolve(BaseInterceptModule):
|
|
|
142
142
|
event.add_tag(f"{rdtype}-{wildcard_tag}")
|
|
143
143
|
|
|
144
144
|
# wildcard event modification (www.evilcorp.com --> _wildcard.evilcorp.com)
|
|
145
|
-
if wildcard_rdtypes and
|
|
145
|
+
if wildcard_rdtypes and "target" not in event.tags:
|
|
146
146
|
# these are the rdtypes that have wildcards
|
|
147
147
|
wildcard_rdtypes_set = set(wildcard_rdtypes)
|
|
148
148
|
# consider the event a full wildcard if all its records are wildcards
|
|
149
149
|
event_is_wildcard = False
|
|
150
150
|
if wildcard_rdtypes_set:
|
|
151
|
-
event_is_wildcard = all(r[0]
|
|
151
|
+
event_is_wildcard = all(r[0] is True for r in wildcard_rdtypes.values())
|
|
152
152
|
|
|
153
153
|
if event_is_wildcard:
|
|
154
|
-
if event.type in ("DNS_NAME",) and
|
|
154
|
+
if event.type in ("DNS_NAME",) and "_wildcard" not in event.data.split("."):
|
|
155
155
|
wildcard_parent = self.helpers.parent_domain(event.host)
|
|
156
156
|
for rdtype, (_is_wildcard, _parent_domain) in wildcard_rdtypes.items():
|
|
157
157
|
if _is_wildcard:
|
|
@@ -273,7 +273,7 @@ class DNSResolve(BaseInterceptModule):
|
|
|
273
273
|
# tag event with errors
|
|
274
274
|
for rdtype, errors in dns_errors.items():
|
|
275
275
|
# only consider it an error if there weren't any results for that rdtype
|
|
276
|
-
if errors and not
|
|
276
|
+
if errors and rdtype not in event.dns_children:
|
|
277
277
|
event.add_tag(f"{rdtype}-error")
|
|
278
278
|
|
|
279
279
|
def get_dns_parent(self, event):
|
|
@@ -307,7 +307,7 @@ class DNSResolve(BaseInterceptModule):
|
|
|
307
307
|
def emit_raw_records(self):
|
|
308
308
|
if self._emit_raw_records is None:
|
|
309
309
|
watching_raw_records = any(
|
|
310
|
-
|
|
310
|
+
"RAW_DNS_RECORD" in m.get_watched_events() for m in self.scan.modules.values()
|
|
311
311
|
)
|
|
312
312
|
omitted_event_types = self.scan.config.get("omit_event_types", [])
|
|
313
313
|
omit_raw_records = "RAW_DNS_RECORD" in omitted_event_types
|
|
@@ -62,7 +62,6 @@ def _exclude_key(original_dict, key_to_exclude):
|
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
def extract_params_url(parsed_url):
|
|
65
|
-
|
|
66
65
|
params = parse_qs(parsed_url.query)
|
|
67
66
|
flat_params = {k: v[0] for k, v in params.items()}
|
|
68
67
|
|
|
@@ -94,7 +93,6 @@ def extract_params_location(location_header_value, original_parsed_url):
|
|
|
94
93
|
|
|
95
94
|
|
|
96
95
|
class YaraRuleSettings:
|
|
97
|
-
|
|
98
96
|
def __init__(self, description, tags, emit_match):
|
|
99
97
|
self.description = description
|
|
100
98
|
self.tags = tags
|
|
@@ -155,7 +153,7 @@ class ExcavateRule:
|
|
|
155
153
|
yara_results = {}
|
|
156
154
|
for h in r.strings:
|
|
157
155
|
yara_results[h.identifier.lstrip("$")] = sorted(
|
|
158
|
-
|
|
156
|
+
{i.matched_data.decode("utf-8", errors="ignore") for i in h.instances}
|
|
159
157
|
)
|
|
160
158
|
await self.process(yara_results, event, yara_rule_settings, discovery_context)
|
|
161
159
|
|
|
@@ -182,7 +180,7 @@ class ExcavateRule:
|
|
|
182
180
|
Returns:
|
|
183
181
|
None
|
|
184
182
|
"""
|
|
185
|
-
for
|
|
183
|
+
for results in yara_results.values():
|
|
186
184
|
for result in results:
|
|
187
185
|
event_data = {"description": f"{discovery_context} {yara_rule_settings.description}"}
|
|
188
186
|
if yara_rule_settings.emit_match:
|
|
@@ -263,7 +261,6 @@ class ExcavateRule:
|
|
|
263
261
|
|
|
264
262
|
|
|
265
263
|
class CustomExtractor(ExcavateRule):
|
|
266
|
-
|
|
267
264
|
def __init__(self, excavate):
|
|
268
265
|
super().__init__(excavate)
|
|
269
266
|
|
|
@@ -317,7 +314,7 @@ class excavate(BaseInternalModule, BaseInterceptModule):
|
|
|
317
314
|
|
|
318
315
|
_module_threads = 8
|
|
319
316
|
|
|
320
|
-
parameter_blacklist =
|
|
317
|
+
parameter_blacklist = {
|
|
321
318
|
p.lower()
|
|
322
319
|
for p in [
|
|
323
320
|
"__VIEWSTATE",
|
|
@@ -332,7 +329,7 @@ class excavate(BaseInternalModule, BaseInterceptModule):
|
|
|
332
329
|
"JSESSIONID",
|
|
333
330
|
"PHPSESSID",
|
|
334
331
|
]
|
|
335
|
-
|
|
332
|
+
}
|
|
336
333
|
|
|
337
334
|
yara_rule_name_regex = re.compile(r"rule\s(\w+)\s{")
|
|
338
335
|
yara_rule_regex = re.compile(r"(?s)((?:rule\s+\w+\s*{[^{}]*(?:{[^{}]*}[^{}]*)*[^{}]*(?:/\S*?}[^/]*?/)*)*})")
|
|
@@ -358,7 +355,6 @@ class excavate(BaseInternalModule, BaseInterceptModule):
|
|
|
358
355
|
)
|
|
359
356
|
|
|
360
357
|
class ParameterExtractor(ExcavateRule):
|
|
361
|
-
|
|
362
358
|
yara_rules = {}
|
|
363
359
|
|
|
364
360
|
class ParameterExtractorRule:
|
|
@@ -372,7 +368,6 @@ class excavate(BaseInternalModule, BaseInterceptModule):
|
|
|
372
368
|
self.result = result
|
|
373
369
|
|
|
374
370
|
class GetJquery(ParameterExtractorRule):
|
|
375
|
-
|
|
376
371
|
name = "GET jquery"
|
|
377
372
|
discovery_regex = r"/\$.get\([^\)].+\)/ nocase"
|
|
378
373
|
extraction_regex = re.compile(r"\$.get\([\'\"](.+)[\'\"].+(\{.+\})\)")
|
|
@@ -393,8 +388,12 @@ class excavate(BaseInternalModule, BaseInterceptModule):
|
|
|
393
388
|
for action, extracted_parameters in extracted_results:
|
|
394
389
|
extracted_parameters_dict = self.convert_to_dict(extracted_parameters)
|
|
395
390
|
for parameter_name, original_value in extracted_parameters_dict.items():
|
|
396
|
-
yield
|
|
397
|
-
|
|
391
|
+
yield (
|
|
392
|
+
self.output_type,
|
|
393
|
+
parameter_name,
|
|
394
|
+
original_value,
|
|
395
|
+
action,
|
|
396
|
+
_exclude_key(extracted_parameters_dict, parameter_name),
|
|
398
397
|
)
|
|
399
398
|
|
|
400
399
|
class PostJquery(GetJquery):
|
|
@@ -418,8 +417,12 @@ class excavate(BaseInternalModule, BaseInterceptModule):
|
|
|
418
417
|
k: v[0] if isinstance(v, list) and len(v) == 1 else v for k, v in query_strings.items()
|
|
419
418
|
}
|
|
420
419
|
for parameter_name, original_value in query_strings_dict.items():
|
|
421
|
-
yield
|
|
422
|
-
|
|
420
|
+
yield (
|
|
421
|
+
self.output_type,
|
|
422
|
+
parameter_name,
|
|
423
|
+
original_value,
|
|
424
|
+
url,
|
|
425
|
+
_exclude_key(query_strings_dict, parameter_name),
|
|
423
426
|
)
|
|
424
427
|
|
|
425
428
|
class GetForm(ParameterExtractorRule):
|
|
@@ -444,8 +447,12 @@ class excavate(BaseInternalModule, BaseInterceptModule):
|
|
|
444
447
|
form_parameters[parameter_name] = original_value
|
|
445
448
|
|
|
446
449
|
for parameter_name, original_value in form_parameters.items():
|
|
447
|
-
yield
|
|
448
|
-
|
|
450
|
+
yield (
|
|
451
|
+
self.output_type,
|
|
452
|
+
parameter_name,
|
|
453
|
+
original_value,
|
|
454
|
+
form_action,
|
|
455
|
+
_exclude_key(form_parameters, parameter_name),
|
|
449
456
|
)
|
|
450
457
|
|
|
451
458
|
class PostForm(GetForm):
|
|
@@ -485,7 +492,6 @@ class excavate(BaseInternalModule, BaseInterceptModule):
|
|
|
485
492
|
endpoint,
|
|
486
493
|
additional_params,
|
|
487
494
|
) in extracted_params:
|
|
488
|
-
|
|
489
495
|
self.excavate.debug(
|
|
490
496
|
f"Found Parameter [{parameter_name}] in [{parameterExtractorSubModule.name}] ParameterExtractor Submodule"
|
|
491
497
|
)
|
|
@@ -497,7 +503,6 @@ class excavate(BaseInternalModule, BaseInterceptModule):
|
|
|
497
503
|
)
|
|
498
504
|
|
|
499
505
|
if self.excavate.helpers.validate_parameter(parameter_name, parameter_type):
|
|
500
|
-
|
|
501
506
|
if self.excavate.in_bl(parameter_name) == False:
|
|
502
507
|
parsed_url = urlparse(url)
|
|
503
508
|
description = f"HTTP Extracted Parameter [{parameter_name}] ({parameterExtractorSubModule.name} Submodule)"
|
|
@@ -532,7 +537,6 @@ class excavate(BaseInternalModule, BaseInterceptModule):
|
|
|
532
537
|
await self.report(domain, event, yara_rule_settings, discovery_context, event_type="DNS_NAME")
|
|
533
538
|
|
|
534
539
|
class EmailExtractor(ExcavateRule):
|
|
535
|
-
|
|
536
540
|
yara_rules = {
|
|
537
541
|
"email": 'rule email { meta: description = "contains email address" strings: $email = /[^\\W_][\\w\\-\\.\\+\']{0,100}@[a-zA-Z0-9\\-]{1,100}(\\.[a-zA-Z0-9\\-]{1,100})*\\.[a-zA-Z]{2,63}/ nocase fullword condition: $email }',
|
|
538
542
|
}
|
|
@@ -551,7 +555,6 @@ class excavate(BaseInternalModule, BaseInterceptModule):
|
|
|
551
555
|
}
|
|
552
556
|
|
|
553
557
|
class ErrorExtractor(ExcavateRule):
|
|
554
|
-
|
|
555
558
|
signatures = {
|
|
556
559
|
"PHP_1": r"/\.php on line [0-9]+/",
|
|
557
560
|
"PHP_2": r"/\.php<\/b> on line <b>[0-9]+/",
|
|
@@ -589,7 +592,6 @@ class excavate(BaseInternalModule, BaseInterceptModule):
|
|
|
589
592
|
await self.report(event_data, event, yara_rule_settings, discovery_context, event_type="FINDING")
|
|
590
593
|
|
|
591
594
|
class SerializationExtractor(ExcavateRule):
|
|
592
|
-
|
|
593
595
|
regexes = {
|
|
594
596
|
"Java": re.compile(r"[^a-zA-Z0-9\/+]rO0[a-zA-Z0-9+\/]+={0,2}"),
|
|
595
597
|
"DOTNET": re.compile(r"[^a-zA-Z0-9\/+]AAEAAAD\/\/[a-zA-Z0-9\/+]+={0,2}"),
|
|
@@ -619,7 +621,6 @@ class excavate(BaseInternalModule, BaseInterceptModule):
|
|
|
619
621
|
await self.report(event_data, event, yara_rule_settings, discovery_context, event_type="FINDING")
|
|
620
622
|
|
|
621
623
|
class FunctionalityExtractor(ExcavateRule):
|
|
622
|
-
|
|
623
624
|
yara_rules = {
|
|
624
625
|
"File_Upload_Functionality": r'rule File_Upload_Functionality { meta: description = "contains file upload functionality" strings: $fileuploadfunc = /<input[^>]+type=["\']?file["\']?[^>]+>/ nocase condition: $fileuploadfunc }',
|
|
625
626
|
"Web_Service_WSDL": r'rule Web_Service_WSDL { meta: emit_match = "True" description = "contains a web service WSDL URL" strings: $wsdl = /https?:\/\/[^\s]*\.(wsdl)/ nocase condition: $wsdl }',
|
|
@@ -633,7 +634,7 @@ class excavate(BaseInternalModule, BaseInterceptModule):
|
|
|
633
634
|
scheme_blacklist = ["javascript", "mailto", "tel", "data", "vbscript", "about", "file"]
|
|
634
635
|
|
|
635
636
|
async def process(self, yara_results, event, yara_rule_settings, discovery_context):
|
|
636
|
-
for
|
|
637
|
+
for results in yara_results.values():
|
|
637
638
|
for url_str in results:
|
|
638
639
|
scheme = url_str.split("://")[0]
|
|
639
640
|
if scheme in self.scheme_blacklist:
|
|
@@ -704,7 +705,6 @@ class excavate(BaseInternalModule, BaseInterceptModule):
|
|
|
704
705
|
tag_attribute_regex = bbot_regexes.tag_attribute_regex
|
|
705
706
|
|
|
706
707
|
async def process(self, yara_results, event, yara_rule_settings, discovery_context):
|
|
707
|
-
|
|
708
708
|
for identifier, results in yara_results.items():
|
|
709
709
|
urls_found = 0
|
|
710
710
|
final_url = ""
|
|
@@ -897,7 +897,6 @@ class excavate(BaseInternalModule, BaseInterceptModule):
|
|
|
897
897
|
decoded_data = await self.helpers.re.recursive_decode(data)
|
|
898
898
|
|
|
899
899
|
if self.parameter_extraction:
|
|
900
|
-
|
|
901
900
|
content_type_lower = content_type.lower() if content_type else ""
|
|
902
901
|
extraction_map = {
|
|
903
902
|
"json": self.helpers.extract_params_json,
|
|
@@ -934,7 +933,6 @@ class excavate(BaseInternalModule, BaseInterceptModule):
|
|
|
934
933
|
self.hugewarning(f"YARA Rule {rule_name} not found in pre-compiled rules")
|
|
935
934
|
|
|
936
935
|
async def handle_event(self, event):
|
|
937
|
-
|
|
938
936
|
if event.type == "HTTP_RESPONSE":
|
|
939
937
|
# Harvest GET parameters from URL, if it came directly from the target, and parameter extraction is enabled
|
|
940
938
|
if (
|
|
@@ -1023,7 +1021,6 @@ class excavate(BaseInternalModule, BaseInterceptModule):
|
|
|
1023
1021
|
|
|
1024
1022
|
# Try to extract parameters from the redirect URL
|
|
1025
1023
|
if self.parameter_extraction:
|
|
1026
|
-
|
|
1027
1024
|
for (
|
|
1028
1025
|
method,
|
|
1029
1026
|
parsed_url,
|
|
@@ -45,10 +45,10 @@ class speculate(BaseInternalModule):
|
|
|
45
45
|
|
|
46
46
|
async def setup(self):
|
|
47
47
|
scan_modules = [m for m in self.scan.modules.values() if m._type == "scan"]
|
|
48
|
-
self.open_port_consumers = any(
|
|
48
|
+
self.open_port_consumers = any("OPEN_TCP_PORT" in m.watched_events for m in scan_modules)
|
|
49
49
|
# only consider active portscanners (still speculate if only passive ones are enabled)
|
|
50
50
|
self.portscanner_enabled = any(
|
|
51
|
-
|
|
51
|
+
"portscan" in m.flags and "active" in m.flags for m in self.scan.modules.values()
|
|
52
52
|
)
|
|
53
53
|
self.emit_open_ports = self.open_port_consumers and not self.portscanner_enabled
|
|
54
54
|
self.range_to_ip = True
|
|
@@ -71,7 +71,7 @@ class speculate(BaseInternalModule):
|
|
|
71
71
|
self.hugewarning(
|
|
72
72
|
f"Selected target ({target_len:,} hosts) is too large, skipping IP_RANGE --> IP_ADDRESS speculation"
|
|
73
73
|
)
|
|
74
|
-
self.hugewarning(
|
|
74
|
+
self.hugewarning('Enabling the "portscan" module is highly recommended')
|
|
75
75
|
self.range_to_ip = False
|
|
76
76
|
|
|
77
77
|
return True
|
|
@@ -126,7 +126,7 @@ class speculate(BaseInternalModule):
|
|
|
126
126
|
parent = self.helpers.parent_domain(event.host_original)
|
|
127
127
|
if parent != event.data:
|
|
128
128
|
await self.emit_event(
|
|
129
|
-
parent, "DNS_NAME", parent=event, context=
|
|
129
|
+
parent, "DNS_NAME", parent=event, context="speculated parent {event.type}: {event.data}"
|
|
130
130
|
)
|
|
131
131
|
|
|
132
132
|
# URL --> OPEN_TCP_PORT
|
bbot/modules/ipneighbor.py
CHANGED
|
@@ -31,7 +31,7 @@ class ipneighbor(BaseModule):
|
|
|
31
31
|
netmask = main_ip.max_prefixlen - min(main_ip.max_prefixlen, self.num_bits)
|
|
32
32
|
network = ipaddress.ip_network(f"{main_ip}/{netmask}", strict=False)
|
|
33
33
|
subnet_hash = hash(network)
|
|
34
|
-
if not
|
|
34
|
+
if subnet_hash not in self.processed:
|
|
35
35
|
self.processed.add(subnet_hash)
|
|
36
36
|
for ip in network:
|
|
37
37
|
if ip != main_ip:
|
bbot/modules/jadx.py
CHANGED
|
@@ -43,7 +43,7 @@ class jadx(BaseModule):
|
|
|
43
43
|
|
|
44
44
|
async def filter_event(self, event):
|
|
45
45
|
if "file" in event.tags:
|
|
46
|
-
if
|
|
46
|
+
if event.data["magic_description"].lower() not in self.allowed_file_types:
|
|
47
47
|
return False, f"Jadx is not able to decompile this file type: {event.data['magic_description']}"
|
|
48
48
|
else:
|
|
49
49
|
return False, "Event is not a file"
|
bbot/modules/newsletters.py
CHANGED
|
@@ -46,11 +46,11 @@ class newsletters(BaseModule):
|
|
|
46
46
|
body = _event.data["body"]
|
|
47
47
|
soup = self.helpers.beautifulsoup(body, "html.parser")
|
|
48
48
|
if soup is False:
|
|
49
|
-
self.debug(
|
|
49
|
+
self.debug("BeautifulSoup returned False")
|
|
50
50
|
return
|
|
51
51
|
result = self.find_type(soup)
|
|
52
52
|
if result:
|
|
53
|
-
description =
|
|
53
|
+
description = "Found a Newsletter Submission Form that could be used for email bombing attacks"
|
|
54
54
|
data = {"host": str(_event.host), "description": description, "url": _event.data["url"]}
|
|
55
55
|
await self.emit_event(
|
|
56
56
|
data,
|
|
@@ -91,15 +91,15 @@ class asset_inventory(CSV):
|
|
|
91
91
|
self.assets[hostkey].absorb_event(event)
|
|
92
92
|
|
|
93
93
|
async def report(self):
|
|
94
|
-
stats =
|
|
95
|
-
totals =
|
|
94
|
+
stats = {}
|
|
95
|
+
totals = {}
|
|
96
96
|
|
|
97
97
|
def increment_stat(stat, value):
|
|
98
98
|
try:
|
|
99
99
|
totals[stat] += 1
|
|
100
100
|
except KeyError:
|
|
101
101
|
totals[stat] = 1
|
|
102
|
-
if not
|
|
102
|
+
if stat not in stats:
|
|
103
103
|
stats[stat] = {}
|
|
104
104
|
try:
|
|
105
105
|
stats[stat][value] += 1
|
|
@@ -263,13 +263,13 @@ class Asset:
|
|
|
263
263
|
if not self.recheck:
|
|
264
264
|
# ports
|
|
265
265
|
ports = [i.strip() for i in row.get("Open Ports", "").split(",")]
|
|
266
|
-
self.ports.update(
|
|
266
|
+
self.ports.update({i for i in ports if i and is_port(i)})
|
|
267
267
|
# findings
|
|
268
268
|
findings = [i.strip() for i in row.get("Findings", "").splitlines()]
|
|
269
|
-
self.findings.update(
|
|
269
|
+
self.findings.update({i for i in findings if i})
|
|
270
270
|
# technologies
|
|
271
271
|
technologies = [i.strip() for i in row.get("Technologies", "").splitlines()]
|
|
272
|
-
self.technologies.update(
|
|
272
|
+
self.technologies.update({i for i in technologies if i})
|
|
273
273
|
# risk rating
|
|
274
274
|
risk_rating = row.get("Risk Rating", "").strip()
|
|
275
275
|
if risk_rating and risk_rating.isdigit() and int(risk_rating) > self.risk_rating:
|
bbot/modules/output/base.py
CHANGED
|
@@ -24,7 +24,7 @@ class BaseOutputModule(BaseModule):
|
|
|
24
24
|
if event.type in ("FINISHED",):
|
|
25
25
|
return True, "its type is FINISHED"
|
|
26
26
|
if self.errored:
|
|
27
|
-
return False,
|
|
27
|
+
return False, "module is in error state"
|
|
28
28
|
# exclude non-watched types
|
|
29
29
|
if not any(t in self.get_watched_events() for t in ("*", event.type)):
|
|
30
30
|
return False, "its type is not in watched_events"
|
bbot/modules/output/csv.py
CHANGED
|
@@ -64,7 +64,7 @@ class CSV(BaseOutputModule):
|
|
|
64
64
|
),
|
|
65
65
|
"Source Module": str(getattr(event, "module_sequence", "")),
|
|
66
66
|
"Scope Distance": str(getattr(event, "scope_distance", "")),
|
|
67
|
-
"Event Tags": ",".join(sorted(
|
|
67
|
+
"Event Tags": ",".join(sorted(getattr(event, "tags", []))),
|
|
68
68
|
"Discovery Path": " --> ".join(discovery_path),
|
|
69
69
|
}
|
|
70
70
|
)
|
bbot/modules/output/stdout.py
CHANGED
|
@@ -20,7 +20,7 @@ class Stdout(BaseOutputModule):
|
|
|
20
20
|
|
|
21
21
|
async def setup(self):
|
|
22
22
|
self.text_format = self.config.get("format", "text").strip().lower()
|
|
23
|
-
if
|
|
23
|
+
if self.text_format not in self.format_choices:
|
|
24
24
|
return (
|
|
25
25
|
False,
|
|
26
26
|
f'Invalid text format choice, "{self.text_format}" (choices: {",".join(self.format_choices)})',
|
|
@@ -33,7 +33,7 @@ class Stdout(BaseOutputModule):
|
|
|
33
33
|
|
|
34
34
|
async def filter_event(self, event):
|
|
35
35
|
if self.accept_event_types:
|
|
36
|
-
if
|
|
36
|
+
if event.type not in self.accept_event_types:
|
|
37
37
|
return False, f'Event type "{event.type}" is not in the allowed event_types'
|
|
38
38
|
return True
|
|
39
39
|
|
|
@@ -82,7 +82,6 @@ class paramminer_headers(BaseModule):
|
|
|
82
82
|
header_regex = re.compile(r"^[!#$%&\'*+\-.^_`|~0-9a-zA-Z]+: [^\r\n]+$")
|
|
83
83
|
|
|
84
84
|
async def setup(self):
|
|
85
|
-
|
|
86
85
|
self.recycle_words = self.config.get("recycle_words", True)
|
|
87
86
|
self.event_dict = {}
|
|
88
87
|
self.already_checked = set()
|
|
@@ -90,11 +89,11 @@ class paramminer_headers(BaseModule):
|
|
|
90
89
|
if not wordlist:
|
|
91
90
|
wordlist = f"{self.helpers.wordlist_dir}/{self.default_wordlist}"
|
|
92
91
|
self.debug(f"Using wordlist: [{wordlist}]")
|
|
93
|
-
self.wl =
|
|
92
|
+
self.wl = {
|
|
94
93
|
h.strip().lower()
|
|
95
94
|
for h in self.helpers.read_file(await self.helpers.wordlist(wordlist))
|
|
96
95
|
if len(h) > 0 and "%" not in h
|
|
97
|
-
|
|
96
|
+
}
|
|
98
97
|
|
|
99
98
|
# check against the boring list (if the option is set)
|
|
100
99
|
if self.config.get("skip_boring_words", True):
|
|
@@ -157,7 +156,6 @@ class paramminer_headers(BaseModule):
|
|
|
157
156
|
)
|
|
158
157
|
|
|
159
158
|
async def handle_event(self, event):
|
|
160
|
-
|
|
161
159
|
# If recycle words is enabled, we will collect WEB_PARAMETERS we find to build our list in finish()
|
|
162
160
|
# We also collect any parameters of type "SPECULATIVE"
|
|
163
161
|
if event.type == "WEB_PARAMETER":
|
|
@@ -201,7 +199,7 @@ class paramminer_headers(BaseModule):
|
|
|
201
199
|
return
|
|
202
200
|
for count, args, kwargs in self.gen_count_args(url):
|
|
203
201
|
r = await self.helpers.request(*args, **kwargs)
|
|
204
|
-
if r is not None and not (
|
|
202
|
+
if r is not None and not (str(r.status_code)[0] in ("4", "5")):
|
|
205
203
|
return count
|
|
206
204
|
|
|
207
205
|
def gen_count_args(self, url):
|
|
@@ -240,8 +238,7 @@ class paramminer_headers(BaseModule):
|
|
|
240
238
|
return await compare_helper.compare(url, headers=test_headers, check_reflection=(len(header_list) == 1))
|
|
241
239
|
|
|
242
240
|
async def finish(self):
|
|
243
|
-
|
|
244
|
-
untested_matches = sorted(list(self.extracted_words_master.copy()))
|
|
241
|
+
untested_matches = sorted(self.extracted_words_master.copy())
|
|
245
242
|
for url, (event, batch_size) in list(self.event_dict.items()):
|
|
246
243
|
try:
|
|
247
244
|
compare_helper = self.helpers.http_compare(url)
|