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
80 changes: 80 additions & 0 deletions bbot/modules/aspnet_bin_exposure.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
from bbot.modules.base import BaseModule


class aspnet_bin_exposure(BaseModule):
watched_events = ["URL"]
produced_events = ["VULNERABILITY"]
flags = ["active", "safe", "web-thorough"]
meta = {
"description": "Check for ASP.NET Security Feature Bypasses (CVE-2023-36899 and CVE-2023-36560)",
"created_date": "2025-01-28",
"author": "@liquidsec",
}

in_scope_only = True
test_dlls = [
"Telerik.Web.UI.dll",
"Newtonsoft.Json.dll",
"System.Net.Http.dll",
"EntityFramework.dll",
"AjaxControlToolkit.dll",
]

@staticmethod
def normalize_url(url):
return str(url.rstrip("/") + "/").lower()

def _incoming_dedup_hash(self, event):
return hash(self.normalize_url(event.data))

async def handle_event(self, event):
normalized_url = self.normalize_url(event.data)
for test_dll in self.test_dlls:
for technique in ["b/(S(X))in/###DLL_PLACEHOLDER###/(S(X))/", "(S(X))/b/(S(X))in/###DLL_PLACEHOLDER###"]:
test_url = f"{normalized_url}{technique.replace('###DLL_PLACEHOLDER###', test_dll)}"
self.debug(f"Sending test URL: [{test_url}]")
kwargs = {"method": "GET", "allow_redirects": False, "timeout": 10}
test_result = await self.helpers.request(test_url, **kwargs)
if test_result:
if test_result.status_code == 200 and (
"content-type" in test_result.headers
and "application/x-msdownload" in test_result.headers["content-type"]
):
self.debug(
f"Got positive result for probe with test url: [{test_url}]. Status Code: [{test_result.status_code}] Content Length: [{len(test_result.content)}]"
)

if test_result.status_code == 200 and (
"content-type" in test_result.headers
and "application/x-msdownload" in test_result.headers["content-type"]
):
confirm_url = (
f"{normalized_url}{technique.replace('###DLL_PLACEHOLDER###', 'oopsnotarealdll.dll')}"
)
confirm_result = await self.helpers.request(confirm_url, **kwargs)

if confirm_result and (
confirm_result.status_code != 200
or not (
"content-type" in confirm_result.headers
and "application/x-msdownload" in confirm_result.headers["content-type"]
)
):
description = f"IIS Bin Directory DLL Exposure. Detection Url: [{test_url}]"
await self.emit_event(
{
"severity": "HIGH",
"host": str(event.host),
"url": normalized_url,
"description": description,
},
"VULNERABILITY",
event,
context="{module} detected IIS Bin Directory DLL Exposure vulnerability",
)
return True

async def filter_event(self, event):
if "dir" in event.tags:
return True
return False
1 change: 1 addition & 0 deletions bbot/presets/web/dotnet-audit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ modules:
- telerik
- ajaxpro
- dotnetnuke
- aspnet_bin_exposure

config:
modules:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from .base import ModuleTestBase
import re


class TestAspnetBinExposure(ModuleTestBase):
targets = ["http://127.0.0.1:8888"]
modules_overrides = ["httpx", "aspnet_bin_exposure"]
config_overrides = {
"modules": {
"aspnet_bin_exposure": {
"test_dlls": [
"Newtonsoft.Json.dll",
]
}
}
}

async def setup_before_prep(self, module_test):
# Simulate successful DLL exposure
expect_args = {
"method": "GET",
"uri": "/b/(S(X))in/Newtonsoft.Json.dll/(S(X))/",
}
respond_args = {
"status": 200,
"headers": {"content-type": "application/x-msdownload"},
"response_data": b"MZ\x90\x00\x03\x00\x00\x00",
}
module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args)

# Simulate failed DLL exposure (confirmation test)
expect_args = {
"method": "GET",
"uri": "/b/(S(X))in/oopsnotarealdll.dll/(S(X))/",
}
respond_args = {"status": 404}
module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args)

# Simulate alternative technique
expect_args = {
"method": "GET",
"uri": "/(S(X))/b/(S(X))in/Newtonsoft.Json.dll",
}
respond_args = {
"status": 200,
"headers": {"content-type": "application/x-msdownload"},
"response_data": b"MZ\x90\x00\x03\x00\x00\x00",
}
module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args)

# Simulate failed alternative technique (confirmation test)
expect_args = {
"method": "GET",
"uri": "/(S(X))/b/(S(X))in/oopsnotarealdll.dll",
}
respond_args = {"status": 404}
module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args)

# Fallback for any other requests
expect_args = {"uri": re.compile(r"^/.*$")}
respond_args = {"status": 404}
module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args)

def check(self, module_test, events):
vulnerability_found = False
for e in events:
if e.type == "VULNERABILITY" and "IIS Bin Directory DLL Exposure" in e.data["description"]:
vulnerability_found = True
assert e.data["severity"] == "HIGH", "Vulnerability severity should be HIGH"
assert "Detection Url" in e.data["description"], "Description should include detection URL"
break

assert vulnerability_found, "No vulnerability event was found"