Skip to content

Commit 7296554

Browse files
Merge pull request #2492 from blacklanternsecurity/dev
Dev -> Stable 2.6.0
2 parents 3b41155 + f4da38f commit 7296554

Some content is hidden

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

55 files changed

+2019
-1511
lines changed

.github/workflows/version_updater.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,12 @@ jobs:
3636
- name: Get current version
3737
id: get-current-version
3838
run: |
39-
version=$(grep -m 1 -oP '(?<=version": ")[^"]*' bbot/modules/deadly/nuclei.py)
39+
version=$(grep -m 1 -oP '(?<=version": ")[^"]*' bbot/modules/nuclei.py)
4040
echo "current_version=$version" >> $GITHUB_ENV
4141
- name: Update version
4242
id: update-version
4343
if: env.latest_version != env.current_version
44-
run: "sed -i '0,/\"version\": \".*\",/ s/\"version\": \".*\",/\"version\": \"${{ env.latest_version }}\",/g' bbot/modules/deadly/nuclei.py"
44+
run: "sed -i '0,/\"version\": \".*\",/ s/\"version\": \".*\",/\"version\": \"${{ env.latest_version }}\",/g' bbot/modules/nuclei.py"
4545
- name: Create pull request to update the version
4646
if: steps.update-version.outcome == 'success'
4747
uses: peter-evans/create-pull-request@v7
@@ -50,7 +50,7 @@ jobs:
5050
commit-message: "Update nuclei"
5151
title: "Update nuclei to ${{ env.latest_version }}"
5252
body: |
53-
This PR uses https://api.github.com/repos/projectdiscovery/nuclei/releases/latest to obtain the latest version of nuclei and update the version in bbot/modules/deadly/nuclei.py."
53+
This PR uses https://api.github.com/repos/projectdiscovery/nuclei/releases/latest to obtain the latest version of nuclei and update the version in bbot/modules/nuclei.py."
5454
5555
# Release notes:
5656
${{ env.release_notes }}

bbot/core/helpers/command.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ async def run_live(self, *command, check=False, text=True, idle_timeout=None, **
123123
proc.send_signal(SIGINT)
124124
raise
125125
except ValueError as e:
126-
command_str = " ".join([str(c) for c in command])
126+
command_str = " ".join(command)
127127
log.warning(f"Error executing command {command_str}: {e}")
128128
log.trace(traceback.format_exc())
129129
continue
@@ -185,7 +185,9 @@ async def _spawn_proc(self, *command, **kwargs):
185185
try:
186186
command, kwargs = self._prepare_command_kwargs(command, kwargs)
187187
except SubprocessError as e:
188-
log.warning(e)
188+
command_str = " ".join([str(s) for s in command])
189+
log.warning(f"Error running command: '{command_str}': {e}")
190+
log.trace(traceback.format_exc())
189191
return None, None, None
190192
_input = kwargs.pop("input", None)
191193
if _input is not None:
@@ -279,6 +281,7 @@ def _prepare_command_kwargs(self, command, kwargs):
279281

280282
if len(command) == 1 and isinstance(command[0], (list, tuple)):
281283
command = command[0]
284+
282285
command = [str(s) for s in command]
283286

284287
if not command:

bbot/core/helpers/depsinstaller/installer.py

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,20 @@ class DepsInstaller:
3232
"bash": "bash",
3333
"which": "which",
3434
"tar": "tar",
35+
"xz": [
36+
{
37+
"name": "Install xz-utils (Debian)",
38+
"package": {"name": ["xz-utils"], "state": "present"},
39+
"become": True,
40+
"when": "ansible_facts['os_family'] == 'Debian'",
41+
},
42+
{
43+
"name": "Install xz (Non-Debian)",
44+
"package": {"name": ["xz"], "state": "present"},
45+
"become": True,
46+
"when": "ansible_facts['os_family'] != 'Debian'",
47+
},
48+
],
3549
# debian why are you like this
3650
"7z": [
3751
{
@@ -53,6 +67,44 @@ class DepsInstaller:
5367
"when": "ansible_facts['distribution'] == 'Fedora'",
5468
},
5569
],
70+
# to compile just about any tool, we need the openssl dev headers
71+
"openssl": [
72+
{
73+
"name": "Install OpenSSL library and development headers (Debian/Ubuntu)",
74+
"package": {"name": ["libssl-dev", "openssl"], "state": "present"},
75+
"become": True,
76+
"when": "ansible_facts['os_family'] == 'Debian'",
77+
"ignore_errors": True,
78+
},
79+
{
80+
"name": "Install OpenSSL library and development headers (RedHat/CentOS/Fedora)",
81+
"package": {"name": ["openssl", "openssl-devel"], "state": "present"},
82+
"become": True,
83+
"when": "ansible_facts['os_family'] == 'RedHat' or ansible_facts['os_family'] == 'Suse' ",
84+
"ignore_errors": True,
85+
},
86+
{
87+
"name": "Install OpenSSL library and development headers (Arch)",
88+
"package": {"name": ["openssl"], "state": "present"},
89+
"become": True,
90+
"when": "ansible_facts['os_family'] == 'Archlinux'",
91+
"ignore_errors": True,
92+
},
93+
{
94+
"name": "Install OpenSSL library and development headers (Alpine)",
95+
"package": {"name": ["openssl", "openssl-dev"], "state": "present"},
96+
"become": True,
97+
"when": "ansible_facts['os_family'] == 'Alpine'",
98+
"ignore_errors": True,
99+
},
100+
{
101+
"name": "Install OpenSSL library and development headers (FreeBSD)",
102+
"package": {"name": ["openssl"], "state": "present"},
103+
"become": True,
104+
"when": "ansible_facts['os_family'] == 'FreeBSD'",
105+
"ignore_errors": True,
106+
},
107+
],
56108
}
57109

