Skip to content

Commit 0201f69

Browse files
authored
Merge branch 'dev' into wayback-upgrade
2 parents f506101 + 3148e7f commit 0201f69

File tree

12 files changed

+193
-184
lines changed

12 files changed

+193
-184
lines changed

.github/workflows/distro_tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
elif [ "$ID" = "arch" ]; then
3131
pacman -Syu --noconfirm curl docker git bash base-devel
3232
elif [ "$ID" = "fedora" ]; then
33-
dnf install -y curl docker git bash gcc make openssl-devel bzip2-devel libffi-devel zlib-devel xz-devel tk-devel gdbm-devel readline-devel sqlite-devel python3-libdnf5
33+
dnf install -y curl docker git bash gcc make patch p7zip p7zip-plugins openssl-devel bzip2-devel libffi-devel zlib-devel xz-devel tk-devel gdbm-devel readline-devel sqlite-devel python3-libdnf5
3434
elif [ "$ID" = "gentoo" ]; then
3535
echo "media-libs/libglvnd X" >> /etc/portage/package.use/libglvnd
3636
emerge-webrsync

bbot/core/helpers/misc.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1333,7 +1333,11 @@ def which(*executables, path=None):
13331333
for e in executables:
13341334
location = shutil.which(e, path=path)
13351335
if location:
1336-
return location
1336+
# Resolve directory symlinks but preserve the binary name.
1337+
# This fixes native 7zip on Fedora where /usr/sbin -> bin symlink
1338+
# causes codec loading to fail when invoked as /usr/sbin/7z.
1339+
resolved_dir = os.path.realpath(os.path.dirname(location))
1340+
return os.path.join(resolved_dir, os.path.basename(location))
13371341

13381342

13391343
def search_dict_by_key(key, d):

bbot/core/helpers/web/envelopes.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,36 @@ def get_subparam(self, key=None, recursive=True):
166166
data = data[segment]
167167
return data
168168

169+
def pack_value(self, value, key=None):
170+
"""
171+
Pack a value through the envelope chain WITHOUT modifying internal state.
172+
"""
173+
if key is None:
174+
key = self.selected_subparam
175+
176+
inner = self.unpacked_data(recursive=False)
177+
178+
if hasattr(inner, "pack_value"):
179+
# Inner is another envelope - delegate down the chain
180+
data = inner.pack_value(value, key)
181+
elif self.singleton:
182+
# At the leaf singleton - use the new value directly
183+
data = value
184+
else:
185+
# At the leaf non-singleton (JSON/XML) - copy the data and substitute
186+
import copy
187+
188+
if key is None:
189+
raise ValueError("No subparam selected for non-singleton envelope")
190+
data = copy.deepcopy(inner)
191+
# In the loop: Traverse all the way down to the parent of the target value (all segments except the last),
192+
target = data
193+
for segment in key[:-1]:
194+
target = target[segment]
195+
# Use the final segment to actually assign the value.
196+
target[key[-1]] = value
197+
return self._pack(data)
198+
169199
def set_subparam(self, key=None, value=None, recursive=True):
170200
envelope = self
171201
if recursive:

bbot/modules/internal/unarchive.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ class unarchive(BaseInternalModule):
1717
async def setup(self):
1818
self.ignore_compressions = ["application/java-archive", "application/vnd.android.package-archive"]
1919
self.compression_methods = {
20-
"zip": ["7z", "x", '-p""', "-aoa", "{filename}", "-o{extract_dir}/"],
20+
"zip": ["7z", "x", "-aoa", "{filename}", "-o{extract_dir}/"],
2121
"bzip2": ["tar", "--overwrite", "-xvjf", "{filename}", "-C", "{extract_dir}/"],
2222
"xz": ["tar", "--overwrite", "-xvJf", "{filename}", "-C", "{extract_dir}/"],
23-
"7z": ["7z", "x", '-p""', "-aoa", "{filename}", "-o{extract_dir}/"],
24-
# "rar": ["7z", "x", '-p""', "-aoa", "{filename}", "-o{extract_dir}/"],
25-
# "lzma": ["7z", "x", '-p""', "-aoa", "{filename}", "-o{extract_dir}/"],
23+
"7z": ["7z", "x", "-aoa", "{filename}", "-o{extract_dir}/"],
24+
# "rar": ["7z", "x", "-aoa", "{filename}", "-o{extract_dir}/"],
25+
# "lzma": ["7z", "x", "-aoa", "{filename}", "-o{extract_dir}/"],
2626
"tar": ["tar", "--overwrite", "-xvf", "{filename}", "-C", "{extract_dir}/"],
2727
"gzip": ["tar", "--overwrite", "-xvzf", "{filename}", "-C", "{extract_dir}/"],
2828
}

bbot/modules/lightfuzz/submodules/base.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -265,13 +265,16 @@ def incoming_probe_value(self, populate_empty=True):
265265

