Skip to content

Commit 0c78fef

Browse files
Merge pull request #2429 from blacklanternsecurity/fix-condition-printing
Optimize --list-preset
2 parents 1ca8943 + 884decc commit 0c78fef

7 files changed

Lines changed: 448 additions & 380 deletions

File tree

bbot/presets/web/lightfuzz-xss.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
description: Discover web parameters and lightly fuzz them, limited to just GET-based xss vulnerabilities. This is an example of a custom lightfuzz preset, selectively enabling a single lightfuzz module.
2+
23
modules:
34
- httpx
45
- lightfuzz

bbot/scanner/preset/path.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
log = logging.getLogger("bbot.presets.path")
77

88
DEFAULT_PRESET_PATH = Path(__file__).parent.parent.parent / "presets"
9+
DEFAULT_PRESET_PATH = DEFAULT_PRESET_PATH.expanduser().resolve()
910

1011

1112
class PresetPath:
@@ -17,7 +18,7 @@ def __init__(self):
1718
self.paths = [DEFAULT_PRESET_PATH]
1819

1920
def find(self, filename):
20-
filename_path = Path(filename).resolve()
21+
filename_path = Path(filename).expanduser().resolve()
2122
extension = filename_path.suffix.lower()
2223
file_candidates = set()
2324
extension_candidates = {".yaml", ".yml"}
@@ -29,16 +30,12 @@ def find(self, filename):
2930
file_candidates.add(f"{filename_path.stem}{ext}")
3031
file_candidates = sorted(file_candidates)
3132
file_candidates_str = ",".join([str(s) for s in file_candidates])
32-
paths_to_search = self.paths
3333
if "/" in str(filename):
34-
if filename_path.parent not in paths_to_search:
35-
paths_to_search.append(filename_path.parent)
36-
log.debug(
37-
f"Searching for preset in {[str(p) for p in paths_to_search]}, file candidates: {file_candidates_str}"
38-
)
39-
for path in paths_to_search:
34+
self.add_path(filename_path.parent)
35+
log.debug(f"Searching for {file_candidates_str} in {[str(p) for p in self.paths]}")
36+
for path in self.paths:
4037
for candidate in file_candidates:
41-
for file in path.rglob(candidate):
38+
for file in path.rglob(f"**/{candidate}"):
4239
if file.is_file():
4340
log.verbose(f'Found preset matching "{filename}" at {file}')
4441
self.add_path(file.parent)
@@ -51,14 +48,19 @@ def __str__(self):
5148
return ":".join([str(s) for s in self.paths])
5249

5350
def add_path(self, path):
54-
path = Path(path).resolve()
51+
path = Path(path).expanduser().resolve()
52+
# skip if already in paths
5553
if path in self.paths:
5654
return
55+
# skip if path is a subdirectory of any path in paths
5756
if any(path.is_relative_to(p) for p in self.paths):
5857
return
58+
# skip if path is not a directory
5959
if not path.is_dir():
6060
log.debug(f'Path "{path.resolve()}" is not a directory')
6161
return
62+
# preemptively remove any paths that are subdirectories of the new path
63+
self.paths = [p for p in self.paths if not p.is_relative_to(path)]
6264
self.paths.append(path)
6365

6466
def __iter__(self):

bbot/scanner/preset/preset.py

Lines changed: 42 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ def blacklist(self):
308308

309309
@property
310310
def preset_dir(self):
311-
return self.bbot_home / "presets"
311+
return (self.bbot_home / "presets").expanduser().resolve()
312312

313313
@property
314314
def default_output_modules(self):
@@ -413,30 +413,32 @@ def bake(self, scan=None):
413413
self.log_debug("Getting baked")
414414
# create a copy of self
415415
baked_preset = copy(self)
416-
baked_preset.scan = scan
416+
417417
# copy core
418418
baked_preset.core = self.core.copy()
419-
# copy module loader
420-
baked_preset._module_loader = self.module_loader.copy()
421-
# prepare os environment
422-
os_environ = baked_preset.environ.prepare()
423-
# find and replace preloaded modules with os environ
424-
# this is different from the config variable substitution because it modifies
425-
# the preloaded modules, i.e. their ansible playbooks
426-
baked_preset.module_loader.find_and_replace(**os_environ)
427-
# update os environ
428-
os.environ.clear()
429-
os.environ.update(os_environ)
430419

431-
# validate flags, config options
432-
baked_preset.validate()
420+
if scan is not None:
421+
baked_preset.scan = scan
422+
# copy module loader
423+
baked_preset._module_loader = self.module_loader.copy()
424+
# prepare os environment
425+
os_environ = baked_preset.environ.prepare()
426+
# find and replace preloaded modules with os environ
427+
# this is different from the config variable substitution because it modifies
428+
# the preloaded modules, i.e. their ansible playbooks
429+
baked_preset.module_loader.find_and_replace(**os_environ)
430+
# update os environ
431+
os.environ.clear()
432+
os.environ.update(os_environ)
433+
434+
# assign baked preset to our scan
435+
scan.preset = baked_preset
433436

434437
# validate log level options
435438
baked_preset.apply_log_level(apply_core=scan is not None)
436439

437-
# assign baked preset to our scan
438-
if scan is not None:
439-
scan.preset = baked_preset
440+
# validate flags, config options
441+
baked_preset.validate()
440442

