Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions bbot/scanner/preset/preset.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,33 @@ def bake(self, scan=None):
for output_module in self.default_output_modules:
baked_preset.add_module(output_module, module_type="output", raise_error=False)

# dnsresolve is the intercept module that resolves every event, tags wildcards/unresolved,
# and rewrites wildcard hits to `_wildcard.parent`. Modules that watch DNS_NAME depend on
# those tags to filter false positives; without it, brute-force and passive-enum modules
# emit unverified and wildcard-tainted names. This catches all three explicit opt-outs:
# `-em dnsresolve`, top-level `dnsresolve: false`, and `dns.disable: true`. We check the
# user actions rather than "dnsresolve in modules" so that internal-module-trimming code
# paths (e.g. `--list-module-options` setting `_default_internal_modules = []`) don't trip
# the gate -- those aren't real scans, and the user hasn't asked to disable anything.
dnsresolve_disabled = (
"dnsresolve" in baked_preset.exclude_modules
or baked_preset.config.get("dnsresolve", True) is False
or baked_preset.config.get("dns", {}).get("disable", False)
)
if dnsresolve_disabled:
dns_name_consumers = sorted(
m
for m in baked_preset.modules
if "DNS_NAME" in baked_preset.preloaded_module(m).get("watched_events", [])
)
if dns_name_consumers:
raise ValidationError(
f"dnsresolve is required by these enabled modules but is disabled: {', '.join(dns_name_consumers)}. "
"Use `dns.minimal: true` (resolves A/AAAA only, skips MX/NS/SRV expansion) "
"if you want to reduce DNS overhead while keeping wildcard and unresolved tagging. "
"If you genuinely want no DNS at all, also disable the modules listed above."
)

# create target object
from bbot.scanner.target import BBOTTarget

Expand Down
17 changes: 17 additions & 0 deletions bbot/test/test_step_1/test_presets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1274,3 +1274,20 @@ def test_preset_file_targets(tmp_path):
target_inputs5 = set(preset5._target_list)
assert "nested.evilcorp.com" in target_inputs5
assert "my_targets.txt" not in target_inputs5


def test_preset_dnsresolve_required_by_dns_name_consumers():
# gate must fire for all three opt-out paths when any DNS_NAME-watching module is enabled
for opt_out in (
{"exclude_modules": ["dnsresolve"], "flags": ["subdomain-enum"]},
{"config": {"dnsresolve": False}, "flags": ["subdomain-enum"]},
{"config": {"dns": {"disable": True}}, "flags": ["subdomain-enum"]},
):
with pytest.raises(ValidationError, match="dnsresolve is required"):
Preset(**opt_out).bake()

# dns.minimal keeps dnsresolve in the pipeline -- must NOT fire
Preset(flags=["subdomain-enum"], config={"dns": {"minimal": True}}).bake()

# disabling dnsresolve with no DNS_NAME consumers enabled is allowed
Preset(exclude_modules=["dnsresolve"]).bake()
Loading