bbot 2.3.2.5855rc0__py3-none-any.whl → 2.3.2.5889rc0__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 CHANGED
@@ -1,4 +1,4 @@
1
1
  # version placeholder (replaced by poetry-dynamic-versioning)
2
- __version__ = "v2.3.2.5855rc"
2
+ __version__ = "v2.3.2.5889rc"
3
3
 
4
4
  from .scanner import Scanner, Preset
@@ -232,7 +232,7 @@ class WebHelper(EngineClient):
232
232
  if success:
233
233
  return filename
234
234
 
235
- async def wordlist(self, path, lines=None, **kwargs):
235
+ async def wordlist(self, path, lines=None, zip=False, zip_filename=None, **kwargs):
236
236
  """
237
237
  Asynchronous function for retrieving wordlists, either from a local path or a URL.
238
238
  Allows for optional line-based truncation and caching. Returns the full path of the wordlist
@@ -242,6 +242,9 @@ class WebHelper(EngineClient):
242
242
  path (str): The local or remote path of the wordlist.
243
243
  lines (int, optional): Number of lines to read from the wordlist.
244
244
  If specified, will return a truncated wordlist with this many lines.
245
+ zip (bool, optional): Whether to unzip the file after downloading. Defaults to False.
246
+ zip_filename (str, optional): The name of the file to extract from the ZIP archive.
247
+ Required if zip is True.
245
248
  cache_hrs (float, optional): Number of hours to cache the downloaded wordlist.
246
249
  Defaults to 720 hours (30 days) for remote wordlists.
247
250
  **kwargs: Additional keyword arguments to pass to the 'download' function for remote wordlists.
@@ -259,6 +262,8 @@ class WebHelper(EngineClient):
259
262
  Fetching and truncating to the first 100 lines
260
263
  >>> wordlist_path = await self.helpers.wordlist("/root/rockyou.txt", lines=100)
261
264
  """
265
+ import zipfile
266
+
262
267
  if not path:
263
268
  raise WordlistError(f"Invalid wordlist: {path}")
264
269
  if "cache_hrs" not in kwargs:
@@ -272,6 +277,18 @@ class WebHelper(EngineClient):
272
277
  if not filename.is_file():
273
278
  raise WordlistError(f"Unable to find wordlist at {path}")
274
279
 
280
+ if zip:
281
+ if not zip_filename:
282
+ raise WordlistError("zip_filename must be specified when zip is True")
283
+ try:
284
+ with zipfile.ZipFile(filename, "r") as zip_ref:
285
+ if zip_filename not in zip_ref.namelist():
286
+ raise WordlistError(f"File {zip_filename} not found in the zip archive {filename}")
287
+ zip_ref.extract(zip_filename, filename.parent)
288
+ filename = filename.parent / zip_filename
289
+ except Exception as e:
290
+ raise WordlistError(f"Error unzipping file {filename}: {e}")
291
+
275
292
  if lines is None:
276
293
  return filename
277
294
  else:
@@ -17,6 +17,7 @@ class ffuf(BaseModule):
17
17
  "lines": 5000,
18
18
  "max_depth": 0,
19
19
  "extensions": "",
20
+ "ignore_case": False,
20
21
  }
21
22
 
22
23
  options_desc = {
@@ -24,6 +25,7 @@ class ffuf(BaseModule):
24
25
  "lines": "take only the first N lines from the wordlist when finding directories",
25
26
  "max_depth": "the maximum directory depth to attempt to solve",
26
27
  "extensions": "Optionally include a list of extensions to extend the keyword with (comma separated)",
28
+ "ignore_case": "Only put lowercase words into the wordlist",
27
29
  }
28
30
 
29
31
  deps_common = ["ffuf"]
@@ -301,11 +303,12 @@ class ffuf(BaseModule):
301
303
  ]
302
304
  if len(pre_emit_temp_canary) == 0:
303
305
  yield found_json
306
+
304
307
  else:
