diff --git a/extensions/metasploit/api.rb b/extensions/metasploit/api.rb index a00036f94..1500c11e8 100644 --- a/extensions/metasploit/api.rb +++ b/extensions/metasploit/api.rb @@ -11,6 +11,7 @@ module BeEF 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') # Load modules from metasploit just after all other module config is loaded def self.post_soft_load @@ -18,7 +19,7 @@ module BeEF if msf.login msf_module_config = {} path = BeEF::Core::Configuration.instance.get('beef.extension.metasploit.path') - if not BeEF::Core::Console::CommandLine.parse[:resetdb] and File.exists?("#{path}msf-exploits.cache") + if !BeEF::Core::Console::CommandLine.parse[:resetdb] && File.exists?("#{path}msf-exploits.cache") print_debug "Attempting to use Metasploit exploits cache file" raw = File.read("#{path}msf-exploits.cache") begin @@ -39,7 +40,7 @@ module BeEF msf_modules = msf.call('module.exploits') count = 1 msf_modules['modules'].each { |m| - next if not m.include? "/browser/" + next if !m.include? "/browser/" m_details = msf.call('module.info', 'exploit', m) if m_details key = 'msf_'+m.split('/').last @@ -70,16 +71,16 @@ module BeEF msf_module_config[key] = { - 'enable'=> true, - 'msf'=> true, - 'msf_key' => m, - 'name'=> m_details['name'], - 'category' => 'Metasploit', + '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 + '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]) @@ -102,12 +103,17 @@ module BeEF def self.get_options(mod) msf_key = BeEF::Core::Configuration.instance.get("beef.module.#{mod}.msf_key") msf = BeEF::Extension::Metasploit::RpcClient.instance - if msf_key != nil and msf.login + if msf_key != nil && msf.login msf_module_options = msf.call('module.options', 'exploit', msf_key) com = BeEF::Core::Models::CommandModule.first(:name => mod) 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} + 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) @@ -132,27 +138,26 @@ module BeEF msf_opts[opt["name"]] = opt["value"] } - if msf_key != nil and msf.login + 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 not hb + if !hb print_error "Could not find hooked browser when attempting to execute module '#{mod}'" return false end bopts = [] - uri = "" if msf_opts['SSL'] - uri += "https://" + proto = 'https' else - uri += "http://" + proto = 'http' end config = BeEF::Core::Configuration.instance.get('beef.extension.metasploit') - uri += config['callback_host'] + ":" + msf_opts['SRVPORT'] + "/" + msf_opts['URIPATH'] + uri = proto + '://' + config['callback_host'] + ":" + msf_opts['SRVPORT'] + "/" + msf_opts['URIPATH'] bopts << {:sploit_url => uri} @@ -172,7 +177,7 @@ module BeEF msf_key = BeEF::Core::Configuration.instance.get("beef.module.#{mod}.msf_key") msf = BeEF::Extension::Metasploit::RpcClient.instance - if msf_key != nil and msf.login + if msf_key != nil && msf.login msf_module_options = msf.call('module.options', 'payload', payload) com = BeEF::Core::Models::CommandModule.first(:name => mod) @@ -184,6 +189,13 @@ module BeEF end end end + + # Mounts the handler for processing Metasploit RESTful API requests. + # + # @param beef_server [BeEF::Core::Server] HTTP server instance + def self.mount_handler(beef_server) + beef_server.mount('/api/msf', BeEF::Extension::Metasploit::MsfRest.new) + end end end end diff --git a/extensions/metasploit/extension.rb b/extensions/metasploit/extension.rb index c874f257e..725f85731 100644 --- a/extensions/metasploit/extension.rb +++ b/extensions/metasploit/extension.rb @@ -9,6 +9,10 @@ module Metasploit extend BeEF::API::Extension + @short_name = 'msf' + @full_name = 'Metasploit' + @description = 'Metasploit integration' + # Translates msf exploit options to beef options array def self.translate_options(msf_options) options = [] @@ -89,3 +93,4 @@ require 'msfrpc-client' require 'extensions/metasploit/rpcclient' require 'extensions/metasploit/api' require 'extensions/metasploit/module' +require 'extensions/metasploit/rest/msf' diff --git a/extensions/metasploit/rest/msf.rb b/extensions/metasploit/rest/msf.rb new file mode 100644 index 000000000..68f3adda5 --- /dev/null +++ b/extensions/metasploit/rest/msf.rb @@ -0,0 +1,150 @@ +# +# Copyright (c) 2006-2014 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - http://beefproject.com +# See the file 'doc/COPYING' for copying permission +# +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 + config = BeEF::Core::Configuration.instance + + # Require a valid API token from a valid IP address + halt 401 unless params[:token] == config.get('beef.api_token') + halt 403 unless BeEF::Core::Rest.permitted_source?(request.ip) + + headers 'Content-Type' => 'application/json; charset=UTF-8', + 'Pragma' => 'no-cache', + 'Cache-Control' => 'no-cache', + 'Expires' => '0' + end + + # 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 + end + + # Returns all the jobs + get '/jobs' do + begin + 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 + 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 + end + + # Stops a job given its id + get '/job/:id/stop' do + result = {} + begin + id = params[:id] + raise InvalidParamError, 'id' if id !~ /\A\d+\Z/ + + removed = @msf.call('job.stop', id) + if !removed.nil? + result['success'] = removed + print_info "[Metasploit] Stopped job [id: #{id}]" + end + rescue InvalidParamError => e + print_error e.message + halt 400 + rescue StandardError => e + print_error "Internal error while stopping job with ID #{id} (#{e.message})" + halt 500 + end + result.to_json + end + + # 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 + end + 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? + super(message) + end + + end + + end + + end + end +end