From 1dea97511bc33d37e4e08e2742bf12de13d19d04 Mon Sep 17 00:00:00 2001 From: Brendan Coles Date: Mon, 11 Jun 2018 11:36:35 +0000 Subject: [PATCH] Code cleanup --- core/api.rb | 193 +++++++----- core/core.rb | 4 - core/extension.rb | 20 +- core/extensions.rb | 10 +- core/loader.rb | 1 + core/main/command.rb | 165 +++++++---- core/main/configuration.rb | 105 ++++--- core/main/crypto.rb | 13 +- core/main/logger.rb | 20 +- core/main/migration.rb | 31 +- core/main/server.rb | 42 ++- core/module.rb | 585 ++++++++++++++++++------------------- core/modules.rb | 46 +-- core/ruby/hash.rb | 7 +- core/ruby/object.rb | 5 + 15 files changed, 689 insertions(+), 558 deletions(-) diff --git a/core/api.rb b/core/api.rb index c85ebd54d..d631e7530 100644 --- a/core/api.rb +++ b/core/api.rb @@ -7,168 +7,205 @@ module BeEF module API + # # Registrar class to handle all registered timed API calls + # class Registrar - include Singleton + # # Create registrar + # def initialize @registry = [] @count = 1 end # Register timed API calls to an owner + # # @param [Class] owner the owner of the API hook # @param [Class] c the API class the owner would like to hook into # @param [String] method the method of the class the owner would like to execute # @param [Array] params an array of parameters that need to be matched before the owner will be called + # def register(owner, c, method, params = []) - if self.verify_api_path(c, method) - if not self.registered?(owner, c, method, params) - id = @count - @registry << { - 'id' => id, - 'owner' => owner, - 'class' => c, - 'method' => method, - 'params' => params - } - @count += 1 - return id - else - print_debug "API Registrar: Attempting to re-register API call #{c.to_s} :#{method.to_s}" - end - else - print_error "API Registrar: Attempted to register non-existant API method #{c.to_s} :#{method.to_s}" + unless verify_api_path(c, method) + print_error "API Registrar: Attempted to register non-existant API method #{c} :#{method}" + return end + + if registered?(owner, c, method, params) + print_debug "API Registrar: Attempting to re-register API call #{c} :#{method}" + return + end + + id = @count + @registry << { + 'id' => id, + 'owner' => owner, + 'class' => c, + 'method' => method, + 'params' => params + } + @count += 1 + + id end + # # Tests whether the owner is registered for an API hook + # # @param [Class] owner the owner of the API hook # @param [Class] c the API class # @param [String] method the method of the class # @param [Array] params an array of parameters that need to be matched + # # @return [Boolean] whether or not the owner is registered + # def registered?(owner, c, method, params = []) - @registry.each{|r| - if r['owner'] == owner and r['class'] == c and r['method'] == method and self.is_matched_params?(r, params) - return true - end - } - return false + @registry.each do |r| + next unless r['owner'] == owner + next unless r['class'] == c + next unless r['method'] == method + next unless is_matched_params? r, params + return true + end + false end + # # Match a timed API call to determine if an API.fire() is required + # # @param [Class] c the target API class # @param [String] method the method of the target API class # @param [Array] params an array of parameters that need to be matched + # # @return [Boolean] whether or not the arguments match an entry in the API registry + # def matched?(c, method, params = []) - @registry.each{|r| - if r['class'] == c and r['method'] == method and self.is_matched_params?(r, params) - return true - end - } - return false + @registry.each do |r| + next unless r['class'] == c + next unless r['method'] == method + next unless is_matched_params? r, params + return true + end + false end + # # Un-registers an API hook + # # @param [Integer] id the ID of the API hook + # def unregister(id) - @registry.delete_if{|r| - r['id'] == id - } + @registry.delete_if {|r| r['id'] == id } end + # # Retrieves all the owners and ID's of an API hook # @param [Class] c the target API class # @param [String] method the method of the target API class # @param [Array] params an array of parameters that need to be matched + # # @return [Array] an array of hashes consisting of two keys :owner and :id + # def get_owners(c, method, params = []) owners = [] - @registry.each{|r| - if r['class'] == c and r['method'] == method - if self.is_matched_params?(r, params) - owners << { :owner => r['owner'], :id => r['id']} - end - end - } - return owners + @registry.each do |r| + next unless r['class'] == c + next unless r['method'] == method + next unless is_matched_params? r, params + owners << { :owner => r['owner'], :id => r['id'] } + end + owners end + # # Verifies that the api_path has been regitered # Verifies the API path has been registered. + # # @note This is a security precaution + # # @param [Class] c the target API class to verify # @param [String] m the target method to verify + # def verify_api_path(c, m) - return (c.const_defined?('API_PATHS') and c.const_get('API_PATHS').has_key?(m)) + (c.const_defined?('API_PATHS') && c.const_get('API_PATHS').key?(m)) end + # # Retrieves the registered symbol reference for an API hook + # # @param [Class] c the target API class to verify # @param [String] m the target method to verify + # # @return [Symbol] the API path + # def get_api_path(c, m) - return (self.verify_api_path(c, m)) ? c.const_get('API_PATHS')[m] : nil; + verify_api_path(c, m) ? c.const_get('API_PATHS')[m] : nil end + # # Matches stored API params to params + # # @note If a stored API parameter has a NilClass the parameter matching is skipped for that parameter # @note By default this method returns true, this is either because the API.fire() did not include any parameters or there were no parameters defined for this registry entry + # # @param [Hash] reg hash of registry element, must contain 'params' key # @param [Array] params array of parameters to be compared to the stored parameters + # # @return [Boolean] whether params matches the stored API parameters + # def is_matched_params?(reg, params) stored = reg['params'] - if stored.length == params.length - matched = true - stored.each_index{|i| - next if stored[i] == nil - if not stored[i] == params[i] - matched = false - end - } - return false if not matched + return true unless stored.length == params.length + + stored.each_index do |i| + next if stored[i].nil? + return false unless stored[i] == params[i] end - return true + + true end + # # Fires all owners registered to this API hook + # # @param [Class] c the target API class # @param [String] m the target API method # @param [Array] *args parameters passed for the API call - # @return [Hash, NilClass] returns either a Hash of :api_id and :data if the owners return data, otherwise NilClass + # + # @return [Hash, NilClass] returns either a Hash of :api_id and :data + # if the owners return data, otherwise NilClass + # def fire(c, m, *args) - mods = self.get_owners(c, m, args) - if mods.length > 0 - data = [] - if self.verify_api_path(c, m) and c.ancestors[0].to_s > "BeEF::API" - method = self.get_api_path(c, m) - mods.each do |mod| - begin - #Only used for API Development (very verbose) - #print_info "API: #{mod} fired #{method}" - result = mod[:owner].method(method).call(*args) - if not result == nil - data << {:api_id => mod[:id], :data => result} - end - rescue => e - print_error "API Fire Error: #{e.message} in #{mod.to_s}.#{method.to_s}()" - end - end - else - print_error "API Path not defined for Class: #{c.to_s} method:#{method.to_s}" - end - return data + mods = get_owners(c, m, args) + return nil unless mods.length.positive? + + unless verify_api_path(c, m) && c.ancestors[0].to_s > 'BeEF::API' + print_error "API Path not defined for Class: #{c} method:#{method}" + return [] end - return nil + + data = [] + method = get_api_path(c, m) + mods.each do |mod| + begin + # Only used for API Development (very verbose) + # print_info "API: #{mod} fired #{method}" + + result = mod[:owner].method(method).call(*args) + unless result.nil? + data << { :api_id => mod[:id], :data => result } + end + rescue => e + print_error "API Fire Error: #{e.message} in #{mod}.#{method}()" + end + end + + data end - end - end end diff --git a/core/core.rb b/core/core.rb index 9a7ce19ff..870779365 100644 --- a/core/core.rb +++ b/core/core.rb @@ -37,7 +37,3 @@ require 'core/main/migration' require 'core/main/console/commandline' require 'core/main/console/banners' -# @note Include rubyzip lib -require 'zip' - - diff --git a/core/extension.rb b/core/extension.rb index a8cc0f5b0..7f9c5b98d 100644 --- a/core/extension.rb +++ b/core/extension.rb @@ -10,36 +10,40 @@ module BeEF # @param [String] ext the extension key # @return [Boolean] whether or not the extension exists in BeEF's configuration def self.is_present(ext) - return BeEF::Core::Configuration.instance.get('beef.extension').has_key?(ext.to_s) + BeEF::Core::Configuration.instance.get('beef.extension').key? ext.to_s end # Checks to see if extension is enabled in configuration # @param [String] ext the extension key # @return [Boolean] whether or not the extension is enabled def self.is_enabled(ext) - return (self.is_present(ext) and BeEF::Core::Configuration.instance.get('beef.extension.'+ext.to_s+'.enable') == true) + return false unless is_present(ext) + BeEF::Core::Configuration.instance.get("beef.extension.#{ext}.enable") == true end # Checks to see if extension has been loaded # @param [String] ext the extension key - # @return [Boolean] whether or not the extension is loaded + # @return [Boolean] whether or not the extension is loaded def self.is_loaded(ext) - return (self.is_enabled(ext) and BeEF::Core::Configuration.instance.get('beef.extension.'+ext.to_s+'.loaded') == true) + return false unless is_enabled(ext) + BeEF::Core::Configuration.instance.get("beef.extension.#{ext}.loaded") == true end # Loads an extension # @param [String] ext the extension key # @return [Boolean] whether or not the extension loaded successfully - # @todo Wrap the require() statement in a try catch block to allow BeEF to fail gracefully if there is a problem with that extension - Issue #480 def self.load(ext) - if File.exists?("#{$root_dir}/extensions/#{ext}/extension.rb") + if File.exist? "#{$root_dir}/extensions/#{ext}/extension.rb" require "#{$root_dir}/extensions/#{ext}/extension.rb" print_debug "Loaded extension: '#{ext}'" - BeEF::Core::Configuration.instance.set('beef.extension.'+ext+'.loaded', true) + BeEF::Core::Configuration.instance.set "beef.extension.#{ext}.loaded", true return true end print_error "Unable to load extension '#{ext}'" - return false + false + rescue => e + print_error "Unable to load extension '#{ext}':" + print_more e.message end end end diff --git a/core/extensions.rb b/core/extensions.rb index 85a3ec0ae..f2cbe76b6 100644 --- a/core/extensions.rb +++ b/core/extensions.rb @@ -9,13 +9,13 @@ module BeEF # Returns configuration of all enabled extensions # @return [Array] an array of extension configuration hashes that are enabled def self.get_enabled - return BeEF::Core::Configuration.instance.get('beef.extension').select { |k,v| v['enable'] == true } + BeEF::Core::Configuration.instance.get('beef.extension').select { |k,v| v['enable'] == true } end # Returns configuration of all loaded extensions # @return [Array] an array of extension configuration hashes that are loaded def self.get_loaded - return BeEF::Core::Configuration.instance.get('beef.extension').select {|k,v| v['loaded'] == true } + BeEF::Core::Configuration.instance.get('beef.extension').select {|k,v| v['loaded'] == true } end # Load all enabled extensions @@ -23,12 +23,10 @@ module BeEF def self.load BeEF::Core::Configuration.instance.load_extensions_config self.get_enabled.each { |k,v| - BeEF::Extension.load(k) + BeEF::Extension.load k } # API post extension load - BeEF::API::Registrar.instance.fire(BeEF::API::Extensions, 'post_load') + BeEF::API::Registrar.instance.fire BeEF::API::Extensions, 'post_load' end - end end - diff --git a/core/loader.rb b/core/loader.rb index 15042363c..73cc5c516 100644 --- a/core/loader.rb +++ b/core/loader.rb @@ -32,6 +32,7 @@ require 'mime/types' require 'optparse' require 'resolv' require 'digest' +require 'zip' # @note Include the filters require 'core/filters' diff --git a/core/main/command.rb b/core/main/command.rb index b234dc260..0faa30bc0 100644 --- a/core/main/command.rb +++ b/core/main/command.rb @@ -6,37 +6,42 @@ module BeEF module Core - + # # @note This module contains a list of utils functions to use when writing commands + # module CommandUtils - + # # Format a string to support multiline in javascript. # @param [String] text String to convert + # # @return [String] Formatted string - def format_multiline(text); text.gsub(/\n/, '\n'); end - + # + def format_multiline(text) + text.gsub(/\n/, '\n') + end end - - + # # @note The Command Module Context is being used when evaluating code in eruby. # In other words, we use that code to add funky functions to the # javascript templates of our commands. + # class CommandContext < Erubis::Context include BeEF::Core::CommandUtils - + # # Constructor # @param [Hash] hash - def initialize(hash=nil); - super(hash); + # + def initialize(hash = nil) + super(hash) end - end + # # @note This class is the base class for all command modules in the framework. - # Two instances of this object are created during the execution of command module. + # Two instances of this object are created during the execution of command module. + # class Command - attr_reader :datastore, :path, :default_command_url, :beefjs_components, :friendlyname attr_accessor :zombie, :command_id, :session_id @@ -44,8 +49,11 @@ module BeEF include BeEF::Core::Constants::Browsers include BeEF::Core::Constants::CommandModule + # # Super class controller + # # @param [String] key command module key + # def initialize(key) config = BeEF::Core::Configuration.instance @@ -61,62 +69,98 @@ module BeEF @beefjs_components = {} end + # # This function is called just before the instructions are sent to hooked browser. + # def pre_send; end + # # Callback method. This function is called when the hooked browser sends results back. + # def callback; end + # # If the command requires some data to be sent back, this function will process them. # @param [] head # @param [Hash] params Hash of parameters # @todo Determine argument "head" type + # def process_zombie_response(head, params); end + # # Returns true if the command needs configurations to work. False if not. # @deprecated This command should not be used since the implementation of the new configuration system - def needs_configuration?; !@datastore.nil?; end + # + def needs_configuration? + !@datastore.nil? + end + # # Returns information about the command in a JSON format. # @return [String] JSON formatted string + # def to_json { - 'Name' => @friendlyname, - 'Description' => BeEF::Core::Configuration.instance.get("beef.module.#{@key}.description"), - 'Category' => BeEF::Core::Configuration.instance.get("beef.module.#{@key}.category"), - 'Data' => BeEF::Module.get_options(@key) + 'Name' => @friendlyname, + 'Description' => BeEF::Core::Configuration.instance.get("beef.module.#{@key}.description"), + 'Category' => BeEF::Core::Configuration.instance.get("beef.module.#{@key}.category"), + 'Data' => BeEF::Module.get_options(@key) }.to_json end + # # Builds the 'datastore' attribute of the command which is used to generate javascript code. # @param [Hash] data Data to be inserted into the datastore - # @todo Confirm argument "data" type - def build_datastore(data); - @datastore = JSON.parse(data) + # @todo TODO Confirm argument "data" type + # + def build_datastore(data) + @datastore = JSON.parse data + rescue => e + print_error "Could not build datastore: #{e.message}" end + # # Sets the datastore for the callback function. This function is meant to be called by the CommandHandler # @param [Hash] http_params HTTP parameters # @param [Hash] http_headers HTTP headers + # def build_callback_datastore(result, command_id, beefhook, http_params, http_headers) @datastore = {'http_headers' => {}} # init the datastore - if http_params != nil && http_headers != nil + if !http_params.nil? && !http_headers.nil? # get, check and add the http_params to the datastore - http_params.keys.each { |http_params_key| - (print_error 'http_params_key is invalid';return) if not BeEF::Filters.is_valid_command_module_datastore_key?(http_params_key) - http_params_value = Erubis::XmlHelper.escape_xml(http_params[http_params_key]) - (print_error 'http_params_value is invalid';return) if not BeEF::Filters.is_valid_command_module_datastore_param?(http_params_value) - @datastore[http_params_key] = http_params_value # add the checked key and value to the datastore - } + http_params.keys.each do |http_params_key| + unless BeEF::Filters.is_valid_command_module_datastore_key? http_params_key + print_error 'http_params_key is invalid' + return + end + + http_params_value = Erubis::XmlHelper.escape_xml http_params[http_params_key] + unless BeEF::Filters.is_valid_command_module_datastore_param?(http_params_value) + print_error 'http_params_value is invalid' + return + end + + # add the checked key and value to the datastore + @datastore[http_params_key] = http_params_value + end # get, check and add the http_headers to the datastore - http_headers.keys.each { |http_header_key| - (print_error 'http_header_key is invalid';return) if not BeEF::Filters.is_valid_command_module_datastore_key?(http_header_key) - http_header_value = Erubis::XmlHelper.escape_xml(http_headers[http_header_key][0]) - (print_error 'http_header_value is invalid';return) if not BeEF::Filters.is_valid_command_module_datastore_param?(http_header_value) - @datastore['http_headers'][http_header_key] = http_header_value # add the checked key and value to the datastore - } + http_headers.keys.each do |http_header_key| + unless BeEF::Filters.is_valid_command_module_datastore_key? http_header_key + print_error 'http_header_key is invalid' + return + end + + http_header_value = Erubis::XmlHelper.escape_xml http_headers[http_header_key][0] + unless BeEF::Filters.is_valid_command_module_datastore_param? http_header_value + print_error 'http_header_value is invalid' + return + end + + # add the checked key and value to the datastore + @datastore['http_headers'][http_header_key] = http_header_value + end end @datastore['results'] = result @@ -124,28 +168,32 @@ module BeEF @datastore['beefhook'] = beefhook end + # # Returns the output of the command. These are the actual instructions sent to the browser. # @return [String] The command output + # def output - f = @path+'command.js' - (print_error "#{f} file does not exist";return) if not File.exists? f + f = "#{@path}command.js" + unless File.exist? f + print_error "File does not exist: #{f}" + return + end command = BeEF::Core::Models::Command.first(:id => @command_id) @eruby = Erubis::FastEruby.new(File.read(f)) - data = BeEF::Core::Configuration.instance.get("beef.module.#{@key}") + #data = BeEF::Core::Configuration.instance.get "beef.module.#{@key}" cc = BeEF::Core::CommandContext.new cc['command_url'] = @default_command_url cc['command_id'] = @command_id - JSON.parse(command['data']).each{|v| + JSON.parse(command['data']).each do |v| cc[v['name']] = v['value'] - } - if self.respond_to?(:execute) - self.execute end - @output = @eruby.evaluate(cc) + execute if respond_to?(:execute) + + @output = @eruby.evaluate cc @output end @@ -155,19 +203,25 @@ module BeEF @results = results end - # If nothing else than the file is specified, the function will map the file to a random path without any extension. + # + # If nothing else than the file is specified, + # the function will map the file to a random path without any extension. + # # @param [String] file File to be mounted # @param [String] path URL path to mounted file # @param [String] extension URL extension # @param [Integer] count The amount of times this file can be accessed before being automatically unmounted # @deprecated This function is possibly deprecated in place of the API - def map_file_to_url(file, path=nil, extension=nil, count=1) - return BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.bind(file, path, extension, count) + # + def map_file_to_url(file, path = nil, extension = nil, count = 1) + BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.bind(file, path, extension, count) end + # # Tells the framework to load a specific module of the BeEFJS library that the command will be using. # @param [String] component String of BeEFJS component to load # @note Example: use 'beef.net.local' + # def use(component) return if @beefjs_components.include? component @@ -176,23 +230,23 @@ module BeEF component_path.gsub!(/\./, '/') component_path.replace "#{$root_dir}/core/main/client/#{component_path}.js" - raise "Invalid beefjs component for command module #{@path}" if not File.exists?(component_path) + raise "Invalid beefjs component for command module #{@path}" unless File.exist? component_path @beefjs_components[component] = component_path end - # @todo Document + # @todo TODO Document def oc_value(name) - option = BeEF::Core::Models::OptionCache.first(:name => name) - return nil if not option - return option.value + option = BeEF::Core::Models::OptionCache.first(:name => name) + return nil unless option + option.value end - # @todo Document - def apply_defaults() - @datastore.each { |opt| - opt["value"] = oc_value(opt["name"]) || opt["value"] - } + # @todo TODO Document + def apply_defaults + @datastore.each do |opt| + opt['value'] = oc_value(opt['name']) || opt['value'] + end end private @@ -201,9 +255,6 @@ module BeEF @eruby @update_zombie @results - end - - end end diff --git a/core/main/configuration.rb b/core/main/configuration.rb index 0d8775a41..48611e1e7 100644 --- a/core/main/configuration.rb +++ b/core/main/configuration.rb @@ -6,26 +6,26 @@ module BeEF module Core - class Configuration - attr_accessor :config # antisnatchor: still a singleton, but implemented by hand because we want to have only one instance # of the Configuration object while having the possibility to specify a parameter to the constructor. # This is why we don't use anymore the default Ruby implementation -> include Singleton - def self.instance() - return @@instance + def self.instance + @@instance end # Loads the default configuration system - # @param [String] configuration_file Configuration file to be loaded, by default loads $root_dir/config.yaml + # @param [String] configuration_file Configuration file to be loaded, + # by default loads $root_dir/config.yaml def initialize(config) - raise Exception::TypeError, '"config" needs to be a string' if not config.string? - raise Exception::TypeError, "Configuration file '#{config}' cannot be found" if not File.exist?(config) + raise Exception::TypeError, "'config' needs to be a string" unless config.string? + raise Exception::TypeError, "Configuration file '#{config}' cannot be found" unless File.exist? config + begin #open base config - @config = self.load(config) + @config = load(config) # set default value if key? does not exist @config.default = nil @@config = config @@ -33,6 +33,7 @@ module BeEF print_error "Fatal Error: cannot load configuration file" print_debug e end + @@instance = self end @@ -40,89 +41,101 @@ module BeEF # @param [String] file YAML file to be loaded # @return [Hash] YAML formatted hash def load(file) - begin - return nil if not File.exists?(file) - raw = File.read(file) - return YAML.load(raw) - rescue => e - print_debug "Unable to load '#{file}' #{e}" - return nil - end + return nil unless File.exist? file + raw = File.read file + YAML.safe_load raw + rescue => e + print_debug "Unable to load '#{file}' #{e}" + nil end + # # Returns the value of a selected key in the configuration file. # @param [String] key Key of configuration item # @return [Hash|String] The resulting value stored against the 'key' + # def get(key) subkeys = key.split('.') lastkey = subkeys.pop subhash = subkeys.inject(@config) do |hash, k| hash[k] end - return (subhash != nil and subhash.has_key?(lastkey)) ? subhash[lastkey] : nil + return nil if subhash.nil? + subhash.key?(lastkey) ? subhash[lastkey] : nil end + # # Sets the give key value pair to the config instance # @param [String] key The configuration key # @param value The value to be stored against the 'key' # @return [Boolean] If the store procedure was successful + # def set(key, value) subkeys = key.split('.').reverse - return false if subkeys.length == 0 - hash = {subkeys.shift.to_s => value} - subkeys.each{|v| - hash = {v.to_s => hash} - } - @config = @config.deep_merge(hash) - return true + return false if subkeys.empty? + + hash = { subkeys.shift.to_s => value } + subkeys.each { |v| hash = {v.to_s => hash} } + @config = @config.deep_merge hash + true end + # # Clears the given key hash # @param [String] key Configuration key to be cleared # @return [Boolean] If the configuration key was cleared + # def clear(key) subkeys = key.split('.') - return false if subkeys.length == 0 + return false if subkeys.empty? + lastkey = subkeys.pop hash = @config - subkeys.each{|v| - hash = hash[v] - } - return (hash.delete(lastkey) == nil) ? false : true + subkeys.each {|v| hash = hash[v] } + hash.delete(lastkey).nil? ? false : true end + # # Load extensions configurations + # def load_extensions_config - self.set('beef.extension', {}) - Dir.glob("#{$root_dir}/extensions/*/config.yaml") do | cf | - y = self.load(cf) - if y != nil - y['beef']['extension'][y['beef']['extension'].keys.first]['path'] = cf.gsub(/config\.yaml/, '').gsub(/#{$root_dir}\//, '') - @config = y.deep_merge(@config) - else + set('beef.extension', {}) + Dir.glob("#{$root_dir}/extensions/*/config.yaml") do |cf| + y = load(cf) + if y.nil? print_error "Unable to load extension configuration '#{cf}'" + next end + + y['beef']['extension'][y['beef']['extension'].keys.first]['path'] = cf.gsub(/config\.yaml/, '').gsub(%r{#{$root_dir}/}, '') + @config = y.deep_merge(@config) end end + # # Load module configurations + # def load_modules_config - self.set('beef.module', {}) + set('beef.module', {}) # support nested sub-categories, like browser/hooked_domain/ajax_fingerprint module_configs = File.join("#{$root_dir}/modules/**", "config.yaml") - Dir.glob(module_configs) do | cf | - y = self.load(cf) - if y != nil - y['beef']['module'][y['beef']['module'].keys.first]['path'] = cf.gsub(/config\.yaml/, '').gsub(/#{$root_dir}\//, '') - @config = y.deep_merge(@config) - # API call for post module config load - BeEF::API::Registrar.instance.fire(BeEF::API::Configuration, 'module_configuration_load', y['beef']['module'].keys.first) - else + Dir.glob(module_configs) do |cf| + y = load(cf) + if y.nil? print_error "Unable to load module configuration '#{cf}'" + next end + + y['beef']['module'][y['beef']['module'].keys.first]['path'] = cf.gsub('config.yaml', '').gsub(%r{#{$root_dir}/}, '') + @config = y.deep_merge @config + # API call for post module config load + BeEF::API::Registrar.instance.fire( + BeEF::API::Configuration, + 'module_configuration_load', + y['beef']['module'].keys.first + ) end end - end end end diff --git a/core/main/crypto.rb b/core/main/crypto.rb index afe85a137..82993e202 100644 --- a/core/main/crypto.rb +++ b/core/main/crypto.rb @@ -6,15 +6,18 @@ module BeEF module Core - module Crypto # @note the minimum length of the security token TOKEN_MINIMUM_LENGTH = 15 + # # Generate a secure random token + # # @param [Integer] len The length of the secure token + # # @return [String] Security token + # def self.secure_token(len = nil) # get default length from config config = BeEF::Core::Configuration.instance @@ -24,12 +27,15 @@ module Core raise Exception::TypeError, "Token length is less than the minimum length enforced by the framework: #{TOKEN_MINIMUM_LENGTH}" if (token_length < TOKEN_MINIMUM_LENGTH) # return random hex string - return OpenSSL::Random.random_bytes(token_length).unpack("H*")[0] + OpenSSL::Random.random_bytes(token_length).unpack("H*")[0] end + # # Generate a secure random token, 20 chars, used as an auth token for the RESTful API. # After creation it's stored in the BeEF configuration object => conf.get('beef.api_token') + # # @return [String] Security token + # def self.api_token config = BeEF::Core::Configuration.instance token_length = 20 @@ -40,9 +46,11 @@ module Core token end + # # Generates a unique identifier for DNS rules. # # @return [String] 8-character hex identifier + # def self.dns_rule_id id = nil length = 4 @@ -56,7 +64,6 @@ module Core id.to_s end - end end end diff --git a/core/main/logger.rb b/core/main/logger.rb index 02bf6e00b..996cab299 100644 --- a/core/main/logger.rb +++ b/core/main/logger.rb @@ -6,9 +6,7 @@ module BeEF module Core - class Logger - include Singleton # Constructor @@ -19,12 +17,15 @@ module Core # if notifications are enabled create a new instance @notifications = BeEF::Extension::Notifications::Notifications unless @config.get('beef.extension.notifications.enable') == false end - + + # # Registers a new event in the logs # @param [String] from The origin of the event (i.e. Authentication, Hooked Browser) # @param [String] event The event description # @param [Integer] hb The id of the hooked browser affected (default = 0 if no HB) + # # @return [Boolean] True if the register was successful + # def register(from, event, hb = 0) # type conversion to enforce standards hb = hb.to_i @@ -33,24 +34,23 @@ module Core time_now = Time.now # arguments type checking - raise Exception::TypeError, '"from" needs to be a string' if not from.string? - raise Exception::TypeError, '"event" needs to be a string' if not event.string? - raise Exception::TypeError, '"Hooked Browser ID" needs to be an integer' if not hb.integer? + raise Exception::TypeError, '"from" needs to be a string' unless from.string? + raise Exception::TypeError, '"event" needs to be a string' unless event.string? + raise Exception::TypeError, '"Hooked Browser ID" needs to be an integer' unless hb.integer? + # logging the new event into the database - @logs.new(:type => "#{from}", :event => "#{event}", :date => time_now, :hooked_browser_id => hb).save + @logs.new(:type => from.to_s, :event => event.to_s, :date => time_now, :hooked_browser_id => hb).save print_debug "Event: #{event}" # if notifications are enabled send the info there too if @notifications @notifications.new(from, event, time_now, hb) end - # return true end - + private @logs - end end end diff --git a/core/main/migration.rb b/core/main/migration.rb index 4c0fc2806..a60f7ca6b 100644 --- a/core/main/migration.rb +++ b/core/main/migration.rb @@ -8,18 +8,22 @@ module BeEF module Core # @note This class migrates and updates values in the database each time you restart BeEF. - # So for example, when you want to add a new command module, you stop BeEF, copy your command module into the framework - # and then restart BeEF. That class will take care of installing automatically the new command module in the db. + # So for example, when you want to add a new command module, you stop BeEF, + # copy your command module into the framework and then restart BeEF. + # That class will take care of installing automatically the new command module in the db. class Migration - include Singleton + # # Updates the database. + # def update_db! update_commands! end + # # Checks for new command modules and updates the database. + # def update_commands! config = BeEF::Core::Configuration.instance @@ -27,22 +31,21 @@ module Core BeEF::Core::Models::CommandModule.all.each do |mod| db_modules << mod.name end - - config.get('beef.module').each{|k,v| - BeEF::Core::Models::CommandModule.new(:name => k, :path => "#{v['path']}module.rb").save if not db_modules.include? k - } + config.get('beef.module').each do |k, v| + h = { :name => k, :path => "#{v['path']}module.rb" } + BeEF::Core::Models::CommandModule.new(h).save unless db_modules.include? k + end - BeEF::Core::Models::CommandModule.all.each{|mod| - if config.get('beef.module.'+mod.name) != nil - config.set('beef.module.'+mod.name+'.db.id', mod.id) - config.set('beef.module.'+mod.name+'.db.path', mod.path) + BeEF::Core::Models::CommandModule.all.each do |mod| + unless config.get("beef.module.#{mod.name}").nil? + config.set "beef.module.#{mod.name}.db.id", mod.id + config.set "beef.module.#{mod.name}.db.path", mod.path end - } + end # Call Migration method - BeEF::API::Registrar.instance.fire(BeEF::API::Migration, 'migrate_commands') - + BeEF::API::Registrar.instance.fire BeEF::API::Migration, 'migrate_commands' end end end diff --git a/core/main/server.rb b/core/main/server.rb index 654d3f0ae..be7fa6f1b 100644 --- a/core/main/server.rb +++ b/core/main/server.rb @@ -10,9 +10,7 @@ Thin::SERVER = nil module BeEF module Core - class Server - include Singleton # @note Grabs the version of beef the framework is deployed on @@ -43,46 +41,56 @@ module BeEF 'beef_public' => @configuration.get('beef.http.public'), 'beef_public_port' => @configuration.get('beef.http.public_port'), 'beef_hook' => @configuration.get('beef.http.hook_file'), - 'beef_proto' => @configuration.get('beef.http.https.enable') == true ? "https" : "http", - 'client_debug' => @configuration.get("beef.client_debug") + 'beef_proto' => @configuration.get('beef.http.https.enable') == true ? 'https' : 'http', + 'client_debug' => @configuration.get('beef.client_debug') } end + # # Mounts a handler, can either be a hard or soft mount + # # @param [String] url The url to mount # @param [Class] http_handler_class Class to call once mount is triggered # @param args Arguments to pass to the http handler class + # def mount(url, http_handler_class, args = nil) # argument type checking - raise Exception::TypeError, '"url" needs to be a string' if not url.string? + raise Exception::TypeError, '"url" needs to be a string' unless url.string? - if args == nil + if args.nil? @mounts[url] = http_handler_class else @mounts[url] = http_handler_class, *args end - print_debug("Server: mounted handler '#{url}'") + print_debug "Server: mounted handler '#{url}'" end + # # Unmounts handler + # # @param [String] url URL to unmount. + # def unmount(url) - raise Exception::TypeError, '"url" needs to be a string' if not url.string? - @mounts.delete(url) + raise Exception::TypeError, '"url" needs to be a string' unless url.string? + @mounts.delete url end + # # Reload the URL map (used by the NetworkStack AssetHandler to mount new URLs at runtime) + # def remap - @rack_app.remap(@mounts) + @rack_app.remap @mounts end + # # Prepares the BeEF http server. + # def prepare # Create http handler for the javascript hook file - self.mount("#{@configuration.get("beef.http.hook_file")}", BeEF::Core::Handlers::HookedBrowsers.new) + mount(@configuration.get("beef.http.hook_file").to_s, BeEF::Core::Handlers::HookedBrowsers.new) # Create handler for the initialization checks (Browser Details) - self.mount("/init", BeEF::Core::Handlers::BrowserDetails) + mount('/init', BeEF::Core::Handlers::BrowserDetails) # Dynamically get the list of all the http handlers using the API and register them BeEF::API::Registrar.instance.fire(BeEF::API::Server, 'mount_handler', self) @@ -111,10 +119,10 @@ module BeEF openssl_version = OpenSSL::OPENSSL_VERSION if openssl_version =~ / 1\.0\.1([a-f])? / print_warning "Warning: #{openssl_version} is vulnerable to Heartbleed (CVE-2014-0160)." - print_more "Upgrade OpenSSL to version 1.0.1g or newer." + print_more 'Upgrade OpenSSL to version 1.0.1g or newer.' end - cert_key = @configuration.get('beef.http.https.key') + cert_key = @configuration.get 'beef.http.https.key' unless cert_key.start_with? '/' cert_key = File.expand_path cert_key, $root_dir end @@ -123,7 +131,7 @@ module BeEF exit 1 end - cert = @configuration.get('beef.http.https.cert') + cert = @configuration.get 'beef.http.https.cert' unless cert.start_with? '/' cert = File.expand_path cert, $root_dir end @@ -146,14 +154,16 @@ module BeEF end end + # # Starts the BeEF http server + # def start @http_server.start rescue RuntimeError => e # port is in use raise unless e.message.include? 'no acceptor' print_error "Another process is already listening on port #{@configuration.get('beef.http.port')}, or you're trying to bind BeEF to an invalid IP." - print_error "Is BeEF already running? Exiting..." + print_error 'Is BeEF already running? Exiting...' exit 127 end end diff --git a/core/module.rb b/core/module.rb index 05e14bdc6..07cb9d26d 100644 --- a/core/module.rb +++ b/core/module.rb @@ -10,28 +10,28 @@ module BeEF # @param [String] mod module key # @return [Boolean] if the module key exists in BeEF's configuration def self.is_present(mod) - return BeEF::Core::Configuration.instance.get('beef.module').has_key?(mod.to_s) + BeEF::Core::Configuration.instance.get('beef.module').key? mod.to_s end # Checks to see if module is enabled in configuration # @param [String] mod module key # @return [Boolean] if the module key is enabled in BeEF's configuration def self.is_enabled(mod) - return (self.is_present(mod) and BeEF::Core::Configuration.instance.get("beef.module.#{mod}.enable") == true) + (is_present(mod) && BeEF::Core::Configuration.instance.get("beef.module.#{mod}.enable") == true) end # Checks to see if the module reports that it has loaded through the configuration # @param [String] mod module key # @return [Boolean] if the module key is loaded in BeEF's configuration def self.is_loaded(mod) - return (self.is_enabled(mod) and BeEF::Core::Configuration.instance.get("beef.module.#{mod}.loaded") == true) + (is_enabled(mod) && BeEF::Core::Configuration.instance.get("beef.module.#{mod}.loaded") == true) end # Returns module class definition # @param [String] mod module key # @return [Class] the module class def self.get_definition(mod) - return BeEF::Core::Command.const_get(BeEF::Core::Configuration.instance.get("beef.module.#{mod}.class")) + BeEF::Core::Command.const_get(BeEF::Core::Configuration.instance.get("beef.module.#{mod}.class")) end # Gets all module options @@ -39,26 +39,30 @@ module BeEF # @return [Hash] a hash of all the module options # @note API Fire: get_options def self.get_options(mod) - if BeEF::API::Registrar.instance.matched?(BeEF::API::Module, 'get_options', [mod]) - options = BeEF::API::Registrar.instance.fire(BeEF::API::Module, 'get_options', mod) + if BeEF::API::Registrar.instance.matched? BeEF::API::Module, 'get_options', [mod] + options = BeEF::API::Registrar.instance.fire BeEF::API::Module, 'get_options', mod mo = [] - options.each{|o| - if o[:data].kind_of?(Array) - mo += o[:data] - else + options.each do |o| + unless o[:data].is_a?(Array) print_debug "API Warning: return result for BeEF::Module.get_options() was not an array." + next end - } + mo += o[:data] + end return mo end - if self.check_hard_load(mod) - class_name = BeEF::Core::Configuration.instance.get("beef.module.#{mod}.class") - class_symbol = BeEF::Core::Command.const_get(class_name) - if class_symbol and class_symbol.respond_to?(:options) - return class_symbol.options - end + + unless check_hard_load mod + print_debug "get_opts called on unloaded module '#{mod}'" + return [] end - return [] + + class_name = BeEF::Core::Configuration.instance.get "beef.module.#{mod}.class" + class_symbol = BeEF::Core::Command.const_get class_name + + return [] unless class_symbol && class_symbol.respond_to?(:options) + + class_symbol.options end # Gets all module payload options @@ -66,11 +70,8 @@ module BeEF # @return [Hash] a hash of all the module options # @note API Fire: get_options def self.get_payload_options(mod,payload) - if BeEF::API::Registrar.instance.matched?(BeEF::API::Module, 'get_payload_options', [mod,nil]) - options = BeEF::API::Registrar.instance.fire(BeEF::API::Module, 'get_payload_options', mod,payload) - return options - end - return [] + return [] unless BeEF::API::Registrar.instance.matched?(BeEF::API::Module, 'get_payload_options', [mod, nil]) + BeEF::API::Registrar.instance.fire(BeEF::API::Module, 'get_payload_options', mod, payload) end # Soft loads a module @@ -83,21 +84,29 @@ module BeEF # API call for pre-soft-load module BeEF::API::Registrar.instance.fire(BeEF::API::Module, 'pre_soft_load', mod) config = BeEF::Core::Configuration.instance + mod_str = "beef.module.#{mod}" - if not config.get("#{mod_str}.loaded") - if not File.exists?("#{$root_dir}/#{config.get("#{mod_str}.path")}/module.rb") - print_debug "Unable to locate module file: #{config.get("#{mod_str}.path")}/module.rb" - return false - end - BeEF::Core::Configuration.instance.set("#{mod_str}.class", mod.capitalize) - self.parse_targets(mod) - print_debug "Soft Load module: '#{mod}'" - # API call for post-soft-load module - BeEF::API::Registrar.instance.fire(BeEF::API::Module, 'post_soft_load', mod) - return true + if config.get("#{mod_str}.loaded") + print_error "Unable to load module '#{mod}'" + return false end - print_error "Unable to load module '#{mod}'" - return false + + mod_path = "#{$root_dir}/#{config.get("#{mod_str}.path")}/module.rb" + unless File.exist? mod_path + print_debug "Unable to locate module file: #{mod_path}" + return false + end + + BeEF::Core::Configuration.instance.set("#{mod_str}.class", mod.capitalize) + parse_targets mod + print_debug "Soft Load module: '#{mod}'" + + # API call for post-soft-load module + BeEF::API::Registrar.instance.fire(BeEF::API::Module, 'post_soft_load', mod) + true + rescue => e + print_error "There was a problem soft loading the module '#{mod}'" + false end # Hard loads a module @@ -110,69 +119,73 @@ module BeEF # API call for pre-hard-load module BeEF::API::Registrar.instance.fire(BeEF::API::Module, 'pre_hard_load', mod) config = BeEF::Core::Configuration.instance - if not self.is_enabled(mod) + + unless is_enabled mod print_error "Hard load attempted on module '#{mod}' that is not enabled." return false end + mod_str = "beef.module.#{mod}" - begin - require config.get("#{mod_str}.path")+'module.rb' - if self.exists?(config.get("#{mod_str}.class")) - # start server mount point - BeEF::Core::Server.instance.mount("/command/#{mod}.js", BeEF::Core::Handlers::Commands, mod) - BeEF::Core::Configuration.instance.set("#{mod_str}.mount", "/command/#{mod}.js") - BeEF::Core::Configuration.instance.set("#{mod_str}.loaded", true) - print_debug "Hard Load module: '#{mod}'" - # API call for post-hard-load module - BeEF::API::Registrar.instance.fire(BeEF::API::Module, 'post_hard_load', mod) - return true - else - print_error "Hard loaded module '#{mod}' but the class BeEF::Core::Commands::#{mod.capitalize} does not exist" - end - rescue => e - BeEF::Core::Configuration.instance.set("#{mod_str}.loaded", false) - print_error "There was a problem loading the module '#{mod}'" - print_debug "Hard load module syntax error: #{e}" + mod_path = "#{config.get("#{mod_str}.path")}/module.rb" + require mod_path + + unless exists? config.get("#{mod_str}.class") + print_error "Hard loaded module '#{mod}' but the class BeEF::Core::Commands::#{mod.capitalize} does not exist" + return false end - return false + + # start server mount point + BeEF::Core::Server.instance.mount("/command/#{mod}.js", BeEF::Core::Handlers::Commands, mod) + BeEF::Core::Configuration.instance.set("#{mod_str}.mount", "/command/#{mod}.js") + BeEF::Core::Configuration.instance.set("#{mod_str}.loaded", true) + print_debug "Hard Load module: '#{mod}'" + + # API call for post-hard-load module + BeEF::API::Registrar.instance.fire(BeEF::API::Module, 'post_hard_load', mod) + true + rescue => e + BeEF::Core::Configuration.instance.set("#{mod_str}.loaded", false) + print_error "There was a problem loading the module '#{mod}'" + print_debug "Hard load module syntax error: #{e}" + false end # Checks to see if a module has been hard loaded, if not a hard load is attempted # @param [String] mod module key # @return [Boolean] if already hard loaded then true otherwise (see #hard_load) def self.check_hard_load(mod) - if not self.is_loaded(mod) - return self.hard_load(mod) - end - return true + return true if is_loaded mod + hard_load mod end # Get module key by database ID # @param [Integer] id module database ID # @return [String] module key def self.get_key_by_database_id(id) - ret = BeEF::Core::Configuration.instance.get('beef.module').select {|k, v| v.has_key?('db') and v['db']['id'].to_i == id.to_i } - return (ret.kind_of?(Array)) ? ret.first.first : ret.keys.first + ret = BeEF::Core::Configuration.instance.get('beef.module').select do |k, v| + v.key?('db') && v['db']['id'].to_i == id.to_i + end + ret.is_a?(Array) ? ret.first.first : ret.keys.first end # Get module key by module class # @param [Class] c module class # @return [String] module key def self.get_key_by_class(c) - ret = BeEF::Core::Configuration.instance.get('beef.module').select {|k, v| v.has_key?('class') and v['class'].to_s == c.to_s } - return (ret.kind_of?(Array)) ? ret.first.first : ret.keys.first + ret = BeEF::Core::Configuration.instance.get('beef.module').select do |k, v| + v.key?('class') && v['class'].to_s.eql?(c.to_s) + end + ret.is_a?(Array) ? ret.first.first : ret.keys.first end # Checks to see if module class exists # @param [String] mod module key # @return [Boolean] returns whether or not the class exists def self.exists?(mod) - begin - kclass = BeEF::Core::Command.const_get(mod.capitalize) - return kclass.is_a?(Class) - rescue NameError - return false - end + kclass = BeEF::Core::Command.const_get mod.capitalize + kclass.is_a? Class + rescue NameError + false end # Checks target configuration to see if browser / version / operating system is supported @@ -187,104 +200,102 @@ module BeEF # Please note this rating system has no correlation to the return constant value BeEF::Core::Constants::CommandModule::* def self.support(mod, opts) target_config = BeEF::Core::Configuration.instance.get("beef.module.#{mod}.target") - if not target_config or not opts.kind_of? Hash - return nil - end - if not opts.key?('browser') + return nil unless target_config + return nil unless opts.is_a? Hash + + unless opts.key? 'browser' print_error "BeEF::Module.support() was passed a hash without a valid browser constant" return nil end + results = [] - target_config.each{|k,m| - m.each{|v| + target_config.each do |k, m| + m.each do |v| case v - when String - if opts['browser'] == v - # if k == BeEF::Core::Constants::CommandModule::VERIFIED_NOT_WORKING - # rating += 1 - # end - results << {'rating' => 2, 'const' => k} - end - when Hash - if opts['browser'] == v.keys.first or v.keys.first == BeEF::Core::Constants::Browsers::ALL - subv = v[v.keys.first] - rating = 1 - #version check - if opts.key?('ver') - if subv.key?('min_ver') - if subv['min_ver'].kind_of? Fixnum and opts['ver'].to_i >= subv['min_ver'] - rating += 1 - else - break - end - end - if subv.key?('max_ver') - if (subv['max_ver'].kind_of? Fixnum and opts['ver'].to_i <= subv['max_ver']) or subv['max_ver'] == "latest" - rating += 1 - else - break - end - end - end - # os check - if opts.key?('os') and subv.key?('os') - match = false - opts['os'].each{|o| - case subv['os'] - when String - if o == subv['os'] - rating += 1 - match = true - elsif subv['os'] == BeEF::Core::Constants::Os::OS_ALL_UA_STR - match = true - end - when Array - subv['os'].each{|p| - if o == p - rating += 1 - match = true - elsif p == BeEF::Core::Constants::Os::OS_ALL_UA_STR - match = true - end - } - end - } - if not match - break - end - end - if rating > 0 - # if k == BeEF::Core::Constants::CommandModule::VERIFIED_NOT_WORKING - # rating += 1 - # end - results << {'rating' => rating, 'const' => k} - end - end - end - if v == BeEF::Core::Constants::Browsers::ALL + when String + if opts['browser'] == v + # if k == BeEF::Core::Constants::CommandModule::VERIFIED_NOT_WORKING + # rating += 1 + # end + results << {'rating' => 2, 'const' => k} + end + when Hash + break if opts['browser'] != v.keys.first && v.keys.first != BeEF::Core::Constants::Browsers::ALL + + subv = v[v.keys.first] rating = 1 - if k == BeEF::Core::Constants::CommandModule::VERIFIED_NOT_WORKING - rating = 1 + # version check + if opts.key?('ver') + if subv.key?('min_ver') + if subv['min_ver'].is_a?(Integer) && opts['ver'].to_i >= subv['min_ver'] + rating += 1 + else + break + end + end + if subv.key?('max_ver') + if (subv['max_ver'].is_a?(Integer) && opts['ver'].to_i <= subv['max_ver']) || subv['max_ver'] == 'latest' + rating += 1 + else + break + end + end end - results << {'rating' => rating, 'const' => k} - end - } - } - if results.count > 0 - result = {} - results.each {|r| - if result == {} - result = {'rating' => r['rating'], 'const' => r['const']} - else - if r['rating'] > result['rating'] - result = {'rating' => r['rating'], 'const' => r['const']} + # os check + if opts.key?('os') && subv.key?('os') + match = false + opts['os'].each do |o| + case subv['os'] + when String + if o == subv['os'] + rating += 1 + match = true + elsif subv['os'].eql? BeEF::Core::Constants::Os::OS_ALL_UA_STR + match = true + end + when Array + subv['os'].each do |p| + if o == p + rating += 1 + match = true + elsif p.eql? BeEF::Core::Constants::Os::OS_ALL_UA_STR + match = true + end + end + end + end + break unless match + end + + if rating.positive? + # if k == BeEF::Core::Constants::CommandModule::VERIFIED_NOT_WORKING + # rating += 1 + # end + results << {'rating' => rating, 'const' => k} end end - } - return result['const'] - else - return BeEF::Core::Constants::CommandModule::VERIFIED_UNKNOWN + + next unless v.eql? BeEF::Core::Constants::Browsers::ALL + + rating = 1 + if k == BeEF::Core::Constants::CommandModule::VERIFIED_NOT_WORKING + rating = 1 + end + + results << {'rating' => rating, 'const' => k} + end end + + return BeEF::Core::Constants::CommandModule::VERIFIED_UNKNOWN unless results.count.positive? + + result = {} + results.each do |r| + if result == {} || r['rating'] > result['rating'] + result = {'rating' => r['rating'], 'const' => r['const']} + end + end + + result['const'] end # Translates module target configuration @@ -293,53 +304,46 @@ module BeEF def self.parse_targets(mod) mod_str = "beef.module.#{mod}" target_config = BeEF::Core::Configuration.instance.get("#{mod_str}.target") - if target_config - targets = {} - target_config.each{|k,v| - begin - if BeEF::Core::Constants::CommandModule.const_defined?('VERIFIED_'+k.upcase) - key = BeEF::Core::Constants::CommandModule.const_get('VERIFIED_'+k.upcase) - if not targets.key?(key) - targets[key] = [] - end - browser = nil - case v - when String - browser = self.match_target_browser(v) - if browser - targets[key] << browser - end - when Array - v.each{|c| - browser = self.match_target_browser(c) - if browser - targets[key] << browser - end - } - when Hash - v.each{|k,c| - browser = self.match_target_browser(k) - if browser - case c - when TrueClass - targets[key] << browser - when Hash - details = self.match_target_browser_spec(c) - if details - targets[key] << {browser => details} - end - end - end - } + return unless target_config + + targets = {} + target_config.each do |k, v| + begin + next unless BeEF::Core::Constants::CommandModule.const_defined? "VERIFIED_#{k.upcase}" + key = BeEF::Core::Constants::CommandModule.const_get "VERIFIED_#{k.upcase}" + targets[key] = [] unless targets.key? key + browser = nil + + case v + when String + browser = match_target_browser v + targets[key] << browser if browser + when Array + v.each do |c| + browser = match_target_browser c + targets[key] << browser if browser + end + when Hash + v.each do |k, c| + browser = match_target_browser k + next unless browser + + case c + when TrueClass + targets[key] << browser + when Hash + details = match_target_browser_spec c + targets[key] << {browser => details} if details end end - rescue NameError - print_error "Module '#{mod}' configuration has invalid target status defined '#{k}'" end - } - BeEF::Core::Configuration.instance.clear("#{mod_str}.target") - BeEF::Core::Configuration.instance.set("#{mod_str}.target", targets) + rescue NameError + print_error "Module '#{mod}' configuration has invalid target status defined '#{k}'" + end end + + BeEF::Core::Configuration.instance.clear "#{mod_str}.target" + BeEF::Core::Configuration.instance.set "#{mod_str}.target", targets end # Translates simple browser target configuration @@ -347,19 +351,17 @@ module BeEF # @param [String] v user defined browser # @return [Constant] a BeEF browser constant def self.match_target_browser(v) - browser = false - if v.class == String - begin - if BeEF::Core::Constants::Browsers.const_defined?(v.upcase) - browser = BeEF::Core::Constants::Browsers.const_get(v.upcase) - end - rescue NameError - print_error "Could not identify browser target specified as '#{v}'" - end - else - print_error "Invalid datatype passed to BeEF::Module.match_target_browser()" + unless v.class == String + print_error 'Invalid datatype passed to BeEF::Module.match_target_browser()' + return false end - return browser + + return false unless BeEF::Core::Constants::Browsers.const_defined? v.upcase + + BeEF::Core::Constants::Browsers.const_get v.upcase + rescue NameError + print_error "Could not identify browser target specified as '#{v}'" + false end # Translates complex browser target configuration @@ -367,35 +369,35 @@ module BeEF # @param [Hash] v user defined browser hash # @return [Hash] BeEF constants hash def self.match_target_browser_spec(v) - browser = {} - if v.class == Hash - if v.key?('max_ver') and (v['max_ver'].is_a?(Fixnum) or v['max_ver'].is_a?(Float) or v['max_ver'] == "latest") - browser['max_ver'] = v['max_ver'] - end - if v.key?('min_ver') and (v['min_ver'].is_a?(Fixnum) or v['min_ver'].is_a?(Float)) - browser['min_ver'] = v['min_ver'] - end - if v.key?('os') - case v['os'] - when String - os = self.match_target_os(v['os']) - if os - browser['os'] = os - end - when Array - browser['os'] = [] - v['os'].each{|c| - os = self.match_target_os(c) - if os - browser['os'] << os - end - } - end - end - else - print_error "Invalid datatype passed to BeEF::Module.match_target_browser_spec()" + unless v.class == Hash + print_error 'Invalid datatype passed to BeEF::Module.match_target_browser_spec()' + return {} end - return browser + + browser = {} + if v.key?('max_ver') && (v['max_ver'].is_a?(Integer) || v['max_ver'].is_a?(Float) || v['max_ver'] == 'latest') + browser['max_ver'] = v['max_ver'] + end + + if v.key?('min_ver') && (v['min_ver'].is_a?(Integer) || v['min_ver'].is_a?(Float)) + browser['min_ver'] = v['min_ver'] + end + + return browser unless v.key? 'os' + + case v['os'] + when String + os = match_target_os v['os'] + browser['os'] = os if os + when Array + browser['os'] = [] + v['os'].each do |c| + os = match_target_os c + browser['os'] << os if os + end + end + + browser end # Translates simple OS target configuration @@ -403,84 +405,81 @@ module BeEF # @param [String] v user defined OS string # @return [Constant] BeEF OS Constant def self.match_target_os(v) - os = false - if v.class == String - begin - if BeEF::Core::Constants::Os.const_defined?("OS_#{v.upcase}_UA_STR") - os = BeEF::Core::Constants::Os.const_get("OS_#{v.upcase}_UA_STR") - end - rescue NameError - print_error "Could not identify OS target specified as '#{v}'" - end - else - print_error "Invalid datatype passed to BeEF::Module.match_target_os()" + unless v.class == String + print_error 'Invalid datatype passed to BeEF::Module.match_target_os()' + return false end - return os + + return false unless BeEF::Core::Constants::Os.const_defined? "OS_#{v.upcase}_UA_STR" + + BeEF::Core::Constants::Os.const_get "OS_#{v.upcase}_UA_STR" + rescue NameError + print_error "Could not identify OS target specified as '#{v}'" + false end # Executes a module # @param [String] mod module key # @param [String] hbsession hooked browser session # @param [Array] opts array of module execute options (see #get_options) - # @return [Fixnum] the command_id associated to the module execution when info is persisted. nil if there are errors. + # @return [Integer] the command_id associated to the module execution when info is persisted. nil if there are errors. # @note The return value of this function does not specify if the module was successful, only that it was executed within the framework def self.execute(mod, hbsession, opts=[]) - if not (self.is_present(mod) and self.is_enabled(mod)) + unless is_present(mod) && is_enabled(mod) print_error "Module not found '#{mod}'. Failed to execute module." return nil end - if BeEF::API::Registrar.instance.matched?(BeEF::API::Module, 'override_execute', [mod, nil,nil]) - BeEF::API::Registrar.instance.fire(BeEF::API::Module, 'override_execute', mod, hbsession,opts) + + if BeEF::API::Registrar.instance.matched? BeEF::API::Module, 'override_execute', [mod, nil, nil] + BeEF::API::Registrar.instance.fire BeEF::API::Module, 'override_execute', mod, hbsession, opts # @note We return not_nil by default as we cannot determine the correct status if multiple API hooks have been called - return 'not_available' # @note using metasploit, we cannot know if the module execution was successful or not + # @note using metasploit, we cannot know if the module execution was successful or not + return 'not_available' end - hb = BeEF::HBManager.get_by_session(hbsession) - if not hb + + hb = BeEF::HBManager.get_by_session hbsession + unless hb print_error "Could not find hooked browser when attempting to execute module '#{mod}'" return nil end - self.check_hard_load(mod) - command_module = self.get_definition(mod).new(mod) + + check_hard_load mod + command_module = get_definition(mod).new(mod) if command_module.respond_to?(:pre_execute) command_module.pre_execute end - h = self.merge_options(mod, []) - c = BeEF::Core::Models::Command.create(:data => self.merge_options(mod, opts).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 + merge_options(mod, []) + c = BeEF::Core::Models::Command.create( + :data => merge_options(mod, opts).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 ) - return c.id + c.id end # Merges default module options with array of custom options # @param [String] mod module key - # @param [Hash] h module options customised by user input + # @param [Hash] opts module options customised by user input # @return [Hash, nil] returns merged options - def self.merge_options(mod, h) - if self.is_present(mod) - self.check_hard_load(mod) - merged = [] - defaults = self.get_options(mod) - defaults.each{|v| - mer = nil - h.each{|o| - if v.has_key?('name') and o.has_key?('name') and v['name'] == o['name'] - mer = v.deep_merge(o) - end - } - if mer != nil - merged.push(mer) - else - merged.push(v) - end - } - return merged - end - return nil - end + def self.merge_options(mod, opts) + return nil unless is_present mod + check_hard_load mod + merged = [] + defaults = get_options mod + defaults.each do |v| + mer = nil + opts.each do |o| + if v.key?('name') && o.key?('name') && v['name'] == o['name'] + mer = v.deep_merge o + end + end + + mer.nil? ? merged.push(v) : merged.push(mer) + end + + merged + end end end - - diff --git a/core/modules.rb b/core/modules.rb index 28e5bab63..6fff47aaa 100644 --- a/core/modules.rb +++ b/core/modules.rb @@ -9,50 +9,56 @@ module BeEF # Return configuration hashes of all modules that are enabled # @return [Array] configuration hashes of all enabled modules def self.get_enabled - return BeEF::Core::Configuration.instance.get('beef.module').select {|k,v| v['enable'] == true and v['category'] != nil } + BeEF::Core::Configuration.instance.get('beef.module').select do |_k, v| + v['enable'] == true && !v['category'].nil? + end end # Return configuration hashes of all modules that are loaded # @return [Array] configuration hashes of all loaded modules def self.get_loaded - return BeEF::Core::Configuration.instance.get('beef.module').select {|k,v| v['loaded'] == true } + BeEF::Core::Configuration.instance.get('beef.module').select do |_k, v| + v['loaded'] == true + end end # Return an array of categories specified in module configuration files # @return [Array] all available module categories sorted alphabetically def self.get_categories categories = [] - BeEF::Core::Configuration.instance.get('beef.module').each {|k,v| - flatcategory = "" - if v['category'].kind_of?(Array) - # Therefore this module has nested categories (sub-folders), munge them together into a string with '/' characters, like a folder. - v['category'].each {|cat| - flatcategory << cat + "/" - } + BeEF::Core::Configuration.instance.get('beef.module').each_value do |v| + flatcategory = '' + if v['category'].is_a?(Array) + # Therefore this module has nested categories (sub-folders), + # munge them together into a string with '/' characters, like a folder. + v['category'].each do |cat| + flatcategory << "#{cat}/" + end else flatcategory = v['category'] end - if not categories.include?(flatcategory) - categories << flatcategory - end - } - return categories.sort.uniq #This is now uniqued, because otherwise the recursive function to build the json tree breaks if there are duplicates. + categories << flatcategory unless categories.include? flatcategory + end + + # This is now uniqued, because otherwise the recursive function to build + # the json tree breaks if there are duplicates. + categories.sort.uniq end # Get all modules currently stored in the database # @return [Array] DataMapper array of all BeEF::Core::Models::CommandModule's in the database def self.get_stored_in_db - return BeEF::Core::Models::CommandModule.all(:order => [:id.asc]) + BeEF::Core::Models::CommandModule.all(:order => [:id.asc]) end - # Loads all enabled modules + # Loads all enabled modules # @note API Fire: post_soft_load def self.load BeEF::Core::Configuration.instance.load_modules_config - self.get_enabled.each { |k,v| - BeEF::Module.soft_load(k) - } - BeEF::API::Registrar.instance.fire(BeEF::API::Modules, 'post_soft_load') + get_enabled.each_key do |k| + BeEF::Module.soft_load k + end + BeEF::API::Registrar.instance.fire BeEF::API::Modules, 'post_soft_load' end end end diff --git a/core/ruby/hash.rb b/core/ruby/hash.rb index 6fa706f76..dc843e1ea 100644 --- a/core/ruby/hash.rb +++ b/core/ruby/hash.rb @@ -8,13 +8,14 @@ class Hash # Recursively deep merge two hashes together # @param [Hash] hash Hash to be merged # @return [Hash] Combined hash - # @note Duplicate keys are overwritten by the value defined in the hash calling deep_merge (not the parameter hash) + # @note Duplicate keys are overwritten by the value defined + # in the hash calling deep_merge (not the parameter hash) # @note http://snippets.dzone.com/posts/show/4706 def deep_merge(hash) target = dup hash.keys.each do |key| - if hash[key].is_a? Hash and self[key].is_a? Hash - target[key] = target[key].deep_merge(hash[key]) + if hash[key].is_a?(Hash) && self[key].is_a?(Hash) + target[key] = target[key].deep_merge hash[key] next end target[key] = hash[key] diff --git a/core/ruby/object.rb b/core/ruby/object.rb index bbd2d6ca4..21142551b 100644 --- a/core/ruby/object.rb +++ b/core/ruby/object.rb @@ -35,4 +35,9 @@ class Object self.is_a?(Class) end + # Returns true if the object is nil, and empty string, or empty array + # @return [Boolean] + def blank? + self.respond_to?(:empty?) ? !!empty? : !self + end end