Skip to content

Commit

Permalink
Amélioration de la détection de Citrix
Browse files Browse the repository at this point in the history
Ajout du cas où la page d'authentification Citrix est sur le /
  • Loading branch information
OussamaBeng committed Jul 4, 2024
1 parent f35d6ed commit ae99e39
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 14 deletions.
37 changes: 35 additions & 2 deletions tests/attack/test_mod_network_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -696,7 +696,7 @@ async def test_detect_citrix_from_title():

assert persister.add_payload.call_count == 1
assert persister.add_payload.call_args_list[0][1]["info"] == (
'{"name": "Citrix Gateway", "version": "", "categories": ["Network Equipment"], "groups": ["Content"]}'
'{"name": "Citrix Gateway", "versions": [], "categories": ["Network Equipment"], "groups": ["Content"]}'
)


Expand Down Expand Up @@ -736,5 +736,38 @@ async def test_detect_citrix_from_class():

assert persister.add_payload.call_count == 1
assert persister.add_payload.call_args_list[0][1]["info"] == (
'{"name": "NetscalerGateway", "version": "", "categories": ["Network Equipment"], "groups": ["Content"]}'
'{"name": "NetscalerGateway", "versions": [], "categories": ["Network Equipment"], "groups": ["Content"]}'
)


@pytest.mark.asyncio
@respx.mock
async def test_detect_citrix_in_root_url():
respx.get("http://perdu.com/").mock(
return_value=httpx.Response(
200,
content='<html><head><title>NetScaler ADC</title></head><body><h1>Perdu sur Internet ?</h1> \
<h2>Pas de panique, on va vous aider <span>NetScaler ADC</span></h2> \
</body></html>'
)
)

respx.get(url__regex=r"http://perdu.com/.*?").mock(return_value=httpx.Response(404))

persister = AsyncMock()

request = Request("http://perdu.com/")
request.path_id = 1

crawler_configuration = CrawlerConfiguration(Request("http://perdu.com/"))
async with AsyncCrawler.with_configuration(crawler_configuration) as crawler:
options = {"timeout": 10, "level": 2, "tasks": 20}

module = ModuleNetworkDevice(crawler, persister, options, Event(), crawler_configuration)

await module.attack(request)

assert persister.add_payload.call_count == 1
assert persister.add_payload.call_args_list[0][1]["info"] == (
'{"name": "NetScaler ADC", "versions": [], "categories": ["Network Equipment"], "groups": ["Content"]}'
)
17 changes: 14 additions & 3 deletions wapitiCore/attack/network_devices/mod_citrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ class ModuleCitrix(Attack):
"""Detect Citrix Devices."""

device_name = "Citrix"
version = ""
version = []

async def check_citrix(self, url):
check_list = ['logon/LogonPoint/']
check_list = ['logon/LogonPoint/', '']

for item in check_list:
full_url = urljoin(url, item)
Expand Down Expand Up @@ -80,6 +80,17 @@ async def detect_citrix_product(self, response_content):
# Extract the product name from the title
self.device_name = title
return True
if "NetScaler" in title:
# Search the product name in the content
product_names = ["NetScaler ADC", "Citrix NetScaler", "NetScaler", "NetScaler AWS"]
matches = soup.find_all('span', text=lambda text: text in product_names)
if matches:
# Set product_name to the first matched product name
self.device_name = matches[0].text
else:
self.device_name = "Citrix"
return True

return False

async def attack(self, request: Request, response: Optional[Response] = None):
Expand All @@ -90,7 +101,7 @@ async def attack(self, request: Request, response: Optional[Response] = None):
if await self.check_citrix(request_to_root.url):
citrix_detected = {
"name": self.device_name,
"version": "",
"versions": self.version,
"categories": ["Network Equipment"],
"groups": ["Content"]
}
Expand Down
4 changes: 2 additions & 2 deletions wapitiCore/attack/network_devices/mod_forti.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
class ModuleForti(NetworkDeviceCommon):
"""Detect Forti."""
device_name = "Fortinet"
version = ""
version = []
fortinet_pattern = re.compile(r'Forti\w+')

async def check_forti(self, url):
Expand Down Expand Up @@ -169,7 +169,7 @@ async def attack(self, request: Request, response: Optional[Response] = None):

forti_detected = {
"name": self.device_name,
"versions": [],
"versions": self.version,
"categories": ["Network Equipment"],
"groups": ["Content"]
}
Expand Down
7 changes: 4 additions & 3 deletions wapitiCore/attack/network_devices/mod_harbor.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class ModuleHarbor(NetworkDeviceCommon):
"""Detect Harbor."""

device_name = "Harbor"
version = ""
version = []

async def check_harbor(self, url):
check_list = ['api/v2.0/systeminfo']
Expand Down Expand Up @@ -67,7 +67,7 @@ async def detect_harbor_version(self, response_content):
data = json.loads(response_content)
# Extract the harbor_version value
if data.get("harbor_version"):
self.version = data.get("harbor_version")
self.version.append(data.get("harbor_version"))
except (json.JSONDecodeError, KeyError) as json_error:
raise ValueError("The URL doesn't contain a valid JSON.") from json_error

Expand All @@ -79,7 +79,7 @@ async def attack(self, request: Request, response: Optional[Response] = None):
if await self.check_harbor(request_to_root.url):
harbor_detected = {
"name": self.device_name,
"versions": [self.version] if self.version else [],
"versions": self.version,
"categories": ["Network Equipment"],
"groups": ["Content"]
}
Expand All @@ -95,6 +95,7 @@ async def attack(self, request: Request, response: Optional[Response] = None):
info=json.dumps(harbor_detected),
wstg=WSTG_CODE
)
self.version.clear()
else:
log_blue(MSG_NO_HARBOR)
except RequestError as req_error:
Expand Down
11 changes: 7 additions & 4 deletions wapitiCore/attack/network_devices/mod_ubika.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@

class ModuleUbika(NetworkDeviceCommon):
"""Detect Ubika."""
version = ""
version = []

async def check_ubika(self, url):
check_list = ['app/monitor/']
Expand All @@ -53,7 +53,7 @@ async def check_ubika(self, url):
return response.is_success and title_tag and "UBIKA" in title_tag.text.strip()

async def get_ubika_version(self, url):
version = ""
versions = []
version_uri = "app/monitor/api/info/product"
full_url = urljoin(url, version_uri)
request = Request(full_url, 'GET')
Expand All @@ -65,7 +65,9 @@ async def get_ubika_version(self, url):

if response.is_success:
version = response.json.get("result", {}).get("product", {}).get("version", '')
return version
if version:
versions.append(version)
return versions

async def attack(self, request: Request, response: Optional[Response] = None):
self.finished = True
Expand All @@ -81,7 +83,7 @@ async def attack(self, request: Request, response: Optional[Response] = None):

ubika_detected = {
"name": "UBIKA WAAP",
"versions": [self.version] if self.version else [],
"versions": self.version,
"categories": ["Network Equipment"],
"groups": ["Content"]
}
Expand All @@ -97,6 +99,7 @@ async def attack(self, request: Request, response: Optional[Response] = None):
info=json.dumps(ubika_detected),
wstg=WSTG_CODE
)
self.version.clear()
else:
log_blue(MSG_NO_UBIKA)
except RequestError as req_error:
Expand Down

0 comments on commit ae99e39

Please sign in to comment.