Skip to content

Commit 333df29

Browse files
Merge pull request #2266 from blacklanternsecurity/dev
Dev -> Stable 2.4.0
2 parents 18a8a16 + 960de7d commit 333df29

51 files changed

Lines changed: 2144 additions & 1142 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/ISSUE_TEMPLATE/bug_report.md

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,18 @@ assignees: ""
77
---
88

99
**Describe the bug**
10-
What happened?
11-
12-
**Expected behavior**
13-
What was supposed to happen?
10+
What happened vs what was expected?
1411

1512
**BBOT Command**
1613
Example: `bbot -m httpx -t evilcorp.com`
1714

1815
**OS, BBOT Installation Method + Version**
1916
Example: `OS: Arch Linux, Installation method: pip, BBOT version: 1.0.3.545`
20-
Note: You can get the bbot version with `bbot --version`
21-
Note: Windows is **not** supported. We have successfully used BBOT on Docker Desktop in the past, however Windows is highly problematic so if you choose this path you are on your own.
17+
Note: You can get the BBOT version with `bbot --version`
18+
Note: BBOT is designed from the ground up to run on Linux. Windows and MacOS are not officially supported. If you are using one of these platforms, it's recommended to use Docker.
2219

2320
**BBOT Config**
24-
Attach your BBOT config (`bbot --current-config`).
25-
26-
**Logs**
27-
If possible, produce the bug while `--debug` is enabled, and attach the relevant parts of `~/.bbot/logs/bbot.debug.log`
21+
Attach your full BBOT preset (to show it, add `--current-preset` to your BBOT command).
2822

29-
**Screenshots**
30-
If applicable, add screenshots to help explain your problem.
23+
**Logs/Screenshots**
24+
If possible, produce the bug while `--debug` is enabled, and attach the relevant parts of the output.

.github/workflows/distro_tests.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ jobs:
5353
python3.11 -m pipx ensurepath
5454
pipx install poetry
5555
"
56+
- name: Set OS Environment Variable
57+
run: echo "OS_NAME=${{ matrix.os }}" | sed 's|[:/]|_|g' >> $GITHUB_ENV
5658
- name: Run tests
5759
run: |
5860
export PATH="$HOME/.local/bin:$PATH"
@@ -66,5 +68,5 @@ jobs:
6668
if: always()
6769
uses: actions/upload-artifact@v4
6870
with:
69-
name: pytest-debug-logs-${{ matrix.os }}
70-
path: pytest_debug_${{ matrix.os }}.log
71+
name: pytest-debug-logs-${{ env.OS_NAME }}
72+
path: pytest_debug.log

.github/workflows/tests.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Tests (Python Versions)
1+
name: Tests
22
on:
33
push:
44
branches:
@@ -24,6 +24,8 @@ jobs:
2424
uses: actions/setup-python@v5
2525
with:
2626
python-version: ${{ matrix.python-version }}
27+
- name: Set Python Version Environment Variable
28+
run: echo "PYTHON_VERSION=${{ matrix.python-version }}" | sed 's|[:/]|_|g' >> $GITHUB_ENV
2729
- name: Install dependencies
2830
run: |
2931
pip install poetry
@@ -39,8 +41,8 @@ jobs:
3941
if: always()
4042
uses: actions/upload-artifact@v4
4143
with:
42-
name: pytest-debug-logs-${{ matrix.python-version }}
43-
path: pytest_debug_${{ matrix.python-version }}.log
44+
name: pytest-debug-logs-${{ env.PYTHON_VERSION }}
45+
path: pytest_debug.log
4446
- name: Upload Code Coverage
4547
uses: codecov/codecov-action@v5
4648
with:

bbot/cli.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,11 @@ async def _main():
161161
all_modules = list(preset.module_loader.preloaded())
162162
scan.helpers.depsinstaller.force_deps = True
163163
succeeded, failed = await scan.helpers.depsinstaller.install(*all_modules)
164-
log.info("Finished installing module dependencies")
165-
return False if failed else True
164+
if failed:
165+
log.hugewarning(f"Failed to install dependencies for the following modules: {', '.join(failed)}")
166+
return False
167+
log.hugesuccess(f"Successfully installed dependencies for the following modules: {', '.join(succeeded)}")
168+
return True
166169

