Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
40 changes: 40 additions & 0 deletions bbot/modules/lightfuzz/submodules/esi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
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.
"""

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"
Loading