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
2 changes: 1 addition & 1 deletion bbot/modules/lightfuzz/lightfuzz.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class lightfuzz(BaseModule):

options = {
"force_common_headers": False,
"enabled_submodules": ["sqli", "cmdi", "xss", "path", "ssti", "crypto", "serial"],
"enabled_submodules": ["sqli", "cmdi", "xss", "path", "ssti", "crypto", "serial", "esi"],
"disable_post": False,
}
options_desc = {
Expand Down
42 changes: 42 additions & 0 deletions bbot/modules/lightfuzz/submodules/esi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from .base import BaseLightfuzz


class esi(BaseLightfuzz):
"""
Detects Edge Side Includes (ESI) processing vulnerabilities.

Tests if the server processes ESI tags by sending a payload containing ESI tags
and checking if the tags are processed (removed) in the response.
"""

# Technique lifted from https://github.com/PortSwigger/active-scan-plus-plus

friendly_name = "Edge Side Includes"

async def check_probe(self, cookies, probe, match):
"""
Sends the probe and checks if the expected match string is found in the response.
"""
probe_result = await self.standard_probe(self.event.data["type"], cookies, probe)
if probe_result and match in probe_result.text:
self.results.append(
{
"type": "FINDING",
"description": f"Edge Side Include. Parameter: [{self.event.data['name']}] Parameter Type: [{self.event.data['type']}]",
}
)
return True
return False

async def fuzz(self):
"""
Main fuzzing method that sends the ESI test payload and checks for processing.
"""
cookies = self.event.data.get("assigned_cookies", {})

# ESI test payload: if ESI is processed, <!--esi--> will be removed
# leaving AABB<!--esx-->CC in the response
payload = "AA<!--esi-->BB<!--esx-->CC"
detection_string = "AABB<!--esx-->CC"

await self.check_probe(cookies, payload, detection_string)
2 changes: 1 addition & 1 deletion bbot/presets/web/lightfuzz-heavy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ modules:
config:
modules:
lightfuzz:
enabled_submodules: [cmdi,crypto,path,serial,sqli,ssti,xss]
enabled_submodules: [cmdi,crypto,path,serial,sqli,ssti,xss,esi]
disable_post: False
2 changes: 1 addition & 1 deletion bbot/presets/web/lightfuzz-medium.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ modules:
config:
modules:
lightfuzz:
enabled_submodules: [cmdi,crypto,path,serial,sqli,ssti,xss]
enabled_submodules: [cmdi,crypto,path,serial,sqli,ssti,xss,esi]
2 changes: 1 addition & 1 deletion bbot/presets/web/lightfuzz-superheavy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ config:
modules:
lightfuzz:
force_common_headers: True # Fuzz common headers like X-Forwarded-For even if they're not observed on the target
enabled_submodules: [cmdi,crypto,path,serial,sqli,ssti,xss]
enabled_submodules: [cmdi,crypto,path,serial,sqli,ssti,xss,esi]
excavate:
speculate_params: True # speculate potential parameters extracted from JSON/XML web responses
70 changes: 69 additions & 1 deletion bbot/test/test_step_2/module_tests/test_module_lightfuzz.py
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,7 @@ class Test_Lightfuzz_urlencoding(Test_Lightfuzz_xss_injs):
"interactsh_disable": True,
"modules": {
"lightfuzz": {
"enabled_submodules": ["cmdi", "crypto", "path", "serial", "sqli", "ssti", "xss"],
"enabled_submodules": ["cmdi", "crypto", "path", "serial", "sqli", "ssti", "xss", "esi"],
}
},
}
Expand Down Expand Up @@ -1817,3 +1817,71 @@ def check(self, module_test, events):

assert web_parameter_emitted, "WEB_PARAMETER for was not emitted"
assert xss_finding_emitted, "XSS FINDING not emitted"


class Test_Lightfuzz_esi(ModuleTestBase):
targets = ["http://127.0.0.1:8888"]
modules_overrides = ["httpx", "lightfuzz", "excavate"]
config_overrides = {
"interactsh_disable": True,
"modules": {
"lightfuzz": {
"enabled_submodules": ["esi"],
}
},
}

def request_handler(self, request):
qs = str(request.query_string.decode())

parameter_block = """
<section class=search>
<form action=/ method=GET>
<input type=text placeholder='Search...' name=search>
<button type=submit class=button>Search</button>
</form>
</section>
"""
if "search=" in qs:
value = qs.split("=")[1]
if "&" in value:
value = value.split("&")[0]
# Decode the URL-encoded value
decoded_value = unquote(value)
# Simulate ESI processing: if the payload contains <!--esi-->, remove it
if "<!--esi-->" in decoded_value:
# ESI processor removes <!--esi--> tag, leaving the rest
processed_value = decoded_value.replace("<!--esi-->", "")
else:
# For non-ESI payloads, just reflect the value as-is
processed_value = decoded_value

esi_block = f"""
<section class=blog-header>
<h1>Search results for '{processed_value}'</h1>
<hr>
</section>
"""
return Response(esi_block, status=200)

return Response(parameter_block, status=200)

async def setup_after_prep(self, module_test):
expect_args = re.compile("/")
module_test.set_expect_requests_handler(expect_args=expect_args, request_handler=self.request_handler)

def check(self, module_test, events):
web_parameter_emitted = False
esi_finding_emitted = False

for e in events:
if e.type == "WEB_PARAMETER":
if "HTTP Extracted Parameter [search]" in e.data["description"]:
web_parameter_emitted = True

if e.type == "FINDING":
if "Edge Side Include. Parameter: [search] Parameter Type: [GETPARAM]" in e.data["description"]:
esi_finding_emitted = True

assert web_parameter_emitted, "WEB_PARAMETER was not emitted"
assert esi_finding_emitted, "ESI FINDING not emitted"