305
- self.warning(
306
- "Baseline changed mid-scan. This is probably due to a WAF turning on a block against you."
308
+ self.verbose(
309
+ f"Would have reported URL [{found_json['url']}], but baseline check failed. This could be due to a WAF turning on mid-scan, or an unusual web server configuration."
307
310
  )
308
- self.warning(f"Aborting the current run against [{url}]")
311
+ self.verbose(f"Aborting the current run against [{url}]")
309
312
  return
310
313
 
311
314
  yield found_json
@@ -328,7 +331,8 @@ class ffuf(BaseModule):
328
331
  return self.helpers.tempfile(virtual_file, pipe=False), len(virtual_file)
329
332
 
330
333
  def generate_wordlist(self, wordlist_file):
331
- wordlist = []
334
+ wordlist_set = set() # Use a set to avoid duplicates
335
+ ignore_case = self.config.get("ignore_case", False) # Get the ignore_case option
332
336
  for line in self.helpers.read_file(wordlist_file):
333
337
  line = line.strip()
334
338
  if not line:
@@ -339,5 +343,7 @@ class ffuf(BaseModule):
339
343
  if any(x in line for x in self.banned_characters):
340
344
  self.debug(f"Skipping adding [{line}] to wordlist because it has a banned character")
341
345
  continue
342
- wordlist.append(line)
343
- return wordlist
346
+ if ignore_case:
347
+ line = line.lower() # Convert to lowercase if ignore_case is enabled
348
+ wordlist_set.add(line) # Add to set to handle duplicates
349
+ return list(wordlist_set) # Convert set back to list before returning
@@ -9,7 +9,6 @@ from bbot.modules.deadly.ffuf import ffuf
9
9
  class ffuf_shortnames(ffuf):
10
10
  watched_events = ["URL_HINT"]
11
11
  produced_events = ["URL_UNVERIFIED"]
12
- deps_pip = ["numpy"]
13
12
  flags = ["aggressive", "active", "iis-shortnames", "web-thorough"]
14
13
  meta = {
15
14
  "description": "Use ffuf in combination IIS shortnames",
@@ -18,7 +17,6 @@ class ffuf_shortnames(ffuf):
18
17
  }
19
18
 
20
19
  options = {
21
- "wordlist": "", # default is defined within setup function
22
20
  "wordlist_extensions": "", # default is defined within setup function
23
21
  "max_depth": 1,
24
22
  "version": "2.0.0",
@@ -26,11 +24,11 @@ class ffuf_shortnames(ffuf):
26
24
  "ignore_redirects": True,
27
25
  "find_common_prefixes": False,
28
26
  "find_delimiters": True,
27
+ "find_subwords": False,
29
28
  "max_predictions": 250,
30
29
  }
31
30
 
32
31
  options_desc = {
33
- "wordlist": "Specify wordlist to use when finding directories",
34
32
  "wordlist_extensions": "Specify wordlist to use when making extension lists",
35
33
  "max_depth": "the maximum directory depth to attempt to solve",
36
34
  "version": "ffuf version",
@@ -38,21 +36,26 @@ class ffuf_shortnames(ffuf):
38
36
  "ignore_redirects": "Explicitly ignore redirects (301,302)",
39
37
  "find_common_prefixes": "Attempt to automatically detect common prefixes and make additional ffuf runs against them",
40
38
  "find_delimiters": "Attempt to detect common delimiters and make additional ffuf runs against them",
39
+ "find_subwords": "Attempt to detect subwords and make additional ffuf runs against them",
41
40
  "max_predictions": "The maximum number of predictions to generate per shortname prefix",
42
41
  }
43
42
 
43
+ deps_pip = ["numpy"]
44
44
  deps_common = ["ffuf"]
45
-
46
45
  in_scope_only = True
47
46
 
48
- def generate_templist(self, prefix, shortname_type):
49
- virtual_file = []
47
+ supplementary_words = ["html", "ajax", "xml", "json", "api"]
50
48
 
51
- for prediction, score in self.predict(prefix, self.max_predictions, model=shortname_type):
52
- self.debug(f"Got prediction: [{prediction}] from prefix [{prefix}] with score [{score}]")
53
- virtual_file.append(prediction)
54
- virtual_file.append(self.canary)
55
- return self.helpers.tempfile(virtual_file, pipe=False), len(virtual_file)
49
+ def generate_templist(self, hint, shortname_type):
50
+ virtual_file = set() # Use a set to avoid duplicates
51
+
52
+ for prediction, score in self.predict(hint, self.max_predictions, model=shortname_type):
53
+ prediction_lower = prediction.lower() # Convert to lowercase
54
+ self.debug(f"Got prediction: [{prediction_lower}] from prefix [{hint}] with score [{score}]")
55
+ virtual_file.add(prediction_lower) # Add to set to ensure uniqueness
56
+
57
+ virtual_file.add(self.canary.lower()) # Ensure canary is also lowercase
58
+ return self.helpers.tempfile(list(virtual_file), pipe=False), len(virtual_file)
56
59
 
57
60
  def predict(self, prefix, n=25, model="endpoint"):
58
61
  predictor_name = f"{model}_predictor"
@@ -92,6 +95,7 @@ class ffuf_shortnames(ffuf):
92
95
  self.wordlist_extensions = await self.helpers.wordlist(wordlist_extensions)
93
96
  self.ignore_redirects = self.config.get("ignore_redirects")
94
97
  self.max_predictions = self.config.get("max_predictions")
98
+ self.find_subwords = self.config.get("find_subwords")
95
99
 
96
100
  class MinimalWordPredictor:
97
101
  def __init__(self):
@@ -116,10 +120,11 @@ class ffuf_shortnames(ffuf):
116
120
  return MinimalWordPredictor
117
121
  return super().find_class(module, name)
118
122
 
119
- endpoint_model = await self.helpers.download(
123
+ self.info("Loading ffuf_shortnames prediction models, could take a while if not cached")
124
+ endpoint_model = await self.helpers.wordlist(
120
125
  "https://raw.githubusercontent.com/blacklanternsecurity/wordpredictor/refs/heads/main/trained_models/endpoints.bin"
121
126
  )
122
- directory_model = await self.helpers.download(
127
+ directory_model = await self.helpers.wordlist(
123
128
  "https://raw.githubusercontent.com/blacklanternsecurity/wordpredictor/refs/heads/main/trained_models/directories.bin"
124
129
  )
125
130
 
@@ -133,8 +138,24 @@ class ffuf_shortnames(ffuf):
133
138
  unpickler = CustomUnpickler(f)
134
139
  self.directory_predictor = unpickler.load()
135
140
 
141
+ self.subword_list = []
142
+ if self.find_subwords:
143
+ self.debug("Acquiring ffuf_shortnames subword list")
144
+ subwords = await self.helpers.wordlist(
145
+ "https://raw.githubusercontent.com/nltk/nltk_data/refs/heads/gh-pages/packages/corpora/words.zip",
146
+ zip=True,
147
+ zip_filename="words/en",
148
+ )
149
+ with open(subwords, "r") as f:
150
+ subword_list_content = f.readlines()
151
+ self.subword_list = {word.lower().strip() for word in subword_list_content if 3 <= len(word.strip()) <= 5}
152
+ self.debug(f"Created subword_list with {len(self.subword_list)} words")
153
+ self.subword_list = self.subword_list.union(self.supplementary_words)
154
+ self.debug(f"Extended subword_list with supplementary words, total size: {len(self.subword_list)}")
155
+
136
156
  self.per_host_collection = {}
137
157
  self.shortname_to_event = {}
158
+
138
159
  return True
139
160
 
140
161
  def build_extension_list(self, event):
@@ -159,10 +180,20 @@ class ffuf_shortnames(ffuf):
159
180
  return None
160
181
 
161
182
  async def filter_event(self, event):
183
+ if "iis-magic-url" in event.tags:
184
+ return False, "iis-magic-url URL_HINTs are not solvable by ffuf_shortnames"
162
185
  if event.parent.type != "URL":
163
186
  return False, "its parent event is not of type URL"
164
187
  return True
165
188
 
189
+ def find_subword(self, word):
190
+ for i in range(len(word), 2, -1): # Start from full length down to 3 characters
191
+ candidate = word[:i]
192
+ if candidate in self.subword_list:
193
+ leftover = word[i:]
194
+ return candidate, leftover
195
+ return None, word # No match found, return None and the original word
196
+
166
197
  async def handle_event(self, event):
167
198
  filename_hint = re.sub(r"~\d", "", event.parsed_url.path.rsplit(".", 1)[0].split("/")[-1]).lower()
168
199
 
@@ -256,6 +287,31 @@ class ffuf_shortnames(ffuf):
256
287
  context=f'{{module}} brute-forced {ext.upper()} files with detected prefix "{ffuf_prefix}" and found {{event.type}}: {{event.data}}',
257
288
  )
258
289
 
290
+ if self.config.get("find_subwords"):
291
+ subword, suffix = self.find_subword(filename_hint)
292
+ if subword:
293
+ if "shortname-directory" in event.tags:
294
+ tempfile, tempfile_len = self.generate_templist(suffix, "directory")
295
+ async for r in self.execute_ffuf(tempfile, root_url, prefix=subword, exts=["/"]):
296
+ await self.emit_event(
297
+ r["url"],
298
+ "URL_UNVERIFIED",
299
+ parent=event,
300
+ tags=[f"status-{r['status']}"],
301
+ context=f'{{module}} brute-forced directories with detected subword "{subword}" and found {{event.type}}: {{event.data}}',
302
+ )
303
+ elif "shortname-endpoint" in event.tags:
304
+ for ext in used_extensions:
305
+ tempfile, tempfile_len = self.generate_templist(suffix, "endpoint")
306
+ async for r in self.execute_ffuf(tempfile, root_url, prefix=subword, suffix=f".{ext}"):
307
+ await self.emit_event(
308
+ r["url"],
309
+ "URL_UNVERIFIED",
310
+ parent=event,
311
+ tags=[f"status-{r['status']}"],
312
+ context=f'{{module}} brute-forced {ext.upper()} files with detected subword "{subword}" and found {{event.type}}: {{event.data}}',
313
+ )
314
+
259
315
  async def finish(self):
260
316
  if self.config.get("find_common_prefixes"):
261
317
  per_host_collection = dict(self.per_host_collection)
@@ -154,6 +154,12 @@ class generic_ssrf(BaseModule):
154
154
  produced_events = ["VULNERABILITY"]
155
155
  flags = ["active", "aggressive", "web-thorough"]
156
156
  meta = {"description": "Check for generic SSRFs", "created_date": "2022-07-30", "author": "@liquidsec"}
157
+ options = {
158
+ "skip_dns_interaction": False,
159
+ }
160
+ options_desc = {
161
+ "skip_dns_interaction": "Do not report DNS interactions (only HTTP interaction)",
162
+ }
157
163
  in_scope_only = True
158
164
 
159
165
  deps_apt = ["curl"]
@@ -163,7 +169,7 @@ class generic_ssrf(BaseModule):
163
169
  self.interactsh_subdomain_tags = {}
164
170
  self.parameter_subdomain_tags_map = {}
165
171
  self.severity = None
166
- self.generic_only = self.config.get("generic_only", False)
172
+ self.skip_dns_interaction = self.config.get("skip_dns_interaction", False)
167
173
 
168
174
  if self.scan.config.get("interactsh_disable", False) is False:
169
175
  try:
@@ -191,6 +197,10 @@ class generic_ssrf(BaseModule):
191
197
  await s.test(event)
192
198
 
193
199
  async def interactsh_callback(self, r):
200
+ protocol = r.get("protocol").upper()
201
+ if protocol == "DNS" and self.skip_dns_interaction:
202
+ return
203
+
194
204
  full_id = r.get("full-id", None)
195
205
  subdomain_tag = full_id.split(".")[0]
196
206
 
@@ -204,24 +214,27 @@ class generic_ssrf(BaseModule):
204
214
  matched_severity = match[2]
205
215
  matched_echoed_response = str(match[3])
206
216
 
207
- # Check if any SSRF parameter is in the DNS request
208
217
  triggering_param = self.parameter_subdomain_tags_map.get(subdomain_tag, None)
209
218
  description = f"Out-of-band interaction: [{matched_technique}]"
210
219
  if triggering_param:
211
220
  self.debug(f"Found triggering parameter: {triggering_param}")
212
221
  description += f" [Triggering Parameter: {triggering_param}]"
213
- description += f" [{r.get('protocol').upper()}] Echoed Response: {matched_echoed_response}"
222
+ description += f" [{protocol}] Echoed Response: {matched_echoed_response}"
214
223
 
215
224
  self.debug(f"Emitting event with description: {description}") # Debug the final description
216
225
 
226
+ event_type = "VULNERABILITY" if protocol == "HTTP" else "FINDING"
227
+ event_data = {
228
+ "host": str(matched_event.host),
229
+ "url": matched_event.data,
230
+ "description": description,
231
+ }
232
+ if protocol == "HTTP":
233
+ event_data["severity"] = matched_severity
234
+
217
235
  await self.emit_event(
218
- {
219
- "severity": matched_severity,
220
- "host": str(matched_event.host),
221
- "url": matched_event.data,
222
- "description": description,
223
- },
224
- "VULNERABILITY",
236
+ event_data,
237
+ event_type,
225
238
  matched_event,
226
239
  context=f"{{module}} scanned {matched_event.data} and detected {{event.type}}: {matched_technique}",
227
240
  )
@@ -241,7 +254,7 @@ class generic_ssrf(BaseModule):
241
254
 
242
255
  async def finish(self):
243
256
  if self.scan.config.get("interactsh_disable", False) is False:
244
- await self.helpers.sleep(2)
257
+ await self.helpers.sleep(5)
245
258
  try:
246
259
  for r in await self.interactsh_instance.poll():
247
260
  await self.interactsh_callback(r)
@@ -17,6 +17,9 @@ config:
17
17
  modules:
18
18
  ffuf:
19
19
  extensions: asp,aspx,ashx,asmx,ascx
20
+ extensions_ignore_case: True
21
+ ffuf_shortnames:
22
+ find_subwords: True
20
23
  telerik:
21
24
  exploit_RAU_crypto: True
22
25
  include_subdirs: True # Run against every directory, not the default first received URL per-host
bbot/test/conftest.py CHANGED
@@ -8,6 +8,8 @@ from pathlib import Path
8
8
  from contextlib import suppress
9
9
  from omegaconf import OmegaConf
10
10
  from pytest_httpserver import HTTPServer
11
+ import time
12
+ import queue
11
13
 
12
14
  from bbot.core import CORE
13
15
  from bbot.core.helpers.misc import execute_sync_or_async
@@ -53,6 +55,12 @@ def silence_live_logging():
53
55
  handler.setLevel(logging.CRITICAL)
54
56
 
55
57
 
58
+ def stop_server(server):
59
+ server.stop()
60
+ while server.is_running():
61
+ time.sleep(0.1) # Wait a bit before checking again
62
+
63
+
56
64
  @pytest.fixture
57
65
  def bbot_httpserver():
58
66
  server = HTTPServer(host="127.0.0.1", port=8888, threaded=True)
@@ -61,11 +69,7 @@ def bbot_httpserver():
61
69
  yield server
62
70
 
63
71
  server.clear()
64
- if server.is_running():
65
- server.stop()
66
-
67
- # this is to check if the client has made any request where no
68
- # `assert_request` was called on it from the test
72
+ stop_server(server) # Ensure the server is fully stopped
69
73
 
70
74
  server.check_assertions()
71
75
  server.clear()
@@ -84,11 +88,7 @@ def bbot_httpserver_ssl():
84
88
  yield server
85
89
 
86
90
  server.clear()
87
- if server.is_running():
88
- server.stop()
89
-
90
- # this is to check if the client has made any request where no
91
- # `assert_request` was called on it from the test
91
+ stop_server(server) # Ensure the server is fully stopped
92
92
 
93
93
  server.check_assertions()
94
94
  server.clear()
@@ -129,7 +129,7 @@ class Interactsh_mock:
129
129
  def __init__(self, name):
130
130
  self.name = name
131
131
  self.log = logging.getLogger(f"bbot.interactsh.{self.name}")
132
- self.interactions = []
132
+ self.interactions = asyncio.Queue() # Use an asyncio queue for async access
133
133
  self.correlation_id = "deadbeef-dead-beef-dead-beefdeadbeef"
134
134
  self.stop = False
135
135
  self.poll_task = None
@@ -138,7 +138,7 @@ class Interactsh_mock:
138
138
  self.log.info(f"Mocking interaction to subdomain tag: {subdomain_tag}")
139
139
  if msg is not None:
140
140
  self.log.info(msg)
141
- self.interactions.append(subdomain_tag)
141
+ self.interactions.put_nowait(subdomain_tag) # Add to the thread-safe queue
142
142
 
143
143
  async def register(self, callback=None):
144
144
  if callable(callback):
@@ -146,27 +146,32 @@ class Interactsh_mock:
146
146
  return "fakedomain.fakeinteractsh.com"
147
147
 
148
148
  async def deregister(self, callback=None):
149
+ await asyncio.sleep(1)
149
150
  self.stop = True
150
151
  if self.poll_task is not None:
151
152
  self.poll_task.cancel()
152
- with suppress(BaseException):
153
+ with suppress(asyncio.CancelledError):
153
154
  await self.poll_task
154
155
 
155
156
  async def poll_loop(self, callback=None):
156
157
  while not self.stop:
157
158
  data_list = await self.poll(callback)
158
159
  if not data_list:
159
- await asyncio.sleep(1)
160
+ await asyncio.sleep(0.5)
160
161
  continue
162
+ await asyncio.sleep(1)
163
+ await self.poll(callback)
161
164
 
162
165
  async def poll(self, callback=None):
163
166
  poll_results = []
164
- for subdomain_tag in self.interactions:
165
- result = {"full-id": f"{subdomain_tag}.fakedomain.fakeinteractsh.com", "protocol": "HTTP"}
166
- poll_results.append(result)
167
- if callback is not None:
168
- await execute_sync_or_async(callback, result)
169
- self.interactions = []
167
+ while not self.interactions.empty():
168
+ subdomain_tag = await self.interactions.get() # Get the first element from the asyncio queue
169
+ for protocol in ["HTTP", "DNS"]:
170
+ result = {"full-id": f"{subdomain_tag}.fakedomain.fakeinteractsh.com", "protocol": protocol}
171
+ poll_results.append(result)
172
+ if callback is not None:
173
+ await execute_sync_or_async(callback, result)
174
+ await asyncio.sleep(0.1)
170
175
  return poll_results
171
176
 
172
177
 
@@ -1,3 +1,4 @@
1
+ import asyncio
1
2
  import re
2
3
  from .base import ModuleTestBase
3
4
  from werkzeug.wrappers import Response
@@ -171,7 +172,5 @@ class TestDotnetnuke_blindssrf(ModuleTestBase):
171
172
  if e.type == "VULNERABILITY" and "DotNetNuke Blind-SSRF (CVE 2017-0929)" in e.data["description"]:
172
173
  dnn_dnnimagehandler_blindssrf = True
173
174
 
174
- assert self.interactsh_mock_instance.interactions == []
175
-
176
175
  assert dnn_technology_detection, "DNN Technology Detection Failed"
177
176
  assert dnn_dnnimagehandler_blindssrf, "dnnimagehandler.ashx Blind SSRF Detection Failed"
@@ -45,6 +45,30 @@ class TestFFUF2(TestFFUF):
45
45
  assert not any(e.type == "URL_UNVERIFIED" and "11111111" in e.data for e in events)
46
46
 
47
47
 
48
+ class TestFFUF_ignorecase(TestFFUF):
49
+ test_wordlist = ["11111111", "Admin", "admin", "zzzjunkword2"]
50
+ config_overrides = {
51
+ "modules": {"ffuf": {"wordlist": tempwordlist(test_wordlist), "extensions": "php", "ignore_case": True}}
52
+ }
53
+
54
+ async def setup_before_prep(self, module_test):
55
+ expect_args = {"method": "GET", "uri": "/admin"}
56
+ respond_args = {"response_data": "alive admin page"}
57
+ module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args)
58
+
59
+ expect_args = {"method": "GET", "uri": "/Admin"}
60
+ respond_args = {"response_data": "alive admin page"}
61
+ module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args)
62
+
63
+ expect_args = {"method": "GET", "uri": "/"}
64
+ respond_args = {"response_data": "alive"}
65
+ module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args)
66
+
67
+ def check(self, module_test, events):
68
+ assert any(e.type == "URL_UNVERIFIED" and "admin" in e.data for e in events)
69
+ assert not any(e.type == "URL_UNVERIFIED" and "Admin" in e.data for e in events)
70
+
71
+
48
72
  class TestFFUFHeaders(TestFFUF):