266266
def outgoing_probe_value(self, outgoing_probe_value):
267267
"""
268-
Transparently modifies the outgoing probe value (fuzz probe being sent to the target), given any envelopes that may have been identified, so that fuzzing within the envelopes can occur.
268+
Transparently packs the outgoing probe value (fuzz probe being sent to the target) through
269+
any envelopes that may have been identified, so that fuzzing within the envelopes can occur.
270+
271+
Uses pack_value() to avoid mutating the envelope's internal state, preventing cross-contamination
272+
between submodules that share the same event/envelope object.
269273
"""
270274
self.debug(f"outgoing_probe_value (before packing): {outgoing_probe_value} / {self.event}")
271275
envelopes = getattr(self.event, "envelopes", None)
272276
if envelopes is not None:
273-
envelopes.set_subparam(value=outgoing_probe_value)
274-
outgoing_probe_value = envelopes.pack()
277+
outgoing_probe_value = envelopes.pack_value(outgoing_probe_value)
275278
self.debug(
276279
f"outgoing_probe_value (after packing): {outgoing_probe_value} with envelopes [{envelopes}] / {self.event}"
277280
)

bbot/test/test_step_1/test_web_envelopes.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,3 +341,77 @@ async def test_web_envelopes():
341341

342342
tiny_base64 = BaseEnvelope.detect("YWJi")
343343
assert isinstance(tiny_base64, TextEnvelope)
344+
345+
346+
async def test_web_envelope_pack_value():
347+
"""
348+
Test pack_value() - encodes a value through the envelope chain without modifying internal state.
349+
"""
350+
import base64
351+
import json
352+
353+
from bbot.core.helpers.web.envelopes import BaseEnvelope
354+
355+
# Text envelope (singleton, transparent)
356+
text_envelope = BaseEnvelope.detect("original_text")
357+
assert text_envelope.pack_value("new_text") == "new_text"
358+
assert text_envelope.get_subparam() == "original_text"
359+
360+
# Hex envelope (singleton chain: hex -> text)
361+
hex_envelope = BaseEnvelope.detect("706172616d") # "param" in hex
362+
packed = hex_envelope.pack_value("modified")
363+
assert packed == "modified".encode().hex()
364+
assert hex_envelope.get_subparam() == "param"
365+
366+
# Base64 envelope (singleton chain: base64 -> text)
367+
b64_envelope = BaseEnvelope.detect("cGFyYW0=") # "param" in base64
368+
packed = b64_envelope.pack_value("modified")
369+
assert packed == base64.b64encode(b"modified").decode()
370+
assert b64_envelope.get_subparam() == "param"
371+
372+
# Nested hex -> base64 -> text chain
373+
nested_envelope = BaseEnvelope.detect("634746795957303d") # hex(base64("param"))
374+
packed = nested_envelope.pack_value("modified")
375+
expected = base64.b64encode(b"modified").decode().encode().hex()
376+
assert packed == expected
377+
assert nested_envelope.get_subparam() == "param"
378+
379+
# URL envelope (singleton chain: url -> text)
380+
url_envelope = BaseEnvelope.detect("a%20b%20c")
381+
packed = url_envelope.pack_value("x y z")
382+
assert packed == "x%20y%20z"
383+
assert url_envelope.get_subparam() == "a b c"
384+
385+
# JSON inside base64 (non-singleton: base64 -> json) - only the selected subparam is substituted in the output
386+
b64_json = BaseEnvelope.detect("eyJwYXJhbTEiOiAidmFsMSIsICJwYXJhbTIiOiB7InBhcmFtMyI6ICJ2YWwzIn19")
387+
b64_json.selected_subparam = ["param2", "param3"]
388+
packed = b64_json.pack_value("new_val3")
389+
decoded_json = json.loads(base64.b64decode(packed).decode())
390+
assert decoded_json["param1"] == "val1"
391+
assert decoded_json["param2"]["param3"] == "new_val3"
392+
assert b64_json.get_subparam() == "val3"
393+
assert b64_json.get_subparam(["param1"]) == "val1"
394+
395+
# Repeated calls do not accumulate - each starts from the original state
396+
hex_envelope = BaseEnvelope.detect("706172616d")
397+
hex_envelope.pack_value("first_modification")
398+
hex_envelope.pack_value("second_modification")
399+
hex_envelope.pack_value("third_modification")
400+
assert hex_envelope.get_subparam() == "param"
401+
402+
# Multiple callers sharing the same envelope each produce correct output independently
403+
shared_envelope = BaseEnvelope.detect("706172616d") # "param" in hex
404+
405+
probe_a = shared_envelope.pack_value("param' OR 1=1--")
406+
assert probe_a == "param' OR 1=1--".encode().hex()
407+
assert shared_envelope.get_subparam() == "param"
408+
409+
probe_b = shared_envelope.pack_value("param| echo 1234 |")
410+
assert probe_b == "param| echo 1234 |".encode().hex()
411+
assert shared_envelope.get_subparam() == "param"
412+
413+
probe_c = shared_envelope.pack_value("../../etc/passwd")
414+
assert probe_c == "../../etc/passwd".encode().hex()
415+
416+
assert shared_envelope.get_subparam() == "param"
417+
assert shared_envelope.pack() == "706172616d"