441443
# now that our requirements / exclusions are validated, we can start enabling modules
442444
# enable scan modules
@@ -483,15 +485,19 @@ def bake(self, scan=None):
483485
from bbot.scanner.target import BBOTTarget
484486

485487
baked_preset._target = BBOTTarget(
486-
*list(self._seeds), whitelist=self._whitelist, blacklist=self._blacklist, strict_scope=self.strict_scope
488+
*list(self._seeds),
489+
whitelist=self._whitelist,
490+
blacklist=self._blacklist,
491+
strict_scope=self.strict_scope,
487492
)
488493

489-
# evaluate conditions
490-
if baked_preset.conditions:
491-
from .conditions import ConditionEvaluator
494+
if scan is not None:
495+
# evaluate conditions
496+
if baked_preset.conditions:
497+
from .conditions import ConditionEvaluator
492498

493-
evaluator = ConditionEvaluator(baked_preset)
494-
evaluator.evaluate()
499+
evaluator = ConditionEvaluator(baked_preset)
500+
evaluator.evaluate()
495501

496502
self._baked = True
497503
return baked_preset
@@ -562,6 +568,12 @@ def strict_scope(self):
562568
return self.scope_config.get("strict", False)
563569

564570
def apply_log_level(self, apply_core=False):
571+
"""
572+
Apply the log level to the preset.
573+
574+
Args:
575+
apply_core (bool, optional): If True, apply the log level to the core logger.
576+
"""
565577
# silent takes precedence
566578
if self.silent:
567579
self.verbose = False
@@ -920,20 +932,17 @@ def all_presets(self):
920932
"""
921933
Recursively find all the presets and return them as a dictionary
922934
"""
923-
preset_dir = self.preset_dir
924-
home_dir = Path.home()
925-
926935
# first, add local preset dir to PRESET_PATH
927936
PRESET_PATH.add_path(self.preset_dir)
928937

929938
# ensure local preset directory exists
930-
mkdir(preset_dir)
939+
mkdir(self.preset_dir)
931940

932941
global DEFAULT_PRESETS
933942
if DEFAULT_PRESETS is None:
934943
presets = {}
935-
for ext in ("yml", "yaml"):
936-
for preset_path in PRESET_PATH:
944+
for preset_path in PRESET_PATH:
945+
for ext in ("yml", "yaml"):
937946
# for every yaml file
938947
for original_filename in preset_path.rglob(f"**/*.{ext}"):
939948
# not including symlinks
@@ -957,18 +966,14 @@ def all_presets(self):
957966

958967
local_preset = original_filename
959968
# populate symlinks in local preset dir
960-
if not original_filename.is_relative_to(preset_dir):
969+
if not original_filename.is_relative_to(self.preset_dir):
961970
relative_preset = original_filename.relative_to(preset_path)
962-
local_preset = preset_dir / relative_preset
971+
local_preset = self.preset_dir / relative_preset
963972
mkdir(local_preset.parent, check_writable=False)
964973
if not local_preset.exists():
965974
local_preset.symlink_to(original_filename)
966975

967-
# collapse home directory into "~"
968-
if local_preset.is_relative_to(home_dir):
969-
local_preset = Path("~") / local_preset.relative_to(home_dir)
970-
971-
presets[local_preset] = (loaded_preset, category, preset_path, original_filename)
976+
presets[local_preset.stem] = (loaded_preset, category, preset_path, original_filename)
972977

973978
# sort by name
974979
DEFAULT_PRESETS = dict(sorted(presets.items(), key=lambda x: x[-1][0].name))

bbot/scripts/docs.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -198,15 +198,15 @@ def update_individual_module_options():
198198
update_md_files("BBOT PRESETS", bbot_presets_table)
199199

200200
# BBOT presets
201-
for yaml_file, (loaded_preset, category, preset_path, original_filename) in DEFAULT_PRESET.all_presets.items():
201+
for _, (loaded_preset, category, preset_path, original_filename) in DEFAULT_PRESET.all_presets.items():
202202
preset_yaml = f"""
203-
```yaml title={yaml_file.name}
203+
```yaml title={preset_path.name}
204204
{loaded_preset._yaml_str}
205205
```
206206
"""
207207
preset_yaml_expandable = f"""
208208
<details>
209-
<summary><b><code>{yaml_file.name}</code></b></summary>
209+
<summary><b><code>{preset_path.name}</code></b></summary>
210210
211211
```yaml
212212
{loaded_preset._yaml_str}
@@ -218,11 +218,11 @@ def update_individual_module_options():
218218
update_md_files(f"BBOT {loaded_preset.name.upper()} PRESET EXPANDABLE", preset_yaml_expandable)
219219

220220
content = []
221-
for yaml_file, (loaded_preset, category, preset_path, original_filename) in DEFAULT_PRESET.all_presets.items():
221+
for _, (loaded_preset, category, preset_path, original_filename) in DEFAULT_PRESET.all_presets.items():
222222
yaml_str = loaded_preset._yaml_str
223223
indent = " " * 4
224224
yaml_str = f"\n{indent}".join(yaml_str.splitlines())
225-
filename = homedir_collapseuser(yaml_file)
225+
filename = homedir_collapseuser(preset_path)
226226

227227
num_modules = len(loaded_preset.scan_modules)
228228
modules = ", ".join(sorted([f"`{m}`" for m in loaded_preset.scan_modules]))

0 commit comments

Comments
 (0)