167170
scan_name = str(scan.name)
168171

@@ -180,13 +183,14 @@ async def _main():
180183

181184
if sys.stdin.isatty():
182185
# warn if any targets belong directly to a cloud provider
183-
for event in scan.target.seeds.events:
184-
if event.type == "DNS_NAME":
185-
cloudcheck_result = scan.helpers.cloudcheck(event.host)
186-
if cloudcheck_result:
187-
scan.hugewarning(
188-
f'YOUR TARGET CONTAINS A CLOUD DOMAIN: "{event.host}". You\'re in for a wild ride!'
189-
)
186+
if not scan.preset.strict_scope:
187+
for event in scan.target.seeds.events:
188+
if event.type == "DNS_NAME":
189+
cloudcheck_result = scan.helpers.cloudcheck(event.host)
190+
if cloudcheck_result:
191+
scan.hugewarning(
192+
f'YOUR TARGET CONTAINS A CLOUD DOMAIN: "{event.host}". You\'re in for a wild ride!'
193+
)
190194

191195
if not options.yes:
192196
log.hugesuccess(f"Scan ready. Press enter to execute {scan.name}")

bbot/core/helpers/depsinstaller/installer.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def __init__(self, parent_helper):
9696
self.ensure_root_lock = Lock()
9797

9898
async def install(self, *modules):
99-
self.install_core_deps()
99+
await self.install_core_deps()
100100
succeeded = []
101101
failed = []
102102
try:
@@ -386,20 +386,34 @@ def ensure_root(self, message=""):
386386
else:
387387
log.warning("Incorrect password")
388388

389-
def install_core_deps(self):
389+
async def install_core_deps(self):
390390
to_install = set()
391391
to_install_friendly = set()
392392
playbook = []
393393
self._install_sudo_askpass()
394394
# ensure tldextract data is cached
395395
self.parent_helper.tldextract("evilcorp.co.uk")
396+
# install any missing commands
396397
for command, package_name_or_playbook in self.CORE_DEPS.items():
397398
if not self.parent_helper.which(command):
398399
to_install_friendly.add(command)
399400
if isinstance(package_name_or_playbook, str):
400401
to_install.add(package_name_or_playbook)
401402
else:
402403
playbook.extend(package_name_or_playbook)
404+
# install ansible community.general collection
405+
if not self.setup_status.get("ansible:community.general", False):
406+
log.info("Installing Ansible Community General Collection")
407+
try:
408+
command = ["ansible-galaxy", "collection", "install", "community.general"]
409+
await self.parent_helper.run(command, check=True)
410+
self.setup_status["ansible:community.general"] = True
411+
log.info("Successfully installed Ansible Community General Collection")
412+
except CalledProcessError as err:
413+
log.warning(
414+
f"Failed to install Ansible Community.General Collection (return code {err.returncode}): {err.stderr}"
415+
)
416+
# construct ansible playbook
403417
if to_install:
404418
playbook.append(
405419
{
@@ -408,6 +422,7 @@ def install_core_deps(self):
408422
"become": True,
409423
}
410424
)
425+
# run playbook
411426
if playbook:
412427
log.info(f"Installing core BBOT dependencies: {','.join(sorted(to_install_friendly))}")
413428
self.ensure_root()

bbot/core/helpers/regex.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ async def search(self, compiled_regex, *args, **kwargs):
3131
self.ensure_compiled_regex(compiled_regex)
3232
return await self.parent_helper.run_in_executor(compiled_regex.search, *args, **kwargs)
3333

34+
async def sub(self, compiled_regex, *args, **kwargs):
35+
self.ensure_compiled_regex(compiled_regex)
36+
return await self.parent_helper.run_in_executor(compiled_regex.sub, *args, **kwargs)
37+
3438
async def findall(self, compiled_regex, *args, **kwargs):
3539
self.ensure_compiled_regex(compiled_regex)
3640
return await self.parent_helper.run_in_executor(compiled_regex.findall, *args, **kwargs)