58110
def __init__(self, parent_helper):
@@ -171,11 +223,6 @@ async def install_module(self, module):
171223
success = True
172224
preloaded = self.all_modules_preloaded[module]
173225

174-
# ansible tasks
175-
ansible_tasks = preloaded["deps"]["ansible"]
176-
if ansible_tasks:
177-
success &= self.tasks(module, ansible_tasks)
178-
179226
# apt
180227
deps_apt = preloaded["deps"]["apt"]
181228
if deps_apt:
@@ -196,7 +243,7 @@ async def install_module(self, module):
196243
deps_common = preloaded["deps"]["common"]
197244
if deps_common:
198245
for dep_common in deps_common:
199-
if self.setup_status.get(dep_common, False) is True:
246+
if self.setup_status.get(dep_common, False) is True and self.deps_behavior != "force_install":
200247
log.debug(
201248
f'Skipping installation of dependency "{dep_common}" for module "{module}" since it is already installed'
202249
)
@@ -206,6 +253,11 @@ async def install_module(self, module):
206253
self.setup_status[dep_common] = result
207254
success &= result
208255

256+
# ansible tasks
257+
ansible_tasks = preloaded["deps"]["ansible"]
258+
if ansible_tasks:
259+
success &= self.tasks(module, ansible_tasks)
260+
209261
return success
210262

211263
async def pip_install(self, packages, constraints=None):

bbot/core/helpers/misc.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -831,7 +831,9 @@ def rand_string(length=10, digits=True, numeric_only=False):
831831
return "".join(random.choice(pool) for _ in range(length))
832832

833833

834-
def truncate_string(s, n):
834+
def truncate_string(s: str, n: int) -> str:
835+
if not isinstance(s, str):
836+
raise ValueError(f"Expected string, got {type(s)}")
835837
if len(s) > n:
836838
return s[: n - 3] + "..."
837839
else:
@@ -1309,7 +1311,7 @@ def make_netloc(host, port=None):
13091311
return f"{host}:{port}"
13101312

13111313