49
73
  test_wordlist = ["11111111", "console", "junkword1", "zzzjunkword2"]
50
74
  config_overrides = {
@@ -8,6 +8,7 @@ class TestFFUFShortnames(ModuleTestBase):
8
8
  "modules": {
9
9
  "ffuf_shortnames": {
10
10
  "find_common_prefixes": True,
11
+ "find_subwords": True,
11
12
  "wordlist": tempwordlist(test_wordlist),
12
13
  }
13
14
  }
@@ -142,6 +143,16 @@ class TestFFUFShortnames(ModuleTestBase):
142
143
  tags=["shortname-endpoint"],
143
144
  )
144
145
  )
146
+
147
+ seed_events.append(
148
+ module_test.scan.make_event(
149
+ "http://127.0.0.1:8888/newpro~1.asp",
150
+ "URL_HINT",
151
+ parent_event,
152
+ module="iis_shortnames",
153
+ tags=["shortname-endpoint"],
154
+ )
155
+ )
145
156
  module_test.scan.target.seeds.events = set(seed_events)
146
157
 
147
158
  expect_args = {"method": "GET", "uri": "/administrator.aspx"}
@@ -172,6 +183,10 @@ class TestFFUFShortnames(ModuleTestBase):
172
183
  respond_args = {"response_data": "alive"}
