Skip to content
Open
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
15 changes: 12 additions & 3 deletions bbot/modules/dnsbrute.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class dnsbrute(subdomain_enum):
"max_depth": 5,
}
options_desc = {
"wordlist": "Subdomain wordlist URL",
"wordlist": "Subdomain wordlist URL or file path. Accepts a list of URLs/paths to merge multiple wordlists (duplicates are removed).",
"max_depth": "How many subdomains deep to brute force, i.e. 5.4.3.2.1.evilcorp.com",
}
deps_common = ["massdns"]
Expand All @@ -24,14 +24,23 @@ class dnsbrute(subdomain_enum):
_qsize = 10000

async def setup_deps(self):
self.subdomain_file = await self.helpers.wordlist(self.config.get("wordlist"))
wordlist = self.config.get("wordlist")
if isinstance(wordlist, str):
wordlist = [wordlist]
else:
wordlist = list(wordlist)
self.subdomain_files = []
for w in wordlist:
self.subdomain_files.append(await self.helpers.wordlist(w))
# tell the dnsbrute helper to fetch the resolver file
await self.helpers.dns.brute.resolver_file()
return True

async def setup(self):
self.max_depth = max(1, self.config.get("max_depth", 5))
self.subdomain_list = set(self.helpers.read_file(self.subdomain_file))
self.subdomain_list = set()
for f in self.subdomain_files:
self.subdomain_list.update(self.helpers.read_file(f))
self.wordlist_size = len(self.subdomain_list)
return await super().setup()

Expand Down
43 changes: 43 additions & 0 deletions bbot/test/test_step_2/module_tests/test_module_dnsbrute.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,49 @@
)


class TestDnsbruteMultiWordlist(ModuleTestBase):
"""Test that multiple wordlists are merged and deduplicated correctly."""

module_name = "dnsbrute"
wordlist_1 = tempwordlist(["www", "shared"])
wordlist_2 = tempwordlist(["asdf", "shared"])
config_overrides = {
"modules": {"dnsbrute": {"wordlist": [str(wordlist_1), str(wordlist_2)], "max_depth": 3}}
}

async def setup_after_prep(self, module_test):
old_run_live = module_test.scan.helpers.run_live

async def new_run_live(*command, check=False, text=True, **kwargs):
if "massdns" in command[:2]:
_input = [l async for l in kwargs["input"]]
if "asdf.blacklanternsecurity.com" in _input:

Check failure

Code scanning / CodeQL

Incomplete URL substring sanitization High test

The string
asdf.blacklanternsecurity.com
may be at an arbitrary position in the sanitized URL.
Comment thread
liquidsec marked this conversation as resolved.
Dismissed
yield """{"name": "asdf.blacklanternsecurity.com.", "type": "A", "class": "IN", "status": "NOERROR", "rx_ts": 1713974911725326170, "data": {"answers": [{"ttl": 86400, "type": "A", "class": "IN", "name": "asdf.blacklanternsecurity.com.", "data": "1.2.3.4."}]}, "flags": ["rd", "ra"], "resolver": "195.226.187.130:53", "proto": "UDP"}"""
else:
async for _ in old_run_live(*command, check=False, text=True, **kwargs):
yield _

module_test.monkeypatch.setattr(module_test.scan.helpers, "run_live", new_run_live)

await module_test.mock_dns(
{
"blacklanternsecurity.com": {"A": ["4.3.2.1"]},
"asdf.blacklanternsecurity.com": {"A": ["1.2.3.4"]},
}
)

def check(self, module_test, events):
# "shared" appears in both wordlists - merged set should deduplicate it
assert module_test.module.wordlist_size == 3, (
f"Expected 3 unique words from 2 overlapping wordlists, got {module_test.module.wordlist_size}"
)
assert module_test.module.subdomain_list == {"www", "asdf", "shared"}
# Confirm the scan found the subdomain coming from the second wordlist
assert any(
e.data == "asdf.blacklanternsecurity.com" and str(e.module) == "dnsbrute" for e in events
), "Expected asdf.blacklanternsecurity.com from second wordlist"


class TestDnsbruteCanaryCheck(ModuleTestBase):
"""Test that the canary check correctly aborts brute-forcing on wildcard domains.

Expand Down
Loading