bbot/test/test_step_2/module_tests/test_module_lightfuzz.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1885,3 +1885,29 @@ def check(self, module_test, events):
18851885

18861886
assert web_parameter_emitted, "WEB_PARAMETER was not emitted"
18871887
assert esi_finding_emitted, "ESI FINDING not emitted"
1888+
1889+
1890+
# Envelope state isolation: crypto error detection with all submodules enabled.
1891+
# Crypto runs after sqli/cmdi/xss/path/ssti. Each prior submodule calls outgoing_probe_value()
1892+
# which must not corrupt the envelope state that crypto reads via incoming_probe_value().
1893+
class Test_Lightfuzz_envelope_isolation_crypto(Test_Lightfuzz_crypto_error):
1894+
config_overrides = {
1895+
"interactsh_disable": True,
1896+
"modules": {
1897+
"lightfuzz": {
1898+
"enabled_submodules": ["sqli", "cmdi", "xss", "path", "ssti", "crypto", "serial", "esi"],
1899+
}
1900+
},
1901+
}
1902+
1903+
1904+
# Envelope state isolation: padding oracle detection with all submodules enabled.
1905+
class Test_Lightfuzz_envelope_isolation_paddingoracle(Test_Lightfuzz_PaddingOracleDetection):
1906+
config_overrides = {
1907+
"interactsh_disable": True,
1908+
"modules": {
1909+
"lightfuzz": {
1910+
"enabled_submodules": ["sqli", "cmdi", "xss", "path", "ssti", "crypto", "serial", "esi"],
1911+
}
1912+
},
1913+
}

bbot/test/test_step_2/module_tests/test_module_unarchive.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,11 @@ async def setup_after_prep(self, module_test):
3333
tar_file = temp_path / "test.tar"
3434
tgz_file = temp_path / "test.tgz"
3535
commands = [
36-
("7z", "a", '-p""', "-aoa", f"{zip_file}", f"{text_file}"),
37-
("7z", "a", '-p""', "-aoa", f"{zip_zip_file}", f"{zip_file}"),
36+
("7z", "a", "-aoa", f"{zip_file}", f"{text_file}"),
37+
("7z", "a", "-aoa", f"{zip_zip_file}", f"{zip_file}"),
3838
("tar", "-C", f"{temp_path}", "-cvjf", f"{bz2_file}", f"{text_file.name}"),
3939
("tar", "-C", f"{temp_path}", "-cvJf", f"{xz_file}", f"{text_file.name}"),
40-
("7z", "a", '-p""', "-aoa", f"{zip7_file}", f"{text_file}"),
40+
("7z", "a", "-aoa", f"{zip7_file}", f"{text_file}"),
4141
# ("tar", "-C", f"{temp_path}", "--lzma", "-cvf", f"{lzma_file}", f"{text_file.name}"),
4242
("tar", "-C", f"{temp_path}", "-cvf", f"{tar_file}", f"{text_file.name}"),
4343
("tar", "-C", f"{temp_path}", "-cvzf", f"{tgz_file}", f"{text_file.name}"),

docs/modules/nuclei.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ The Nuclei module has many configuration options:
5252
| modules.nuclei.silent | bool | Don't display nuclei's banner or status messages | False |
5353
| modules.nuclei.tags | str | execute a subset of templates that contain the provided tags | |
5454
| modules.nuclei.templates | str | template or template directory paths to include in the scan | |
55-
| modules.nuclei.version | str | nuclei version | 3.6.2 |
55+
| modules.nuclei.version | str | nuclei version | 3.7.0 |
5656
<!-- END BBOT MODULE OPTIONS NUCLEI -->
5757
5858
Most of these you probably will **NOT** want to change. In particular, we advise against changing the version of Nuclei, as it's possible the latest version won't work right with BBOT.

docs/scanning/configuration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@ In addition to the stated options for each module, the following universal optio
451451
| modules.nuclei.silent | bool | Don't display nuclei's banner or status messages | False |
452452
| modules.nuclei.tags | str | execute a subset of templates that contain the provided tags | |
453453
| modules.nuclei.templates | str | template or template directory paths to include in the scan | |
454-
| modules.nuclei.version | str | nuclei version | 3.6.2 |
454+
| modules.nuclei.version | str | nuclei version | 3.7.0 |
455455
| modules.oauth.try_all | bool | Check for OAUTH/IODC on every subdomain and URL. | False |
456456
| modules.paramminer_cookies.recycle_words | bool | Attempt to use words found during the scan on all other endpoints | False |
457457
| modules.paramminer_cookies.skip_boring_words | bool | Remove commonly uninteresting words from the wordlist | True |

0 commit comments

Comments
 (0)