173
184
  module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args)
174
185
 
186
+ expect_args = {"method": "GET", "uri": "/newproxy.aspx"}
187
+ respond_args = {"response_data": "alive"}
188
+ module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args)
189
+
175
190
  def check(self, module_test, events):
176
191
  basic_detection = False
177
192
  directory_detection = False
@@ -180,6 +195,7 @@ class TestFFUFShortnames(ModuleTestBase):
180
195
  directory_delimiter_detection = False
181
196
  prefix_delimiter_detection = False
182
197
  short_extensions_detection = False
198
+ subword_detection = False
183
199
 
184
200
  for e in events:
185
201
  if e.type == "URL_UNVERIFIED":
@@ -197,6 +213,8 @@ class TestFFUFShortnames(ModuleTestBase):
197
213
  prefix_delimiter_detection = True
198
214
  if e.data == "http://127.0.0.1:8888/short.pl":
199
215
  short_extensions_detection = True
216
+ if e.data == "http://127.0.0.1:8888/newproxy.aspx":
217
+ subword_detection = True
200
218
 
201
219
  assert basic_detection
202
220
  assert directory_detection
@@ -205,3 +223,4 @@ class TestFFUFShortnames(ModuleTestBase):
205
223
  assert directory_delimiter_detection
206
224
  assert prefix_delimiter_detection