bbot/core/helpers/validators.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ def clean_url(url: str, url_querystring_remove=True):
176176
scheme = parsed.scheme
177177
except ValueError:
178178
scheme = "https"
179+
port = None
179180
with suppress(Exception):
180181
port = parsed.port
181182
if port is None:

bbot/core/helpers/web/client.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ def build_request(self, *args, **kwargs):
8383
# TODO: re-enable this
8484
if self._target.in_scope(str(request.url)):
8585
for hk, hv in self._web_config.get("http_headers", {}).items():
86+
hv = str(hv)
8687
# don't clobber headers
8788
if hk not in request.headers:
8889
request.headers[hk] = hv

bbot/core/helpers/web/web.py

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ async def download(self, url, **kwargs):
232232
if success:
233233
return filename
234234

235-
async def wordlist(self, path, lines=None, **kwargs):
235+
async def wordlist(self, path, lines=None, zip=False, zip_filename=None, **kwargs):
236236
"""
237237
Asynchronous function for retrieving wordlists, either from a local path or a URL.
238238
Allows for optional line-based truncation and caching. Returns the full path of the wordlist
@@ -242,6 +242,9 @@ async def wordlist(self, path, lines=None, **kwargs):
242242
path (str): The local or remote path of the wordlist.
243243
lines (int, optional): Number of lines to read from the wordlist.
244244
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.
245248
cache_hrs (float, optional): Number of hours to cache the downloaded wordlist.
246249
Defaults to 720 hours (30 days) for remote wordlists.
247250
**kwargs: Additional keyword arguments to pass to the 'download' function for remote wordlists.
@@ -259,6 +262,8 @@ async def wordlist(self, path, lines=None, **kwargs):
259262
Fetching and truncating to the first 100 lines
260263
>>> wordlist_path = await self.helpers.wordlist("/root/rockyou.txt", lines=100)
261264
"""
265+
import zipfile
266+
262267
if not path:
263268
raise WordlistError(f"Invalid wordlist: {path}")
264269
if "cache_hrs" not in kwargs:
@@ -272,6 +277,18 @@ async def wordlist(self, path, lines=None, **kwargs):
272277
if not filename.is_file():
273278
raise WordlistError(f"Unable to find wordlist at {path}")
274279

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+
275292
if lines is None:
276293
return filename
277294
else:
@@ -336,6 +353,7 @@ async def curl(self, *args, **kwargs):
336353
ignore_bbot_global_settings = kwargs.get("ignore_bbot_global_settings", False)
337354

338355
if ignore_bbot_global_settings:
356+
http_timeout = 20 # setting 20 as a worse-case setting
339357
log.debug("ignore_bbot_global_settings enabled. Global settings will not be applied")
340358
else:
341359
http_timeout = self.parent_helper.web_config.get("http_timeout", 20)
@@ -349,12 +367,12 @@ async def curl(self, *args, **kwargs):
349367
for hk, hv in self.web_config.get("http_headers", {}).items():
350368
headers[hk] = hv
351369

352-
# add the timeout
353-
if "timeout" not in kwargs:
354-
timeout = http_timeout
370+
# add the timeout
371+
if "timeout" not in kwargs:
372+
timeout = http_timeout
355373

356-
curl_command.append("-m")
357-
curl_command.append(str(timeout))
374+
curl_command.append("-m")
375+
curl_command.append(str(timeout))
358376

359377
for k, v in headers.items():
360378
if isinstance(v, list):
@@ -400,7 +418,7 @@ async def curl(self, *args, **kwargs):
400418
if raw_body:
401419
curl_command.append("-d")
402420
curl_command.append(raw_body)
403-
421+
log.verbose(f"Running curl command: {curl_command}")
404422
output = (await self.parent_helper.run(curl_command)).stdout
405423
return output
406424

bbot/modules/badsecrets.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class badsecrets(BaseModule):
1717
options_desc = {
1818
"custom_secrets": "Include custom secrets loaded from a local file",
1919
}
20-
deps_pip = ["badsecrets~=0.6.21"]
20+
deps_pip = ["badsecrets~=0.9.29"]
2121

2222
async def setup(self):
2323
self.custom_secrets = None

0 commit comments

Comments
 (0)