1312-
def which(*executables):
1314+
def which(*executables, path=None):
13131315
"""Finds the full path of the first available executable from a list of executables.
13141316
13151317
Args:
@@ -1325,7 +1327,7 @@ def which(*executables):
13251327
import shutil
13261328

13271329
for e in executables:
1328-
location = shutil.which(e)
1330+
location = shutil.which(e, path=path)
13291331
if location:
13301332
return location
13311333

@@ -1642,7 +1644,7 @@ def filesize(f):
16421644
return 0
16431645

16441646

1645-
def rm_rf(f):
1647+
def rm_rf(f, ignore_errors=False):
16461648
"""Recursively delete a directory
16471649
16481650
Args:
@@ -1653,7 +1655,7 @@ def rm_rf(f):
16531655
"""
16541656
import shutil
16551657

1656-
shutil.rmtree(f)
1658+
shutil.rmtree(f, ignore_errors=ignore_errors)
16571659

16581660

16591661
def clean_old(d, keep=10, filter=lambda x: True, key=latest_mtime, reverse=True, raise_error=False):

bbot/core/helpers/names_generator.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"delicious",
5151
"demented",
5252
"demonic",
53+
"demonstrative",
5354
"depraved",
5455
"depressed",
5556
"deranged",
@@ -172,6 +173,7 @@
172173
"pasty",
173174
"peckish",
174175
"pedantic",
176+
"pensive",
175177
"pernicious",
176178
"perturbed",
177179
"perverted",
@@ -201,8 +203,10 @@
201203
"reckless",
202204
"reductive",
203205
"ripped",
206+
"ruthless",
204207
"sadistic",
205208
"satanic",
209+
"saucy",
206210
"savvy",
207211
"scheming",
208212
"schizophrenic",
@@ -668,6 +672,7 @@
668672
"tracy",
669673
"travis",
670674
"treebeard",
675+
"trent",
671676
"triss",
672677
"tyler",
673678
"tyrell",

bbot/core/helpers/web/engine.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ async def _acatch(self, url, raise_error):
208208
raise
209209
else:
210210
log.warning(
211-
f"Invalid URL (possibly due to dangerous redirect) on request to : {url}: {truncate_string(e, 200)}"
211+
f"Invalid URL (possibly due to dangerous redirect) on request to : {url}: {truncate_string(str(e), 200)}"
212212
)
213213
log.trace(traceback.format_exc())
214214
except ssl.SSLError as e:

bbot/core/shared_deps.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,24 @@
108108
"when": "ansible_facts['os_family'] == 'Debian'",
109109
"ignore_errors": True,
110110
},
111+
{
112+
"name": "Get latest Chromium version (Darwin x86_64)",
113+
"uri": {
114+
"url": "https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Mac%2FLAST_CHANGE?alt=media",
115+
"return_content": True,
116+
},
117+
"register": "chromium_version_darwin_x86_64",
118+
"when": "ansible_facts['os_family'] == 'Darwin' and ansible_facts['architecture'] == 'x86_64'",
119+
},
120+
{
121+
"name": "Get latest Chromium version (Darwin arm64)",
122+
"uri": {
123+
"url": "https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Mac_Arm%2FLAST_CHANGE?alt=media",
124+
"return_content": True,
125+
},
126+
"register": "chromium_version_darwin_arm64",
127+
"when": "ansible_facts['os_family'] == 'Darwin' and ansible_facts['architecture'] == 'arm64'",
128+
},
111129
{
112130
"name": "Download Chromium (Debian)",
113131
"unarchive": {
@@ -119,6 +137,26 @@
119137
"when": "ansible_facts['os_family'] == 'Debian'",
120138
"ignore_errors": True,
121139
},
140+
{
141+
"name": "Download Chromium (Darwin x86_64)",
142+
"unarchive": {
143+
"src": "https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Mac%2F{{ chromium_version_darwin_x86_64.content }}%2Fchrome-mac.zip?alt=media",
144+
"remote_src": True,
145+
"dest": "#{BBOT_TOOLS}",
146+
"creates": "#{BBOT_TOOLS}/chrome-mac",
147+
},
148+
"when": "ansible_facts['os_family'] == 'Darwin' and ansible_facts['architecture'] == 'x86_64'",
149+
},
150+
{
151+
"name": "Download Chromium (Darwin arm64)",
152+
"unarchive": {
153+
"src": "https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Mac_Arm%2F{{ chromium_version_darwin_arm64.content }}%2Fchrome-mac.zip?alt=media",
154+
"remote_src": True,
155+
"dest": "#{BBOT_TOOLS}",
156+
"creates": "#{BBOT_TOOLS}/chrome-mac",
157+
},
158+
"when": "ansible_facts['os_family'] == 'Darwin' and ansible_facts['architecture'] == 'arm64'",
159+
},
122160
# Because Ubuntu is a special snowflake, we have to bend over backwards to fix the chrome sandbox
123161
# see https://chromium.googlesource.com/chromium/src/+/main/docs/security/apparmor-userns-restrictions.md
124162
{

bbot/modules/apkpure.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ async def setup(self):
2222
if output_folder:
2323
self.output_dir = Path(output_folder) / "apk_files"
2424
else:
25-
self.output_dir = self.helpers.temp_dir / "apk_files"
25+
self.output_dir = self.scan.temp_dir / "apk_files"
2626
self.helpers.mkdir(self.output_dir)
2727
return await super().setup()
2828

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
from bbot.modules.base import BaseModule
2+
3+
4+
class aspnet_bin_exposure(BaseModule):
5+
watched_events = ["URL"]
6+
produced_events = ["VULNERABILITY"]
7+
flags = ["active", "safe", "web-thorough"]
8+
meta = {
9+
"description": "Check for ASP.NET Security Feature Bypasses (CVE-2023-36899 and CVE-2023-36560)",
10+
"created_date": "2025-01-28",
11+
"author": "@liquidsec",
12+
}
13+
14+
in_scope_only = True
15+
test_dlls = [
16+
"Telerik.Web.UI.dll",
17+
"Newtonsoft.Json.dll",
18+
"System.Net.Http.dll",
19+
"EntityFramework.dll",
20+
"AjaxControlToolkit.dll",
21+
]
22+
23+
@staticmethod
24+
def normalize_url(url):
25+
return str(url.rstrip("/") + "/").lower()
26+
27+
def _incoming_dedup_hash(self, event):
28+
return hash(self.normalize_url(event.data))
29+
30+
async def handle_event(self, event):
31+
normalized_url = self.normalize_url(event.data)
32+
for test_dll in self.test_dlls:
33+
for technique in ["b/(S(X))in/###DLL_PLACEHOLDER###/(S(X))/", "(S(X))/b/(S(X))in/###DLL_PLACEHOLDER###"]:
34+
test_url = f"{normalized_url}{technique.replace('###DLL_PLACEHOLDER###', test_dll)}"
35+
self.debug(f"Sending test URL: [{test_url}]")
36+
kwargs = {"method": "GET", "allow_redirects": False, "timeout": 10}
37+
test_result = await self.helpers.request(test_url, **kwargs)
38+
if test_result:
39+
if test_result.status_code == 200 and (
40+
"content-type" in test_result.headers
41+
and "application/x-msdownload" in test_result.headers["content-type"]
42+
):
43+
self.debug(
44+
f"Got positive result for probe with test url: [{test_url}]. Status Code: [{test_result.status_code}] Content Length: [{len(test_result.content)}]"
45+
)
46+
47+
if test_result.status_code == 200 and (
48+
"content-type" in test_result.headers
49+
and "application/x-msdownload" in test_result.headers["content-type"]
50+
):
51+
confirm_url = (
52+
f"{normalized_url}{technique.replace('###DLL_PLACEHOLDER###', 'oopsnotarealdll.dll')}"
53+
)
54+
confirm_result = await self.helpers.request(confirm_url, **kwargs)
55+
56+
if confirm_result and (
57+
confirm_result.status_code != 200
58+
or not (
59+
"content-type" in confirm_result.headers
60+
and "application/x-msdownload" in confirm_result.headers["content-type"]
61+
)
62+
):
63+
description = f"IIS Bin Directory DLL Exposure. Detection Url: [{test_url}]"
64+
await self.emit_event(
65+
{
66+
"severity": "HIGH",
67+
"host": str(event.host),
68+
"url": normalized_url,
69+
"description": description,
70+
},
71+
"VULNERABILITY",
72+
event,
73+
context="{module} detected IIS Bin Directory DLL Exposure vulnerability",
74+
)
75+
return True
76+
77+
async def filter_event(self, event):
78+
if "dir" in event.tags:
79+
return True
80+
return False

0 commit comments

Comments
 (0)