207
225
  assert short_extensions_detection
226
+ assert subword_detection
@@ -1,4 +1,5 @@
1
1
  import re
2
+ import asyncio
2
3
  from werkzeug.wrappers import Response
3
4
 
4
5
  from .base import ModuleTestBase
@@ -23,15 +24,16 @@ class TestGeneric_SSRF(ModuleTestBase):
23
24
  elif request.method == "POST":
24
25
  subdomain_tag = extract_subdomain_tag(request.data.decode())
25
26
  if subdomain_tag:
26
- self.interactsh_mock_instance.mock_interaction(
27
- subdomain_tag, msg=f"{request.method}: {request.data.decode()}"
27
+ asyncio.run(
28
+ self.interactsh_mock_instance.mock_interaction(
29
+ subdomain_tag, msg=f"{request.method}: {request.data.decode()}"
30
+ )
28
31
  )
29
32
 
30
33
  return Response("alive", status=200)
31
34
 
32
35
  async def setup_before_prep(self, module_test):
33
36
  self.interactsh_mock_instance = module_test.mock_interactsh("generic_ssrf")
34
- self.interactsh_mock_instance.mock_interaction("asdf")
35
37
  module_test.monkeypatch.setattr(
36
38
  module_test.scan.helpers, "interactsh", lambda *args, **kwargs: self.interactsh_mock_instance
37
39
  )
@@ -41,6 +43,18 @@ class TestGeneric_SSRF(ModuleTestBase):
41
43
  module_test.set_expect_requests_handler(expect_args=expect_args, request_handler=self.request_handler)
42
44
 
43
45
  def check(self, module_test, events):
46
+ total_vulnerabilities = 0
47
+ total_findings = 0
48
+
49
+ for e in events:
50
+ if e.type == "VULNERABILITY":
51
+ total_vulnerabilities += 1
52
+ elif e.type == "FINDING":
53
+ total_findings += 1
54
+
55
+ assert total_vulnerabilities == 30, "Incorrect number of vulnerabilities detected"
56
+ assert total_findings == 30, "Incorrect number of findings detected"
57
+
44
58
  assert any(
45
59
  e.type == "VULNERABILITY"
46
60
  and "Out-of-band interaction: [Generic SSRF (GET)]"
@@ -55,3 +69,20 @@ class TestGeneric_SSRF(ModuleTestBase):
55
69
  e.type == "VULNERABILITY" and "Out-of-band interaction: [Generic XXE] [HTTP]" in e.data["description"]
56
70
  for e in events
57
71
  ), "Failed to detect Generic SSRF (XXE)"
72
+
73
+
74
+ class TestGeneric_SSRF_httponly(TestGeneric_SSRF):
75
+ config_overrides = {"modules": {"generic_ssrf": {"skip_dns_interaction": True}}}
76
+
77
+ def check(self, module_test, events):
78
+ total_vulnerabilities = 0
79
+ total_findings = 0
80
+
81
+ for e in events:
82
+ if e.type == "VULNERABILITY":
83
+ total_vulnerabilities += 1
84
+ elif e.type == "FINDING":
85
+ total_findings += 1
86
+
87
+ assert total_vulnerabilities == 30, "Incorrect number of vulnerabilities detected"
88
+ assert total_findings == 0, "Incorrect number of findings detected"
@@ -1,3 +1,4 @@
1
+ import asyncio
1
2
  import re
2
3
  from werkzeug.wrappers import Response
3
4
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: bbot
3
- Version: 2.3.2.5855rc0
3
+ Version: 2.3.2.5889rc0
4
4
  Summary: OSINT automation for hackers.
5
5
  License: GPL-3.0
6
6
  Keywords: python,cli,automation,osint,threat-intel,intelligence,neo4j,scanner,python-library,hacking,recursion,pentesting,recon,command-line-tool,bugbounty,subdomains,security-tools,subdomain-scanner,osint-framework,attack-surface,subdomain-enumeration,osint-tool
@@ -1,4 +1,4 @@
1
- bbot/__init__.py,sha256=GYangEfD1f4_MA0LLwbhpk2RCgKo7FV5-VUt_2zsS-0,130
1
+ bbot/__init__.py,sha256=s95CAchRkg61vjHu7w4wiXXBH-BYyVsWnZryET4F-Ng,130
2
2
  bbot/cli.py,sha256=hrzJX07sK3psSQWa461BXFuOxgCA94iztsw8syLdpNw,10830
3
3
  bbot/core/__init__.py,sha256=l255GJE_DvUnWvrRb0J5lG-iMztJ8zVvoweDOfegGtI,46
4
4
  bbot/core/config/__init__.py,sha256=zYNw2Me6tsEr8hOOkLb4BQ97GB7Kis2k--G81S8vofU,342
@@ -42,7 +42,7 @@ bbot/core/helpers/web/__init__.py,sha256=pIEkL3DhjaGTSmZ7D3yKKYwWpntoLRILekV2wWs
42
42
  bbot/core/helpers/web/client.py,sha256=UTjNnsAz-bPO6GEc1lEm_ZhatwaVOIGG7nRIn7KdZAQ,3562
43
43
  bbot/core/helpers/web/engine.py,sha256=mzXpYmlB1pvNSXs8FuliGMv7myUwAcQWTtnMHqblMHA,8875
44
44
  bbot/core/helpers/web/ssl_context.py,sha256=aWVgl-d0HoE8B4EBKNxaa5UAzQmx79DjDByfBw9tezo,356
45
- bbot/core/helpers/web/web.py,sha256=0k5-GEudua_yJSPW5pmmIquElG1Z5oy3qxN5kOKGmlM,22766
45
+ bbot/core/helpers/web/web.py,sha256=sesBGwqXRmePwQIScfg8PbrlCPMArQVj55E1aJsWctI,23685
46
46
  bbot/core/helpers/wordcloud.py,sha256=QM8Z1N01_hXrRFKQjvRL-IzOOC7ZMKjuSBID3u77Sxg,19809
47
47
  bbot/core/modules.py,sha256=U0Z2UoZAOPG9lLvR9Juc3UwdWCc_xbktF4t_NoiKPrY,31385
48
48
  bbot/core/multiprocess.py,sha256=ocQHanskJ09gHwe7RZmwNdZyCOQyeyUoIHCtLbtvXUk,1771
@@ -81,7 +81,7 @@ bbot/modules/code_repository.py,sha256=x70Z45VnNNMF8BPkHfGWZXsZXw_fStGB3y0-8jbP1
81
81
  bbot/modules/credshed.py,sha256=HAF5wgRGKIIpdMAe4mIAtkZRLmFYjMFyXtjjst6RJ20,4203
82
82
  bbot/modules/crt.py,sha256=6Zm90VKXwYYN6Sab0gwwhTARrtnQIqALJTVtFWMMTGk,1369
83
83
  bbot/modules/deadly/dastardly.py,sha256=dxPkJUfAsuddDDuI_uVyTUxkJ5eps92nSrPtpBOTlQg,5315
84
- bbot/modules/deadly/ffuf.py,sha256=ho1vLBh4Knf8lV5RLDcecCLQbWCl7GELvymQiuCfgF8,14236
84
+ bbot/modules/deadly/ffuf.py,sha256=sCledEgIi1ZgAhb-XlwbLIH3wBD5dSELZAEun_pNLOk,14745
85
85
  bbot/modules/deadly/nuclei.py,sha256=hUoqdN_o3f1DQ30I6ltlW63NHT6OGhivoWi8gNlLMuQ,17808
86
86
  bbot/modules/deadly/vhost.py,sha256=m7RdR0w7Hs38IGVHUu_3Er-_5ABVdalRG_8znQepxD0,5456
87
87
  bbot/modules/dehashed.py,sha256=iyzWHmJs6zC7FsRhw9_AdkckQKCf_0oNnL9RwG409r0,5071
@@ -98,11 +98,11 @@ bbot/modules/dockerhub.py,sha256=JQkujjqvQRzQuvHjQ7JbFs_VlJj8dLRPRObAkBgUQhc,349
98
98
  bbot/modules/dotnetnuke.py,sha256=zipcHyNYr2FEecStb1Yrm938ps01RvHV8NnyqAvnGGc,10537
99
99
  bbot/modules/emailformat.py,sha256=RLPJW-xitYB-VT4Lp08qVzFkXx_kMyV_035JT_Yf4fM,1082
100
100
  bbot/modules/extractous.py,sha256=VSGKmHPAA_4r62jaN8Yqi3JcjehjxpI2lhe8i2j786s,4648
101
- bbot/modules/ffuf_shortnames.py,sha256=n6y3FBxgM7CwFBQVSfVYjuQaTOCgjaq2Q2LmdJz-P6Y,15302
101
+ bbot/modules/ffuf_shortnames.py,sha256=iJnm6bvFyQXfzOGnUioficE6v59QkaErY7mo4m6m7o0,18618
102
102
  bbot/modules/filedownload.py,sha256=TOxftfxguaRSEKI8oG79XVRQqUGg1_IhYDYl_Jw9eYc,8694
103
103
  bbot/modules/fingerprintx.py,sha256=rdlR9d64AntAhbS_eJzh8bZCeLPTJPSKdkdKdhH_qAo,3269
104
104
  bbot/modules/fullhunt.py,sha256=zeehQb9akBSbHW9dF4icH8Vfd8LqoTrpIvnQEEMWes8,1311
105
- bbot/modules/generic_ssrf.py,sha256=xsST08_Ka816NmTbUvCC3i_OBZgaEFjDVVQGRuh2tlw,8813
105
+ bbot/modules/generic_ssrf.py,sha256=KFdcHpUV9-Z7oN7emzbirimsNc2xZ_1IFqnsfIkEbcM,9196
106
106
  bbot/modules/git.py,sha256=CMDarsmBemZEzZSeQTzB70XD8IRdwdG39zXpwDdgZbw,1383
107
107
  bbot/modules/git_clone.py,sha256=XFZXx0k97EMY3E5PZzdNvqQzZddOfRMaVp5ol2gk11s,2468
108
108
  bbot/modules/github_codesearch.py,sha256=a-r2vE9N9WyBpFUiKCsg0TK4Qn7DaEGyVRTUKzkDLWA,3641
@@ -218,7 +218,7 @@ bbot/presets/subdomain-enum.yml,sha256=tn9h8TlVB_uS3nKZFUP72HzceoRONSef66mGLWzxj
218
218
  bbot/presets/tech-detect.yml,sha256=0eEzviy33kZojXpUfKVK0lHhiQrNAopCMEJNL8Clunw,176
219
219
  bbot/presets/web/dirbust-heavy.yml,sha256=NDqu7p0Hx1RsZCVnaEWRgI_iL9O0io-tvWerxJf36SM,653
220
220
  bbot/presets/web/dirbust-light.yml,sha256=5zSANdjKfYh49kFlsElYY2G6acVrZFzDCEkyqwU6oOQ,203
221
- bbot/presets/web/dotnet-audit.yml,sha256=FdUaBUftkzr9TX3evpJec3oZTSU4o77FVKwTgWqyxHU,438
221
+ bbot/presets/web/dotnet-audit.yml,sha256=FViiccDXG08P3INNe06bLPeatejbw8Kb1HW5xgdUJNU,520
222
222
  bbot/presets/web/iis-shortnames.yml,sha256=EcYKMpl-cI8Xb79_u4wQS42yFXxDpLH9OqINcFUXoTE,176
223
223
  bbot/presets/web/paramminer.yml,sha256=VuiXkxrOAeqXlk9Gmuo938_REvbbTH6-lxTrlyWAvZ4,163
224
224
  bbot/presets/web-basic.yml,sha256=6YWSYclbuf9yr8-gILDpLvOUj5QjP4rlarm5_d5iBFw,79
@@ -239,7 +239,7 @@ bbot/scanner/target.py,sha256=EQwtFZLDeNlqt8JupyBEksqeQ_c_i3NARSWf3mQQC4k,12128
239
239
  bbot/scripts/docs.py,sha256=ZLY9-O6OeEElzOUvTglO5EMkRv1s4aEuxJb2CthCVsI,10782
240
240
  bbot/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
241
241
  bbot/test/bbot_fixtures.py,sha256=JZhqObsSQ5H2RJZCkq4eNaGo6DcxKYMPQ1XFcbE8vQg,9995
242
- bbot/test/conftest.py,sha256=SGyPKXAZNkDclnHs13rfYMq3GH3pqq8yTeGhzfvMuPk,11486
242
+ bbot/test/conftest.py,sha256=dQhpZ-DMkcc0ANiBPvO2Th-QNTecfvMHn0nryL-96i4,11796
243
243
  bbot/test/coverage.cfg,sha256=ko9RacAYsJxWJCL8aEuNtkAOtP9lexYiDbeFWe8Tp8Y,31
244
244
  bbot/test/fastapi_test.py,sha256=9OhOFRyagXTshMsnuzuKIcR4uzS6VW65m7h9KgB4jSA,381
245
245
  bbot/test/owasp_mastg.apk,sha256=Hai_V9JmEJ-aB8Ab9xEaGXXOAfGQudkUvNOuPb75byE,66651
@@ -320,17 +320,17 @@ bbot/test/test_step_2/module_tests/test_module_dnsresolve.py,sha256=15LEcggP_eVY
320
320
  bbot/test/test_step_2/module_tests/test_module_dnstlsrpt.py,sha256=8xXSFo0vwKfehIqgF41tbEkL1vbp6RIB8kiO8TSH4NU,2648
321
321
  bbot/test/test_step_2/module_tests/test_module_docker_pull.py,sha256=-JSAo51dS3Ie9RaLBcWK0kfbg8bCPr7mohpFGAwOKPQ,27988
322
322
  bbot/test/test_step_2/module_tests/test_module_dockerhub.py,sha256=9T8CFcFP32MOppUmSVNBUSifnk2kMONqzW_7vvvKdpk,3907
323
- bbot/test/test_step_2/module_tests/test_module_dotnetnuke.py,sha256=voi1C_v7VeaRe_-yzCybO9FUxnFf9qzWkoUY66KYiGI,8114
323
+ bbot/test/test_step_2/module_tests/test_module_dotnetnuke.py,sha256=ps2u_yeDXSkhALGqtg574C5-YvyueRJMjEIoSoTRThc,8064
324
324
  bbot/test/test_step_2/module_tests/test_module_emailformat.py,sha256=cKxBPnEQ4AiRKV_-hSYEE6756ypst3hi6MN0L5RTukY,461
325
325
  bbot/test/test_step_2/module_tests/test_module_emails.py,sha256=bZjtO8N3GG2_g6SUEYprAFLcsi7SlwNPJJ0nODfrWYU,944
326
326
  bbot/test/test_step_2/module_tests/test_module_excavate.py,sha256=eROTkAHYo5lLqJVAVpSl-wprp2-YNQkT9hcaqHEEf7I,43604
327
327
  bbot/test/test_step_2/module_tests/test_module_extractous.py,sha256=PuTE5rkEIFPwU9lhCYpTgNSkrVjcXm8PClbfOkfRS84,17973
328
- bbot/test/test_step_2/module_tests/test_module_ffuf.py,sha256=aSB49aN77sw-2LNTDHckiEEaHAn_85xCJno1shdOwus,2964
329
- bbot/test/test_step_2/module_tests/test_module_ffuf_shortnames.py,sha256=1KVSl_gQSud4ITgFHF4uh37WcIl4wnp7vqbOlrRsB88,7635
328
+ bbot/test/test_step_2/module_tests/test_module_ffuf.py,sha256=z8ihAM1WYss7QGXIjbi67cekg8iOemDjaM8YR9_qSEs,4100
329
+ bbot/test/test_step_2/module_tests/test_module_ffuf_shortnames.py,sha256=aq8ycPMJmFJJO6mqM2EaFZoKBEpm6Umfz05OMMwxu4Q,8354
330
330
  bbot/test/test_step_2/module_tests/test_module_filedownload.py,sha256=ZLPlBVs8CMWofLZAl63zdYMryVdYXykoaxE4jBGED8I,4304
331
331
  bbot/test/test_step_2/module_tests/test_module_fingerprintx.py,sha256=nU3jxbkGcmPYiSzc6thJhNvjAFb4qVxcR7rkOAvjB18,445
332
332
  bbot/test/test_step_2/module_tests/test_module_fullhunt.py,sha256=NblfNHQrE82j-cESvm66hpN-ooKZwR1kEwJDTk_BXac,1946
333
- bbot/test/test_step_2/module_tests/test_module_generic_ssrf.py,sha256=rUs2icFxc-u5GKdzvlpnV1IOa9lcrGVbP5G5cKmn3Yo,2214
333
+ bbot/test/test_step_2/module_tests/test_module_generic_ssrf.py,sha256=ZhfZpH0QTl6_YftGoZzZk6_2x0ZDnWjZ7vNZMTibBHw,3228
334
334
  bbot/test/test_step_2/module_tests/test_module_git.py,sha256=gyBS3vZUWAyatGlcY26mGOYeqXSqJA5pbhJWgTmLqNo,1656
335
335
  bbot/test/test_step_2/module_tests/test_module_git_clone.py,sha256=Mo0Q7bCXcrkGWJc3-u5y4sdfC13ei-qj79aKvEbnkk4,13198
336
336
  bbot/test/test_step_2/module_tests/test_module_github_codesearch.py,sha256=M50xBiGG2EuPGXDJU6uFsSUE-fhqZl3CzYtNdszW7LM,4735
@@ -340,7 +340,7 @@ bbot/test/test_step_2/module_tests/test_module_gitlab.py,sha256=fnwE7BWTU6EQquKd
340
340
  bbot/test/test_step_2/module_tests/test_module_google_playstore.py,sha256=uTRqpAGI9HI-rOk_6jdV44OoSqi0QQQ3aTVzvuV0dtc,3034
341
341
  bbot/test/test_step_2/module_tests/test_module_gowitness.py,sha256=EH3NIMDA3XgZz1yffu-PnRCrlZJODakGPfzgnU7Ls_s,6501
342
342
  bbot/test/test_step_2/module_tests/test_module_hackertarget.py,sha256=ldhNKxGk5fwq87zVptQDyfQ-cn3FzbWvpadKEO3h4ic,609
343
- bbot/test/test_step_2/module_tests/test_module_host_header.py,sha256=w1x0MyKNiUol4hlw7CijhMwEMEL5aBddbZZjOcEgv_k,2672
343
+ bbot/test/test_step_2/module_tests/test_module_host_header.py,sha256=-kod71F8OrWz9GhnXLo06jY6ISnh0EWs95bJTlY36ho,2687
344
344
  bbot/test/test_step_2/module_tests/test_module_http.py,sha256=KhsQvqpVa6zmMa79jV4liv_NAv25wrSaO6h_x0AA12c,2127
345
345
  bbot/test/test_step_2/module_tests/test_module_httpx.py,sha256=kUSJd0eu-e7rj1hnaQyUn5V01JJF7eUlsUjhVAqGvu0,5761
346
346
  bbot/test/test_step_2/module_tests/test_module_hunt.py,sha256=xSnPevrLgFe-umWjvF-X8hOavZCn1s1sClXKM3WBLpE,744
@@ -423,8 +423,8 @@ bbot/wordlists/raft-small-extensions-lowercase_CLEANED.txt,sha256=ZSIVebs7ptMvHx
423
423
  bbot/wordlists/top_open_ports_nmap.txt,sha256=LmdFYkfapSxn1pVuQC2LkOIY2hMLgG-Xts7DVtYzweM,42727
424
424
  bbot/wordlists/valid_url_schemes.txt,sha256=0B_VAr9Dv7aYhwi6JSBDU-3M76vNtzN0qEC_RNLo7HE,3310
425
425
  bbot/wordlists/wordninja_dns.txt.gz,sha256=DYHvvfW0TvzrVwyprqODAk4tGOxv5ezNmCPSdPuDUnQ,570241
426
- bbot-2.3.2.5855rc0.dist-info/LICENSE,sha256=GzeCzK17hhQQDNow0_r0L8OfLpeTKQjFQwBQU7ZUymg,32473
427
- bbot-2.3.2.5855rc0.dist-info/METADATA,sha256=8nDHIExmXjt2z4dZQim4aPWfY3EC4tz19YqRNSMbMHc,18224
428
- bbot-2.3.2.5855rc0.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
429
- bbot-2.3.2.5855rc0.dist-info/entry_points.txt,sha256=cWjvcU_lLrzzJgjcjF7yeGuRA_eDS8pQ-kmPUAyOBfo,38
430
- bbot-2.3.2.5855rc0.dist-info/RECORD,,
426
+ bbot-2.3.2.5889rc0.dist-info/LICENSE,sha256=GzeCzK17hhQQDNow0_r0L8OfLpeTKQjFQwBQU7ZUymg,32473
427
+ bbot-2.3.2.5889rc0.dist-info/METADATA,sha256=ARrEoX3gXC9hWPDf2FWNjfpfZpR6Ta9BCdSudw0fvMg,18224
428
+ bbot-2.3.2.5889rc0.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
429
+ bbot-2.3.2.5889rc0.dist-info/entry_points.txt,sha256=cWjvcU_lLrzzJgjcjF7yeGuRA_eDS8pQ-kmPUAyOBfo,38
430
+ bbot-2.3.2.5889rc0.dist-info/RECORD,,