Core: Resolve many Rubocop violations (#2282)
This commit is contained in:
18
core/api.rb
18
core/api.rb
@@ -117,7 +117,7 @@ module BeEF
|
||||
next unless r['method'] == method
|
||||
next unless is_matched_params? r, params
|
||||
|
||||
owners << { :owner => r['owner'], :id => r['id'] }
|
||||
owners << { owner: r['owner'], id: r['id'] }
|
||||
end
|
||||
owners
|
||||
end
|
||||
@@ -192,17 +192,13 @@ module BeEF
|
||||
data = []
|
||||
method = get_api_path(clss, mthd)
|
||||
mods.each do |mod|
|
||||
begin
|
||||
# Only used for API Development (very verbose)
|
||||
# print_info "API: #{mod} fired #{method}"
|
||||
# Only used for API Development (very verbose)
|
||||
# print_info "API: #{mod} fired #{method}"
|
||||
|
||||
result = mod[:owner].method(method).call(*args)
|
||||
unless result.nil?
|
||||
data << { :api_id => mod[:id], :data => result }
|
||||
end
|
||||
rescue => e
|
||||
print_error "API Fire Error: #{e.message} in #{mod}.#{method}()"
|
||||
end
|
||||
result = mod[:owner].method(method).call(*args)
|
||||
data << { api_id: mod[:id], data: result } unless result.nil?
|
||||
rescue StandardError => e
|
||||
print_error "API Fire Error: #{e.message} in #{mod}.#{method}()"
|
||||
end
|
||||
|
||||
data
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4,40 +4,37 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module API
|
||||
module Server
|
||||
|
||||
# @note Defined API Paths
|
||||
API_PATHS = {
|
||||
module API
|
||||
module Server
|
||||
# @note Defined API Paths
|
||||
API_PATHS = {
|
||||
'mount_handler' => :mount_handler,
|
||||
'pre_http_start' => :pre_http_start
|
||||
}
|
||||
|
||||
# Fires just before the HTTP Server is started
|
||||
# @param [Object] http_hook_server HTTP Server object
|
||||
def pre_http_start(http_hook_server); end
|
||||
|
||||
# Fires just after handlers have been mounted
|
||||
# @param [Object] server HTTP Server object
|
||||
def mount_handler(server); end
|
||||
|
||||
# Mounts a handler
|
||||
# @param [String] url URL to be mounted
|
||||
# @param [Class] http_handler_class the handler Class
|
||||
# @param [Array] args an array of arguments
|
||||
# @note This is a direct API call and does not have to be registered to be used
|
||||
def self.mount(url, http_handler_class, args = nil)
|
||||
BeEF::Core::Server.instance.mount(url, http_handler_class, *args)
|
||||
end
|
||||
}.freeze
|
||||
|
||||
# Unmounts a handler
|
||||
# @param [String] url URL to be unmounted
|
||||
# @note This is a direct API call and does not have to be registered to be used
|
||||
def self.unmount(url)
|
||||
# Fires just before the HTTP Server is started
|
||||
# @param [Object] http_hook_server HTTP Server object
|
||||
def pre_http_start(http_hook_server); end
|
||||
|
||||
# Fires just after handlers have been mounted
|
||||
# @param [Object] server HTTP Server object
|
||||
def mount_handler(server); end
|
||||
|
||||
# Mounts a handler
|
||||
# @param [String] url URL to be mounted
|
||||
# @param [Class] http_handler_class the handler Class
|
||||
# @param [Array] args an array of arguments
|
||||
# @note This is a direct API call and does not have to be registered to be used
|
||||
def self.mount(url, http_handler_class, args = nil)
|
||||
BeEF::Core::Server.instance.mount(url, http_handler_class, *args)
|
||||
end
|
||||
|
||||
# Unmounts a handler
|
||||
# @param [String] url URL to be unmounted
|
||||
# @note This is a direct API call and does not have to be registered to be used
|
||||
def self.unmount(url)
|
||||
BeEF::Core::Server.instance.unmount(url)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,22 +5,20 @@
|
||||
#
|
||||
module BeEF
|
||||
module API
|
||||
|
||||
module Command
|
||||
end
|
||||
|
||||
module Module
|
||||
|
||||
# @note Defined API Paths
|
||||
API_PATHS = {
|
||||
'pre_soft_load' => :pre_soft_load,
|
||||
'post_soft_load' => :post_soft_load,
|
||||
'pre_hard_load' => :pre_hard_load,
|
||||
'post_hard_load' => :post_hard_load,
|
||||
'get_options' => :get_options,
|
||||
'get_payload_options' => :get_payload_options,
|
||||
'override_execute' => :override_execute
|
||||
}
|
||||
'pre_soft_load' => :pre_soft_load,
|
||||
'post_soft_load' => :post_soft_load,
|
||||
'pre_hard_load' => :pre_hard_load,
|
||||
'post_hard_load' => :post_hard_load,
|
||||
'get_options' => :get_options,
|
||||
'get_payload_options' => :get_payload_options,
|
||||
'override_execute' => :override_execute
|
||||
}.freeze
|
||||
|
||||
# Fired before a module soft load
|
||||
# @param [String] mod module key of module about to be soft loaded
|
||||
@@ -54,8 +52,6 @@ module BeEF
|
||||
# @return [Hash] a hash of options
|
||||
# @note the option hash is merged with all other API hook's returned hash. Hooking this API method prevents the default options being returned.
|
||||
def get_payload_options; end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#
|
||||
module BeEF
|
||||
module Core
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -14,7 +13,6 @@ require 'core/main/router/router'
|
||||
require 'core/main/router/api'
|
||||
require 'core/main/router/error_responses'
|
||||
|
||||
|
||||
## @note Include http server functions for beef
|
||||
require 'core/main/server'
|
||||
require 'core/main/handlers/modules/beefjs'
|
||||
|
||||
@@ -4,9 +4,8 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Core
|
||||
|
||||
end
|
||||
module Core
|
||||
end
|
||||
end
|
||||
|
||||
# @note Includes database models - the order must be consistent otherwise DataMapper goes crazy
|
||||
@@ -38,4 +37,3 @@ require 'core/main/geoip'
|
||||
# @note Include the command line parser and the banner printer
|
||||
require 'core/main/console/commandline'
|
||||
require 'core/main/console/banners'
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
|
||||
# Checks to see if extension is set inside the configuration
|
||||
# @param [String] ext the extension key
|
||||
# @return [Boolean] whether or not the extension exists in BeEF's configuration
|
||||
@@ -15,9 +14,10 @@ module BeEF
|
||||
|
||||
# Checks to see if extension is enabled in configuration
|
||||
# @param [String] ext the extension key
|
||||
# @return [Boolean] whether or not the extension is enabled
|
||||
# @return [Boolean] whether or not the extension is enabled
|
||||
def self.is_enabled(ext)
|
||||
return false unless is_present(ext)
|
||||
|
||||
BeEF::Core::Configuration.instance.get("beef.extension.#{ext}.enable") == true
|
||||
end
|
||||
|
||||
@@ -26,10 +26,11 @@ module BeEF
|
||||
# @return [Boolean] whether or not the extension is loaded
|
||||
def self.is_loaded(ext)
|
||||
return false unless is_enabled(ext)
|
||||
|
||||
BeEF::Core::Configuration.instance.get("beef.extension.#{ext}.loaded") == true
|
||||
end
|
||||
|
||||
# Loads an extension
|
||||
# Loads an extension
|
||||
# @param [String] ext the extension key
|
||||
# @return [Boolean] whether or not the extension loaded successfully
|
||||
def self.load(ext)
|
||||
@@ -41,7 +42,7 @@ module BeEF
|
||||
end
|
||||
print_error "Unable to load extension '#{ext}'"
|
||||
false
|
||||
rescue => e
|
||||
rescue StandardError => e
|
||||
print_error "Unable to load extension '#{ext}':"
|
||||
print_more e.message
|
||||
end
|
||||
|
||||
@@ -5,12 +5,11 @@
|
||||
#
|
||||
module BeEF
|
||||
module Extensions
|
||||
|
||||
# Returns configuration of all enabled extensions
|
||||
# @return [Array] an array of extension configuration hashes that are enabled
|
||||
def self.get_enabled
|
||||
BeEF::Core::Configuration.instance.get('beef.extension').select { |k,v| v['enable'] == true }
|
||||
rescue => e
|
||||
BeEF::Core::Configuration.instance.get('beef.extension').select { |_k, v| v['enable'] == true }
|
||||
rescue StandardError => e
|
||||
print_error "Failed to get enabled extensions: #{e.message}"
|
||||
print_error e.backtrace
|
||||
end
|
||||
@@ -18,8 +17,8 @@ module BeEF
|
||||
# Returns configuration of all loaded extensions
|
||||
# @return [Array] an array of extension configuration hashes that are loaded
|
||||
def self.get_loaded
|
||||
BeEF::Core::Configuration.instance.get('beef.extension').select {|k,v| v['loaded'] == true }
|
||||
rescue => e
|
||||
BeEF::Core::Configuration.instance.get('beef.extension').select { |_k, v| v['loaded'] == true }
|
||||
rescue StandardError => e
|
||||
print_error "Failed to get loaded extensions: #{e.message}"
|
||||
print_error e.backtrace
|
||||
end
|
||||
@@ -28,12 +27,12 @@ module BeEF
|
||||
# @note API fire for post_load
|
||||
def self.load
|
||||
BeEF::Core::Configuration.instance.load_extensions_config
|
||||
self.get_enabled.each { |k,v|
|
||||
get_enabled.each do |k, _v|
|
||||
BeEF::Extension.load k
|
||||
}
|
||||
end
|
||||
# API post extension load
|
||||
BeEF::API::Registrar.instance.fire BeEF::API::Extensions, 'post_load'
|
||||
rescue => e
|
||||
rescue StandardError => e
|
||||
print_error "Failed to load extensions: #{e.message}"
|
||||
print_error e.backtrace
|
||||
end
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#
|
||||
module BeEF
|
||||
module Filters
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -4,196 +4,211 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Filters
|
||||
module Filters
|
||||
# Check if the string is not empty and not nil
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] Whether the string is not empty
|
||||
def self.is_non_empty_string?(str)
|
||||
return false if str.nil?
|
||||
return false unless str.is_a? String
|
||||
return false if str.empty?
|
||||
|
||||
# Check if the string is not empty and not nil
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] Whether the string is not empty
|
||||
def self.is_non_empty_string?(str)
|
||||
return false if str.nil?
|
||||
return false unless str.is_a? String
|
||||
return false if str.empty?
|
||||
true
|
||||
true
|
||||
end
|
||||
|
||||
# Check if only the characters in 'chars' are in 'str'
|
||||
# @param [String] chars List of characters to match
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] Whether or not the only characters in str are specified in chars
|
||||
def self.only?(chars, str)
|
||||
regex = Regexp.new('[^' + chars + ']')
|
||||
regex.match(str.encode('UTF-8', invalid: :replace, undef: :replace, replace: '')).nil?
|
||||
end
|
||||
|
||||
# Check if one or more characters in 'chars' are in 'str'
|
||||
# @param [String] chars List of characters to match
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] Whether one of the characters exists in the string
|
||||
def self.exists?(chars, str)
|
||||
regex = Regexp.new(chars)
|
||||
!regex.match(str.encode('UTF-8', invalid: :replace, undef: :replace, replace: '')).nil?
|
||||
end
|
||||
|
||||
# Check for null char
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has a null character
|
||||
def self.has_null?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
|
||||
exists?('\x00', str)
|
||||
end
|
||||
|
||||
# Check for non-printable char
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] Whether or not the string has non-printable characters
|
||||
def self.has_non_printable_char?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
|
||||
!only?('[:print:]', str)
|
||||
end
|
||||
|
||||
# Check if num characters only
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string only contains numbers
|
||||
def self.nums_only?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
|
||||
only?('0-9', str)
|
||||
end
|
||||
|
||||
# Check if valid float
|
||||
# @param [String] str String for float testing
|
||||
# @return [Boolean] If the string is a valid float
|
||||
def self.is_valid_float?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false unless only?('0-9\.', str)
|
||||
|
||||
!(str =~ /^\d+\.\d+$/).nil?
|
||||
end
|
||||
|
||||
# Check if hex characters only
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string only contains hex characters
|
||||
def self.hexs_only?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
|
||||
only?('0123456789ABCDEFabcdef', str)
|
||||
end
|
||||
|
||||
# Check if first character is a number
|
||||
# @param [String] String for testing
|
||||
# @return [Boolean] If the first character of the string is a number
|
||||
def self.first_char_is_num?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
|
||||
!(str =~ /^\d.*/).nil?
|
||||
end
|
||||
|
||||
# Check for space characters: \t\n\r\f
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has a whitespace character
|
||||
def self.has_whitespace_char?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
|
||||
exists?('\s', str)
|
||||
end
|
||||
|
||||
# Check for non word characters: a-zA-Z0-9
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string only has alphanums
|
||||
def self.alphanums_only?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
|
||||
only?('a-zA-Z0-9', str)
|
||||
end
|
||||
|
||||
# @overload self.is_valid_ip?(ip, version)
|
||||
# Checks if the given string is a valid IP address
|
||||
# @param [String] ip string to be tested
|
||||
# @param [Symbol] version IP version (either <code>:ipv4</code> or <code>:ipv6</code>)
|
||||
# @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 <code>:ipv4</code> or <code>:ipv6</code>)
|
||||
# @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
|
||||
|
||||
@@ -4,148 +4,159 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Filters
|
||||
module Filters
|
||||
# Check the browser type value - for example, 'FF'
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid browser name characters
|
||||
def self.is_valid_browsername?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if str.length > 2
|
||||
return false if has_non_printable_char?(str)
|
||||
|
||||
# Check the browser type value - for example, 'FF'
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid browser name characters
|
||||
def self.is_valid_browsername?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if str.length > 2
|
||||
return false if has_non_printable_char?(str)
|
||||
true
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
# Check the Operating System name value - for example, 'Windows XP'
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid Operating System name characters
|
||||
def self.is_valid_osname?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if has_non_printable_char?(str)
|
||||
return false if str.length < 2
|
||||
true
|
||||
end
|
||||
# Check the Operating System name value - for example, 'Windows XP'
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid Operating System name characters
|
||||
def self.is_valid_osname?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if has_non_printable_char?(str)
|
||||
return false if str.length < 2
|
||||
|
||||
# Check the Hardware name value - for example, 'iPhone'
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid Hardware name characters
|
||||
def self.is_valid_hwname?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if has_non_printable_char?(str)
|
||||
return false if str.length < 2
|
||||
true
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
# Verify the browser version string is valid
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid browser version characters
|
||||
def self.is_valid_browserversion?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if has_non_printable_char?(str)
|
||||
return true if str.eql? "UNKNOWN"
|
||||
return true if str.eql? "ALL"
|
||||
return false if not nums_only?(str) and not is_valid_float?(str)
|
||||
return false if str.length > 20
|
||||
true
|
||||
end
|
||||
# Check the Hardware name value - for example, 'iPhone'
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid Hardware name characters
|
||||
def self.is_valid_hwname?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if has_non_printable_char?(str)
|
||||
return false if str.length < 2
|
||||
|
||||
# Verify the os version string is valid
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid os version characters
|
||||
def self.is_valid_osversion?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if has_non_printable_char?(str)
|
||||
return true if str.eql? "UNKNOWN"
|
||||
return true if str.eql? "ALL"
|
||||
return false unless BeEF::Filters::only?("a-zA-Z0-9.<=> ", str)
|
||||
return false if str.length > 20
|
||||
true
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
# Verify the browser/UA string is valid
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid browser / ua string characters
|
||||
def self.is_valid_browserstring?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if has_non_printable_char?(str)
|
||||
return false if str.length > 300
|
||||
true
|
||||
end
|
||||
|
||||
# Verify the cookies are valid
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid cookie characters
|
||||
def self.is_valid_cookies?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if has_non_printable_char?(str)
|
||||
return false if str.length > 2000
|
||||
true
|
||||
end
|
||||
# Verify the browser version string is valid
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid browser version characters
|
||||
def self.is_valid_browserversion?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if has_non_printable_char?(str)
|
||||
return true if str.eql? 'UNKNOWN'
|
||||
return true if str.eql? 'ALL'
|
||||
return false if !nums_only?(str) and !is_valid_float?(str)
|
||||
return false if str.length > 20
|
||||
|
||||
# Verify the system platform is valid
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid system platform characters
|
||||
def self.is_valid_system_platform?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if has_non_printable_char?(str)
|
||||
return false if str.length > 200
|
||||
true
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
# Verify the date stamp is valid
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid date stamp characters
|
||||
def self.is_valid_date_stamp?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if has_non_printable_char?(str)
|
||||
return false if str.length > 200
|
||||
true
|
||||
end
|
||||
# Verify the os version string is valid
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid os version characters
|
||||
def self.is_valid_osversion?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if has_non_printable_char?(str)
|
||||
return true if str.eql? 'UNKNOWN'
|
||||
return true if str.eql? 'ALL'
|
||||
return false unless BeEF::Filters.only?('a-zA-Z0-9.<=> ', str)
|
||||
return false if str.length > 20
|
||||
|
||||
# Verify the CPU type string is valid
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid CPU type characters
|
||||
def self.is_valid_cpu?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if has_non_printable_char?(str)
|
||||
return false if str.length > 200
|
||||
true
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
# Verify the memory string is valid
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid memory type characters
|
||||
def self.is_valid_memory?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if has_non_printable_char?(str)
|
||||
return false if str.length > 200
|
||||
true
|
||||
end
|
||||
# Verify the browser/UA string is valid
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid browser / ua string characters
|
||||
def self.is_valid_browserstring?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if has_non_printable_char?(str)
|
||||
return false if str.length > 300
|
||||
|
||||
# Verify the GPU type string is valid
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid GPU type characters
|
||||
def self.is_valid_gpu?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if has_non_printable_char?(str)
|
||||
return false if str.length > 200
|
||||
true
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
# Verify the browser_plugins string is valid
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid browser plugin characters
|
||||
# @note This string can be empty if there are no browser plugins
|
||||
# @todo Verify if the ruby version statement is still necessary
|
||||
def self.is_valid_browser_plugins?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if str.length > 1000
|
||||
if str.encoding === Encoding.find('UTF-8')
|
||||
return (str =~ /[^\w\d\s()-.,';_!\302\256]/u).nil?
|
||||
else
|
||||
return (str =~ /[^\w\d\s()-.,';_!\302\256]/n).nil?
|
||||
# Verify the cookies are valid
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid cookie characters
|
||||
def self.is_valid_cookies?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if has_non_printable_char?(str)
|
||||
return false if str.length > 2000
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
# Verify the system platform is valid
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid system platform characters
|
||||
def self.is_valid_system_platform?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if has_non_printable_char?(str)
|
||||
return false if str.length > 200
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
# Verify the date stamp is valid
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid date stamp characters
|
||||
def self.is_valid_date_stamp?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if has_non_printable_char?(str)
|
||||
return false if str.length > 200
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
# Verify the CPU type string is valid
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid CPU type characters
|
||||
def self.is_valid_cpu?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if has_non_printable_char?(str)
|
||||
return false if str.length > 200
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
# Verify the memory string is valid
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid memory type characters
|
||||
def self.is_valid_memory?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if has_non_printable_char?(str)
|
||||
return false if str.length > 200
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
# Verify the GPU type string is valid
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid GPU type characters
|
||||
def self.is_valid_gpu?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if has_non_printable_char?(str)
|
||||
return false if str.length > 200
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
# Verify the browser_plugins string is valid
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid browser plugin characters
|
||||
# @note This string can be empty if there are no browser plugins
|
||||
# @todo Verify if the ruby version statement is still necessary
|
||||
def self.is_valid_browser_plugins?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if str.length > 1000
|
||||
|
||||
if str.encoding === Encoding.find('UTF-8')
|
||||
(str =~ /[^\w\d\s()-.,';_!\302\256]/u).nil?
|
||||
else
|
||||
(str =~ /[^\w\d\s()-.,';_!\302\256]/n).nil?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4,27 +4,27 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Filters
|
||||
|
||||
# Verify the page title string is valid
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string is a valid page title
|
||||
def self.is_valid_pagetitle?(str)
|
||||
return false unless str.is_a? String
|
||||
return false if has_non_printable_char?(str)
|
||||
return false if str.length > 500 # CxF Increased this because some page titles are MUCH longer
|
||||
true
|
||||
end
|
||||
module Filters
|
||||
# Verify the page title string is valid
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string is a valid page title
|
||||
def self.is_valid_pagetitle?(str)
|
||||
return false unless str.is_a? String
|
||||
return false if has_non_printable_char?(str)
|
||||
return false if str.length > 500 # CxF Increased this because some page titles are MUCH longer
|
||||
|
||||
# Verify the page referrer string is valid
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string is a valid referrer
|
||||
def self.is_valid_pagereferrer?(str)
|
||||
return false unless str.is_a? String
|
||||
return false if has_non_printable_char?(str)
|
||||
return false if str.length > 350
|
||||
true
|
||||
true
|
||||
end
|
||||
|
||||
# Verify the page referrer string is valid
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string is a valid referrer
|
||||
def self.is_valid_pagereferrer?(str)
|
||||
return false unless str.is_a? String
|
||||
return false if has_non_printable_char?(str)
|
||||
return false if str.length > 350
|
||||
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,12 +5,11 @@
|
||||
#
|
||||
module BeEF
|
||||
module HBManager
|
||||
|
||||
# Get hooked browser by session id
|
||||
# @param [String] sid hooked browser session id string
|
||||
# @return [BeEF::Core::Models::HookedBrowser] returns the associated Hooked Browser
|
||||
def self.get_by_session(sid)
|
||||
BeEF::Core::Models::HookedBrowser.where(:session => sid).first
|
||||
BeEF::Core::Models::HookedBrowser.where(session: sid).first
|
||||
end
|
||||
|
||||
# Get hooked browser by id
|
||||
@@ -19,6 +18,5 @@ module BeEF
|
||||
def self.get_by_id(id)
|
||||
BeEF::Core::Models::HookedBrowser.find(id)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,8 +12,8 @@ module BeEF
|
||||
attr_writer :logger
|
||||
|
||||
def logger
|
||||
@logger ||= Logger.new("#{$home_dir}/beef.log").tap do |log|
|
||||
log.progname = self.name
|
||||
@logger ||= Logger.new("#{$home_dir}/beef.log").tap do |log|
|
||||
log.progname = name
|
||||
log.level = Logger::WARN
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
module BeEF
|
||||
module Core
|
||||
module AutorunEngine
|
||||
|
||||
class Engine
|
||||
|
||||
include Singleton
|
||||
|
||||
def initialize
|
||||
@@ -20,8 +18,8 @@ module BeEF
|
||||
|
||||
@debug_on = @config.get('beef.debug')
|
||||
|
||||
@VERSION = ['<','<=','==','>=','>','ALL']
|
||||
@VERSION_STR = ['XP','Vista']
|
||||
@VERSION = ['<', '<=', '==', '>=', '>', 'ALL']
|
||||
@VERSION_STR = %w[XP Vista]
|
||||
end
|
||||
|
||||
# Check if the hooked browser type/version and OS type/version match any Rule-sets
|
||||
@@ -30,13 +28,12 @@ module BeEF
|
||||
def run(hb_id, browser_name, browser_version, os_name, os_version)
|
||||
are = BeEF::Core::AutorunEngine::Engine.instance
|
||||
match_rules = are.match(browser_name, browser_version, os_name, os_version)
|
||||
are.trigger(match_rules, hb_id) if match_rules !=nil && match_rules.length > 0
|
||||
are.trigger(match_rules, hb_id) if !match_rules.nil? && match_rules.length > 0
|
||||
end
|
||||
|
||||
# Prepare and return the JavaScript of the modules to be sent.
|
||||
# It also updates the rules ARE execution table with timings
|
||||
def trigger(rule_ids, hb_id)
|
||||
|
||||
hb = BeEF::HBManager.get_by_id(hb_id)
|
||||
hb_session = hb.session
|
||||
|
||||
@@ -48,26 +45,25 @@ module BeEF
|
||||
execution_delay = JSON.parse(rule.execution_delay)
|
||||
chain_mode = rule.chain_mode
|
||||
|
||||
mods_bodies = Array.new
|
||||
mods_codes = Array.new
|
||||
mods_conditions = Array.new
|
||||
mods_bodies = []
|
||||
mods_codes = []
|
||||
mods_conditions = []
|
||||
|
||||
# this ensures that if both rule A and rule B call the same module in sequential mode,
|
||||
# execution will be correct preventing wrapper functions to be called with equal names.
|
||||
rule_token = SecureRandom.hex(5)
|
||||
|
||||
modules.each do |cmd_mod|
|
||||
mod = BeEF::Core::Models::CommandModule.where(:name => cmd_mod['name']).first
|
||||
mod = BeEF::Core::Models::CommandModule.where(name: cmd_mod['name']).first
|
||||
options = []
|
||||
replace_input = false
|
||||
cmd_mod['options'].each do|k,v|
|
||||
options.push({'name' => k, 'value' => v})
|
||||
cmd_mod['options'].each do |k, v|
|
||||
options.push({ 'name' => k, 'value' => v })
|
||||
replace_input = true if v == '<<mod_input>>'
|
||||
end
|
||||
|
||||
command_body = prepare_command(mod, options, hb_id, replace_input, rule_token)
|
||||
|
||||
|
||||
mods_bodies.push(command_body)
|
||||
mods_codes.push(cmd_mod['code'])
|
||||
mods_conditions.push(cmd_mod['condition'])
|
||||
@@ -75,32 +71,31 @@ module BeEF
|
||||
|
||||
# Depending on the chosen chain mode (sequential or nested/forward), prepare the appropriate wrapper
|
||||
case chain_mode
|
||||
when 'nested-forward'
|
||||
wrapper = prepare_nested_forward_wrapper(mods_bodies, mods_codes, mods_conditions, execution_order, rule_token)
|
||||
when 'sequential'
|
||||
wrapper = prepare_sequential_wrapper(mods_bodies, execution_order, execution_delay, rule_token)
|
||||
else
|
||||
wrapper = nil
|
||||
print_error "Chain mode looks wrong!"
|
||||
# TODO catch error, which should never happen as values are checked way before ;-)
|
||||
when 'nested-forward'
|
||||
wrapper = prepare_nested_forward_wrapper(mods_bodies, mods_codes, mods_conditions, execution_order, rule_token)
|
||||
when 'sequential'
|
||||
wrapper = prepare_sequential_wrapper(mods_bodies, execution_order, execution_delay, rule_token)
|
||||
else
|
||||
wrapper = nil
|
||||
print_error 'Chain mode looks wrong!'
|
||||
# TODO: catch error, which should never happen as values are checked way before ;-)
|
||||
end
|
||||
|
||||
are_exec = BeEF::Core::Models::Execution.new(
|
||||
:session_id => hb_session,
|
||||
:mod_count => modules.length,
|
||||
:mod_successful => 0,
|
||||
:rule_token => rule_token,
|
||||
:mod_body => wrapper,
|
||||
:is_sent => false,
|
||||
:id => rule_id
|
||||
session_id: hb_session,
|
||||
mod_count: modules.length,
|
||||
mod_successful: 0,
|
||||
rule_token: rule_token,
|
||||
mod_body: wrapper,
|
||||
is_sent: false,
|
||||
id: rule_id
|
||||
)
|
||||
are_exec.save!
|
||||
# Once Engine.check() verified that the hooked browser match a Rule, trigger the Rule ;-)
|
||||
print_more "Triggering ruleset #{rule_ids.to_s} on HB #{hb_id}"
|
||||
print_more "Triggering ruleset #{rule_ids} on HB #{hb_id}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Wraps module bodies in their own function, using setTimeout to trigger them with an eventual delay.
|
||||
# Launch order is also taken care of.
|
||||
# - sequential chain with delays (setTimeout stuff)
|
||||
@@ -114,7 +109,7 @@ module BeEF
|
||||
delayed_exec = ''
|
||||
c = 0
|
||||
while c < mods.length
|
||||
delayed_exec += %Q| setTimeout(function(){#{mods[order[c]][:mod_name]}_#{rule_token}();}, #{delay[c]}); |
|
||||
delayed_exec += %| setTimeout(function(){#{mods[order[c]][:mod_name]}_#{rule_token}();}, #{delay[c]}); |
|
||||
mod_body = mods[order[c]][:mod_body].to_s.gsub("#{mods[order[c]][:mod_name]}_mod_output", "#{mods[order[c]][:mod_name]}_#{rule_token}_mod_output")
|
||||
wrapped_mod = "#{mod_body}\n"
|
||||
wrapper += wrapped_mod
|
||||
@@ -141,16 +136,17 @@ module BeEF
|
||||
# if the first once return with success. Also, the second module has the possibility of mangling first
|
||||
# module output and use it as input for some of its module inputs.
|
||||
def prepare_nested_forward_wrapper(mods, code, conditions, order, rule_token)
|
||||
wrapper, delayed_exec = '',''
|
||||
delayed_exec_footers = Array.new
|
||||
wrapper = ''
|
||||
delayed_exec = ''
|
||||
delayed_exec_footers = []
|
||||
c = 0
|
||||
|
||||
while c < mods.length
|
||||
if mods.length == 1
|
||||
i = c
|
||||
else
|
||||
i = c + 1
|
||||
end
|
||||
i = if mods.length == 1
|
||||
c
|
||||
else
|
||||
c + 1
|
||||
end
|
||||
|
||||
code_snippet = ''
|
||||
mod_input = ''
|
||||
@@ -159,11 +155,11 @@ module BeEF
|
||||
mod_input = 'mod_input'
|
||||
end
|
||||
|
||||
conditions[i] = true if conditions[i] == nil || conditions[i] == ''
|
||||
conditions[i] = true if conditions[i].nil? || conditions[i] == ''
|
||||
|
||||
if c == 0
|
||||
# this is the first wrapper to prepare
|
||||
delayed_exec += %Q|
|
||||
delayed_exec += %|
|
||||
function #{mods[order[c]][:mod_name]}_#{rule_token}_f(){
|
||||
#{mods[order[c]][:mod_name]}_#{rule_token}();
|
||||
|
||||
@@ -185,7 +181,7 @@ module BeEF
|
||||
#{mods[order[c]][:mod_name]}_#{rule_token}_mod_output = mod_result[1];
|
||||
|
|
||||
|
||||
delayed_exec_footer = %Q|
|
||||
delayed_exec_footer = %|
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,10 +194,10 @@ module BeEF
|
||||
delayed_exec_footers.push(delayed_exec_footer)
|
||||
|
||||
elsif c < mods.length - 1
|
||||
code_snippet = code_snippet.to_s.gsub(mods[order[c-1]][:mod_name], "#{mods[order[c-1]][:mod_name]}_#{rule_token}")
|
||||
code_snippet = code_snippet.to_s.gsub(mods[order[c - 1]][:mod_name], "#{mods[order[c - 1]][:mod_name]}_#{rule_token}")
|
||||
|
||||
# this is one of the wrappers in the middle of the chain
|
||||
delayed_exec += %Q|
|
||||
delayed_exec += %|
|
||||
function #{mods[order[c]][:mod_name]}_#{rule_token}_f(){
|
||||
if(#{mods[order[c]][:mod_name]}_#{rule_token}_can_exec){
|
||||
#{code_snippet}
|
||||
@@ -223,7 +219,7 @@ module BeEF
|
||||
#{mods[order[c]][:mod_name]}_#{rule_token}_mod_output = mod_result[1];
|
||||
|
|
||||
|
||||
delayed_exec_footer = %Q|
|
||||
delayed_exec_footer = %|
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -236,9 +232,9 @@ module BeEF
|
||||
|
||||
delayed_exec_footers.push(delayed_exec_footer)
|
||||
else
|
||||
code_snippet = code_snippet.to_s.gsub(mods[order[c-1]][:mod_name], "#{mods[order[c-1]][:mod_name]}_#{rule_token}")
|
||||
code_snippet = code_snippet.to_s.gsub(mods[order[c - 1]][:mod_name], "#{mods[order[c - 1]][:mod_name]}_#{rule_token}")
|
||||
# this is the last wrapper to prepare
|
||||
delayed_exec += %Q|
|
||||
delayed_exec += %|
|
||||
function #{mods[order[c]][:mod_name]}_#{rule_token}_f(){
|
||||
if(#{mods[order[c]][:mod_name]}_#{rule_token}_can_exec){
|
||||
#{code_snippet}
|
||||
@@ -258,7 +254,6 @@ module BeEF
|
||||
wrapper
|
||||
end
|
||||
|
||||
|
||||
# prepare the command module (compiling the Erubis templating stuff), eventually obfuscate it,
|
||||
# and store it in the database.
|
||||
# Returns the raw module body after template substitution.
|
||||
@@ -266,16 +261,16 @@ module BeEF
|
||||
config = BeEF::Core::Configuration.instance
|
||||
begin
|
||||
command = BeEF::Core::Models::Command.new(
|
||||
:data => options.to_json,
|
||||
:hooked_browser_id => hb_id,
|
||||
:command_module_id => BeEF::Core::Configuration.instance.get("beef.module.#{mod.name}.db.id"),
|
||||
:creationdate => Time.new.to_i,
|
||||
:instructions_sent => true
|
||||
data: options.to_json,
|
||||
hooked_browser_id: hb_id,
|
||||
command_module_id: BeEF::Core::Configuration.instance.get("beef.module.#{mod.name}.db.id"),
|
||||
creationdate: Time.new.to_i,
|
||||
instructions_sent: true
|
||||
)
|
||||
command.save!
|
||||
|
||||
command_module = BeEF::Core::Models::CommandModule.find(mod.id)
|
||||
if (command_module.path.match(/^Dynamic/))
|
||||
if command_module.path.match(/^Dynamic/)
|
||||
# metasploit and similar integrations
|
||||
command_module = BeEF::Modules::Commands.const_get(command_module.path.split('/').last.capitalize).new
|
||||
else
|
||||
@@ -293,18 +288,18 @@ module BeEF
|
||||
|
||||
build_missing_beefjs_components(command_module.beefjs_components) unless command_module.beefjs_components.empty?
|
||||
|
||||
if config.get("beef.extension.evasion.enable")
|
||||
if config.get('beef.extension.evasion.enable')
|
||||
evasion = BeEF::Extension::Evasion::Evasion.instance
|
||||
command_body = evasion.obfuscate(command_module.output) + "\n\n"
|
||||
else
|
||||
command_body = command_module.output + "\n\n"
|
||||
command_body = command_module.output + "\n\n"
|
||||
end
|
||||
|
||||
# @note prints the event to the console
|
||||
print_more "Preparing JS for command id [#{command.id}], module [#{mod.name}]"
|
||||
|
||||
replace_input ? mod_input = 'mod_input' : mod_input = ''
|
||||
result = %Q|
|
||||
mod_input = replace_input ? 'mod_input' : ''
|
||||
result = %|
|
||||
var #{mod.name}_#{rule_token} = function(#{mod_input}){
|
||||
#{clean_command_body(command_body, replace_input)}
|
||||
};
|
||||
@@ -312,8 +307,8 @@ module BeEF
|
||||
var #{mod.name}_#{rule_token}_mod_output = null;
|
||||
|
|
||||
|
||||
return {:mod_name => mod.name, :mod_body => result}
|
||||
rescue => e
|
||||
{ mod_name: mod.name, mod_body: result }
|
||||
rescue StandardError => e
|
||||
print_error e.message
|
||||
print_debug e.backtrace.join("\n")
|
||||
end
|
||||
@@ -324,56 +319,47 @@ module BeEF
|
||||
#
|
||||
# Also replace <<mod_input>> with mod_input variable if needed for chaining module output/input
|
||||
def clean_command_body(command_body, replace_input)
|
||||
begin
|
||||
cmd_body = command_body.lines.map(&:chomp)
|
||||
wrapper_start_index,wrapper_end_index = nil
|
||||
cmd_body = command_body.lines.map(&:chomp)
|
||||
wrapper_start_index, wrapper_end_index = nil
|
||||
|
||||
cmd_body.each_with_index do |line, index|
|
||||
if line.to_s =~ /^(beef|[a-zA-Z]+)\.execute\(function\(\)/
|
||||
wrapper_start_index = index
|
||||
break
|
||||
end
|
||||
end
|
||||
if wrapper_start_index.nil?
|
||||
print_error "[ARE] Could not find module start index"
|
||||
cmd_body.each_with_index do |line, index|
|
||||
if line.to_s =~ /^(beef|[a-zA-Z]+)\.execute\(function\(\)/
|
||||
wrapper_start_index = index
|
||||
break
|
||||
end
|
||||
end
|
||||
print_error '[ARE] Could not find module start index' if wrapper_start_index.nil?
|
||||
|
||||
cmd_body.reverse.each_with_index do |line, index|
|
||||
if line.include?('});')
|
||||
wrapper_end_index = index
|
||||
break
|
||||
end
|
||||
end
|
||||
if wrapper_end_index.nil?
|
||||
print_error "[ARE] Could not find module end index"
|
||||
cmd_body.reverse.each_with_index do |line, index|
|
||||
if line.include?('});')
|
||||
wrapper_end_index = index
|
||||
break
|
||||
end
|
||||
end
|
||||
print_error '[ARE] Could not find module end index' if wrapper_end_index.nil?
|
||||
|
||||
cleaned_cmd_body = cmd_body.slice(wrapper_start_index..-(wrapper_end_index+1)).join("\n")
|
||||
if cleaned_cmd_body.eql?('')
|
||||
print_error "[ARE] No command to send"
|
||||
end
|
||||
cleaned_cmd_body = cmd_body.slice(wrapper_start_index..-(wrapper_end_index + 1)).join("\n")
|
||||
print_error '[ARE] No command to send' if cleaned_cmd_body.eql?('')
|
||||
|
||||
# check if <<mod_input>> should be replaced with a variable name (depending if the variable is a string or number)
|
||||
if replace_input
|
||||
if cleaned_cmd_body.include?('"<<mod_input>>"')
|
||||
final_cmd_body = cleaned_cmd_body.gsub('"<<mod_input>>"','mod_input')
|
||||
elsif cleaned_cmd_body.include?('\'<<mod_input>>\'')
|
||||
final_cmd_body = cleaned_cmd_body.gsub('\'<<mod_input>>\'','mod_input')
|
||||
elsif cleaned_cmd_body.include?('<<mod_input>>')
|
||||
final_cmd_body = cleaned_cmd_body.gsub('\'<<mod_input>>\'','mod_input')
|
||||
else
|
||||
return cleaned_cmd_body
|
||||
end
|
||||
return final_cmd_body
|
||||
# check if <<mod_input>> should be replaced with a variable name (depending if the variable is a string or number)
|
||||
if replace_input
|
||||
if cleaned_cmd_body.include?('"<<mod_input>>"')
|
||||
final_cmd_body = cleaned_cmd_body.gsub('"<<mod_input>>"', 'mod_input')
|
||||
elsif cleaned_cmd_body.include?('\'<<mod_input>>\'')
|
||||
final_cmd_body = cleaned_cmd_body.gsub('\'<<mod_input>>\'', 'mod_input')
|
||||
elsif cleaned_cmd_body.include?('<<mod_input>>')
|
||||
final_cmd_body = cleaned_cmd_body.gsub('\'<<mod_input>>\'', 'mod_input')
|
||||
else
|
||||
return cleaned_cmd_body
|
||||
end
|
||||
rescue => e
|
||||
print_error "[ARE] There is likely a problem with the module's command.js parsing. Check Engine.clean_command_body"
|
||||
final_cmd_body
|
||||
else
|
||||
cleaned_cmd_body
|
||||
end
|
||||
rescue StandardError => e
|
||||
print_error "[ARE] There is likely a problem with the module's command.js parsing. Check Engine.clean_command_body. #{e.message}"
|
||||
end
|
||||
|
||||
|
||||
# Checks if there are any ARE rules to be triggered for the specified hooked browser
|
||||
#
|
||||
# Note: browser version checks are supporting only major versions, ex: C 43, IE 11
|
||||
@@ -382,105 +368,119 @@ module BeEF
|
||||
# Returns an array with rule IDs that matched and should be triggered.
|
||||
# if rule_id is specified, checks will be executed only against the specified rule (useful
|
||||
# for dynamic triggering of new rulesets ar runtime)
|
||||
def match(browser, browser_version, os, os_version, rule_id=nil)
|
||||
def match(browser, browser_version, os, os_version, rule_id = nil)
|
||||
match_rules = []
|
||||
if rule_id != nil
|
||||
rules = [BeEF::Core::Models::Rule.find(rule_id)]
|
||||
else
|
||||
rules = BeEF::Core::Models::Rule.all
|
||||
end
|
||||
return nil if rules == nil
|
||||
rules = if rule_id.nil?
|
||||
BeEF::Core::Models::Rule.all
|
||||
else
|
||||
[BeEF::Core::Models::Rule.find(rule_id)]
|
||||
end
|
||||
return nil if rules.nil?
|
||||
return nil unless rules.length > 0
|
||||
|
||||
print_info "[ARE] Checking if any defined rules should be triggered on target."
|
||||
# TODO handle cases where there are multiple ARE rules for the same hooked browser.
|
||||
print_info '[ARE] Checking if any defined rules should be triggered on target.'
|
||||
# TODO: handle cases where there are multiple ARE rules for the same hooked browser.
|
||||
# TODO the above works well, but maybe rules need to have priority or something?
|
||||
rules.each do |rule|
|
||||
begin
|
||||
browser_match, os_match = false, false
|
||||
browser_match = false
|
||||
os_match = false
|
||||
|
||||
b_ver_cond = rule.browser_version.split(' ').first
|
||||
b_ver = rule.browser_version.split(' ').last
|
||||
b_ver_cond = rule.browser_version.split(' ').first
|
||||
b_ver = rule.browser_version.split(' ').last
|
||||
|
||||
os_ver_rule_cond = rule.os_version.split(' ').first
|
||||
os_ver_rule_maj = rule.os_version.split(' ').last.split('.').first
|
||||
os_ver_rule_min = rule.os_version.split(' ').last.split('.').last
|
||||
os_ver_rule_cond = rule.os_version.split(' ').first
|
||||
os_ver_rule_maj = rule.os_version.split(' ').last.split('.').first
|
||||
os_ver_rule_min = rule.os_version.split(' ').last.split('.').last
|
||||
|
||||
# Most of the times Linux/*BSD OS doesn't return any version
|
||||
# (TODO: improve OS detection on these operating systems)
|
||||
if os_version != nil && !@VERSION_STR.include?(os_version)
|
||||
os_ver_hook_maj = os_version.split('.').first
|
||||
os_ver_hook_min = os_version.split('.').last
|
||||
# Most of the times Linux/*BSD OS doesn't return any version
|
||||
# (TODO: improve OS detection on these operating systems)
|
||||
if !os_version.nil? && !@VERSION_STR.include?(os_version)
|
||||
os_ver_hook_maj = os_version.split('.').first
|
||||
os_ver_hook_min = os_version.split('.').last
|
||||
|
||||
# the following assignments to 0 are need for later checks like:
|
||||
# 8.1 >= 7, because if the version doesn't have minor versions, maj/min are the same
|
||||
os_ver_hook_min = 0 if os_version.split('.').length == 1
|
||||
os_ver_rule_min = 0 if rule.os_version.split('.').length == 1
|
||||
else
|
||||
# most probably Windows XP or Vista. the following is a hack as Microsoft had the brilliant idea
|
||||
# to switch from strings to numbers in OS versioning. To prevent rewriting code later on,
|
||||
# we say that XP is Windows 5.0 and Vista is Windows 6.0. Easier for comparison later on.
|
||||
os_ver_hook_maj, os_ver_hook_min = 5, 0 if os_version == 'XP'
|
||||
os_ver_hook_maj, os_ver_hook_min = 6, 0 if os_version == 'Vista'
|
||||
# the following assignments to 0 are need for later checks like:
|
||||
# 8.1 >= 7, because if the version doesn't have minor versions, maj/min are the same
|
||||
os_ver_hook_min = 0 if os_version.split('.').length == 1
|
||||
os_ver_rule_min = 0 if rule.os_version.split('.').length == 1
|
||||
else
|
||||
# most probably Windows XP or Vista. the following is a hack as Microsoft had the brilliant idea
|
||||
# to switch from strings to numbers in OS versioning. To prevent rewriting code later on,
|
||||
# we say that XP is Windows 5.0 and Vista is Windows 6.0. Easier for comparison later on.
|
||||
if os_version == 'XP'
|
||||
os_ver_hook_maj = 5
|
||||
os_ver_hook_min = 0
|
||||
end
|
||||
|
||||
os_ver_rule_maj, os_ver_rule_min = 5, 0 if os_ver_rule_maj == 'XP'
|
||||
os_ver_rule_maj, os_ver_rule_min = 6, 0 if os_ver_rule_maj == 'Vista'
|
||||
|
||||
next unless @VERSION.include?(b_ver_cond)
|
||||
next unless BeEF::Filters::is_valid_browserversion?(b_ver)
|
||||
|
||||
next unless @VERSION.include?(os_ver_rule_cond) || @VERSION_STR.include?(os_ver_rule_cond)
|
||||
# os_ver without checks as it can be very different or even empty, for instance on linux/bsd)
|
||||
|
||||
# skip rule unless the browser matches
|
||||
browser_match = false
|
||||
# check if rule specifies multiple browsers
|
||||
if rule.browser !~ /\A[A-Z]+\Z/
|
||||
rule.browser.gsub(/[^A-Z,]/i, '').split(',').each do |b|
|
||||
browser_match = true if b == browser || b == 'ALL'
|
||||
end
|
||||
# else, only one browser
|
||||
else
|
||||
next unless rule.browser == 'ALL' || browser == rule.browser
|
||||
# check if the browser version matches
|
||||
browser_version_match = compare_versions(browser_version.to_s, b_ver_cond, b_ver.to_s)
|
||||
if browser_version_match
|
||||
browser_match = true
|
||||
else
|
||||
browser_match = false
|
||||
end
|
||||
print_more "Browser version check -> (hook) #{browser_version} #{rule.browser_version} (rule) : #{browser_version_match}"
|
||||
if os_version == 'Vista'
|
||||
os_ver_hook_maj = 6
|
||||
os_ver_hook_min = 0
|
||||
end
|
||||
next unless browser_match
|
||||
|
||||
# skip rule unless the OS matches
|
||||
next unless rule.os == 'ALL' || os == rule.os
|
||||
|
||||
# check if the OS versions match
|
||||
if os_version != nil || rule.os_version != 'ALL'
|
||||
os_major_version_match = compare_versions(os_ver_hook_maj.to_s, os_ver_rule_cond, os_ver_rule_maj.to_s)
|
||||
os_minor_version_match = compare_versions(os_ver_hook_min.to_s, os_ver_rule_cond, os_ver_rule_min.to_s)
|
||||
else
|
||||
# os_version_match = true if (browser doesn't return an OS version || rule OS version is ALL )
|
||||
os_major_version_match, os_minor_version_match = true, true
|
||||
end
|
||||
|
||||
os_match = true if os_ver_rule_cond == 'ALL' || (os_major_version_match && os_minor_version_match)
|
||||
print_more "OS version check -> (hook) #{os_version} #{rule.os_version} (rule): #{os_major_version_match && os_minor_version_match}"
|
||||
|
||||
if browser_match && os_match
|
||||
print_more "Hooked browser and OS type/version MATCH rule: #{rule.name}."
|
||||
match_rules.push(rule.id)
|
||||
end
|
||||
rescue => e
|
||||
print_error e.message
|
||||
print_debug e.backtrace.join("\n")
|
||||
end
|
||||
|
||||
if os_ver_rule_maj == 'XP'
|
||||
os_ver_rule_maj = 5
|
||||
os_ver_rule_min = 0
|
||||
end
|
||||
if os_ver_rule_maj == 'Vista'
|
||||
os_ver_rule_maj = 6
|
||||
os_ver_rule_min = 0
|
||||
end
|
||||
|
||||
next unless @VERSION.include?(b_ver_cond)
|
||||
next unless BeEF::Filters.is_valid_browserversion?(b_ver)
|
||||
|
||||
next unless @VERSION.include?(os_ver_rule_cond) || @VERSION_STR.include?(os_ver_rule_cond)
|
||||
|
||||
# os_ver without checks as it can be very different or even empty, for instance on linux/bsd)
|
||||
|
||||
# skip rule unless the browser matches
|
||||
browser_match = false
|
||||
# check if rule specifies multiple browsers
|
||||
if rule.browser =~ /\A[A-Z]+\Z/
|
||||
next unless rule.browser == 'ALL' || browser == rule.browser
|
||||
|
||||
# check if the browser version matches
|
||||
browser_version_match = compare_versions(browser_version.to_s, b_ver_cond, b_ver.to_s)
|
||||
browser_match = if browser_version_match
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
print_more "Browser version check -> (hook) #{browser_version} #{rule.browser_version} (rule) : #{browser_version_match}"
|
||||
else
|
||||
rule.browser.gsub(/[^A-Z,]/i, '').split(',').each do |b|
|
||||
browser_match = true if b == browser || b == 'ALL'
|
||||
end
|
||||
# else, only one browser
|
||||
end
|
||||
next unless browser_match
|
||||
|
||||
# skip rule unless the OS matches
|
||||
next unless rule.os == 'ALL' || os == rule.os
|
||||
|
||||
# check if the OS versions match
|
||||
if !os_version.nil? || rule.os_version != 'ALL'
|
||||
os_major_version_match = compare_versions(os_ver_hook_maj.to_s, os_ver_rule_cond, os_ver_rule_maj.to_s)
|
||||
os_minor_version_match = compare_versions(os_ver_hook_min.to_s, os_ver_rule_cond, os_ver_rule_min.to_s)
|
||||
else
|
||||
# os_version_match = true if (browser doesn't return an OS version || rule OS version is ALL )
|
||||
os_major_version_match = true
|
||||
os_minor_version_match = true
|
||||
end
|
||||
|
||||
os_match = true if os_ver_rule_cond == 'ALL' || (os_major_version_match && os_minor_version_match)
|
||||
print_more "OS version check -> (hook) #{os_version} #{rule.os_version} (rule): #{os_major_version_match && os_minor_version_match}"
|
||||
|
||||
if browser_match && os_match
|
||||
print_more "Hooked browser and OS type/version MATCH rule: #{rule.name}."
|
||||
match_rules.push(rule.id)
|
||||
end
|
||||
rescue StandardError => e
|
||||
print_error e.message
|
||||
print_debug e.backtrace.join("\n")
|
||||
end
|
||||
print_more "Found [#{match_rules.length}/#{rules.length}] ARE rules matching the hooked browser type/version."
|
||||
|
||||
return match_rules
|
||||
match_rules
|
||||
end
|
||||
|
||||
# compare versions
|
||||
@@ -491,7 +491,8 @@ module BeEF
|
||||
return true if cond == '<' && ver_a < ver_b
|
||||
return true if cond == '>=' && ver_a >= ver_b
|
||||
return true if cond == '>' && ver_a > ver_b
|
||||
return false
|
||||
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,84 +6,80 @@
|
||||
module BeEF
|
||||
module Core
|
||||
module AutorunEngine
|
||||
|
||||
class Parser
|
||||
|
||||
include Singleton
|
||||
|
||||
def initialize
|
||||
@config = BeEF::Core::Configuration.instance
|
||||
end
|
||||
|
||||
BROWSER = ['FF','C','IE','S','O','ALL']
|
||||
OS = ['Linux','Windows','OSX','Android','iOS','BlackBerry','ALL']
|
||||
VERSION = ['<','<=','==','>=','>','ALL','Vista','XP']
|
||||
CHAIN_MODE = ['sequential','nested-forward']
|
||||
BROWSER = %w[FF C IE S O ALL]
|
||||
OS = %w[Linux Windows OSX Android iOS BlackBerry ALL]
|
||||
VERSION = ['<', '<=', '==', '>=', '>', 'ALL', 'Vista', 'XP']
|
||||
CHAIN_MODE = %w[sequential nested-forward]
|
||||
MAX_VER_LEN = 15
|
||||
# Parse a JSON ARE file and returns an Hash with the value mappings
|
||||
def parse(name,author,browser, browser_version, os, os_version, modules, exec_order, exec_delay, chain_mode)
|
||||
begin
|
||||
success = [true]
|
||||
def parse(name, author, browser, browser_version, os, os_version, modules, exec_order, exec_delay, chain_mode)
|
||||
success = [true]
|
||||
|
||||
return [false, 'Illegal chain_mode definition'] unless CHAIN_MODE.include?(chain_mode)
|
||||
return [false, 'Illegal rule name'] unless BeEF::Filters.is_non_empty_string?(name)
|
||||
return [false, 'Illegal author name'] unless BeEF::Filters.is_non_empty_string?(author)
|
||||
# if multiple browsers were specified, check each browser
|
||||
if browser.kind_of?(Array)
|
||||
browser.each do |b|
|
||||
return [false, 'Illegal browser definition'] unless BROWSER.include?(b)
|
||||
end
|
||||
# else, if only one browser was specified, check browser and browser version
|
||||
else
|
||||
return [false, 'Illegal browser definition'] unless BROWSER.include?(browser)
|
||||
if browser_version != 'ALL'
|
||||
return [false, 'Illegal browser_version definition'] unless
|
||||
VERSION.include?(browser_version[0,2].gsub(/\s+/,'')) &&
|
||||
BeEF::Filters::is_valid_browserversion?(browser_version[2..-1].gsub(/\s+/,'')) && browser_version.length < MAX_VER_LEN
|
||||
end
|
||||
return [false, 'Illegal chain_mode definition'] unless CHAIN_MODE.include?(chain_mode)
|
||||
return [false, 'Illegal rule name'] unless BeEF::Filters.is_non_empty_string?(name)
|
||||
return [false, 'Illegal author name'] unless BeEF::Filters.is_non_empty_string?(author)
|
||||
|
||||
# if multiple browsers were specified, check each browser
|
||||
if browser.is_a?(Array)
|
||||
browser.each do |b|
|
||||
return [false, 'Illegal browser definition'] unless BROWSER.include?(b)
|
||||
end
|
||||
# else, if only one browser was specified, check browser and browser version
|
||||
else
|
||||
return [false, 'Illegal browser definition'] unless BROWSER.include?(browser)
|
||||
|
||||
if os_version != 'ALL'
|
||||
return [false, 'Illegal os_version definition'] unless
|
||||
VERSION.include?(os_version[0,2].gsub(/\s+/,'')) &&
|
||||
BeEF::Filters::is_valid_osversion?(os_version[2..-1].gsub(/\s+/,'')) && os_version.length < MAX_VER_LEN
|
||||
if browser_version != 'ALL' && !(VERSION.include?(browser_version[0, 2].gsub(/\s+/, '')) &&
|
||||
BeEF::Filters.is_valid_browserversion?(browser_version[2..-1].gsub(/\s+/, '')) && browser_version.length < MAX_VER_LEN)
|
||||
return [false, 'Illegal browser_version definition']
|
||||
end
|
||||
|
||||
return [false, 'Illegal os definition'] unless OS.include?(os)
|
||||
|
||||
# check if module names, conditions and options are ok
|
||||
modules.each do |cmd_mod|
|
||||
mod = BeEF::Core::Models::CommandModule.where(:name => cmd_mod['name']).first
|
||||
if mod != nil
|
||||
modk = BeEF::Module.get_key_by_database_id(mod.id)
|
||||
mod_options = BeEF::Module.get_options(modk)
|
||||
|
||||
opt_count = 0
|
||||
mod_options.each do |opt|
|
||||
if opt['name'] == cmd_mod['options'].keys[opt_count]
|
||||
opt_count += 1
|
||||
else
|
||||
return [false, "The specified option (#{cmd_mod['options'].keys[opt_count]
|
||||
}) for module (#{cmd_mod['name']}) does not exist"]
|
||||
end
|
||||
end
|
||||
else
|
||||
return [false, "The specified module name (#{cmd_mod['name']}) does not exist"]
|
||||
end
|
||||
end
|
||||
|
||||
exec_order.each{ |order| return [false, 'execution_order values must be Integers'] unless order.integer?}
|
||||
exec_delay.each{ |delay| return [false, 'execution_delay values must be Integers'] unless delay.integer?}
|
||||
|
||||
return [false, 'execution_order and execution_delay values must be consistent with modules numbers'] unless
|
||||
modules.size == exec_order.size && modules.size == exec_delay.size
|
||||
|
||||
success
|
||||
rescue => e
|
||||
print_error "#{e.message}"
|
||||
print_debug "#{e.backtrace.join("\n")}"
|
||||
return [false, 'Something went wrong.']
|
||||
end
|
||||
|
||||
if os_version != 'ALL' && !(VERSION.include?(os_version[0, 2].gsub(/\s+/, '')) &&
|
||||
BeEF::Filters.is_valid_osversion?(os_version[2..-1].gsub(/\s+/, '')) && os_version.length < MAX_VER_LEN)
|
||||
return [false, 'Illegal os_version definition']
|
||||
end
|
||||
|
||||
return [false, 'Illegal os definition'] unless OS.include?(os)
|
||||
|
||||
# check if module names, conditions and options are ok
|
||||
modules.each do |cmd_mod|
|
||||
mod = BeEF::Core::Models::CommandModule.where(name: cmd_mod['name']).first
|
||||
if mod.nil?
|
||||
return [false, "The specified module name (#{cmd_mod['name']}) does not exist"]
|
||||
else
|
||||
modk = BeEF::Module.get_key_by_database_id(mod.id)
|
||||
mod_options = BeEF::Module.get_options(modk)
|
||||
|
||||
opt_count = 0
|
||||
mod_options.each do |opt|
|
||||
if opt['name'] == cmd_mod['options'].keys[opt_count]
|
||||
opt_count += 1
|
||||
else
|
||||
return [false, "The specified option (#{cmd_mod['options'].keys[opt_count]
|
||||
}) for module (#{cmd_mod['name']}) does not exist"]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
exec_order.each { |order| return [false, 'execution_order values must be Integers'] unless order.integer? }
|
||||
exec_delay.each { |delay| return [false, 'execution_delay values must be Integers'] unless delay.integer? }
|
||||
|
||||
return [false, 'execution_order and execution_delay values must be consistent with modules numbers'] unless
|
||||
modules.size == exec_order.size && modules.size == exec_delay.size
|
||||
|
||||
success
|
||||
rescue StandardError => e
|
||||
print_error e.message.to_s
|
||||
print_debug e.backtrace.join("\n").to_s
|
||||
[false, 'Something went wrong.']
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
module BeEF
|
||||
module Core
|
||||
module AutorunEngine
|
||||
|
||||
class RuleLoader
|
||||
|
||||
include Singleton
|
||||
|
||||
def initialize
|
||||
@@ -18,78 +16,74 @@ module BeEF
|
||||
|
||||
# this expects parsed JSON as input
|
||||
def load(data)
|
||||
begin
|
||||
name = data['name']
|
||||
author = data['author']
|
||||
browser = data['browser'] || 'ALL'
|
||||
browser_version = data['browser_version'] || 'ALL'
|
||||
os = data['os'] || 'ALL'
|
||||
os_version = data['os_version'] || 'ALL'
|
||||
modules = data['modules']
|
||||
exec_order = data['execution_order']
|
||||
exec_delay = data['execution_delay']
|
||||
chain_mode = data['chain_mode']
|
||||
|
||||
name = data['name']
|
||||
author = data['author']
|
||||
browser = data['browser']||'ALL'
|
||||
browser_version = data['browser_version']||'ALL'
|
||||
os = data['os']||'ALL'
|
||||
os_version = data['os_version']||'ALL'
|
||||
modules = data['modules']
|
||||
exec_order = data['execution_order']
|
||||
exec_delay = data['execution_delay']
|
||||
chain_mode = data['chain_mode']
|
||||
parser_result = BeEF::Core::AutorunEngine::Parser.instance.parse(
|
||||
name, author, browser, browser_version, os, os_version, modules, exec_order, exec_delay, chain_mode
|
||||
)
|
||||
|
||||
parser_result = BeEF::Core::AutorunEngine::Parser.instance.parse(
|
||||
name,author,browser,browser_version,os,os_version,modules,exec_order,exec_delay,chain_mode)
|
||||
|
||||
if parser_result.length == 1 && parser_result.first
|
||||
print_info "[ARE] Ruleset (#{name}) parsed and stored successfully."
|
||||
if @debug_on
|
||||
print_more "Target Browser: #{browser} (#{browser_version})"
|
||||
print_more "Target OS: #{os} (#{os_version})"
|
||||
print_more "Modules to Trigger:"
|
||||
modules.each do |mod|
|
||||
print_more "(*) Name: #{mod['name']}"
|
||||
print_more "(*) Condition: #{mod['condition']}"
|
||||
print_more "(*) Code: #{mod['code']}"
|
||||
print_more "(*) Options:"
|
||||
mod['options'].each do |key,value|
|
||||
print_more "\t#{key}: (#{value})"
|
||||
end
|
||||
end
|
||||
print_more "Exec order: #{exec_order}"
|
||||
print_more "Exec delay: #{exec_delay}"
|
||||
if parser_result.length == 1 && parser_result.first
|
||||
print_info "[ARE] Ruleset (#{name}) parsed and stored successfully."
|
||||
if @debug_on
|
||||
print_more "Target Browser: #{browser} (#{browser_version})"
|
||||
print_more "Target OS: #{os} (#{os_version})"
|
||||
print_more 'Modules to Trigger:'
|
||||
modules.each do |mod|
|
||||
print_more "(*) Name: #{mod['name']}"
|
||||
print_more "(*) Condition: #{mod['condition']}"
|
||||
print_more "(*) Code: #{mod['code']}"
|
||||
print_more '(*) Options:'
|
||||
mod['options'].each do |key, value|
|
||||
print_more "\t#{key}: (#{value})"
|
||||
end
|
||||
end
|
||||
are_rule = BeEF::Core::Models::Rule.new(
|
||||
:name => name,
|
||||
:author => author,
|
||||
:browser => browser,
|
||||
:browser_version => browser_version,
|
||||
:os => os,
|
||||
:os_version => os_version,
|
||||
:modules => modules.to_json,
|
||||
:execution_order => exec_order,
|
||||
:execution_delay => exec_delay,
|
||||
:chain_mode => chain_mode)
|
||||
are_rule.save
|
||||
return { 'success' => true, 'rule_id' => are_rule.id}
|
||||
else
|
||||
print_error "[ARE] Ruleset (#{name}): ERROR. " + parser_result.last
|
||||
return { 'success' => false, 'error' => parser_result.last }
|
||||
print_more "Exec order: #{exec_order}"
|
||||
print_more "Exec delay: #{exec_delay}"
|
||||
end
|
||||
|
||||
rescue => e
|
||||
err = 'Malformed JSON ruleset.'
|
||||
print_error "[ARE] Ruleset (#{name}): ERROR. #{e} #{e.backtrace}"
|
||||
return { 'success' => false, 'error' => err }
|
||||
are_rule = BeEF::Core::Models::Rule.new(
|
||||
name: name,
|
||||
author: author,
|
||||
browser: browser,
|
||||
browser_version: browser_version,
|
||||
os: os,
|
||||
os_version: os_version,
|
||||
modules: modules.to_json,
|
||||
execution_order: exec_order,
|
||||
execution_delay: exec_delay,
|
||||
chain_mode: chain_mode
|
||||
)
|
||||
are_rule.save
|
||||
{ 'success' => true, 'rule_id' => are_rule.id }
|
||||
else
|
||||
print_error "[ARE] Ruleset (#{name}): ERROR. " + parser_result.last
|
||||
{ 'success' => false, 'error' => parser_result.last }
|
||||
end
|
||||
rescue StandardError => e
|
||||
err = 'Malformed JSON ruleset.'
|
||||
print_error "[ARE] Ruleset (#{name}): ERROR. #{e} #{e.backtrace}"
|
||||
{ 'success' => false, 'error' => err }
|
||||
end
|
||||
|
||||
def load_file(json_rule_path)
|
||||
begin
|
||||
rule_file = File.open(json_rule_path, 'r:UTF-8', &:read)
|
||||
self.load JSON.parse(rule_file)
|
||||
rescue => e
|
||||
print_error "[ARE] Failed to load ruleset from #{json_rule_path}"
|
||||
end
|
||||
rule_file = File.open(json_rule_path, 'r:UTF-8', &:read)
|
||||
self.load JSON.parse(rule_file)
|
||||
rescue StandardError => e
|
||||
print_error "[ARE] Failed to load ruleset from #{json_rule_path}: #{e.message}"
|
||||
end
|
||||
|
||||
def load_directory
|
||||
Dir.glob("#{$root_dir}/arerules/enabled/**/*.json") do |rule|
|
||||
print_debug "[ARE] Processing rule: #{rule}"
|
||||
self.load_file rule
|
||||
load_file rule
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -43,7 +43,7 @@ module BeEF
|
||||
#
|
||||
class Command
|
||||
attr_reader :datastore, :path, :default_command_url, :beefjs_components, :friendlyname,
|
||||
:config
|
||||
:config
|
||||
attr_accessor :zombie, :command_id, :session_id
|
||||
|
||||
include BeEF::Core::CommandUtils
|
||||
@@ -100,12 +100,12 @@ module BeEF
|
||||
# Returns information about the command in a JSON format.
|
||||
# @return [String] JSON formatted string
|
||||
#
|
||||
def to_json
|
||||
def to_json(*_args)
|
||||
{
|
||||
'Name' => @friendlyname,
|
||||
'Name' => @friendlyname,
|
||||
'Description' => BeEF::Core::Configuration.instance.get("beef.module.#{@key}.description"),
|
||||
'Category' => BeEF::Core::Configuration.instance.get("beef.module.#{@key}.category"),
|
||||
'Data' => BeEF::Module.get_options(@key)
|
||||
'Category' => BeEF::Core::Configuration.instance.get("beef.module.#{@key}.category"),
|
||||
'Data' => BeEF::Module.get_options(@key)
|
||||
}.to_json
|
||||
end
|
||||
|
||||
@@ -116,7 +116,7 @@ module BeEF
|
||||
#
|
||||
def build_datastore(data)
|
||||
@datastore = JSON.parse data
|
||||
rescue => e
|
||||
rescue StandardError => e
|
||||
print_error "Could not build datastore: #{e.message}"
|
||||
end
|
||||
|
||||
@@ -126,7 +126,7 @@ module BeEF
|
||||
# @param [Hash] http_headers HTTP headers
|
||||
#
|
||||
def build_callback_datastore(result, command_id, beefhook, http_params, http_headers)
|
||||
@datastore = {'http_headers' => {}} # init the datastore
|
||||
@datastore = { 'http_headers' => {} } # init the datastore
|
||||
|
||||
if !http_params.nil? && !http_headers.nil?
|
||||
# get, check and add the http_params to the datastore
|
||||
@@ -166,7 +166,7 @@ module BeEF
|
||||
|
||||
@datastore['results'] = result
|
||||
@datastore['cid'] = command_id
|
||||
@datastore['beefhook'] = beefhook
|
||||
@datastore['beefhook'] = beefhook
|
||||
end
|
||||
|
||||
#
|
||||
@@ -184,7 +184,7 @@ module BeEF
|
||||
|
||||
@eruby = Erubis::FastEruby.new(File.read(f))
|
||||
|
||||
#data = BeEF::Core::Configuration.instance.get "beef.module.#{@key}"
|
||||
# data = BeEF::Core::Configuration.instance.get "beef.module.#{@key}"
|
||||
cc = BeEF::Core::CommandContext.new
|
||||
cc['command_url'] = @default_command_url
|
||||
cc['command_id'] = @command_id
|
||||
@@ -226,7 +226,7 @@ module BeEF
|
||||
def use(component)
|
||||
return if @beefjs_components.include? component
|
||||
|
||||
component_path = '/'+component
|
||||
component_path = '/' + component
|
||||
component_path.gsub!(/beef./, '')
|
||||
component_path.gsub!(/\./, '/')
|
||||
component_path.replace "#{$root_dir}/core/main/client/#{component_path}.js"
|
||||
@@ -238,8 +238,9 @@ module BeEF
|
||||
|
||||
# @todo TODO Document
|
||||
def oc_value(name)
|
||||
option = BeEF::Core::Models::OptionCache.where(:name => name).first
|
||||
option = BeEF::Core::Models::OptionCache.where(name: name).first
|
||||
return nil unless option
|
||||
|
||||
option.value
|
||||
end
|
||||
|
||||
@@ -250,8 +251,6 @@ module BeEF
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@use_template
|
||||
@eruby
|
||||
@update_zombie
|
||||
|
||||
@@ -24,12 +24,12 @@ module BeEF
|
||||
raise TypeError, "Configuration file '#{config}' cannot be found" unless File.exist? config
|
||||
|
||||
begin
|
||||
#open base config
|
||||
# open base config
|
||||
@config = load(config)
|
||||
# set default value if key? does not exist
|
||||
@config.default = nil
|
||||
@@config = config
|
||||
rescue => e
|
||||
rescue StandardError => e
|
||||
print_error "Fatal Error: cannot load configuration file '#{config}' : #{e.message}"
|
||||
print_error e.backtrace
|
||||
end
|
||||
@@ -42,9 +42,10 @@ module BeEF
|
||||
# @return [Hash] YAML formatted hash
|
||||
def load(file)
|
||||
return nil unless File.exist? file
|
||||
|
||||
raw = File.read file
|
||||
YAML.safe_load raw
|
||||
rescue => e
|
||||
rescue StandardError => e
|
||||
print_debug "Unable to load configuration file '#{file}' : #{e.message}"
|
||||
print_error e.backtrace
|
||||
end
|
||||
@@ -56,7 +57,7 @@ module BeEF
|
||||
if @config.empty?
|
||||
print_error 'Configuration file is empty'
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if @config['beef'].nil?
|
||||
print_error "Configuration file is malformed: 'beef' is nil"
|
||||
@@ -136,17 +137,17 @@ module BeEF
|
||||
def public_enabled?
|
||||
!get('beef.http.public.host').nil?
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# Returns the beef protocol that is used by external resources
|
||||
# e.g. hooked browsers
|
||||
def beef_proto
|
||||
if public_enabled? && public_https_enabled? then
|
||||
return 'https'
|
||||
if public_enabled? && public_https_enabled?
|
||||
'https'
|
||||
elsif public_enabled? && !public_https_enabled?
|
||||
return 'http'
|
||||
'http'
|
||||
elsif !public_enabled?
|
||||
return local_proto
|
||||
local_proto
|
||||
end
|
||||
end
|
||||
|
||||
@@ -201,6 +202,7 @@ module BeEF
|
||||
hash[k]
|
||||
end
|
||||
return nil if subhash.nil?
|
||||
|
||||
subhash.key?(lastkey) ? subhash[lastkey] : nil
|
||||
end
|
||||
|
||||
@@ -215,7 +217,7 @@ module BeEF
|
||||
return false if subkeys.empty?
|
||||
|
||||
hash = { subkeys.shift.to_s => value }
|
||||
subkeys.each { |v| hash = {v.to_s => hash} }
|
||||
subkeys.each { |v| hash = { v.to_s => hash } }
|
||||
@config = @config.deep_merge hash
|
||||
true
|
||||
end
|
||||
@@ -231,7 +233,7 @@ module BeEF
|
||||
|
||||
lastkey = subkeys.pop
|
||||
hash = @config
|
||||
subkeys.each {|v| hash = hash[v] }
|
||||
subkeys.each { |v| hash = hash[v] }
|
||||
hash.delete(lastkey).nil? ? false : true
|
||||
end
|
||||
|
||||
@@ -258,7 +260,7 @@ module BeEF
|
||||
def load_modules_config
|
||||
set('beef.module', {})
|
||||
# support nested sub-categories, like browser/hooked_domain/ajax_fingerprint
|
||||
module_configs = File.join("#{$root_dir}/modules/**", "config.yaml")
|
||||
module_configs = File.join("#{$root_dir}/modules/**", 'config.yaml')
|
||||
Dir.glob(module_configs) do |cf|
|
||||
y = load(cf)
|
||||
if y.nil?
|
||||
@@ -280,9 +282,8 @@ module BeEF
|
||||
private
|
||||
|
||||
def validate_public_config_variable?(config)
|
||||
return true if (config['beef']['http']['public'].is_a?(Hash) ||
|
||||
config['beef']['http']['public'].is_a?(NilClass))
|
||||
|
||||
return true if config['beef']['http']['public'].is_a?(Hash) ||
|
||||
config['beef']['http']['public'].is_a?(NilClass)
|
||||
|
||||
print_error 'Config path beef.http.public is deprecated.'
|
||||
print_error 'Please use the new format for public variables found'
|
||||
|
||||
@@ -4,140 +4,138 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Core
|
||||
module Console
|
||||
module Core
|
||||
module Console
|
||||
module Banners
|
||||
class << self
|
||||
attr_accessor :interfaces
|
||||
|
||||
module Banners
|
||||
class << self
|
||||
attr_accessor :interfaces
|
||||
|
||||
#
|
||||
# Prints BeEF's ascii art
|
||||
#
|
||||
def print_ascii_art
|
||||
if File.exists?('core/main/console/beef.ascii')
|
||||
File.open('core/main/console/beef.ascii', 'r') do |f|
|
||||
#
|
||||
# Prints BeEF's ascii art
|
||||
#
|
||||
def print_ascii_art
|
||||
if File.exist?('core/main/console/beef.ascii')
|
||||
File.open('core/main/console/beef.ascii', 'r') do |f|
|
||||
while line = f.gets
|
||||
puts line
|
||||
puts line
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Prints BeEF's welcome message
|
||||
#
|
||||
def print_welcome_msg
|
||||
config = BeEF::Core::Configuration.instance
|
||||
version = config.get('beef.version')
|
||||
print_info "Browser Exploitation Framework (BeEF) #{version}"
|
||||
data = "Twit: @beefproject\n"
|
||||
data += "Site: https://beefproject.com\n"
|
||||
data += "Blog: http://blog.beefproject.com\n"
|
||||
data += "Wiki: https://github.com/beefproject/beef/wiki\n"
|
||||
print_more data
|
||||
print_info 'Project Creator: ' + 'Wade Alcorn'.red + ' (@WadeAlcorn)'
|
||||
end
|
||||
|
||||
#
|
||||
# Prints the number of network interfaces beef is operating on.
|
||||
# Looks like that:
|
||||
#
|
||||
# [14:06:48][*] 5 network interfaces were detected.
|
||||
#
|
||||
def print_network_interfaces_count
|
||||
# get the configuration information
|
||||
configuration = BeEF::Core::Configuration.instance
|
||||
# local host
|
||||
beef_host = configuration.local_host
|
||||
|
||||
# create an array of the interfaces the framework is listening on
|
||||
if beef_host == '0.0.0.0' # the framework will listen on all interfaces
|
||||
interfaces = Socket.ip_address_list.map { |x| x.ip_address if x.ipv4? }
|
||||
interfaces.delete_if { |x| x.nil? } # remove if the entry is nill
|
||||
else # the framework will listen on only one interface
|
||||
interfaces = [beef_host]
|
||||
end
|
||||
|
||||
self.interfaces = interfaces
|
||||
|
||||
# output the banner to the console
|
||||
print_info "#{interfaces.count} network interfaces were detected."
|
||||
end
|
||||
|
||||
#
|
||||
# Prints the route to the network interfaces beef has been deployed on.
|
||||
# Looks like that:
|
||||
#
|
||||
# [14:06:48][+] running on network interface: 192.168.255.1
|
||||
# [14:06:48] | Hook URL: http://192.168.255.1:3000/hook.js
|
||||
# [14:06:48] | UI URL: http://192.168.255.1:3000/ui/panel
|
||||
# [14:06:48][+] running on network interface: 127.0.0.1
|
||||
# [14:06:48] | Hook URL: http://127.0.0.1:3000/hook.js
|
||||
# [14:06:48] | UI URL: http://127.0.0.1:3000/ui/panel
|
||||
#
|
||||
def print_network_interfaces_routes
|
||||
configuration = BeEF::Core::Configuration.instance
|
||||
# local config settings
|
||||
proto = configuration.local_proto
|
||||
hook_file = configuration.hook_file_path
|
||||
admin_ui = configuration.get('beef.extension.admin_ui.enable') ? true : false
|
||||
admin_ui_path = configuration.get('beef.extension.admin_ui.base_path')
|
||||
|
||||
# display the hook URL and Admin UI URL on each interface from the interfaces array
|
||||
interfaces.map do |host|
|
||||
print_info "running on network interface: #{host}"
|
||||
port = configuration.local_port
|
||||
data = "Hook URL: #{proto}://#{host}:#{port}#{hook_file}\n"
|
||||
data += "UI URL: #{proto}://#{host}:#{port}#{admin_ui_path}/panel\n" if admin_ui
|
||||
print_more data
|
||||
end
|
||||
|
||||
# display the public hook URL and Admin UI URL
|
||||
if configuration.public_enabled?
|
||||
print_info 'Public:'
|
||||
data = "Hook URL: #{configuration.hook_url}\n"
|
||||
data += "UI URL: #{configuration.beef_url_str}#{admin_ui_path}/panel\n" if admin_ui
|
||||
print_more data
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Print loaded extensions
|
||||
#
|
||||
def print_loaded_extensions
|
||||
extensions = BeEF::Extensions.get_loaded
|
||||
print_info "#{extensions.size} extensions enabled:"
|
||||
output = ''
|
||||
|
||||
extensions.each do |_key, ext|
|
||||
output << "#{ext['name']}\n"
|
||||
end
|
||||
|
||||
print_more output
|
||||
end
|
||||
|
||||
#
|
||||
# Print loaded modules
|
||||
#
|
||||
def print_loaded_modules
|
||||
print_info "#{BeEF::Modules.get_enabled.count} modules enabled."
|
||||
end
|
||||
|
||||
#
|
||||
# Print WebSocket servers
|
||||
#
|
||||
def print_websocket_servers
|
||||
config = BeEF::Core::Configuration.instance
|
||||
ws_poll_timeout = config.get('beef.http.websocket.ws_poll_timeout')
|
||||
print_info "Starting WebSocket server ws://#{config.beef_host}:#{config.get('beef.http.websocket.port').to_i} [timer: #{ws_poll_timeout}]"
|
||||
if config.get('beef.http.websocket.secure')
|
||||
print_info "Starting WebSocketSecure server on wss://[#{config.beef_host}:#{config.get('beef.http.websocket.secure_port').to_i} [timer: #{ws_poll_timeout}]"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Prints BeEF's welcome message
|
||||
#
|
||||
def print_welcome_msg
|
||||
config = BeEF::Core::Configuration.instance
|
||||
version = config.get('beef.version')
|
||||
print_info "Browser Exploitation Framework (BeEF) #{version}"
|
||||
data = "Twit: @beefproject\n"
|
||||
data += "Site: https://beefproject.com\n"
|
||||
data += "Blog: http://blog.beefproject.com\n"
|
||||
data += "Wiki: https://github.com/beefproject/beef/wiki\n"
|
||||
print_more data
|
||||
print_info "Project Creator: " + "Wade Alcorn".red + " (@WadeAlcorn)"
|
||||
end
|
||||
|
||||
#
|
||||
# Prints the number of network interfaces beef is operating on.
|
||||
# Looks like that:
|
||||
#
|
||||
# [14:06:48][*] 5 network interfaces were detected.
|
||||
#
|
||||
def print_network_interfaces_count
|
||||
# get the configuration information
|
||||
configuration = BeEF::Core::Configuration.instance
|
||||
# local host
|
||||
beef_host = configuration.local_host
|
||||
|
||||
# create an array of the interfaces the framework is listening on
|
||||
if beef_host == '0.0.0.0' # the framework will listen on all interfaces
|
||||
interfaces = Socket.ip_address_list.map {|x| x.ip_address if x.ipv4?}
|
||||
interfaces.delete_if {|x| x.nil?} # remove if the entry is nill
|
||||
else # the framework will listen on only one interface
|
||||
interfaces = [beef_host]
|
||||
end
|
||||
|
||||
self.interfaces = interfaces
|
||||
|
||||
# output the banner to the console
|
||||
print_info "#{interfaces.count} network interfaces were detected."
|
||||
end
|
||||
|
||||
#
|
||||
# Prints the route to the network interfaces beef has been deployed on.
|
||||
# Looks like that:
|
||||
#
|
||||
# [14:06:48][+] running on network interface: 192.168.255.1
|
||||
# [14:06:48] | Hook URL: http://192.168.255.1:3000/hook.js
|
||||
# [14:06:48] | UI URL: http://192.168.255.1:3000/ui/panel
|
||||
# [14:06:48][+] running on network interface: 127.0.0.1
|
||||
# [14:06:48] | Hook URL: http://127.0.0.1:3000/hook.js
|
||||
# [14:06:48] | UI URL: http://127.0.0.1:3000/ui/panel
|
||||
#
|
||||
def print_network_interfaces_routes
|
||||
configuration = BeEF::Core::Configuration.instance
|
||||
# local config settings
|
||||
proto = configuration.local_proto
|
||||
hook_file = configuration.hook_file_path
|
||||
admin_ui = configuration.get("beef.extension.admin_ui.enable") ? true : false
|
||||
admin_ui_path = configuration.get("beef.extension.admin_ui.base_path")
|
||||
|
||||
# display the hook URL and Admin UI URL on each interface from the interfaces array
|
||||
self.interfaces.map do |host|
|
||||
print_info "running on network interface: #{host}"
|
||||
port = configuration.local_port
|
||||
data = "Hook URL: #{proto}://#{host}:#{port}#{hook_file}\n"
|
||||
data += "UI URL: #{proto}://#{host}:#{port}#{admin_ui_path}/panel\n" if admin_ui
|
||||
print_more data
|
||||
end
|
||||
|
||||
# display the public hook URL and Admin UI URL
|
||||
if configuration.public_enabled?
|
||||
print_info 'Public:'
|
||||
data = "Hook URL: #{configuration.hook_url}\n"
|
||||
data += "UI URL: #{configuration.beef_url_str}#{admin_ui_path}/panel\n" if admin_ui
|
||||
print_more data
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Print loaded extensions
|
||||
#
|
||||
def print_loaded_extensions
|
||||
extensions = BeEF::Extensions.get_loaded
|
||||
print_info "#{extensions.size} extensions enabled:"
|
||||
output = ''
|
||||
|
||||
extensions.each do |key, ext|
|
||||
output << "#{ext['name']}\n"
|
||||
end
|
||||
|
||||
print_more output
|
||||
end
|
||||
|
||||
#
|
||||
# Print loaded modules
|
||||
#
|
||||
def print_loaded_modules
|
||||
print_info "#{BeEF::Modules::get_enabled.count} modules enabled."
|
||||
end
|
||||
|
||||
#
|
||||
# Print WebSocket servers
|
||||
#
|
||||
def print_websocket_servers
|
||||
config = BeEF::Core::Configuration.instance
|
||||
ws_poll_timeout = config.get('beef.http.websocket.ws_poll_timeout')
|
||||
print_info "Starting WebSocket server ws://#{config.beef_host}:#{config.get("beef.http.websocket.port").to_i} [timer: #{ws_poll_timeout}]"
|
||||
if config.get("beef.http.websocket.secure")
|
||||
print_info "Starting WebSocketSecure server on wss://[#{config.beef_host}:#{config.get("beef.http.websocket.secure_port").to_i} [timer: #{ws_poll_timeout}]"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,14 +10,13 @@ module BeEF
|
||||
# This module parses the command line argument when running beef.
|
||||
#
|
||||
module CommandLine
|
||||
|
||||
@options = Hash.new
|
||||
@options = {}
|
||||
@options[:verbose] = false
|
||||
@options[:resetdb] = false
|
||||
@options[:ascii_art] = false
|
||||
@options[:ext_config] = ""
|
||||
@options[:port] = ""
|
||||
@options[:ws_port] = ""
|
||||
@options[:ext_config] = ''
|
||||
@options[:port] = ''
|
||||
@options[:ws_port] = ''
|
||||
@options[:interactive] = false
|
||||
@options[:update_disabled] = false
|
||||
@options[:update_auto] = false
|
||||
@@ -31,56 +30,52 @@ module BeEF
|
||||
def self.parse
|
||||
return @options if @already_parsed
|
||||
|
||||
begin
|
||||
optparse = OptionParser.new do |opts|
|
||||
opts.on('-x', '--reset', 'Reset the database') do
|
||||
@options[:resetdb] = true
|
||||
end
|
||||
|
||||
opts.on('-v', '--verbose', 'Display debug information') do
|
||||
@options[:verbose] = true
|
||||
end
|
||||
|
||||
opts.on('-a', '--ascii_art', 'Prints BeEF ascii art') do
|
||||
@options[:ascii_art] = true
|
||||
end
|
||||
|
||||
opts.on('-c', '--config FILE', "Load a different configuration file: if it's called custom-config.yaml, git automatically ignores it.") do |f|
|
||||
@options[:ext_config] = f
|
||||
end
|
||||
|
||||
opts.on('-p', '--port PORT', 'Change the default BeEF listening port') do |p|
|
||||
@options[:port] = p
|
||||
end
|
||||
|
||||
opts.on('-w', '--wsport WS_PORT', 'Change the default BeEF WebSocket listening port') do |ws_port|
|
||||
@options[:ws_port] = ws_port
|
||||
end
|
||||
|
||||
opts.on('-ud', '--update_disabled', 'Skips update') do
|
||||
@options[:update_disabled] = true
|
||||
end
|
||||
|
||||
opts.on('-ua', '--update_auto', 'Automatic update with no prompt') do
|
||||
@options[:update_auto] = true
|
||||
end
|
||||
|
||||
#opts.on('-i', '--interactive', 'Starts with the Console Shell activated') do
|
||||
# @options[:interactive] = true
|
||||
#end
|
||||
optparse = OptionParser.new do |opts|
|
||||
opts.on('-x', '--reset', 'Reset the database') do
|
||||
@options[:resetdb] = true
|
||||
end
|
||||
|
||||
optparse.parse!
|
||||
@already_parsed = true
|
||||
@options
|
||||
rescue OptionParser::InvalidOption => e
|
||||
puts "Invalid command line option provided. Please run beef --help"
|
||||
exit 1
|
||||
opts.on('-v', '--verbose', 'Display debug information') do
|
||||
@options[:verbose] = true
|
||||
end
|
||||
|
||||
opts.on('-a', '--ascii_art', 'Prints BeEF ascii art') do
|
||||
@options[:ascii_art] = true
|
||||
end
|
||||
|
||||
opts.on('-c', '--config FILE', "Load a different configuration file: if it's called custom-config.yaml, git automatically ignores it.") do |f|
|
||||
@options[:ext_config] = f
|
||||
end
|
||||
|
||||
opts.on('-p', '--port PORT', 'Change the default BeEF listening port') do |p|
|
||||
@options[:port] = p
|
||||
end
|
||||
|
||||
opts.on('-w', '--wsport WS_PORT', 'Change the default BeEF WebSocket listening port') do |ws_port|
|
||||
@options[:ws_port] = ws_port
|
||||
end
|
||||
|
||||
opts.on('-ud', '--update_disabled', 'Skips update') do
|
||||
@options[:update_disabled] = true
|
||||
end
|
||||
|
||||
opts.on('-ua', '--update_auto', 'Automatic update with no prompt') do
|
||||
@options[:update_auto] = true
|
||||
end
|
||||
|
||||
# opts.on('-i', '--interactive', 'Starts with the Console Shell activated') do
|
||||
# @options[:interactive] = true
|
||||
# end
|
||||
end
|
||||
|
||||
optparse.parse!
|
||||
@already_parsed = true
|
||||
@options
|
||||
rescue OptionParser::InvalidOption
|
||||
puts 'Invalid command line option provided. Please run beef --help'
|
||||
exit 1
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -7,10 +7,8 @@
|
||||
module BeEF
|
||||
module Core
|
||||
module Constants
|
||||
|
||||
# @note The OS'es strings for os detection.
|
||||
module Os
|
||||
|
||||
OS_UNKNOWN_IMG = 'unknown.png'
|
||||
OS_WINDOWS_UA_STR = 'Windows'
|
||||
OS_WINDOWS_IMG = 'win.png'
|
||||
@@ -50,37 +48,35 @@ module BeEF
|
||||
# @return [String] Constant name of matched operating system, returns 'ALL' if nothing are matched
|
||||
def self.match_os(name)
|
||||
case name.downcase
|
||||
when /win/
|
||||
OS_WINDOWS_UA_STR
|
||||
when /lin/
|
||||
OS_LINUX_UA_STR
|
||||
when /os x/, /osx/, /mac/
|
||||
OS_MAC_UA_STR
|
||||
when /qnx/
|
||||
OS_QNX_UA_STR
|
||||
when /sun/
|
||||
OS_SUNOS_UA_STR
|
||||
when /beos/
|
||||
OS_BEOS_UA_STR
|
||||
when /openbsd/
|
||||
OS_OPENBSD_UA_STR
|
||||
when /ios/, /iphone/, /ipad/, /ipod/
|
||||
OS_IOS_UA_STR
|
||||
when /maemo/
|
||||
OS_MAEMO_UA_STR
|
||||
when /blackberry/
|
||||
OS_BLACKBERRY_UA_STR
|
||||
when /android/
|
||||
OS_ANDROID_UA_STR
|
||||
when /aros/
|
||||
OS_AROS_UA_STR
|
||||
else
|
||||
'ALL'
|
||||
when /win/
|
||||
OS_WINDOWS_UA_STR
|
||||
when /lin/
|
||||
OS_LINUX_UA_STR
|
||||
when /os x/, /osx/, /mac/
|
||||
OS_MAC_UA_STR
|
||||
when /qnx/
|
||||
OS_QNX_UA_STR
|
||||
when /sun/
|
||||
OS_SUNOS_UA_STR
|
||||
when /beos/
|
||||
OS_BEOS_UA_STR
|
||||
when /openbsd/
|
||||
OS_OPENBSD_UA_STR
|
||||
when /ios/, /iphone/, /ipad/, /ipod/
|
||||
OS_IOS_UA_STR
|
||||
when /maemo/
|
||||
OS_MAEMO_UA_STR
|
||||
when /blackberry/
|
||||
OS_BLACKBERRY_UA_STR
|
||||
when /android/
|
||||
OS_ANDROID_UA_STR
|
||||
when /aros/
|
||||
OS_AROS_UA_STR
|
||||
else
|
||||
'ALL'
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,90 +6,89 @@
|
||||
require 'securerandom'
|
||||
|
||||
module BeEF
|
||||
module Core
|
||||
module Crypto
|
||||
# @note the minimum length of the security token
|
||||
TOKEN_MINIMUM_LENGTH = 15
|
||||
|
||||
#
|
||||
# Generate a secure random token
|
||||
#
|
||||
# @param [Integer] len The length of the secure token
|
||||
#
|
||||
# @return [String] Security token
|
||||
#
|
||||
def self.secure_token(len = nil)
|
||||
# get default length from config
|
||||
config = BeEF::Core::Configuration.instance
|
||||
token_length = len || config.get('beef.crypto_default_value_length').to_i
|
||||
|
||||
# type checking
|
||||
raise TypeError, "Token length is less than the minimum length enforced by the framework: #{TOKEN_MINIMUM_LENGTH}" if (token_length < TOKEN_MINIMUM_LENGTH)
|
||||
|
||||
# return random hex string
|
||||
SecureRandom.random_bytes(token_length).unpack("H*")[0]
|
||||
end
|
||||
module Core
|
||||
module Crypto
|
||||
# @note the minimum length of the security token
|
||||
TOKEN_MINIMUM_LENGTH = 15
|
||||
|
||||
#
|
||||
# Generate a secure random token, 20 chars, used as an auth token for the RESTful API.
|
||||
# After creation it's stored in the BeEF configuration object => conf.get('beef.api_token')
|
||||
#
|
||||
# @return [String] Security token
|
||||
#
|
||||
def self.api_token
|
||||
|
||||
config = BeEF::Core::Configuration.instance
|
||||
token_length = 20
|
||||
#
|
||||
# Generate a secure random token
|
||||
#
|
||||
# @param [Integer] len The length of the secure token
|
||||
#
|
||||
# @return [String] Security token
|
||||
#
|
||||
def self.secure_token(len = nil)
|
||||
# get default length from config
|
||||
config = BeEF::Core::Configuration.instance
|
||||
token_length = len || config.get('beef.crypto_default_value_length').to_i
|
||||
|
||||
# return random hex string
|
||||
token = SecureRandom.random_bytes(token_length).unpack("H*")[0]
|
||||
config.set('beef.api_token', token)
|
||||
token
|
||||
end
|
||||
# type checking
|
||||
raise TypeError, "Token length is less than the minimum length enforced by the framework: #{TOKEN_MINIMUM_LENGTH}" if token_length < TOKEN_MINIMUM_LENGTH
|
||||
|
||||
#
|
||||
# Generates a random alphanumeric string
|
||||
# Note: this isn't securely random
|
||||
# @todo use SecureRandom once Ruby 2.4 is EOL
|
||||
#
|
||||
# @param length integer length of returned string
|
||||
#
|
||||
def self.random_alphanum_string(length = 10)
|
||||
raise TypeError, 'Invalid length' unless length.integer?
|
||||
raise TypeError, 'Invalid length' unless length.positive?
|
||||
|
||||
[*('a'..'z'),*('A'..'Z'),*('0'..'9')].shuffle[0,length].join
|
||||
end
|
||||
|
||||
#
|
||||
# Generates a random hex string
|
||||
#
|
||||
# @param length integer length of returned string
|
||||
#
|
||||
def self.random_hex_string(length = 10)
|
||||
raise TypeError, 'Invalid length' unless length.integer?
|
||||
raise TypeError, 'Invalid length' unless length.positive?
|
||||
|
||||
SecureRandom.random_bytes(length).unpack('H*').first[0...length]
|
||||
end
|
||||
|
||||
#
|
||||
# Generates a unique identifier for DNS rules.
|
||||
#
|
||||
# @return [String] 8-character hex identifier
|
||||
#
|
||||
def self.dns_rule_id
|
||||
id = nil
|
||||
|
||||
begin
|
||||
id = random_hex_string(8)
|
||||
BeEF::Core::Models::Dns::Rule.all.each { |rule| throw StandardError if id == rule.id }
|
||||
rescue StandardError
|
||||
retry
|
||||
# return random hex string
|
||||
SecureRandom.random_bytes(token_length).unpack1('H*')
|
||||
end
|
||||
|
||||
id.to_s
|
||||
#
|
||||
# Generate a secure random token, 20 chars, used as an auth token for the RESTful API.
|
||||
# After creation it's stored in the BeEF configuration object => conf.get('beef.api_token')
|
||||
#
|
||||
# @return [String] Security token
|
||||
#
|
||||
def self.api_token
|
||||
config = BeEF::Core::Configuration.instance
|
||||
token_length = 20
|
||||
|
||||
# return random hex string
|
||||
token = SecureRandom.random_bytes(token_length).unpack1('H*')
|
||||
config.set('beef.api_token', token)
|
||||
token
|
||||
end
|
||||
|
||||
#
|
||||
# Generates a random alphanumeric string
|
||||
# Note: this isn't securely random
|
||||
# @todo use SecureRandom once Ruby 2.4 is EOL
|
||||
#
|
||||
# @param length integer length of returned string
|
||||
#
|
||||
def self.random_alphanum_string(length = 10)
|
||||
raise TypeError, 'Invalid length' unless length.integer?
|
||||
raise TypeError, 'Invalid length' unless length.positive?
|
||||
|
||||
[*('a'..'z'), *('A'..'Z'), *('0'..'9')].shuffle[0, length].join
|
||||
end
|
||||
|
||||
#
|
||||
# Generates a random hex string
|
||||
#
|
||||
# @param length integer length of returned string
|
||||
#
|
||||
def self.random_hex_string(length = 10)
|
||||
raise TypeError, 'Invalid length' unless length.integer?
|
||||
raise TypeError, 'Invalid length' unless length.positive?
|
||||
|
||||
SecureRandom.random_bytes(length).unpack1('H*')[0...length]
|
||||
end
|
||||
|
||||
#
|
||||
# Generates a unique identifier for DNS rules.
|
||||
#
|
||||
# @return [String] 8-character hex identifier
|
||||
#
|
||||
def self.dns_rule_id
|
||||
id = nil
|
||||
|
||||
begin
|
||||
id = random_hex_string(8)
|
||||
BeEF::Core::Models::Dns::Rule.all.each { |rule| throw StandardError if id == rule.id }
|
||||
rescue StandardError
|
||||
retry
|
||||
end
|
||||
|
||||
id.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -8,7 +8,6 @@ module BeEF
|
||||
module Handlers
|
||||
# @note Retrieves information about the browser (type, version, plugins etc.)
|
||||
class BrowserDetails
|
||||
|
||||
@data = {}
|
||||
|
||||
HB = BeEF::Core::Models::HookedBrowser
|
||||
@@ -16,69 +15,70 @@ module BeEF
|
||||
|
||||
def initialize(data)
|
||||
@data = data
|
||||
setup()
|
||||
setup
|
||||
end
|
||||
|
||||
def err_msg(error)
|
||||
print_error "[Browser Details] #{error}"
|
||||
end
|
||||
|
||||
def setup()
|
||||
print_debug "[INIT] Processing Browser Details..."
|
||||
def setup
|
||||
print_debug '[INIT] Processing Browser Details...'
|
||||
config = BeEF::Core::Configuration.instance
|
||||
|
||||
# validate hook session value
|
||||
session_id = get_param(@data, 'beefhook')
|
||||
print_debug "[INIT] Processing Browser Details for session #{session_id}"
|
||||
(self.err_msg "session id is invalid"; return) if not BeEF::Filters.is_valid_hook_session_id?(session_id)
|
||||
hooked_browser = HB.where(:session => session_id).first
|
||||
return if not hooked_browser.nil? # browser is already registered with framework
|
||||
unless BeEF::Filters.is_valid_hook_session_id?(session_id)
|
||||
(err_msg 'session id is invalid'
|
||||
return)
|
||||
end
|
||||
hooked_browser = HB.where(session: session_id).first
|
||||
return unless hooked_browser.nil? # browser is already registered with framework
|
||||
|
||||
# create the structure representing the hooked browser
|
||||
zombie = BeEF::Core::Models::HookedBrowser.new(:ip => @data['request'].ip, :session => session_id)
|
||||
zombie = BeEF::Core::Models::HookedBrowser.new(ip: @data['request'].ip, session: session_id)
|
||||
zombie.firstseen = Time.new.to_i
|
||||
|
||||
# hooked window host name
|
||||
log_zombie_port = 0
|
||||
if not @data['results']['browser.window.hostname'].nil?
|
||||
if !@data['results']['browser.window.hostname'].nil?
|
||||
log_zombie_domain = @data['results']['browser.window.hostname']
|
||||
elsif (not @data['request'].referer.nil?) and (not @data['request'].referer.empty?)
|
||||
elsif !@data['request'].referer.nil? and !@data['request'].referer.empty?
|
||||
referer = @data['request'].referer
|
||||
if referer.start_with?("https://")
|
||||
log_zombie_port = 443
|
||||
else
|
||||
log_zombie_port = 80
|
||||
end
|
||||
log_zombie_domain=referer.gsub('http://', '').gsub('https://', '').split('/')[0]
|
||||
log_zombie_port = if referer.start_with?('https://')
|
||||
443
|
||||
else
|
||||
80
|
||||
end
|
||||
log_zombie_domain = referer.gsub('http://', '').gsub('https://', '').split('/')[0]
|
||||
else
|
||||
log_zombie_domain="unknown" # Probably local file open
|
||||
log_zombie_domain = 'unknown' # Probably local file open
|
||||
end
|
||||
|
||||
# hooked window host port
|
||||
if not @data['results']['browser.window.hostport'].nil?
|
||||
log_zombie_port = @data['results']['browser.window.hostport']
|
||||
if @data['results']['browser.window.hostport'].nil?
|
||||
log_zombie_domain_parts = log_zombie_domain.split(':')
|
||||
log_zombie_port = log_zombie_domain_parts[1].to_i if log_zombie_domain_parts.length > 1
|
||||
else
|
||||
log_zombie_domain_parts=log_zombie_domain.split(':')
|
||||
if log_zombie_domain_parts.length > 1 then
|
||||
log_zombie_port=log_zombie_domain_parts[1].to_i
|
||||
end
|
||||
log_zombie_port = @data['results']['browser.window.hostport']
|
||||
end
|
||||
|
||||
zombie.domain = log_zombie_domain
|
||||
zombie.port = log_zombie_port
|
||||
|
||||
#Parse http_headers. Unfortunately Rack doesn't provide a util-method to get them :(
|
||||
@http_headers = Hash.new
|
||||
http_header = @data['request'].env.select { |k, v| k.to_s.start_with? 'HTTP_' }
|
||||
.each { |key, value|
|
||||
# Parse http_headers. Unfortunately Rack doesn't provide a util-method to get them :(
|
||||
@http_headers = {}
|
||||
http_header = @data['request'].env.select { |k, _v| k.to_s.start_with? 'HTTP_' }
|
||||
.each do |key, value|
|
||||
@http_headers[key.sub(/^HTTP_/, '')] = value.force_encoding('UTF-8')
|
||||
}
|
||||
end
|
||||
zombie.httpheaders = @http_headers.to_json
|
||||
zombie.save!
|
||||
#print_debug "[INIT] HTTP Headers: #{zombie.httpheaders}"
|
||||
# print_debug "[INIT] HTTP Headers: #{zombie.httpheaders}"
|
||||
|
||||
# add a log entry for the newly hooked browser
|
||||
BeEF::Core::Logger.instance.register('Zombie', "#{zombie.ip} just joined the horde from the domain: #{log_zombie_domain}:#{log_zombie_port.to_s}", "#{zombie.id}")
|
||||
BeEF::Core::Logger.instance.register('Zombie', "#{zombie.ip} just joined the horde from the domain: #{log_zombie_domain}:#{log_zombie_port}", zombie.id.to_s)
|
||||
|
||||
# get and store browser name
|
||||
browser_name = get_param(@data['results'], 'browser.name')
|
||||
@@ -89,23 +89,21 @@ module BeEF
|
||||
browser_friendly_name = BeEF::Core::Constants::Browsers.friendly_name(browser_name)
|
||||
BD.set(session_id, 'browser.name.friendly', browser_friendly_name)
|
||||
else
|
||||
self.err_msg "Invalid browser name returned from the hook browser's initial connection."
|
||||
err_msg "Invalid browser name returned from the hook browser's initial connection."
|
||||
end
|
||||
|
||||
if BeEF::Filters.is_valid_ip?(zombie.ip)
|
||||
BD.set(session_id, 'network.ipaddress', zombie.ip)
|
||||
else
|
||||
self.err_msg "Invalid IP address returned from the hook browser's initial connection."
|
||||
err_msg "Invalid IP address returned from the hook browser's initial connection."
|
||||
end
|
||||
|
||||
# lookup zombie host name
|
||||
if config.get('beef.dns_hostname_lookup')
|
||||
begin
|
||||
host_name = Resolv.getname(zombie.ip).to_s
|
||||
if BeEF::Filters.is_valid_hostname?(host_name)
|
||||
BD.set(session_id, 'host.name', host_name)
|
||||
end
|
||||
rescue
|
||||
BD.set(session_id, 'host.name', host_name) if BeEF::Filters.is_valid_hostname?(host_name)
|
||||
rescue StandardError
|
||||
print_debug "[INIT] Reverse lookup failed - No results for IP address '#{zombie.ip}'"
|
||||
end
|
||||
end
|
||||
@@ -119,62 +117,112 @@ module BeEF
|
||||
print_debug "[INIT] Geolocation failed - No results for IP address '#{zombie.ip}'"
|
||||
else
|
||||
# print_debug "[INIT] Geolocation results: #{geoip}"
|
||||
BeEF::Core::Logger.instance.register('Zombie', "#{zombie.ip} is connecting from: #{geoip}", "#{zombie.id}")
|
||||
BeEF::Core::Logger.instance.register('Zombie', "#{zombie.ip} is connecting from: #{geoip}", zombie.id.to_s)
|
||||
BD.set(
|
||||
session_id,
|
||||
'location.city',
|
||||
"#{geoip['city']['names']['en'] rescue 'Unknown'}")
|
||||
(begin
|
||||
geoip['city']['names']['en']
|
||||
rescue StandardError
|
||||
'Unknown'
|
||||
end).to_s
|
||||
)
|
||||
BD.set(
|
||||
session_id,
|
||||
'location.country',
|
||||
"#{geoip['country']['names']['en'] rescue 'Unknown' }")
|
||||
(begin
|
||||
geoip['country']['names']['en']
|
||||
rescue StandardError
|
||||
'Unknown'
|
||||
end).to_s
|
||||
)
|
||||
BD.set(
|
||||
session_id,
|
||||
'location.country.isocode',
|
||||
"#{geoip['country']['iso_code'] rescue 'Unknown'}")
|
||||
(begin
|
||||
geoip['country']['iso_code']
|
||||
rescue StandardError
|
||||
'Unknown'
|
||||
end).to_s
|
||||
)
|
||||
BD.set(
|
||||
session_id,
|
||||
'location.country.registered_country',
|
||||
"#{geoip['registered_country']['names']['en'] rescue 'Unknown'}")
|
||||
(begin
|
||||
geoip['registered_country']['names']['en']
|
||||
rescue StandardError
|
||||
'Unknown'
|
||||
end).to_s
|
||||
)
|
||||
BD.set(
|
||||
session_id,
|
||||
'location.country.registered_country.isocode',
|
||||
"#{geoip['registered_country']['iso_code'] rescue 'Unknown'}")
|
||||
(begin
|
||||
geoip['registered_country']['iso_code']
|
||||
rescue StandardError
|
||||
'Unknown'
|
||||
end).to_s
|
||||
)
|
||||
BD.set(
|
||||
session_id,
|
||||
'location.continent',
|
||||
"#{geoip['continent']['names']['en'] rescue 'Unknown'}")
|
||||
(begin
|
||||
geoip['continent']['names']['en']
|
||||
rescue StandardError
|
||||
'Unknown'
|
||||
end).to_s
|
||||
)
|
||||
BD.set(
|
||||
session_id,
|
||||
'location.continent.code',
|
||||
"#{geoip['continent']['code'] rescue 'Unknown'}")
|
||||
(begin
|
||||
geoip['continent']['code']
|
||||
rescue StandardError
|
||||
'Unknown'
|
||||
end).to_s
|
||||
)
|
||||
BD.set(
|
||||
session_id,
|
||||
'location.latitude',
|
||||
"#{geoip['location']['latitude'] rescue 'Unknown'}")
|
||||
(begin
|
||||
geoip['location']['latitude']
|
||||
rescue StandardError
|
||||
'Unknown'
|
||||
end).to_s
|
||||
)
|
||||
BD.set(
|
||||
session_id,
|
||||
'location.longitude',
|
||||
"#{geoip['location']['longitude'] rescue 'Unknown'}")
|
||||
(begin
|
||||
geoip['location']['longitude']
|
||||
rescue StandardError
|
||||
'Unknown'
|
||||
end).to_s
|
||||
)
|
||||
BD.set(
|
||||
session_id,
|
||||
'location.timezone',
|
||||
"#{geoip['location']['time_zone'] rescue 'Unknown'}")
|
||||
(begin
|
||||
geoip['location']['time_zone']
|
||||
rescue StandardError
|
||||
'Unknown'
|
||||
end).to_s
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# detect browser proxy
|
||||
using_proxy = false
|
||||
[
|
||||
'CLIENT_IP',
|
||||
'FORWARDED_FOR',
|
||||
'FORWARDED',
|
||||
'FORWARDED_FOR_IP',
|
||||
'PROXY_CONNECTION',
|
||||
'PROXY_AUTHENTICATE',
|
||||
'X_FORWARDED',
|
||||
'X_FORWARDED_FOR',
|
||||
'VIA'
|
||||
%w[
|
||||
CLIENT_IP
|
||||
FORWARDED_FOR
|
||||
FORWARDED
|
||||
FORWARDED_FOR_IP
|
||||
PROXY_CONNECTION
|
||||
PROXY_AUTHENTICATE
|
||||
X_FORWARDED
|
||||
X_FORWARDED_FOR
|
||||
VIA
|
||||
].each do |header|
|
||||
unless JSON.parse(zombie.httpheaders)[header].nil?
|
||||
using_proxy = true
|
||||
@@ -184,15 +232,15 @@ module BeEF
|
||||
|
||||
# retrieve proxy client IP
|
||||
proxy_clients = []
|
||||
[
|
||||
'CLIENT_IP',
|
||||
'FORWARDED_FOR',
|
||||
'FORWARDED',
|
||||
'FORWARDED_FOR_IP',
|
||||
'X_FORWARDED',
|
||||
'X_FORWARDED_FOR'
|
||||
%w[
|
||||
CLIENT_IP
|
||||
FORWARDED_FOR
|
||||
FORWARDED
|
||||
FORWARDED_FOR_IP
|
||||
X_FORWARDED
|
||||
X_FORWARDED_FOR
|
||||
].each do |header|
|
||||
proxy_clients << "#{JSON.parse(zombie.httpheaders)[header]}" unless JSON.parse(zombie.httpheaders)[header].nil?
|
||||
proxy_clients << (JSON.parse(zombie.httpheaders)[header]).to_s unless JSON.parse(zombie.httpheaders)[header].nil?
|
||||
end
|
||||
|
||||
# retrieve proxy server
|
||||
@@ -203,20 +251,18 @@ module BeEF
|
||||
BD.set(session_id, 'network.proxy', 'Yes')
|
||||
proxy_log_string = "#{zombie.ip} is using a proxy"
|
||||
unless proxy_clients.empty?
|
||||
BD.set(session_id, 'network.proxy.client', "#{proxy_clients.sort.uniq.join(',')}")
|
||||
BD.set(session_id, 'network.proxy.client', proxy_clients.sort.uniq.join(',').to_s)
|
||||
proxy_log_string += " [client: #{proxy_clients.sort.uniq.join(',')}]"
|
||||
end
|
||||
unless proxy_server.nil?
|
||||
BD.set(session_id, 'network.proxy.server', "#{proxy_server}")
|
||||
BD.set(session_id, 'network.proxy.server', proxy_server.to_s)
|
||||
proxy_log_string += " [server: #{proxy_server}]"
|
||||
if config.get("beef.extension.network.enable") == true
|
||||
if proxy_server =~ /^([\d\.]+):([\d]+)$/
|
||||
print_debug("Hooked browser [id:#{zombie.id}] is using a proxy [ip: #{$1}]")
|
||||
BeEF::Core::Models::NetworkHost.create(:hooked_browser_id => session_id, :ip => $1, :type => 'Proxy')
|
||||
end
|
||||
if config.get('beef.extension.network.enable') == true && (proxy_server =~ /^([\d.]+):(\d+)$/)
|
||||
print_debug("Hooked browser [id:#{zombie.id}] is using a proxy [ip: #{Regexp.last_match(1)}]")
|
||||
BeEF::Core::Models::NetworkHost.create(hooked_browser_id: session_id, ip: Regexp.last_match(1), type: 'Proxy')
|
||||
end
|
||||
end
|
||||
BeEF::Core::Logger.instance.register('Zombie', "#{proxy_log_string}", "#{zombie.id}")
|
||||
BeEF::Core::Logger.instance.register('Zombie', proxy_log_string.to_s, zombie.id.to_s)
|
||||
end
|
||||
|
||||
# get and store browser version
|
||||
@@ -224,7 +270,7 @@ module BeEF
|
||||
if BeEF::Filters.is_valid_browserversion?(browser_version)
|
||||
BD.set(session_id, 'browser.version', browser_version)
|
||||
else
|
||||
self.err_msg "Invalid browser version returned from the hook browser's initial connection."
|
||||
err_msg "Invalid browser version returned from the hook browser's initial connection."
|
||||
end
|
||||
|
||||
# get and store browser string
|
||||
@@ -232,7 +278,7 @@ module BeEF
|
||||
if BeEF::Filters.is_valid_browserstring?(browser_string)
|
||||
BD.set(session_id, 'browser.name.reported', browser_string)
|
||||
else
|
||||
self.err_msg "Invalid value for 'browser.name.reported' returned from the hook browser's initial connection."
|
||||
err_msg "Invalid value for 'browser.name.reported' returned from the hook browser's initial connection."
|
||||
end
|
||||
|
||||
# get and store browser engine
|
||||
@@ -240,7 +286,7 @@ module BeEF
|
||||
if BeEF::Filters.is_valid_browserstring?(browser_engine)
|
||||
BD.set(session_id, 'browser.engine', browser_engine)
|
||||
else
|
||||
self.err_msg "Invalid value for 'browser.engine' returned from the hook browser's initial connection."
|
||||
err_msg "Invalid value for 'browser.engine' returned from the hook browser's initial connection."
|
||||
end
|
||||
|
||||
# get and store browser language
|
||||
@@ -252,7 +298,7 @@ module BeEF
|
||||
if BeEF::Filters.is_valid_cookies?(cookies)
|
||||
BD.set(session_id, 'browser.window.cookies', cookies)
|
||||
else
|
||||
self.err_msg "Invalid cookies returned from the hook browser's initial connection."
|
||||
err_msg "Invalid cookies returned from the hook browser's initial connection."
|
||||
end
|
||||
|
||||
# get and store the OS name
|
||||
@@ -260,7 +306,7 @@ module BeEF
|
||||
if BeEF::Filters.is_valid_osname?(os_name)
|
||||
BD.set(session_id, 'host.os.name', os_name)
|
||||
else
|
||||
self.err_msg "Invalid operating system name returned from the hook browser's initial connection."
|
||||
err_msg "Invalid operating system name returned from the hook browser's initial connection."
|
||||
end
|
||||
|
||||
# get and store the OS family
|
||||
@@ -268,7 +314,7 @@ module BeEF
|
||||
if BeEF::Filters.is_valid_osname?(os_family)
|
||||
BD.set(session_id, 'host.os.family', os_family)
|
||||
else
|
||||
self.err_msg "Invalid value for 'host.os.family' returned from the hook browser's initial connection."
|
||||
err_msg "Invalid value for 'host.os.family' returned from the hook browser's initial connection."
|
||||
end
|
||||
|
||||
# get and store the OS version
|
||||
@@ -289,7 +335,7 @@ module BeEF
|
||||
if BeEF::Filters.is_valid_hwname?(hw_type)
|
||||
BD.set(session_id, 'hardware.type', hw_type)
|
||||
else
|
||||
self.err_msg "Invalid value for 'hardware.type' returned from the hook browser's initial connection."
|
||||
err_msg "Invalid value for 'hardware.type' returned from the hook browser's initial connection."
|
||||
end
|
||||
|
||||
# get and store the date
|
||||
@@ -297,7 +343,7 @@ module BeEF
|
||||
if BeEF::Filters.is_valid_date_stamp?(date_stamp)
|
||||
BD.set(session_id, 'browser.date.datestamp', date_stamp)
|
||||
else
|
||||
self.err_msg "Invalid date returned from the hook browser's initial connection."
|
||||
err_msg "Invalid date returned from the hook browser's initial connection."
|
||||
end
|
||||
|
||||
# get and store page title
|
||||
@@ -305,7 +351,7 @@ module BeEF
|
||||
if BeEF::Filters.is_valid_pagetitle?(page_title)
|
||||
BD.set(session_id, 'browser.window.title', page_title)
|
||||
else
|
||||
self.err_msg "Invalid value for 'browser.window.title' returned from the hook browser's initial connection."
|
||||
err_msg "Invalid value for 'browser.window.title' returned from the hook browser's initial connection."
|
||||
end
|
||||
|
||||
# get and store page origin
|
||||
@@ -313,7 +359,7 @@ module BeEF
|
||||
if BeEF::Filters.is_valid_url?(origin)
|
||||
BD.set(session_id, 'browser.window.origin', origin)
|
||||
else
|
||||
self.err_msg "Invalid value for 'browser.window.uri' returned from the hook browser's initial connection."
|
||||
err_msg "Invalid value for 'browser.window.uri' returned from the hook browser's initial connection."
|
||||
end
|
||||
|
||||
# get and store page uri
|
||||
@@ -321,7 +367,7 @@ module BeEF
|
||||
if BeEF::Filters.is_valid_url?(page_uri)
|
||||
BD.set(session_id, 'browser.window.uri', page_uri)
|
||||
else
|
||||
self.err_msg "Invalid value for 'browser.window.uri' returned from the hook browser's initial connection."
|
||||
err_msg "Invalid value for 'browser.window.uri' returned from the hook browser's initial connection."
|
||||
end
|
||||
|
||||
# get and store the page referrer
|
||||
@@ -329,7 +375,7 @@ module BeEF
|
||||
if BeEF::Filters.is_valid_pagereferrer?(page_referrer)
|
||||
BD.set(session_id, 'browser.window.referrer', page_referrer)
|
||||
else
|
||||
self.err_msg "Invalid value for 'browser.window.referrer' returned from the hook browser's initial connection."
|
||||
err_msg "Invalid value for 'browser.window.referrer' returned from the hook browser's initial connection."
|
||||
end
|
||||
|
||||
# get and store hooked window host port
|
||||
@@ -337,7 +383,7 @@ module BeEF
|
||||
if BeEF::Filters.is_valid_hostname?(host_name)
|
||||
BD.set(session_id, 'browser.window.hostname', host_name)
|
||||
else
|
||||
self.err_msg "Invalid valid for 'browser.window.hostname' returned from the hook browser's initial connection."
|
||||
err_msg "Invalid valid for 'browser.window.hostname' returned from the hook browser's initial connection."
|
||||
end
|
||||
|
||||
# get and store hooked window host port
|
||||
@@ -345,7 +391,7 @@ module BeEF
|
||||
if BeEF::Filters.is_valid_port?(host_port)
|
||||
BD.set(session_id, 'browser.window.hostport', host_port)
|
||||
else
|
||||
self.err_msg "Invalid valid for 'browser.window.hostport' returned from the hook browser's initial connection."
|
||||
err_msg "Invalid valid for 'browser.window.hostport' returned from the hook browser's initial connection."
|
||||
end
|
||||
|
||||
# get and store the browser plugins
|
||||
@@ -353,7 +399,7 @@ module BeEF
|
||||
if BeEF::Filters.is_valid_browser_plugins?(browser_plugins)
|
||||
BD.set(session_id, 'browser.plugins', browser_plugins)
|
||||
else
|
||||
self.err_msg "Invalid browser plugins returned from the hook browser's initial connection."
|
||||
err_msg "Invalid browser plugins returned from the hook browser's initial connection."
|
||||
end
|
||||
|
||||
# get and store the system platform
|
||||
@@ -361,7 +407,7 @@ module BeEF
|
||||
if BeEF::Filters.is_valid_system_platform?(system_platform)
|
||||
BD.set(session_id, 'browser.platform', system_platform)
|
||||
else
|
||||
self.err_msg "Invalid browser platform returned from the hook browser's initial connection."
|
||||
err_msg "Invalid browser platform returned from the hook browser's initial connection."
|
||||
end
|
||||
|
||||
# get and store the zombie screen color depth
|
||||
@@ -369,7 +415,7 @@ module BeEF
|
||||
if BeEF::Filters.nums_only?(screen_colordepth)
|
||||
BD.set(session_id, 'hardware.screen.colordepth', screen_colordepth)
|
||||
else
|
||||
self.err_msg "Invalid value for 'hardware.screen.colordepth' returned from the hook browser's initial connection."
|
||||
err_msg "Invalid value for 'hardware.screen.colordepth' returned from the hook browser's initial connection."
|
||||
end
|
||||
|
||||
# get and store the zombie screen width
|
||||
@@ -377,7 +423,7 @@ module BeEF
|
||||
if BeEF::Filters.nums_only?(screen_size_width)
|
||||
BD.set(session_id, 'hardware.screen.size.width', screen_size_width)
|
||||
else
|
||||
self.err_msg "Invalid value for 'hardware.screen.size.width' returned from the hook browser's initial connection."
|
||||
err_msg "Invalid value for 'hardware.screen.size.width' returned from the hook browser's initial connection."
|
||||
end
|
||||
|
||||
# get and store the zombie screen height
|
||||
@@ -385,16 +431,15 @@ module BeEF
|
||||
if BeEF::Filters.nums_only?(screen_size_height)
|
||||
BD.set(session_id, 'hardware.screen.size.height', screen_size_height)
|
||||
else
|
||||
self.err_msg "Invalid value for 'hardware.screen.size.height' returned from the hook browser's initial connection."
|
||||
err_msg "Invalid value for 'hardware.screen.size.height' returned from the hook browser's initial connection."
|
||||
end
|
||||
|
||||
|
||||
# get and store the window height
|
||||
window_height = get_param(@data['results'], 'browser.window.size.height')
|
||||
if BeEF::Filters.nums_only?(window_height)
|
||||
BD.set(session_id, 'browser.window.size.height', window_height)
|
||||
else
|
||||
self.err_msg "Invalid value for 'browser.window.size.height' returned from the hook browser's initial connection."
|
||||
err_msg "Invalid value for 'browser.window.size.height' returned from the hook browser's initial connection."
|
||||
end
|
||||
|
||||
# get and store the window width
|
||||
@@ -402,18 +447,18 @@ module BeEF
|
||||
if BeEF::Filters.nums_only?(window_width)
|
||||
BD.set(session_id, 'browser.window.size.width', window_width)
|
||||
else
|
||||
self.err_msg "Invalid value for 'browser.window.size.width' returned from the hook browser's initial connection."
|
||||
err_msg "Invalid value for 'browser.window.size.width' returned from the hook browser's initial connection."
|
||||
end
|
||||
|
||||
# store and log IP details of host
|
||||
print_debug("Hooked browser [id:#{zombie.id}] has IP [ip: #{zombie.ip}]")
|
||||
|
||||
if os_name != nil and os_version != nil
|
||||
BeEF::Core::Models::NetworkHost.create(:hooked_browser => zombie, :ip => zombie.ip, :ntype => 'Host', :os => os_name + "-" + os_version)
|
||||
elsif os_name != nil
|
||||
BeEF::Core::Models::NetworkHost.create(:hooked_browser => zombie, :ip => zombie.ip, :ntype => 'Host', :os => os_name)
|
||||
if !os_name.nil? and !os_version.nil?
|
||||
BeEF::Core::Models::NetworkHost.create(hooked_browser: zombie, ip: zombie.ip, ntype: 'Host', os: os_name + '-' + os_version)
|
||||
elsif !os_name.nil?
|
||||
BeEF::Core::Models::NetworkHost.create(hooked_browser: zombie, ip: zombie.ip, ntype: 'Host', os: os_name)
|
||||
else
|
||||
BeEF::Core::Models::NetworkHost.create(:hooked_browser => zombie, :ip => zombie.ip, :ntype => 'Host')
|
||||
BeEF::Core::Models::NetworkHost.create(hooked_browser: zombie, ip: zombie.ip, ntype: 'Host')
|
||||
end
|
||||
|
||||
# get and store the yes|no value for browser capabilities
|
||||
@@ -432,14 +477,14 @@ module BeEF
|
||||
'browser.capabilities.webworker',
|
||||
'browser.capabilities.websocket',
|
||||
'browser.capabilities.webgl',
|
||||
'browser.capabilities.webrtc',
|
||||
'browser.capabilities.webrtc'
|
||||
]
|
||||
capabilities.each do |k|
|
||||
v = get_param(@data['results'], k)
|
||||
if BeEF::Filters.is_valid_yes_no?(v)
|
||||
BD.set(session_id, k, v)
|
||||
else
|
||||
self.err_msg "Invalid value for #{k} returned from the hook browser's initial connection."
|
||||
err_msg "Invalid value for #{k} returned from the hook browser's initial connection."
|
||||
end
|
||||
end
|
||||
|
||||
@@ -448,7 +493,7 @@ module BeEF
|
||||
if BeEF::Filters.is_valid_memory?(memory)
|
||||
BD.set(session_id, 'hardware.memory', memory)
|
||||
else
|
||||
self.err_msg "Invalid value for 'hardware.memory' returned from the hook browser's initial connection."
|
||||
err_msg "Invalid value for 'hardware.memory' returned from the hook browser's initial connection."
|
||||
end
|
||||
|
||||
# get and store the value for hardware.gpu
|
||||
@@ -456,7 +501,7 @@ module BeEF
|
||||
if BeEF::Filters.is_valid_gpu?(gpu)
|
||||
BD.set(session_id, 'hardware.gpu', gpu)
|
||||
else
|
||||
self.err_msg "Invalid value for 'hardware.gpu' returned from the hook browser's initial connection."
|
||||
err_msg "Invalid value for 'hardware.gpu' returned from the hook browser's initial connection."
|
||||
end
|
||||
|
||||
# get and store the value for hardware.gpu.vendor
|
||||
@@ -464,7 +509,7 @@ module BeEF
|
||||
if BeEF::Filters.is_valid_gpu?(gpu_vendor)
|
||||
BD.set(session_id, 'hardware.gpu.vendor', gpu_vendor)
|
||||
else
|
||||
self.err_msg "Invalid value for 'hardware.gpu.vendor' returned from the hook browser's initial connection."
|
||||
err_msg "Invalid value for 'hardware.gpu.vendor' returned from the hook browser's initial connection."
|
||||
end
|
||||
|
||||
# get and store the value for hardware.cpu.arch
|
||||
@@ -472,7 +517,7 @@ module BeEF
|
||||
if BeEF::Filters.is_valid_cpu?(cpu_arch)
|
||||
BD.set(session_id, 'hardware.cpu.arch', cpu_arch)
|
||||
else
|
||||
self.err_msg "Invalid value for 'hardware.cpu.arch' returned from the hook browser's initial connection."
|
||||
err_msg "Invalid value for 'hardware.cpu.arch' returned from the hook browser's initial connection."
|
||||
end
|
||||
|
||||
# get and store the value for hardware.cpu.cores
|
||||
@@ -480,15 +525,15 @@ module BeEF
|
||||
if BeEF::Filters.alphanums_only?(cpu_cores)
|
||||
BD.set(session_id, 'hardware.cpu.cores', cpu_cores)
|
||||
else
|
||||
self.err_msg "Invalid value for 'hardware.cpu.cores' returned from the hook browser's initial connection."
|
||||
err_msg "Invalid value for 'hardware.cpu.cores' returned from the hook browser's initial connection."
|
||||
end
|
||||
|
||||
# get and store the value for hardware.battery.level
|
||||
battery_level = get_param(@data['results'], 'hardware.battery.level')
|
||||
if battery_level == 'unknown' || battery_level =~ /\A[\d\.]+%\z/
|
||||
if battery_level == 'unknown' || battery_level =~ /\A[\d.]+%\z/
|
||||
BD.set(session_id, 'hardware.battery.level', battery_level)
|
||||
else
|
||||
self.err_msg "Invalid value for 'hardware.battery.level' returned from the hook browser's initial connection."
|
||||
err_msg "Invalid value for 'hardware.battery.level' returned from the hook browser's initial connection."
|
||||
end
|
||||
|
||||
# get and store the value for hardware.screen.touchenabled
|
||||
@@ -496,7 +541,7 @@ module BeEF
|
||||
if BeEF::Filters.is_valid_yes_no?(touch_enabled)
|
||||
BD.set(session_id, 'hardware.screen.touchenabled', touch_enabled)
|
||||
else
|
||||
self.err_msg "Invalid value for hardware.screen.touchenabled returned from the hook browser's initial connection."
|
||||
err_msg "Invalid value for hardware.screen.touchenabled returned from the hook browser's initial connection."
|
||||
end
|
||||
|
||||
if config.get('beef.integration.phishing_frenzy.enable')
|
||||
@@ -506,34 +551,29 @@ module BeEF
|
||||
if BeEF::Filters.alphanums_only?(victim_uid)
|
||||
BD.set(session_id, 'PhishingFrenzyUID', victim_uid)
|
||||
else
|
||||
self.err_msg "Invalid PhishingFrenzy Victim UID returned from the hook browser's initial connection."
|
||||
err_msg "Invalid PhishingFrenzy Victim UID returned from the hook browser's initial connection."
|
||||
end
|
||||
end
|
||||
|
||||
# log a few info of newly hooked zombie in the console
|
||||
print_info "New Hooked Browser [id:#{zombie.id}, ip:#{zombie.ip}, browser:#{browser_name}-#{browser_version}, os:#{os_name}-#{os_version}], hooked domain [#{log_zombie_domain}:#{log_zombie_port.to_s}]"
|
||||
print_info "New Hooked Browser [id:#{zombie.id}, ip:#{zombie.ip}, browser:#{browser_name}-#{browser_version}, os:#{os_name}-#{os_version}], hooked domain [#{log_zombie_domain}:#{log_zombie_port}]"
|
||||
|
||||
# add localhost as network host
|
||||
if config.get('beef.extension.network.enable')
|
||||
print_debug("Hooked browser has network interface 127.0.0.1")
|
||||
BeEF::Core::Models::NetworkHost.create(:hooked_browser_id => session_id, :ip => '127.0.0.1', :hostname => 'localhost', :os => BeEF::Core::Models::BrowserDetails.get(session_id, 'host.os.name'))
|
||||
print_debug('Hooked browser has network interface 127.0.0.1')
|
||||
BeEF::Core::Models::NetworkHost.create(hooked_browser_id: session_id, ip: '127.0.0.1', hostname: 'localhost',
|
||||
os: BeEF::Core::Models::BrowserDetails.get(session_id, 'host.os.name'))
|
||||
end
|
||||
|
||||
# check if any ARE rules shall be triggered only if the channel is != WebSockets (XHR). If the channel
|
||||
# is WebSockets, then ARe rules are triggered after channel is established.
|
||||
unless config.get("beef.http.websocket.enable")
|
||||
BeEF::Core::AutorunEngine::Engine.instance.run(zombie.id, browser_name, browser_version, os_name, os_version)
|
||||
end
|
||||
BeEF::Core::AutorunEngine::Engine.instance.run(zombie.id, browser_name, browser_version, os_name, os_version) unless config.get('beef.http.websocket.enable')
|
||||
end
|
||||
|
||||
def get_param(query, key)
|
||||
(query.class == Hash and query.has_key?(key)) ? query[key].to_s : nil
|
||||
(query.instance_of?(Hash) and query.has_key?(key)) ? query[key].to_s : nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -4,107 +4,107 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Core
|
||||
module Handlers
|
||||
class Commands
|
||||
module Core
|
||||
module Handlers
|
||||
class Commands
|
||||
include BeEF::Core::Handlers::Modules::BeEFJS
|
||||
include BeEF::Core::Handlers::Modules::Command
|
||||
|
||||
include BeEF::Core::Handlers::Modules::BeEFJS
|
||||
include BeEF::Core::Handlers::Modules::Command
|
||||
@data = {}
|
||||
|
||||
@data = {}
|
||||
#
|
||||
# Handles command data
|
||||
#
|
||||
# @param [Hash] data Data from command execution
|
||||
# @param [Class] kclass Class of command
|
||||
#
|
||||
# @todo Confirm argument data variable type [radoen]: type is Hash confirmed.
|
||||
#
|
||||
def initialize(data, kclass)
|
||||
@kclass = BeEF::Core::Command.const_get(kclass.capitalize)
|
||||
@data = data
|
||||
setup
|
||||
end
|
||||
|
||||
#
|
||||
# Handles command data
|
||||
#
|
||||
# @param [Hash] data Data from command execution
|
||||
# @param [Class] kclass Class of command
|
||||
#
|
||||
# @todo Confirm argument data variable type [radoen]: type is Hash confirmed.
|
||||
#
|
||||
def initialize(data, kclass)
|
||||
@kclass = BeEF::Core::Command.const_get(kclass.capitalize)
|
||||
@data = data
|
||||
setup
|
||||
end
|
||||
#
|
||||
# @note Initial setup function, creates the command module and saves details to datastore
|
||||
#
|
||||
def setup
|
||||
@http_params = @data['request'].params
|
||||
@http_header = {}
|
||||
http_header = @data['request'].env.select { |k, _v| k.to_s.start_with? 'HTTP_' }.each do |key, value|
|
||||
@http_header[key.sub(/^HTTP_/, '')] = value.force_encoding('UTF-8')
|
||||
end
|
||||
|
||||
#
|
||||
# @note Initial setup function, creates the command module and saves details to datastore
|
||||
#
|
||||
def setup
|
||||
@http_params = @data['request'].params
|
||||
@http_header = {}
|
||||
http_header = @data['request'].env.select { |k, v| k.to_s.start_with? 'HTTP_' }.each { |key, value|
|
||||
@http_header[key.sub(/^HTTP_/, '')] = value.force_encoding('UTF-8')
|
||||
}
|
||||
# @note get and check command id from the request
|
||||
command_id = get_param(@data, 'cid')
|
||||
unless command_id.integer?
|
||||
print_error 'command_id is invalid'
|
||||
return
|
||||
end
|
||||
|
||||
# @note get and check command id from the request
|
||||
command_id = get_param(@data, 'cid')
|
||||
unless command_id.integer?
|
||||
print_error "command_id is invalid"
|
||||
return
|
||||
# @note get and check session id from the request
|
||||
beefhook = get_param(@data, 'beefhook')
|
||||
unless BeEF::Filters.is_valid_hook_session_id?(beefhook)
|
||||
print_error 'BeEF hook is invalid'
|
||||
return
|
||||
end
|
||||
|
||||
result = get_param(@data, 'results')
|
||||
|
||||
# @note create the command module to handle the response
|
||||
command = @kclass.new(BeEF::Module.get_key_by_class(@kclass))
|
||||
command.build_callback_datastore(result, command_id, beefhook, @http_params, @http_header)
|
||||
command.session_id = beefhook
|
||||
command.post_execute if command.respond_to?(:post_execute)
|
||||
|
||||
# @todo this is the part that store result on db and the modify
|
||||
# will be accessible from all the framework and so UI too
|
||||
# @note get/set details for datastore and log entry
|
||||
command_friendly_name = command.friendlyname
|
||||
if command_friendly_name.empty?
|
||||
print_error 'command friendly name is empty'
|
||||
return
|
||||
end
|
||||
|
||||
command_status = @data['status']
|
||||
unless command_status.integer?
|
||||
print_error 'command status is invalid'
|
||||
return
|
||||
end
|
||||
|
||||
command_results = @data['results']
|
||||
if command_results.empty?
|
||||
print_error 'command results are empty'
|
||||
return
|
||||
end
|
||||
|
||||
# @note save the command module results to the datastore and create a log entry
|
||||
command_results = { 'data' => command_results }
|
||||
BeEF::Core::Models::Command.save_result(
|
||||
beefhook,
|
||||
command_id,
|
||||
command_friendly_name,
|
||||
command_results,
|
||||
command_status
|
||||
)
|
||||
end
|
||||
|
||||
#
|
||||
# @note Returns parameter from hash
|
||||
#
|
||||
# @param [Hash] query Hash of data to return data from
|
||||
# @param [String] key Key to search for and return inside `query`
|
||||
#
|
||||
# @return Value referenced in hash at the supplied key
|
||||
#
|
||||
def get_param(query, key)
|
||||
return unless query.instance_of?(Hash)
|
||||
return unless query.key?(key)
|
||||
|
||||
query[key]
|
||||
end
|
||||
end
|
||||
|
||||
# @note get and check session id from the request
|
||||
beefhook = get_param(@data, 'beefhook')
|
||||
unless BeEF::Filters.is_valid_hook_session_id?(beefhook)
|
||||
print_error "BeEF hook is invalid"
|
||||
return
|
||||
end
|
||||
|
||||
result = get_param(@data, 'results')
|
||||
|
||||
# @note create the command module to handle the response
|
||||
command = @kclass.new(BeEF::Module.get_key_by_class(@kclass))
|
||||
command.build_callback_datastore(result, command_id, beefhook, @http_params, @http_header)
|
||||
command.session_id = beefhook
|
||||
command.post_execute if command.respond_to?(:post_execute)
|
||||
|
||||
# @todo this is the part that store result on db and the modify
|
||||
# will be accessible from all the framework and so UI too
|
||||
# @note get/set details for datastore and log entry
|
||||
command_friendly_name = command.friendlyname
|
||||
if command_friendly_name.empty?
|
||||
print_error 'command friendly name is empty'
|
||||
return
|
||||
end
|
||||
|
||||
command_status = @data['status']
|
||||
unless command_status.integer?
|
||||
print_error 'command status is invalid'
|
||||
return
|
||||
end
|
||||
|
||||
command_results = @data['results']
|
||||
if command_results.empty?
|
||||
print_error 'command results are empty'
|
||||
return
|
||||
end
|
||||
|
||||
# @note save the command module results to the datastore and create a log entry
|
||||
command_results = { 'data' => command_results }
|
||||
BeEF::Core::Models::Command.save_result(
|
||||
beefhook,
|
||||
command_id,
|
||||
command_friendly_name,
|
||||
command_results,
|
||||
command_status
|
||||
)
|
||||
end
|
||||
|
||||
#
|
||||
# @note Returns parameter from hash
|
||||
#
|
||||
# @param [Hash] query Hash of data to return data from
|
||||
# @param [String] key Key to search for and return inside `query`
|
||||
#
|
||||
# @return Value referenced in hash at the supplied key
|
||||
#
|
||||
def get_param(query, key)
|
||||
return unless query.class == Hash
|
||||
return unless query.key?(key)
|
||||
query[key]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,137 +4,135 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Core
|
||||
module Handlers
|
||||
module Core
|
||||
module Handlers
|
||||
# @note This class handles connections from hooked browsers to the framework.
|
||||
class HookedBrowsers < BeEF::Core::Router::Router
|
||||
include BeEF::Core::Handlers::Modules::BeEFJS
|
||||
include BeEF::Core::Handlers::Modules::LegacyBeEFJS
|
||||
include BeEF::Core::Handlers::Modules::Command
|
||||
|
||||
# @note This class handles connections from hooked browsers to the framework.
|
||||
class HookedBrowsers < BeEF::Core::Router::Router
|
||||
|
||||
|
||||
include BeEF::Core::Handlers::Modules::BeEFJS
|
||||
include BeEF::Core::Handlers::Modules::LegacyBeEFJS
|
||||
include BeEF::Core::Handlers::Modules::Command
|
||||
|
||||
#antisnatchor: we don't want to have anti-xss/anti-framing headers in the HTTP response for the hook file.
|
||||
configure do
|
||||
disable :protection
|
||||
end
|
||||
|
||||
# Process HTTP requests sent by a hooked browser to the framework.
|
||||
# It will update the database to add or update the current hooked browser
|
||||
# and deploy some command modules or extensions to the hooked browser.
|
||||
get '/' do
|
||||
@body = ''
|
||||
params = request.query_string
|
||||
#@response = Rack::Response.new(body=[], 200, header={})
|
||||
config = BeEF::Core::Configuration.instance
|
||||
|
||||
# @note check source ip address of browser
|
||||
permitted_hooking_subnet = config.get('beef.restrictions.permitted_hooking_subnet')
|
||||
if permitted_hooking_subnet.nil? || permitted_hooking_subnet.empty?
|
||||
BeEF::Core::Logger.instance.register('Target Range', "Attempted hook from outside of permitted hooking subnet (#{request.ip}) rejected.")
|
||||
error 404
|
||||
end
|
||||
|
||||
found = false
|
||||
permitted_hooking_subnet.each do |subnet|
|
||||
found = true if IPAddr.new(subnet).include?(request.ip)
|
||||
end
|
||||
|
||||
unless found
|
||||
BeEF::Core::Logger.instance.register('Target Range', "Attempted hook from outside of permitted hooking subnet (#{request.ip}) rejected.")
|
||||
error 404
|
||||
end
|
||||
|
||||
excluded_hooking_subnet = config.get('beef.restrictions.excluded_hooking_subnet')
|
||||
unless excluded_hooking_subnet.nil? || excluded_hooking_subnet.empty?
|
||||
excluded_ip_hooked = false
|
||||
|
||||
excluded_hooking_subnet.each do |subnet|
|
||||
excluded_ip_hooked = true if IPAddr.new(subnet).include?(request.ip)
|
||||
# antisnatchor: we don't want to have anti-xss/anti-framing headers in the HTTP response for the hook file.
|
||||
configure do
|
||||
disable :protection
|
||||
end
|
||||
|
||||
if excluded_ip_hooked
|
||||
BeEF::Core::Logger.instance.register('Target Range', "Attempted hook from excluded hooking subnet (#{request.ip}) rejected.")
|
||||
error 404
|
||||
end
|
||||
end
|
||||
# Process HTTP requests sent by a hooked browser to the framework.
|
||||
# It will update the database to add or update the current hooked browser
|
||||
# and deploy some command modules or extensions to the hooked browser.
|
||||
get '/' do
|
||||
@body = ''
|
||||
params = request.query_string
|
||||
# @response = Rack::Response.new(body=[], 200, header={})
|
||||
config = BeEF::Core::Configuration.instance
|
||||
|
||||
# @note get zombie if already hooked the framework
|
||||
hook_session_name = config.get('beef.http.hook_session_name')
|
||||
hook_session_id = request[hook_session_name]
|
||||
begin
|
||||
raise ActiveRecord::RecordNotFound if hook_session_id.nil?
|
||||
hooked_browser = BeEF::Core::Models::HookedBrowser.where(:session => hook_session_id).first
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
hooked_browser = false
|
||||
end
|
||||
|
||||
# @note is a new browser so return instructions to set up the hook
|
||||
if not hooked_browser
|
||||
|
||||
# @note generate the instructions to hook the browser
|
||||
host_name = request.host
|
||||
(print_error "Invalid host name";return) if not BeEF::Filters.is_valid_hostname?(host_name)
|
||||
|
||||
# Generate the hook js provided to the hookwed browser (the magic happens here)
|
||||
if BeEF::Core::Configuration.instance.get("beef.http.websocket.enable")
|
||||
build_beefjs!(host_name)
|
||||
else
|
||||
legacy_build_beefjs!(host_name)
|
||||
end
|
||||
# @note is a known browser so send instructions
|
||||
else
|
||||
# @note Check if we haven't seen this browser for a while, log an event if we haven't
|
||||
if (Time.new.to_i - hooked_browser.lastseen.to_i) > 60
|
||||
BeEF::Core::Logger.instance.register('Zombie',"#{hooked_browser.ip} appears to have come back online","#{hooked_browser.id}")
|
||||
end
|
||||
|
||||
# @note record the last poll from the browser
|
||||
hooked_browser.lastseen = Time.new.to_i
|
||||
|
||||
# @note Check for a change in zombie IP and log an event
|
||||
if config.get('beef.http.use_x_forward_for') == true
|
||||
if hooked_browser.ip != request.env["HTTP_X_FORWARDED_FOR"]
|
||||
BeEF::Core::Logger.instance.register('Zombie',"IP address has changed from #{hooked_browser.ip} to #{request.env["HTTP_X_FORWARDED_FOR"]}","#{hooked_browser.id}")
|
||||
hooked_browser.ip = request.env["HTTP_X_FORWARDED_FOR"]
|
||||
# @note check source ip address of browser
|
||||
permitted_hooking_subnet = config.get('beef.restrictions.permitted_hooking_subnet')
|
||||
if permitted_hooking_subnet.nil? || permitted_hooking_subnet.empty?
|
||||
BeEF::Core::Logger.instance.register('Target Range', "Attempted hook from outside of permitted hooking subnet (#{request.ip}) rejected.")
|
||||
error 404
|
||||
end
|
||||
else
|
||||
if hooked_browser.ip != request.ip
|
||||
BeEF::Core::Logger.instance.register('Zombie',"IP address has changed from #{hooked_browser.ip} to #{request.ip}","#{hooked_browser.id}")
|
||||
hooked_browser.ip = request.ip
|
||||
|
||||
found = false
|
||||
permitted_hooking_subnet.each do |subnet|
|
||||
found = true if IPAddr.new(subnet).include?(request.ip)
|
||||
end
|
||||
|
||||
unless found
|
||||
BeEF::Core::Logger.instance.register('Target Range', "Attempted hook from outside of permitted hooking subnet (#{request.ip}) rejected.")
|
||||
error 404
|
||||
end
|
||||
|
||||
excluded_hooking_subnet = config.get('beef.restrictions.excluded_hooking_subnet')
|
||||
unless excluded_hooking_subnet.nil? || excluded_hooking_subnet.empty?
|
||||
excluded_ip_hooked = false
|
||||
|
||||
excluded_hooking_subnet.each do |subnet|
|
||||
excluded_ip_hooked = true if IPAddr.new(subnet).include?(request.ip)
|
||||
end
|
||||
|
||||
if excluded_ip_hooked
|
||||
BeEF::Core::Logger.instance.register('Target Range', "Attempted hook from excluded hooking subnet (#{request.ip}) rejected.")
|
||||
error 404
|
||||
end
|
||||
end
|
||||
|
||||
# @note get zombie if already hooked the framework
|
||||
hook_session_name = config.get('beef.http.hook_session_name')
|
||||
hook_session_id = request[hook_session_name]
|
||||
begin
|
||||
raise ActiveRecord::RecordNotFound if hook_session_id.nil?
|
||||
|
||||
hooked_browser = BeEF::Core::Models::HookedBrowser.where(session: hook_session_id).first
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
hooked_browser = false
|
||||
end
|
||||
|
||||
# @note is a new browser so return instructions to set up the hook
|
||||
if hooked_browser
|
||||
# @note Check if we haven't seen this browser for a while, log an event if we haven't
|
||||
if (Time.new.to_i - hooked_browser.lastseen.to_i) > 60
|
||||
BeEF::Core::Logger.instance.register('Zombie', "#{hooked_browser.ip} appears to have come back online", hooked_browser.id.to_s)
|
||||
end
|
||||
|
||||
# @note record the last poll from the browser
|
||||
hooked_browser.lastseen = Time.new.to_i
|
||||
|
||||
# @note Check for a change in zombie IP and log an event
|
||||
if config.get('beef.http.use_x_forward_for') == true
|
||||
if hooked_browser.ip != request.env['HTTP_X_FORWARDED_FOR']
|
||||
BeEF::Core::Logger.instance.register('Zombie', "IP address has changed from #{hooked_browser.ip} to #{request.env['HTTP_X_FORWARDED_FOR']}", hooked_browser.id.to_s)
|
||||
hooked_browser.ip = request.env['HTTP_X_FORWARDED_FOR']
|
||||
end
|
||||
elsif hooked_browser.ip != request.ip
|
||||
BeEF::Core::Logger.instance.register('Zombie', "IP address has changed from #{hooked_browser.ip} to #{request.ip}", hooked_browser.id.to_s)
|
||||
hooked_browser.ip = request.ip
|
||||
end
|
||||
|
||||
hooked_browser.count!
|
||||
hooked_browser.save!
|
||||
|
||||
# @note add all available command module instructions to the response
|
||||
zombie_commands = BeEF::Core::Models::Command.where(hooked_browser_id: hooked_browser.id, instructions_sent: false)
|
||||
zombie_commands.each { |command| add_command_instructions(command, hooked_browser) }
|
||||
|
||||
# @note Check if there are any ARE rules to be triggered. If is_sent=false rules are triggered
|
||||
are_executions = BeEF::Core::Models::Execution.where(is_sent: false, session_id: hook_session_id)
|
||||
are_executions.each do |are_exec|
|
||||
@body += are_exec.mod_body
|
||||
are_exec.update(is_sent: true, exec_time: Time.new.to_i)
|
||||
end
|
||||
|
||||
# @note We dynamically get the list of all browser hook handler using the API and register them
|
||||
BeEF::API::Registrar.instance.fire(BeEF::API::Server::Hook, 'pre_hook_send', hooked_browser, @body, params, request, response)
|
||||
else
|
||||
|
||||
# @note generate the instructions to hook the browser
|
||||
host_name = request.host
|
||||
unless BeEF::Filters.is_valid_hostname?(host_name)
|
||||
(print_error 'Invalid host name'
|
||||
return)
|
||||
end
|
||||
|
||||
# Generate the hook js provided to the hookwed browser (the magic happens here)
|
||||
if BeEF::Core::Configuration.instance.get('beef.http.websocket.enable')
|
||||
build_beefjs!(host_name)
|
||||
else
|
||||
legacy_build_beefjs!(host_name)
|
||||
end
|
||||
# @note is a known browser so send instructions
|
||||
end
|
||||
|
||||
# @note set response headers and body
|
||||
headers 'Pragma' => 'no-cache',
|
||||
'Cache-Control' => 'no-cache',
|
||||
'Expires' => '0',
|
||||
'Content-Type' => 'text/javascript',
|
||||
'Access-Control-Allow-Origin' => '*',
|
||||
'Access-Control-Allow-Methods' => 'POST, GET'
|
||||
@body
|
||||
end
|
||||
|
||||
hooked_browser.count!
|
||||
hooked_browser.save!
|
||||
|
||||
# @note add all available command module instructions to the response
|
||||
zombie_commands = BeEF::Core::Models::Command.where(:hooked_browser_id => hooked_browser.id, :instructions_sent => false)
|
||||
zombie_commands.each{|command| add_command_instructions(command, hooked_browser)}
|
||||
|
||||
# @note Check if there are any ARE rules to be triggered. If is_sent=false rules are triggered
|
||||
are_executions = BeEF::Core::Models::Execution.where(:is_sent => false, :session_id => hook_session_id)
|
||||
are_executions.each do |are_exec|
|
||||
@body += are_exec.mod_body
|
||||
are_exec.update(:is_sent => true, :exec_time => Time.new.to_i)
|
||||
end
|
||||
|
||||
# @note We dynamically get the list of all browser hook handler using the API and register them
|
||||
BeEF::API::Registrar.instance.fire(BeEF::API::Server::Hook, 'pre_hook_send', hooked_browser, @body, params, request, response)
|
||||
end
|
||||
|
||||
# @note set response headers and body
|
||||
headers 'Pragma' => 'no-cache',
|
||||
'Cache-Control' => 'no-cache',
|
||||
'Expires' => '0',
|
||||
'Content-Type' => 'text/javascript',
|
||||
'Access-Control-Allow-Origin' => '*',
|
||||
'Access-Control-Allow-Methods' => 'POST, GET'
|
||||
@body
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,10 +7,8 @@ module BeEF
|
||||
module Core
|
||||
module Handlers
|
||||
module Modules
|
||||
|
||||
# @note Purpose: avoid rewriting several times the same code.
|
||||
module BeEFJS
|
||||
|
||||
# Builds the default beefjs library (all default components of the library).
|
||||
# @param [Object] req_host The request object
|
||||
def build_beefjs!(req_host)
|
||||
@@ -21,31 +19,30 @@ module BeEF
|
||||
beef_js_path = "#{$root_dir}/core/main/client/"
|
||||
|
||||
# @note External libraries (like jQuery) that are not evaluated with Eruby and possibly not obfuscated
|
||||
ext_js_sub_files = %w(lib/jquery-1.12.4.min.js lib/jquery-migrate-1.4.1.js lib/evercookie.js lib/json2.js lib/mdetect.js lib/platform.js lib/jquery.blockUI.js)
|
||||
ext_js_sub_files = %w[lib/jquery-1.12.4.min.js lib/jquery-migrate-1.4.1.js lib/evercookie.js lib/json2.js lib/mdetect.js lib/platform.js lib/jquery.blockUI.js]
|
||||
|
||||
# @note BeEF libraries: need Eruby evaluation and obfuscation
|
||||
beef_js_sub_files = %w(beef.js browser.js browser/cookie.js browser/popup.js session.js os.js hardware.js dom.js logger.js net.js updater.js encode/base64.js encode/json.js net/local.js init.js mitb.js geolocation.js net/dns.js net/connection.js net/cors.js net/requester.js net/xssrays.js net/portscanner.js are.js)
|
||||
beef_js_sub_files = %w[beef.js browser.js browser/cookie.js browser/popup.js session.js os.js hardware.js dom.js logger.js net.js updater.js encode/base64.js
|
||||
encode/json.js net/local.js init.js mitb.js geolocation.js net/dns.js net/connection.js net/cors.js net/requester.js net/xssrays.js net/portscanner.js are.js]
|
||||
# @note Load websocket library only if WS server is enabled in config.yaml
|
||||
if config.get("beef.http.websocket.enable") == true
|
||||
beef_js_sub_files << "websocket.js"
|
||||
end
|
||||
beef_js_sub_files << 'websocket.js' if config.get('beef.http.websocket.enable') == true
|
||||
# @note Load webrtc library only if WebRTC extension is enabled
|
||||
if config.get("beef.extension.webrtc.enable") == true
|
||||
beef_js_sub_files << "lib/webrtcadapter.js"
|
||||
beef_js_sub_files << "webrtc.js"
|
||||
if config.get('beef.extension.webrtc.enable') == true
|
||||
beef_js_sub_files << 'lib/webrtcadapter.js'
|
||||
beef_js_sub_files << 'webrtc.js'
|
||||
end
|
||||
|
||||
# @note antisnatchor: leave timeout.js as the last one!
|
||||
beef_js_sub_files << "timeout.js"
|
||||
beef_js_sub_files << 'timeout.js'
|
||||
|
||||
ext_js_to_obfuscate = ''
|
||||
ext_js_to_not_obfuscate = ''
|
||||
|
||||
# @note If Evasion is enabled, the final ext_js string will be ext_js_to_obfuscate + ext_js_to_not_obfuscate
|
||||
# @note If Evasion is disabled, the final ext_js will be just ext_js_to_not_obfuscate
|
||||
ext_js_sub_files.each { |ext_js_sub_file|
|
||||
if config.get("beef.extension.evasion.enable")
|
||||
if config.get("beef.extension.evasion.exclude_core_js").include?(ext_js_sub_file)
|
||||
ext_js_sub_files.each do |ext_js_sub_file|
|
||||
if config.get('beef.extension.evasion.enable')
|
||||
if config.get('beef.extension.evasion.exclude_core_js').include?(ext_js_sub_file)
|
||||
print_debug "Excluding #{ext_js_sub_file} from core files obfuscation list"
|
||||
# do not obfuscate the file
|
||||
ext_js_sub_file_path = beef_js_path + ext_js_sub_file
|
||||
@@ -59,66 +56,58 @@ module BeEF
|
||||
ext_js_sub_file_path = beef_js_path + ext_js_sub_file
|
||||
ext_js_to_not_obfuscate << (File.read(ext_js_sub_file_path) + "\n\n")
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
# @note construct the beef_js string from file(s)
|
||||
beef_js_sub_files.each { |beef_js_sub_file|
|
||||
beef_js_sub_files.each do |beef_js_sub_file|
|
||||
beef_js_sub_file_path = beef_js_path + beef_js_sub_file
|
||||
beef_js << (File.read(beef_js_sub_file_path) + "\n\n")
|
||||
}
|
||||
end
|
||||
|
||||
# @note create the config for the hooked browser session
|
||||
hook_session_config = BeEF::Core::Server.instance.to_h
|
||||
|
||||
# @note if http_host="0.0.0.0" in config ini, use the host requested by client
|
||||
unless hook_session_config['beef_public'].nil?
|
||||
if hook_session_config['beef_host'] != hook_session_config['beef_public']
|
||||
hook_session_config['beef_host'] = hook_session_config['beef_public']
|
||||
hook_session_config['beef_url'].sub!(/#{hook_session_config['beef_host']}/, hook_session_config['beef_public'])
|
||||
end
|
||||
if !hook_session_config['beef_public'].nil? && (hook_session_config['beef_host'] != hook_session_config['beef_public'])
|
||||
hook_session_config['beef_host'] = hook_session_config['beef_public']
|
||||
hook_session_config['beef_url'].sub!(/#{hook_session_config['beef_host']}/, hook_session_config['beef_public'])
|
||||
end
|
||||
if hook_session_config['beef_host'].eql? "0.0.0.0"
|
||||
if hook_session_config['beef_host'].eql? '0.0.0.0'
|
||||
hook_session_config['beef_host'] = req_host
|
||||
hook_session_config['beef_url'].sub!(/0\.0\.0\.0/, req_host)
|
||||
end
|
||||
|
||||
# @note set the XHR-polling timeout
|
||||
hook_session_config['xhr_poll_timeout'] = config.get("beef.http.xhr_poll_timeout")
|
||||
hook_session_config['xhr_poll_timeout'] = config.get('beef.http.xhr_poll_timeout')
|
||||
|
||||
# @note set the hook file path and BeEF's cookie name
|
||||
hook_session_config['hook_file'] = config.get("beef.http.hook_file")
|
||||
hook_session_config['hook_session_name'] = config.get("beef.http.hook_session_name")
|
||||
hook_session_config['hook_file'] = config.get('beef.http.hook_file')
|
||||
hook_session_config['hook_session_name'] = config.get('beef.http.hook_session_name')
|
||||
|
||||
# @note if http_port <> public_port in config ini, use the public_port
|
||||
unless hook_session_config['beef_public_port'].nil?
|
||||
if hook_session_config['beef_port'] != hook_session_config['beef_public_port']
|
||||
hook_session_config['beef_port'] = hook_session_config['beef_public_port']
|
||||
hook_session_config['beef_url'].sub!(/#{hook_session_config['beef_port']}/, hook_session_config['beef_public_port'])
|
||||
if hook_session_config['beef_public_port'] == '443'
|
||||
hook_session_config['beef_url'].sub!(/http:/, 'https:')
|
||||
end
|
||||
end
|
||||
if !hook_session_config['beef_public_port'].nil? && (hook_session_config['beef_port'] != hook_session_config['beef_public_port'])
|
||||
hook_session_config['beef_port'] = hook_session_config['beef_public_port']
|
||||
hook_session_config['beef_url'].sub!(/#{hook_session_config['beef_port']}/, hook_session_config['beef_public_port'])
|
||||
hook_session_config['beef_url'].sub!(/http:/, 'https:') if hook_session_config['beef_public_port'] == '443'
|
||||
end
|
||||
|
||||
# @note Set some WebSocket properties
|
||||
if config.get("beef.http.websocket.enable")
|
||||
hook_session_config['websocket_secure'] = config.get("beef.http.websocket.secure")
|
||||
hook_session_config['websocket_port'] = config.get("beef.http.websocket.port")
|
||||
hook_session_config['ws_poll_timeout'] = config.get("beef.http.websocket.ws_poll_timeout")
|
||||
hook_session_config['ws_connect_timeout'] = config.get("beef.http.websocket.ws_connect_timeout")
|
||||
hook_session_config['websocket_sec_port']= config.get("beef.http.websocket.secure_port")
|
||||
if config.get('beef.http.websocket.enable')
|
||||
hook_session_config['websocket_secure'] = config.get('beef.http.websocket.secure')
|
||||
hook_session_config['websocket_port'] = config.get('beef.http.websocket.port')
|
||||
hook_session_config['ws_poll_timeout'] = config.get('beef.http.websocket.ws_poll_timeout')
|
||||
hook_session_config['ws_connect_timeout'] = config.get('beef.http.websocket.ws_connect_timeout')
|
||||
hook_session_config['websocket_sec_port'] = config.get('beef.http.websocket.secure_port')
|
||||
end
|
||||
|
||||
# @note Set if PhishingFrenzy integration is enabled
|
||||
if config.get("beef.integration.phishing_frenzy.enable")
|
||||
hook_session_config['phishing_frenzy_enable'] = config.get("beef.integration.phishing_frenzy.enable")
|
||||
end
|
||||
hook_session_config['phishing_frenzy_enable'] = config.get('beef.integration.phishing_frenzy.enable') if config.get('beef.integration.phishing_frenzy.enable')
|
||||
|
||||
# @note populate place holders in the beef_js string and set the response body
|
||||
eruby = Erubis::FastEruby.new(beef_js)
|
||||
@hook = eruby.evaluate(hook_session_config)
|
||||
|
||||
if config.get("beef.extension.evasion.enable")
|
||||
if config.get('beef.extension.evasion.enable')
|
||||
evasion = BeEF::Extension::Evasion::Evasion.instance
|
||||
@final_hook = ext_js_to_not_obfuscate + evasion.add_bootstrapper + evasion.obfuscate(ext_js_to_obfuscate + @hook)
|
||||
else
|
||||
@@ -127,7 +116,6 @@ module BeEF
|
||||
|
||||
# @note Return the final hook to be sent to the browser
|
||||
@body << @final_hook
|
||||
|
||||
end
|
||||
|
||||
# Finds the path to js components
|
||||
@@ -139,7 +127,7 @@ module BeEF
|
||||
component_path.gsub!(/\./, '/')
|
||||
component_path.replace "#{$root_dir}/core/main/client/#{component_path}.js"
|
||||
|
||||
return false if not File.exists? component_path
|
||||
return false unless File.exist? component_path
|
||||
|
||||
component_path
|
||||
end
|
||||
@@ -152,11 +140,12 @@ module BeEF
|
||||
|
||||
if beefjs_components.is_a? String
|
||||
beefjs_components_path = find_beefjs_component_path(beefjs_components)
|
||||
raise "Invalid component: could not build the beefjs file" if not beefjs_components_path
|
||||
beefjs_components = {beefjs_components => beefjs_components_path}
|
||||
raise 'Invalid component: could not build the beefjs file' unless beefjs_components_path
|
||||
|
||||
beefjs_components = { beefjs_components => beefjs_components_path }
|
||||
end
|
||||
|
||||
beefjs_components.keys.each { |k|
|
||||
beefjs_components.keys.each do |k|
|
||||
next if @beef_js_cmps.include? beefjs_components[k]
|
||||
|
||||
# @note path to the component
|
||||
@@ -164,11 +153,11 @@ module BeEF
|
||||
|
||||
# @note we output the component to the hooked browser
|
||||
config = BeEF::Core::Configuration.instance
|
||||
if config.get("beef.extension.evasion.enable")
|
||||
if config.get('beef.extension.evasion.enable')
|
||||
evasion = BeEF::Extension::Evasion::Evasion.instance
|
||||
@body << evasion.obfuscate(File.read(component_path) + "\n\n")
|
||||
else
|
||||
@body << File.read(component_path) + "\n\n"
|
||||
@body << (File.read(component_path) + "\n\n")
|
||||
end
|
||||
|
||||
# @note finally we add the component to the list of components already generated so it does not get generated numerous times.
|
||||
@@ -177,7 +166,7 @@ module BeEF
|
||||
else
|
||||
@beef_js_cmps += ",#{component_path}"
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,29 +7,48 @@ module BeEF
|
||||
module Core
|
||||
module Handlers
|
||||
module Modules
|
||||
|
||||
module Command
|
||||
|
||||
# Adds the command module instructions to a hooked browser's http response.
|
||||
# @param [Object] command Command object
|
||||
# @param [Object] hooked_browser Hooked Browser object
|
||||
def add_command_instructions(command, hooked_browser)
|
||||
(print_error "hooked_browser is nil"; return) if hooked_browser.nil?
|
||||
(print_error "hooked_browser.session is nil"; return) if hooked_browser.session.nil?
|
||||
(print_error "hooked_browser is nil"; return) if command.nil?
|
||||
(print_error "hooked_browser.command_module_id is nil"; return) if command.command_module_id.nil?
|
||||
if hooked_browser.nil?
|
||||
(print_error 'hooked_browser is nil'
|
||||
return)
|
||||
end
|
||||
if hooked_browser.session.nil?
|
||||
(print_error 'hooked_browser.session is nil'
|
||||
return)
|
||||
end
|
||||
if command.nil?
|
||||
(print_error 'hooked_browser is nil'
|
||||
return)
|
||||
end
|
||||
if command.command_module_id.nil?
|
||||
(print_error 'hooked_browser.command_module_id is nil'
|
||||
return)
|
||||
end
|
||||
|
||||
config = BeEF::Core::Configuration.instance
|
||||
# @note get the command module
|
||||
command_module = BeEF::Core::Models::CommandModule.where(:id => command.command_module_id).first
|
||||
(print_error "command_module is nil"; return) if command_module.nil?
|
||||
(print_error "command_module.path is nil"; return) if command_module.path.nil?
|
||||
command_module = BeEF::Core::Models::CommandModule.where(id: command.command_module_id).first
|
||||
if command_module.nil?
|
||||
(print_error 'command_module is nil'
|
||||
return)
|
||||
end
|
||||
if command_module.path.nil?
|
||||
(print_error 'command_module.path is nil'
|
||||
return)
|
||||
end
|
||||
|
||||
if (command_module.path.match(/^Dynamic/))
|
||||
if command_module.path.match(/^Dynamic/)
|
||||
command_module = BeEF::Modules::Commands.const_get(command_module.path.split('/').last.capitalize).new
|
||||
else
|
||||
key = BeEF::Module.get_key_by_database_id(command.command_module_id)
|
||||
(print_error "Could not find command module with ID #{command.command_module_id}"; return) if key.nil?
|
||||
if key.nil?
|
||||
(print_error "Could not find command module with ID #{command.command_module_id}"
|
||||
return)
|
||||
end
|
||||
command_module = BeEF::Core::Command.const_get(config.get("beef.module.#{key}.class")).new(key)
|
||||
end
|
||||
|
||||
@@ -42,25 +61,25 @@ module BeEF
|
||||
|
||||
ws = BeEF::Core::Websocket::Websocket.instance
|
||||
|
||||
if config.get("beef.extension.evasion.enable")
|
||||
if config.get('beef.extension.evasion.enable')
|
||||
evasion = BeEF::Extension::Evasion::Evasion.instance
|
||||
@output = evasion.obfuscate(command_module.output)
|
||||
else
|
||||
@output = command_module.output
|
||||
end
|
||||
|
||||
#todo antisnatchor: remove this gsub crap adding some hook packing.
|
||||
if config.get("beef.http.websocket.enable") && ws.getsocket(hooked_browser.session)
|
||||
#content = command_module.output.gsub('//
|
||||
#//
|
||||
#// Copyright (c) 2006-2022 Wade Alcorn - wade@bindshell.net
|
||||
#// Browser Exploitation Framework (BeEF) - http://beefproject.com
|
||||
#// See the file 'doc/COPYING' for copying permission
|
||||
#//
|
||||
#//', "")
|
||||
# TODO: antisnatchor: remove this gsub crap adding some hook packing.
|
||||
if config.get('beef.http.websocket.enable') && ws.getsocket(hooked_browser.session)
|
||||
# content = command_module.output.gsub('//
|
||||
# //
|
||||
# // Copyright (c) 2006-2022 Wade Alcorn - wade@bindshell.net
|
||||
# // Browser Exploitation Framework (BeEF) - http://beefproject.com
|
||||
# // See the file 'doc/COPYING' for copying permission
|
||||
# //
|
||||
# //', "")
|
||||
ws.send(@output, hooked_browser.session)
|
||||
else
|
||||
@body << @output + "\n\n"
|
||||
@body << (@output + "\n\n")
|
||||
end
|
||||
# @note prints the event to the console
|
||||
if BeEF::Settings.console?
|
||||
@@ -72,9 +91,7 @@ module BeEF
|
||||
command.instructions_sent = true
|
||||
command.save!
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,10 +7,8 @@ module BeEF
|
||||
module Core
|
||||
module Handlers
|
||||
module Modules
|
||||
|
||||
# @note Purpose: avoid rewriting several times the same code.
|
||||
module LegacyBeEFJS
|
||||
|
||||
# Builds the default beefjs library (all default components of the library).
|
||||
# @param [Object] req_host The request object
|
||||
def legacy_build_beefjs!(req_host)
|
||||
@@ -21,31 +19,30 @@ module BeEF
|
||||
beef_js_path = "#{$root_dir}/core/main/client/"
|
||||
|
||||
# @note External libraries (like jQuery) that are not evaluated with Eruby and possibly not obfuscated
|
||||
ext_js_sub_files = %w(lib/jquery-1.12.4.min.js lib/jquery-migrate-1.4.1.js lib/evercookie.js lib/json2.js lib/mdetect.js lib/platform.js lib/jquery.blockUI.js)
|
||||
ext_js_sub_files = %w[lib/jquery-1.12.4.min.js lib/jquery-migrate-1.4.1.js lib/evercookie.js lib/json2.js lib/mdetect.js lib/platform.js lib/jquery.blockUI.js]
|
||||
|
||||
# @note BeEF libraries: need Eruby evaluation and obfuscation
|
||||
beef_js_sub_files = %w(beef.js browser.js browser/cookie.js browser/popup.js session.js os.js hardware.js dom.js logger.js net.js updater.js encode/base64.js encode/json.js net/local.js init.js mitb.js geolocation.js net/dns.js net/connection.js net/cors.js net/requester.js net/xssrays.js net/portscanner.js are.js)
|
||||
beef_js_sub_files = %w[beef.js browser.js browser/cookie.js browser/popup.js session.js os.js hardware.js dom.js logger.js net.js updater.js encode/base64.js
|
||||
encode/json.js net/local.js init.js mitb.js geolocation.js net/dns.js net/connection.js net/cors.js net/requester.js net/xssrays.js net/portscanner.js are.js]
|
||||
# @note Load websocket library only if WS server is enabled in config.yaml
|
||||
if config.get("beef.http.websocket.enable") == true
|
||||
beef_js_sub_files << "websocket.js"
|
||||
end
|
||||
beef_js_sub_files << 'websocket.js' if config.get('beef.http.websocket.enable') == true
|
||||
# @note Load webrtc library only if WebRTC extension is enabled
|
||||
if config.get("beef.extension.webrtc.enable") == true
|
||||
beef_js_sub_files << "lib/webrtcadapter.js"
|
||||
beef_js_sub_files << "webrtc.js"
|
||||
if config.get('beef.extension.webrtc.enable') == true
|
||||
beef_js_sub_files << 'lib/webrtcadapter.js'
|
||||
beef_js_sub_files << 'webrtc.js'
|
||||
end
|
||||
|
||||
# @note antisnatchor: leave timeout.js as the last one!
|
||||
beef_js_sub_files << "timeout.js"
|
||||
beef_js_sub_files << 'timeout.js'
|
||||
|
||||
ext_js_to_obfuscate = ''
|
||||
ext_js_to_not_obfuscate = ''
|
||||
|
||||
# @note If Evasion is enabled, the final ext_js string will be ext_js_to_obfuscate + ext_js_to_not_obfuscate
|
||||
# @note If Evasion is disabled, the final ext_js will be just ext_js_to_not_obfuscate
|
||||
ext_js_sub_files.each { |ext_js_sub_file|
|
||||
if config.get("beef.extension.evasion.enable")
|
||||
if config.get("beef.extension.evasion.exclude_core_js").include?(ext_js_sub_file)
|
||||
ext_js_sub_files.each do |ext_js_sub_file|
|
||||
if config.get('beef.extension.evasion.enable')
|
||||
if config.get('beef.extension.evasion.exclude_core_js').include?(ext_js_sub_file)
|
||||
print_debug "Excluding #{ext_js_sub_file} from core files obfuscation list"
|
||||
# do not obfuscate the file
|
||||
ext_js_sub_file_path = beef_js_path + ext_js_sub_file
|
||||
@@ -59,66 +56,58 @@ module BeEF
|
||||
ext_js_sub_file_path = beef_js_path + ext_js_sub_file
|
||||
ext_js_to_not_obfuscate << (File.read(ext_js_sub_file_path) + "\n\n")
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
# @note construct the beef_js string from file(s)
|
||||
beef_js_sub_files.each { |beef_js_sub_file|
|
||||
beef_js_sub_files.each do |beef_js_sub_file|
|
||||
beef_js_sub_file_path = beef_js_path + beef_js_sub_file
|
||||
beef_js << (File.read(beef_js_sub_file_path) + "\n\n")
|
||||
}
|
||||
end
|
||||
|
||||
# @note create the config for the hooked browser session
|
||||
hook_session_config = BeEF::Core::Server.instance.to_h
|
||||
|
||||
# @note if http_host="0.0.0.0" in config ini, use the host requested by client
|
||||
unless hook_session_config['beef_public'].nil?
|
||||
if hook_session_config['beef_host'] != hook_session_config['beef_public']
|
||||
hook_session_config['beef_host'] = hook_session_config['beef_public']
|
||||
hook_session_config['beef_url'].sub!(/#{hook_session_config['beef_host']}/, hook_session_config['beef_public'])
|
||||
end
|
||||
if !hook_session_config['beef_public'].nil? && (hook_session_config['beef_host'] != hook_session_config['beef_public'])
|
||||
hook_session_config['beef_host'] = hook_session_config['beef_public']
|
||||
hook_session_config['beef_url'].sub!(/#{hook_session_config['beef_host']}/, hook_session_config['beef_public'])
|
||||
end
|
||||
if hook_session_config['beef_host'].eql? "0.0.0.0"
|
||||
if hook_session_config['beef_host'].eql? '0.0.0.0'
|
||||
hook_session_config['beef_host'] = req_host
|
||||
hook_session_config['beef_url'].sub!(/0\.0\.0\.0/, req_host)
|
||||
end
|
||||
|
||||
# @note set the XHR-polling timeout
|
||||
hook_session_config['xhr_poll_timeout'] = config.get("beef.http.xhr_poll_timeout")
|
||||
hook_session_config['xhr_poll_timeout'] = config.get('beef.http.xhr_poll_timeout')
|
||||
|
||||
# @note set the hook file path and BeEF's cookie name
|
||||
hook_session_config['hook_file'] = config.get("beef.http.hook_file")
|
||||
hook_session_config['hook_session_name'] = config.get("beef.http.hook_session_name")
|
||||
hook_session_config['hook_file'] = config.get('beef.http.hook_file')
|
||||
hook_session_config['hook_session_name'] = config.get('beef.http.hook_session_name')
|
||||
|
||||
# @note if http_port <> public_port in config ini, use the public_port
|
||||
unless hook_session_config['beef_public_port'].nil?
|
||||
if hook_session_config['beef_port'] != hook_session_config['beef_public_port']
|
||||
hook_session_config['beef_port'] = hook_session_config['beef_public_port']
|
||||
hook_session_config['beef_url'].sub!(/#{hook_session_config['beef_port']}/, hook_session_config['beef_public_port'])
|
||||
if hook_session_config['beef_public_port'] == '443'
|
||||
hook_session_config['beef_url'].sub!(/http:/, 'https:')
|
||||
end
|
||||
end
|
||||
if !hook_session_config['beef_public_port'].nil? && (hook_session_config['beef_port'] != hook_session_config['beef_public_port'])
|
||||
hook_session_config['beef_port'] = hook_session_config['beef_public_port']
|
||||
hook_session_config['beef_url'].sub!(/#{hook_session_config['beef_port']}/, hook_session_config['beef_public_port'])
|
||||
hook_session_config['beef_url'].sub!(/http:/, 'https:') if hook_session_config['beef_public_port'] == '443'
|
||||
end
|
||||
|
||||
# @note Set some WebSocket properties
|
||||
if config.get("beef.http.websocket.enable")
|
||||
hook_session_config['websocket_secure'] = config.get("beef.http.websocket.secure")
|
||||
hook_session_config['websocket_port'] = config.get("beef.http.websocket.port")
|
||||
hook_session_config['ws_poll_timeout'] = config.get("beef.http.websocket.ws_poll_timeout")
|
||||
hook_session_config['ws_connect_timeout'] = config.get("beef.http.websocket.ws_connect_timeout")
|
||||
hook_session_config['websocket_sec_port']= config.get("beef.http.websocket.secure_port")
|
||||
if config.get('beef.http.websocket.enable')
|
||||
hook_session_config['websocket_secure'] = config.get('beef.http.websocket.secure')
|
||||
hook_session_config['websocket_port'] = config.get('beef.http.websocket.port')
|
||||
hook_session_config['ws_poll_timeout'] = config.get('beef.http.websocket.ws_poll_timeout')
|
||||
hook_session_config['ws_connect_timeout'] = config.get('beef.http.websocket.ws_connect_timeout')
|
||||
hook_session_config['websocket_sec_port'] = config.get('beef.http.websocket.secure_port')
|
||||
end
|
||||
|
||||
# @note Set if PhishingFrenzy integration is enabled
|
||||
if config.get("beef.integration.phishing_frenzy.enable")
|
||||
hook_session_config['phishing_frenzy_enable'] = config.get("beef.integration.phishing_frenzy.enable")
|
||||
end
|
||||
hook_session_config['phishing_frenzy_enable'] = config.get('beef.integration.phishing_frenzy.enable') if config.get('beef.integration.phishing_frenzy.enable')
|
||||
|
||||
# @note populate place holders in the beef_js string and set the response body
|
||||
eruby = Erubis::FastEruby.new(beef_js)
|
||||
@hook = eruby.evaluate(hook_session_config)
|
||||
|
||||
if config.get("beef.extension.evasion.enable")
|
||||
if config.get('beef.extension.evasion.enable')
|
||||
evasion = BeEF::Extension::Evasion::Evasion.instance
|
||||
@final_hook = ext_js_to_not_obfuscate + evasion.add_bootstrapper + evasion.obfuscate(ext_js_to_obfuscate + @hook)
|
||||
else
|
||||
@@ -127,7 +116,6 @@ module BeEF
|
||||
|
||||
# @note Return the final hook to be sent to the browser
|
||||
@body << @final_hook
|
||||
|
||||
end
|
||||
|
||||
# Finds the path to js components
|
||||
@@ -139,7 +127,7 @@ module BeEF
|
||||
component_path.gsub!(/\./, '/')
|
||||
component_path.replace "#{$root_dir}/core/main/client/#{component_path}.js"
|
||||
|
||||
return false if not File.exists? component_path
|
||||
return false unless File.exist? component_path
|
||||
|
||||
component_path
|
||||
end
|
||||
@@ -152,11 +140,12 @@ module BeEF
|
||||
|
||||
if beefjs_components.is_a? String
|
||||
beefjs_components_path = find_beefjs_component_path(beefjs_components)
|
||||
raise "Invalid component: could not build the beefjs file" if not beefjs_components_path
|
||||
beefjs_components = {beefjs_components => beefjs_components_path}
|
||||
raise 'Invalid component: could not build the beefjs file' unless beefjs_components_path
|
||||
|
||||
beefjs_components = { beefjs_components => beefjs_components_path }
|
||||
end
|
||||
|
||||
beefjs_components.keys.each { |k|
|
||||
beefjs_components.keys.each do |k|
|
||||
next if @beef_js_cmps.include? beefjs_components[k]
|
||||
|
||||
# @note path to the component
|
||||
@@ -164,11 +153,11 @@ module BeEF
|
||||
|
||||
# @note we output the component to the hooked browser
|
||||
config = BeEF::Core::Configuration.instance
|
||||
if config.get("beef.extension.evasion.enable")
|
||||
if config.get('beef.extension.evasion.enable')
|
||||
evasion = BeEF::Extension::Evasion::Evasion.instance
|
||||
@body << evasion.obfuscate(File.read(component_path) + "\n\n")
|
||||
else
|
||||
@body << File.read(component_path) + "\n\n"
|
||||
@body << (File.read(component_path) + "\n\n")
|
||||
end
|
||||
|
||||
# @note finally we add the component to the list of components already generated so it does not get generated numerous times.
|
||||
@@ -177,7 +166,7 @@ module BeEF
|
||||
else
|
||||
@beef_js_cmps += ",#{component_path}"
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,53 +5,50 @@
|
||||
#
|
||||
|
||||
module BeEF
|
||||
module Core
|
||||
class Logger
|
||||
include Singleton
|
||||
|
||||
# Constructor
|
||||
def initialize
|
||||
@logs = BeEF::Core::Models::Log
|
||||
@config = BeEF::Core::Configuration.instance
|
||||
module Core
|
||||
class Logger
|
||||
include Singleton
|
||||
|
||||
# if notifications are enabled create a new instance
|
||||
notifications_enabled = @config.get('beef.extension.notifications.enable')
|
||||
@notifications = BeEF::Extension::Notifications::Notifications unless (notifications_enabled == false or notifications_enabled.nil?)
|
||||
end
|
||||
# Constructor
|
||||
def initialize
|
||||
@logs = BeEF::Core::Models::Log
|
||||
@config = BeEF::Core::Configuration.instance
|
||||
|
||||
#
|
||||
# Registers a new event in the logs
|
||||
# @param [String] from The origin of the event (i.e. Authentication, Hooked Browser)
|
||||
# @param [String] event The event description
|
||||
# @param [Integer] hb The id of the hooked browser affected (default = 0 if no HB)
|
||||
#
|
||||
# @return [Boolean] True if the register was successful
|
||||
#
|
||||
def register(from, event, hb = 0)
|
||||
# type conversion to enforce standards
|
||||
hb = hb.to_i
|
||||
|
||||
# get time now
|
||||
time_now = Time.now
|
||||
|
||||
# arguments type checking
|
||||
raise TypeError, '"from" needs to be a string' unless from.string?
|
||||
raise TypeError, '"event" needs to be a string' unless event.string?
|
||||
raise TypeError, '"Hooked Browser ID" needs to be an integer' unless hb.integer?
|
||||
|
||||
# logging the new event into the database
|
||||
@logs.create(:logtype => from.to_s, :event => event.to_s, :date => time_now, :hooked_browser_id => hb).save!
|
||||
print_debug "Event: #{event}"
|
||||
# if notifications are enabled send the info there too
|
||||
if @notifications
|
||||
@notifications.new(from, event, time_now, hb)
|
||||
# if notifications are enabled create a new instance
|
||||
notifications_enabled = @config.get('beef.extension.notifications.enable')
|
||||
@notifications = BeEF::Extension::Notifications::Notifications unless notifications_enabled == false or notifications_enabled.nil?
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
private
|
||||
@logs
|
||||
#
|
||||
# Registers a new event in the logs
|
||||
# @param [String] from The origin of the event (i.e. Authentication, Hooked Browser)
|
||||
# @param [String] event The event description
|
||||
# @param [Integer] hb The id of the hooked browser affected (default = 0 if no HB)
|
||||
#
|
||||
# @return [Boolean] True if the register was successful
|
||||
#
|
||||
def register(from, event, hb = 0)
|
||||
# type conversion to enforce standards
|
||||
hb = hb.to_i
|
||||
|
||||
# get time now
|
||||
time_now = Time.now
|
||||
|
||||
# arguments type checking
|
||||
raise TypeError, '"from" needs to be a string' unless from.string?
|
||||
raise TypeError, '"event" needs to be a string' unless event.string?
|
||||
raise TypeError, '"Hooked Browser ID" needs to be an integer' unless hb.integer?
|
||||
|
||||
# logging the new event into the database
|
||||
@logs.create(logtype: from.to_s, event: event.to_s, date: time_now, hooked_browser_id: hb).save!
|
||||
print_debug "Event: #{event}"
|
||||
# if notifications are enabled send the info there too
|
||||
@notifications.new(from, event, time_now, hb) if @notifications
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
@logs
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,44 +5,43 @@
|
||||
#
|
||||
|
||||
module BeEF
|
||||
module Core
|
||||
module Core
|
||||
# @note This class migrates and updates values in the database each time you restart BeEF.
|
||||
# So for example, when you want to add a new command module, you stop BeEF,
|
||||
# copy your command module into the framework and then restart BeEF.
|
||||
# That class will take care of installing automatically the new command module in the db.
|
||||
class Migration
|
||||
include Singleton
|
||||
|
||||
# @note This class migrates and updates values in the database each time you restart BeEF.
|
||||
# So for example, when you want to add a new command module, you stop BeEF,
|
||||
# copy your command module into the framework and then restart BeEF.
|
||||
# That class will take care of installing automatically the new command module in the db.
|
||||
class Migration
|
||||
include Singleton
|
||||
|
||||
#
|
||||
# Updates the database.
|
||||
#
|
||||
def update_db!
|
||||
update_commands!
|
||||
end
|
||||
|
||||
#
|
||||
# Checks for new command modules and updates the database.
|
||||
#
|
||||
def update_commands!
|
||||
config = BeEF::Core::Configuration.instance
|
||||
|
||||
db_modules = BeEF::Core::Models::CommandModule.all.pluck(:name)
|
||||
|
||||
config.get('beef.module').each do |k, v|
|
||||
BeEF::Core::Models::CommandModule.new(name: k, path: "#{v['path']}module.rb").save! unless db_modules.include? k
|
||||
#
|
||||
# Updates the database.
|
||||
#
|
||||
def update_db!
|
||||
update_commands!
|
||||
end
|
||||
|
||||
BeEF::Core::Models::CommandModule.all.each do |mod|
|
||||
unless config.get("beef.module.#{mod.name}").nil?
|
||||
config.set "beef.module.#{mod.name}.db.id", mod.id
|
||||
config.set "beef.module.#{mod.name}.db.path", mod.path
|
||||
|
||||
#
|
||||
# Checks for new command modules and updates the database.
|
||||
#
|
||||
def update_commands!
|
||||
config = BeEF::Core::Configuration.instance
|
||||
|
||||
db_modules = BeEF::Core::Models::CommandModule.all.pluck(:name)
|
||||
|
||||
config.get('beef.module').each do |k, v|
|
||||
BeEF::Core::Models::CommandModule.new(name: k, path: "#{v['path']}module.rb").save! unless db_modules.include? k
|
||||
end
|
||||
end
|
||||
|
||||
# Call Migration method
|
||||
BeEF::API::Registrar.instance.fire BeEF::API::Migration, 'migrate_commands'
|
||||
BeEF::Core::Models::CommandModule.all.each do |mod|
|
||||
unless config.get("beef.module.#{mod.name}").nil?
|
||||
config.set "beef.module.#{mod.name}.db.id", mod.id
|
||||
config.set "beef.module.#{mod.name}.db.path", mod.path
|
||||
end
|
||||
end
|
||||
|
||||
# Call Migration method
|
||||
BeEF::API::Registrar.instance.fire BeEF::API::Migration, 'migrate_commands'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4,55 +4,55 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Core
|
||||
module Models
|
||||
#
|
||||
# Table stores the details of browsers.
|
||||
#
|
||||
# For example, the type and version of browser the hooked browsers are using.
|
||||
#
|
||||
class BrowserDetails < BeEF::Core::Model
|
||||
|
||||
#
|
||||
# Returns the requested value from the data store
|
||||
#
|
||||
def self.get(session_id, key)
|
||||
browserdetail = self.where(:session_id => session_id, :detail_key => key).first
|
||||
|
||||
return nil if browserdetail.nil?
|
||||
return nil if browserdetail.detail_value.nil?
|
||||
return browserdetail.detail_value
|
||||
end
|
||||
|
||||
#
|
||||
# Stores or updates an existing key->value pair in the data store
|
||||
#
|
||||
def self.set(session_id, detail_key, detail_value)
|
||||
browserdetails = BeEF::Core::Models::BrowserDetails.where(
|
||||
:session_id => session_id,
|
||||
:detail_key => detail_key ).first
|
||||
if browserdetails.nil?
|
||||
# store the new browser details key/value
|
||||
browserdetails = BeEF::Core::Models::BrowserDetails.new(
|
||||
:session_id => session_id,
|
||||
:detail_key => detail_key,
|
||||
:detail_value => detail_value || '')
|
||||
result = browserdetails.save!
|
||||
else
|
||||
# update the browser details key/value
|
||||
browserdetails.detail_value = detail_value || ''
|
||||
result = browserdetails.save!
|
||||
print_debug "Browser has updated '#{detail_key}' to '#{detail_value}'"
|
||||
end
|
||||
module Core
|
||||
module Models
|
||||
#
|
||||
# Table stores the details of browsers.
|
||||
#
|
||||
# For example, the type and version of browser the hooked browsers are using.
|
||||
#
|
||||
class BrowserDetails < BeEF::Core::Model
|
||||
#
|
||||
# Returns the requested value from the data store
|
||||
#
|
||||
def self.get(session_id, key)
|
||||
browserdetail = where(session_id: session_id, detail_key: key).first
|
||||
|
||||
# if the attempt to save the browser details fails return a bad request
|
||||
if result.nil?
|
||||
print_error "Failed to save browser details: #{detail_key}=#{detail_value}"
|
||||
return nil if browserdetail.nil?
|
||||
return nil if browserdetail.detail_value.nil?
|
||||
|
||||
browserdetail.detail_value
|
||||
end
|
||||
|
||||
#
|
||||
# Stores or updates an existing key->value pair in the data store
|
||||
#
|
||||
def self.set(session_id, detail_key, detail_value)
|
||||
browserdetails = BeEF::Core::Models::BrowserDetails.where(
|
||||
session_id: session_id,
|
||||
detail_key: detail_key
|
||||
).first
|
||||
if browserdetails.nil?
|
||||
# store the new browser details key/value
|
||||
browserdetails = BeEF::Core::Models::BrowserDetails.new(
|
||||
session_id: session_id,
|
||||
detail_key: detail_key,
|
||||
detail_value: detail_value || ''
|
||||
)
|
||||
result = browserdetails.save!
|
||||
else
|
||||
# update the browser details key/value
|
||||
browserdetails.detail_value = detail_value || ''
|
||||
result = browserdetails.save!
|
||||
print_debug "Browser has updated '#{detail_key}' to '#{detail_value}'"
|
||||
end
|
||||
|
||||
# if the attempt to save the browser details fails return a bad request
|
||||
print_error "Failed to save browser details: #{detail_key}=#{detail_value}" if result.nil?
|
||||
|
||||
browserdetails
|
||||
end
|
||||
end
|
||||
|
||||
browserdetails
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4,256 +4,251 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Core
|
||||
module NetworkStack
|
||||
module Handlers
|
||||
|
||||
# @note Class defining BeEF assets
|
||||
class AssetHandler
|
||||
|
||||
# @note call BeEF::Core::NetworkStack::Handlers::AssetHandler.instance
|
||||
include Singleton
|
||||
|
||||
attr_reader :allocations, :root_dir
|
||||
|
||||
# Starts the AssetHandler instance
|
||||
def initialize
|
||||
@allocations = {}
|
||||
@sockets = {}
|
||||
@http_server = BeEF::Core::Server.instance
|
||||
@root_dir = File.expand_path('../../../../', __FILE__)
|
||||
end
|
||||
module Core
|
||||
module NetworkStack
|
||||
module Handlers
|
||||
# @note Class defining BeEF assets
|
||||
class AssetHandler
|
||||
# @note call BeEF::Core::NetworkStack::Handlers::AssetHandler.instance
|
||||
include Singleton
|
||||
|
||||
# Binds a redirector to a mount point
|
||||
# @param [String] target The target for the redirector
|
||||
# @param [String] path An optional URL path to mount the redirector to (can be nil for a random path)
|
||||
# @return [String] URL Path of the redirector
|
||||
# @todo This function, similar to bind(), should accept a hooked browser session to limit the mounted file to a certain session etc.
|
||||
def bind_redirect(target, path=nil)
|
||||
url = build_url(path,nil)
|
||||
@allocations[url] = {'target' => target}
|
||||
@http_server.mount(url,BeEF::Core::NetworkStack::Handlers::Redirector.new(target))
|
||||
@http_server.remap
|
||||
print_info "Redirector to [" + target + "] bound to url [" + url + "]"
|
||||
url
|
||||
rescue => e
|
||||
print_error "Failed to mount #{path} : #{e.message}"
|
||||
print_error e.backtrace
|
||||
end
|
||||
attr_reader :allocations, :root_dir
|
||||
|
||||
# Binds raw HTTP to a mount point
|
||||
# @param [Integer] status HTTP status code to return
|
||||
# @param [String] headers HTTP headers as a JSON string to return
|
||||
# @param [String] body HTTP body to return
|
||||
# @param [String] path URL path to mount the asset to TODO (can be nil for random path)
|
||||
# @todo @param [Integer] count The amount of times the asset can be accessed before being automatically unbinded (-1 = unlimited)
|
||||
def bind_raw(status, header, body, path=nil, count=-1)
|
||||
url = build_url(path,nil)
|
||||
@allocations[url] = {}
|
||||
@http_server.mount(
|
||||
url,
|
||||
BeEF::Core::NetworkStack::Handlers::Raw.new(status, header, body)
|
||||
)
|
||||
@http_server.remap
|
||||
print_info "Raw HTTP bound to url [" + url + "]"
|
||||
url
|
||||
rescue => e
|
||||
print_error "Failed to mount #{path} : #{e.message}"
|
||||
print_error e.backtrace
|
||||
end
|
||||
|
||||
# Binds a file to a mount point
|
||||
# @param [String] file File path to asset
|
||||
# @param [String] path URL path to mount the asset to (can be nil for random path)
|
||||
# @param [String] extension File extension (.x). If == nil content-type is text/plain, otherwise use the right one via MIME::Types.type_for()
|
||||
# @param [Integer] count The amount of times the asset can be accessed before being automatically unbinded (-1 = unlimited)
|
||||
# @return [String] URL Path of mounted asset
|
||||
# @todo This function should accept a hooked browser session to limit the mounted file to a certain session
|
||||
def bind(file, path=nil, extension=nil, count=-1)
|
||||
unless File.exist? "#{root_dir}#{file}"
|
||||
print_error "Failed to mount file #{root_dir}#{file}. File does not exist"
|
||||
return
|
||||
end
|
||||
|
||||
url = build_url(path, extension)
|
||||
@allocations[url] = {'file' => "#{root_dir}#{file}",
|
||||
'path' => path,
|
||||
'extension' => extension,
|
||||
'count' => count}
|
||||
|
||||
resp_body = File.read("#{root_dir}#{file}")
|
||||
|
||||
if extension.nil? || MIME::Types.type_for(extension).empty?
|
||||
content_type = 'text/plain'
|
||||
else
|
||||
content_type = MIME::Types.type_for(extension).first.content_type
|
||||
end
|
||||
|
||||
@http_server.mount(
|
||||
url,
|
||||
BeEF::Core::NetworkStack::Handlers::Raw.new('200', {'Content-Type' => content_type}, resp_body)
|
||||
)
|
||||
|
||||
@http_server.remap
|
||||
print_info "File [#{file}] bound to Url [#{url}] using Content-type [#{content_type}]"
|
||||
|
||||
url
|
||||
rescue => e
|
||||
print_error "Failed to mount file '#{root_dir}#{file}' to #{path} : #{e.message}"
|
||||
print_error e.backtrace
|
||||
end
|
||||
|
||||
# Binds a file to a mount point (cached for 1 year)
|
||||
# @param [String] file File path to asset
|
||||
# @param [String] path URL path to mount the asset to (can be nil for random path)
|
||||
# @param [String] extension File extension (.x). If == nil content-type is text/plain, otherwise use the right one via MIME::Types.type_for()
|
||||
# @param [Integer] count The amount of times the asset can be accessed before being automatically unbinded (-1 = unlimited)
|
||||
# @return [String] URL Path of mounted asset
|
||||
# @todo This function should accept a hooked browser session to limit the mounted file to a certain session
|
||||
def bind_cached(file, path=nil, extension=nil, count=-1)
|
||||
unless File.exist? "#{root_dir}#{file}"
|
||||
print_error "Failed to mount file #{root_dir}#{file}. File does not exist"
|
||||
return
|
||||
end
|
||||
|
||||
url = build_url(path, extension)
|
||||
@allocations[url] = {'file' => "#{root_dir}#{file}",
|
||||
'path' => path,
|
||||
'extension' => extension,
|
||||
'count' => count}
|
||||
|
||||
resp_body = File.read("#{root_dir}#{file}")
|
||||
|
||||
if extension.nil? || MIME::Types.type_for(extension).empty?
|
||||
content_type = 'text/plain'
|
||||
else
|
||||
content_type = MIME::Types.type_for(extension).first.content_type
|
||||
end
|
||||
|
||||
@http_server.mount(
|
||||
url,
|
||||
BeEF::Core::NetworkStack::Handlers::Raw.new(
|
||||
'200', {
|
||||
'Content-Type' => content_type,
|
||||
'Expires' => CGI.rfc1123_date(Time.now+(60*60*24*365)) },
|
||||
resp_body)
|
||||
)
|
||||
|
||||
@http_server.remap
|
||||
print_info "File [#{file}] bound to Url [#{url}] using Content-type [#{content_type}]"
|
||||
|
||||
url
|
||||
rescue => e
|
||||
print_error "Failed to mount file '#{root_dir}#{file}' to #{path} : #{e.message}"
|
||||
print_error e.backtrace
|
||||
end
|
||||
|
||||
# Unbinds a file from a mount point
|
||||
# @param [String] url URL path of asset to be unbinded
|
||||
#TODO: check why is throwing exception
|
||||
def unbind(url)
|
||||
@allocations.delete(url)
|
||||
@http_server.unmount(url)
|
||||
@http_server.remap
|
||||
print_info "Url [#{url}] unmounted"
|
||||
end
|
||||
|
||||
# use it like: bind_socket("irc","0.0.0.0",6667)
|
||||
def bind_socket(name, host, port)
|
||||
unless @sockets[name].nil?
|
||||
print_error "Bind Socket [#{name}] is already listening on [#{host}:#{port}]."
|
||||
return
|
||||
end
|
||||
|
||||
t = Thread.new {
|
||||
server = TCPServer.new(host,port)
|
||||
loop do
|
||||
Thread.start(server.accept) do |client|
|
||||
data = ""
|
||||
recv_length = 1024
|
||||
threshold = 1024 * 512
|
||||
while (tmp = client.recv(recv_length))
|
||||
data += tmp
|
||||
break if tmp.length < recv_length || tmp.length == recv_length
|
||||
# 512 KB max of incoming data
|
||||
break if data > threshold
|
||||
end
|
||||
if data.size > threshold
|
||||
print_error "More than 512 KB of data incoming for Bind Socket [#{name}]. For security purposes client connection is closed, and data not saved."
|
||||
else
|
||||
@sockets[name] = {'thread' => t, 'data' => data}
|
||||
print_info "Bind Socket [#{name}] received [#{data.size}] bytes of data."
|
||||
print_debug "Bind Socket [#{name}] received:\n#{data}"
|
||||
end
|
||||
client.close
|
||||
# Starts the AssetHandler instance
|
||||
def initialize
|
||||
@allocations = {}
|
||||
@sockets = {}
|
||||
@http_server = BeEF::Core::Server.instance
|
||||
@root_dir = File.expand_path('../../..', __dir__)
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
print_info "Bind socket [#{name}] listening on [#{host}:#{port}]."
|
||||
end
|
||||
# Binds a redirector to a mount point
|
||||
# @param [String] target The target for the redirector
|
||||
# @param [String] path An optional URL path to mount the redirector to (can be nil for a random path)
|
||||
# @return [String] URL Path of the redirector
|
||||
# @todo This function, similar to bind(), should accept a hooked browser session to limit the mounted file to a certain session etc.
|
||||
def bind_redirect(target, path = nil)
|
||||
url = build_url(path, nil)
|
||||
@allocations[url] = { 'target' => target }
|
||||
@http_server.mount(url, BeEF::Core::NetworkStack::Handlers::Redirector.new(target))
|
||||
@http_server.remap
|
||||
print_info 'Redirector to [' + target + '] bound to url [' + url + ']'
|
||||
url
|
||||
rescue StandardError => e
|
||||
print_error "Failed to mount #{path} : #{e.message}"
|
||||
print_error e.backtrace
|
||||
end
|
||||
|
||||
def get_socket_data(name)
|
||||
if @sockets[name].nil?
|
||||
print_error "Bind Socket [#{name}] does not exists."
|
||||
return
|
||||
end
|
||||
@sockets[name]['data']
|
||||
end
|
||||
# Binds raw HTTP to a mount point
|
||||
# @param [Integer] status HTTP status code to return
|
||||
# @param [String] headers HTTP headers as a JSON string to return
|
||||
# @param [String] body HTTP body to return
|
||||
# @param [String] path URL path to mount the asset to TODO (can be nil for random path)
|
||||
# @todo @param [Integer] count The amount of times the asset can be accessed before being automatically unbinded (-1 = unlimited)
|
||||
def bind_raw(status, header, body, path = nil, _count = -1)
|
||||
url = build_url(path, nil)
|
||||
@allocations[url] = {}
|
||||
@http_server.mount(
|
||||
url,
|
||||
BeEF::Core::NetworkStack::Handlers::Raw.new(status, header, body)
|
||||
)
|
||||
@http_server.remap
|
||||
print_info 'Raw HTTP bound to url [' + url + ']'
|
||||
url
|
||||
rescue StandardError => e
|
||||
print_error "Failed to mount #{path} : #{e.message}"
|
||||
print_error e.backtrace
|
||||
end
|
||||
|
||||
def unbind_socket(name)
|
||||
t = @sockets[name]['thread']
|
||||
if t.alive?
|
||||
print_debug "Thread to be killed: #{t}"
|
||||
Thread.kill(t)
|
||||
print_info "Bind Socket [#{name}] killed."
|
||||
else
|
||||
print_info "Bind Socket [#{name}] ALREADY killed."
|
||||
end
|
||||
end
|
||||
|
||||
# Builds a URL based on the path and extension, if neither are passed a random URL will be generated
|
||||
# @param [String] path URL Path defined by bind()
|
||||
# @param [String] extension Extension defined by bind()
|
||||
# @param [Integer] length The amount of characters to be used when generating a random URL
|
||||
# @return [String] Generated URL
|
||||
def build_url(path, extension, length=20)
|
||||
url = (path == nil) ? '/'+rand(36**length).to_s(36) : path
|
||||
url += (extension == nil) ? '' : '.'+extension
|
||||
url
|
||||
end
|
||||
|
||||
# Checks if the file is allocated, if the file isn't return true to pass onto FileHandler.
|
||||
# @param [String] url URL Path of mounted file
|
||||
# @return [Boolean] Returns true if the file is mounted
|
||||
def check(url)
|
||||
return false unless @allocations.has_key?(url)
|
||||
|
||||
count = @allocations[url]['count']
|
||||
|
||||
if count == -1
|
||||
return true
|
||||
end
|
||||
|
||||
if count > 0
|
||||
if (count - 1) == 0
|
||||
unbind(url)
|
||||
else
|
||||
@allocations[url]['count'] = count - 1
|
||||
# Binds a file to a mount point
|
||||
# @param [String] file File path to asset
|
||||
# @param [String] path URL path to mount the asset to (can be nil for random path)
|
||||
# @param [String] extension File extension (.x). If == nil content-type is text/plain, otherwise use the right one via MIME::Types.type_for()
|
||||
# @param [Integer] count The amount of times the asset can be accessed before being automatically unbinded (-1 = unlimited)
|
||||
# @return [String] URL Path of mounted asset
|
||||
# @todo This function should accept a hooked browser session to limit the mounted file to a certain session
|
||||
def bind(file, path = nil, extension = nil, count = -1)
|
||||
unless File.exist? "#{root_dir}#{file}"
|
||||
print_error "Failed to mount file #{root_dir}#{file}. File does not exist"
|
||||
return
|
||||
end
|
||||
return true
|
||||
|
||||
url = build_url(path, extension)
|
||||
@allocations[url] = { 'file' => "#{root_dir}#{file}",
|
||||
'path' => path,
|
||||
'extension' => extension,
|
||||
'count' => count }
|
||||
|
||||
resp_body = File.read("#{root_dir}#{file}")
|
||||
|
||||
content_type = if extension.nil? || MIME::Types.type_for(extension).empty?
|
||||
'text/plain'
|
||||
else
|
||||
MIME::Types.type_for(extension).first.content_type
|
||||
end
|
||||
|
||||
@http_server.mount(
|
||||
url,
|
||||
BeEF::Core::NetworkStack::Handlers::Raw.new('200', { 'Content-Type' => content_type }, resp_body)
|
||||
)
|
||||
|
||||
@http_server.remap
|
||||
print_info "File [#{file}] bound to Url [#{url}] using Content-type [#{content_type}]"
|
||||
|
||||
url
|
||||
rescue StandardError => e
|
||||
print_error "Failed to mount file '#{root_dir}#{file}' to #{path} : #{e.message}"
|
||||
print_error e.backtrace
|
||||
end
|
||||
|
||||
# Binds a file to a mount point (cached for 1 year)
|
||||
# @param [String] file File path to asset
|
||||
# @param [String] path URL path to mount the asset to (can be nil for random path)
|
||||
# @param [String] extension File extension (.x). If == nil content-type is text/plain, otherwise use the right one via MIME::Types.type_for()
|
||||
# @param [Integer] count The amount of times the asset can be accessed before being automatically unbinded (-1 = unlimited)
|
||||
# @return [String] URL Path of mounted asset
|
||||
# @todo This function should accept a hooked browser session to limit the mounted file to a certain session
|
||||
def bind_cached(file, path = nil, extension = nil, count = -1)
|
||||
unless File.exist? "#{root_dir}#{file}"
|
||||
print_error "Failed to mount file #{root_dir}#{file}. File does not exist"
|
||||
return
|
||||
end
|
||||
|
||||
url = build_url(path, extension)
|
||||
@allocations[url] = { 'file' => "#{root_dir}#{file}",
|
||||
'path' => path,
|
||||
'extension' => extension,
|
||||
'count' => count }
|
||||
|
||||
resp_body = File.read("#{root_dir}#{file}")
|
||||
|
||||
content_type = if extension.nil? || MIME::Types.type_for(extension).empty?
|
||||
'text/plain'
|
||||
else
|
||||
MIME::Types.type_for(extension).first.content_type
|
||||
end
|
||||
|
||||
@http_server.mount(
|
||||
url,
|
||||
BeEF::Core::NetworkStack::Handlers::Raw.new(
|
||||
'200', {
|
||||
'Content-Type' => content_type,
|
||||
'Expires' => CGI.rfc1123_date(Time.now + (60 * 60 * 24 * 365))
|
||||
},
|
||||
resp_body
|
||||
)
|
||||
)
|
||||
|
||||
@http_server.remap
|
||||
print_info "File [#{file}] bound to Url [#{url}] using Content-type [#{content_type}]"
|
||||
|
||||
url
|
||||
rescue StandardError => e
|
||||
print_error "Failed to mount file '#{root_dir}#{file}' to #{path} : #{e.message}"
|
||||
print_error e.backtrace
|
||||
end
|
||||
|
||||
# Unbinds a file from a mount point
|
||||
# @param [String] url URL path of asset to be unbinded
|
||||
# TODO: check why is throwing exception
|
||||
def unbind(url)
|
||||
@allocations.delete(url)
|
||||
@http_server.unmount(url)
|
||||
@http_server.remap
|
||||
print_info "Url [#{url}] unmounted"
|
||||
end
|
||||
|
||||
# use it like: bind_socket("irc","0.0.0.0",6667)
|
||||
def bind_socket(name, host, port)
|
||||
unless @sockets[name].nil?
|
||||
print_error "Bind Socket [#{name}] is already listening on [#{host}:#{port}]."
|
||||
return
|
||||
end
|
||||
|
||||
t = Thread.new do
|
||||
server = TCPServer.new(host, port)
|
||||
loop do
|
||||
Thread.start(server.accept) do |client|
|
||||
data = ''
|
||||
recv_length = 1024
|
||||
threshold = 1024 * 512
|
||||
while (tmp = client.recv(recv_length))
|
||||
data += tmp
|
||||
break if tmp.length < recv_length || tmp.length == recv_length
|
||||
# 512 KB max of incoming data
|
||||
break if data > threshold
|
||||
end
|
||||
if data.size > threshold
|
||||
print_error "More than 512 KB of data incoming for Bind Socket [#{name}]. For security purposes client connection is closed, and data not saved."
|
||||
else
|
||||
@sockets[name] = { 'thread' => t, 'data' => data }
|
||||
print_info "Bind Socket [#{name}] received [#{data.size}] bytes of data."
|
||||
print_debug "Bind Socket [#{name}] received:\n#{data}"
|
||||
end
|
||||
client.close
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
print_info "Bind socket [#{name}] listening on [#{host}:#{port}]."
|
||||
end
|
||||
|
||||
def get_socket_data(name)
|
||||
if @sockets[name].nil?
|
||||
print_error "Bind Socket [#{name}] does not exists."
|
||||
return
|
||||
end
|
||||
@sockets[name]['data']
|
||||
end
|
||||
|
||||
def unbind_socket(name)
|
||||
t = @sockets[name]['thread']
|
||||
if t.alive?
|
||||
print_debug "Thread to be killed: #{t}"
|
||||
Thread.kill(t)
|
||||
print_info "Bind Socket [#{name}] killed."
|
||||
else
|
||||
print_info "Bind Socket [#{name}] ALREADY killed."
|
||||
end
|
||||
end
|
||||
|
||||
# Builds a URL based on the path and extension, if neither are passed a random URL will be generated
|
||||
# @param [String] path URL Path defined by bind()
|
||||
# @param [String] extension Extension defined by bind()
|
||||
# @param [Integer] length The amount of characters to be used when generating a random URL
|
||||
# @return [String] Generated URL
|
||||
def build_url(path, extension, length = 20)
|
||||
url = path.nil? ? '/' + rand(36**length).to_s(36) : path
|
||||
url += extension.nil? ? '' : '.' + extension
|
||||
url
|
||||
end
|
||||
|
||||
# Checks if the file is allocated, if the file isn't return true to pass onto FileHandler.
|
||||
# @param [String] url URL Path of mounted file
|
||||
# @return [Boolean] Returns true if the file is mounted
|
||||
def check(url)
|
||||
return false unless @allocations.has_key?(url)
|
||||
|
||||
count = @allocations[url]['count']
|
||||
|
||||
return true if count == -1
|
||||
|
||||
if count > 0
|
||||
if (count - 1) == 0
|
||||
unbind(url)
|
||||
else
|
||||
@allocations[url]['count'] = count - 1
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
@http_server
|
||||
@allocations
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
@http_server
|
||||
@allocations
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,18 +7,16 @@ module BeEF
|
||||
module Core
|
||||
module NetworkStack
|
||||
module Handlers
|
||||
|
||||
# @note DynamicHandler is used reconstruct segmented traffic from the hooked browser
|
||||
class DynamicReconstruction < BeEF::Core::Router::Router
|
||||
|
||||
# @note holds packet queue
|
||||
PQ = Array.new()
|
||||
PQ = []
|
||||
|
||||
# @note obtain dynamic mount points from HttpHookServer
|
||||
MOUNTS = BeEF::Core::Server.instance.mounts
|
||||
|
||||
before do
|
||||
error 404 unless !params.empty?
|
||||
error 404 if params.empty?
|
||||
headers 'Pragma' => 'no-cache',
|
||||
'Cache-Control' => 'no-cache',
|
||||
'Expires' => '0'
|
||||
@@ -34,55 +32,50 @@ module BeEF
|
||||
'Access-Control-Allow-Methods' => 'POST, GET'
|
||||
begin
|
||||
PQ << {
|
||||
:beefhook => params[:bh],
|
||||
:stream_id => Integer(params[:sid]),
|
||||
:packet_id => Integer(params[:pid]),
|
||||
:packet_count => Integer(params[:pc]),
|
||||
:data => params[:d]
|
||||
beefhook: params[:bh],
|
||||
stream_id: Integer(params[:sid]),
|
||||
packet_id: Integer(params[:pid]),
|
||||
packet_count: Integer(params[:pc]),
|
||||
data: params[:d]
|
||||
}
|
||||
rescue TypeError, ArgumentError => e
|
||||
print_error "Hooked browser returned an invalid argument: #{e}"
|
||||
end
|
||||
|
||||
Thread.new {
|
||||
check_packets()
|
||||
}
|
||||
Thread.new do
|
||||
check_packets
|
||||
end
|
||||
end
|
||||
|
||||
# Check packets goes through the PQ array and attempts to reconstruct the stream from multiple packets
|
||||
def check_packets()
|
||||
checked = Array.new()
|
||||
def check_packets
|
||||
checked = []
|
||||
PQ.each do |packet|
|
||||
if (checked.include?(packet[:beefhook]+':'+String(packet[:stream_id])))
|
||||
next
|
||||
end
|
||||
checked << packet[:beefhook]+':'+String(packet[:stream_id])
|
||||
next if checked.include?(packet[:beefhook] + ':' + String(packet[:stream_id]))
|
||||
|
||||
checked << (packet[:beefhook] + ':' + String(packet[:stream_id]))
|
||||
pc = 0
|
||||
PQ.each do |p|
|
||||
if (packet[:beefhook] == p[:beefhook] and packet[:stream_id] == p[:stream_id])
|
||||
pc += 1
|
||||
end
|
||||
pc += 1 if packet[:beefhook] == p[:beefhook] and packet[:stream_id] == p[:stream_id]
|
||||
end
|
||||
if (packet[:packet_count] == pc)
|
||||
packets = expunge(packet[:beefhook], packet[:stream_id])
|
||||
data = ''
|
||||
packets.each_with_index do |sp, i|
|
||||
if (packet[:beefhook] == sp[:beefhook] and packet[:stream_id] == sp[:stream_id])
|
||||
data += sp[:data]
|
||||
end
|
||||
end
|
||||
b64 = Base64.decode64(data)
|
||||
begin
|
||||
res = JSON.parse(b64).first
|
||||
res['beefhook'] = packet[:beefhook]
|
||||
res['request'] = request
|
||||
res['beefsession'] = request[BeEF::Core::Configuration.instance.get('beef.http.hook_session_name')]
|
||||
execute(res)
|
||||
rescue JSON::ParserError => e
|
||||
print_debug 'Network stack could not decode packet stream.'
|
||||
print_debug 'Dumping Stream Data [base64]: '+data
|
||||
print_debug 'Dumping Stream Data: '+b64
|
||||
end
|
||||
next unless packet[:packet_count] == pc
|
||||
|
||||
packets = expunge(packet[:beefhook], packet[:stream_id])
|
||||
data = ''
|
||||
packets.each_with_index do |sp, _i|
|
||||
data += sp[:data] if packet[:beefhook] == sp[:beefhook] and packet[:stream_id] == sp[:stream_id]
|
||||
end
|
||||
b64 = Base64.decode64(data)
|
||||
begin
|
||||
res = JSON.parse(b64).first
|
||||
res['beefhook'] = packet[:beefhook]
|
||||
res['request'] = request
|
||||
res['beefsession'] = request[BeEF::Core::Configuration.instance.get('beef.http.hook_session_name')]
|
||||
execute(res)
|
||||
rescue JSON::ParserError => e
|
||||
print_debug 'Network stack could not decode packet stream.'
|
||||
print_debug 'Dumping Stream Data [base64]: ' + data
|
||||
print_debug 'Dumping Stream Data: ' + b64
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -100,8 +93,8 @@ module BeEF
|
||||
# @param [Hash] data Hash of data that has been rebuilt by the dynamic reconstruction
|
||||
def execute(data)
|
||||
handler = get_param(data, 'handler')
|
||||
if (MOUNTS.has_key?(handler))
|
||||
if (MOUNTS[handler].class == Array and MOUNTS[handler].length == 2)
|
||||
if MOUNTS.has_key?(handler)
|
||||
if MOUNTS[handler].instance_of?(Array) and MOUNTS[handler].length == 2
|
||||
MOUNTS[handler][0].new(data, MOUNTS[handler][1])
|
||||
else
|
||||
MOUNTS[handler].new(data)
|
||||
@@ -115,6 +108,7 @@ module BeEF
|
||||
# @return Value associated with `key`
|
||||
def get_param(query, key)
|
||||
return nil if query[key].nil?
|
||||
|
||||
query[key]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,32 +7,27 @@ module BeEF
|
||||
module Core
|
||||
module NetworkStack
|
||||
module Handlers
|
||||
|
||||
class Raw
|
||||
def initialize(status, header = {}, body = nil)
|
||||
@status = status
|
||||
@header = header
|
||||
@body = body
|
||||
end
|
||||
|
||||
def initialize(status, header={}, body=nil)
|
||||
@status = status
|
||||
@header = header
|
||||
@body = body
|
||||
end
|
||||
def call(_env)
|
||||
# [@status, @header, @body]
|
||||
@response = Rack::Response.new(
|
||||
body = @body,
|
||||
status = @status,
|
||||
header = @header
|
||||
)
|
||||
end
|
||||
|
||||
def call(env)
|
||||
# [@status, @header, @body]
|
||||
@response = Rack::Response.new(
|
||||
body = @body,
|
||||
status = @status,
|
||||
header = @header
|
||||
)
|
||||
end
|
||||
@request
|
||||
|
||||
private
|
||||
|
||||
@request
|
||||
|
||||
@response
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@response
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,36 +7,31 @@ module BeEF
|
||||
module Core
|
||||
module NetworkStack
|
||||
module Handlers
|
||||
|
||||
# @note Redirector is used as a Rack app for mounting HTTP redirectors, instead of content
|
||||
# @todo Add new options to specify what kind of redirect you want to achieve
|
||||
class Redirector
|
||||
@target = ''
|
||||
|
||||
@target = ""
|
||||
def initialize(target)
|
||||
@target = target
|
||||
end
|
||||
|
||||
def initialize(target)
|
||||
@target = target
|
||||
end
|
||||
def call(_env)
|
||||
@response = Rack::Response.new(
|
||||
body = ['302 found'],
|
||||
status = 302,
|
||||
header = {
|
||||
'Content-Type' => 'text',
|
||||
'Location' => @target
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
def call(env)
|
||||
@response = Rack::Response.new(
|
||||
body = ['302 found'],
|
||||
status = 302,
|
||||
header = {
|
||||
'Content-Type' => 'text',
|
||||
'Location' => @target
|
||||
}
|
||||
)
|
||||
end
|
||||
@request
|
||||
|
||||
private
|
||||
|
||||
@request
|
||||
|
||||
@response
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@response
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -27,32 +27,28 @@ module BeEF
|
||||
secure = @@config.get('beef.http.websocket.secure')
|
||||
|
||||
# @note Start a WSS server socket
|
||||
if (secure)
|
||||
if secure
|
||||
cert_key = @@config.get('beef.http.https.key')
|
||||
unless cert_key.start_with? '/'
|
||||
cert_key = File.expand_path cert_key, $root_dir
|
||||
end
|
||||
cert_key = File.expand_path cert_key, $root_dir unless cert_key.start_with? '/'
|
||||
unless File.exist? cert_key
|
||||
print_error "Error: #{cert_key} does not exist"
|
||||
exit 1
|
||||
end
|
||||
|
||||
cert = @@config.get('beef.http.https.cert')
|
||||
unless cert.start_with? '/'
|
||||
cert = File.expand_path cert, $root_dir
|
||||
end
|
||||
cert = File.expand_path cert, $root_dir unless cert.start_with? '/'
|
||||
unless File.exist? cert
|
||||
print_error "Error: #{cert} does not exist"
|
||||
exit 1
|
||||
end
|
||||
|
||||
ws_secure_options = {
|
||||
:host => @@config.get('beef.http.host'),
|
||||
:port => @@config.get('beef.http.websocket.secure_port'),
|
||||
:secure => true,
|
||||
:tls_options => {
|
||||
:cert_chain_file => cert,
|
||||
:private_key_file => cert_key,
|
||||
host: @@config.get('beef.http.host'),
|
||||
port: @@config.get('beef.http.websocket.secure_port'),
|
||||
secure: true,
|
||||
tls_options: {
|
||||
cert_chain_file: cert,
|
||||
private_key_file: cert_key
|
||||
}
|
||||
}
|
||||
start_websocket_server(ws_secure_options)
|
||||
@@ -60,9 +56,9 @@ module BeEF
|
||||
|
||||
# @note Start a WS server socket
|
||||
ws_options = {
|
||||
:host => @@config.get('beef.http.host'),
|
||||
:port => @@config.get('beef.http.websocket.port'),
|
||||
:secure => false
|
||||
host: @@config.get('beef.http.host'),
|
||||
port: @@config.get('beef.http.websocket.port'),
|
||||
secure: false
|
||||
}
|
||||
start_websocket_server(ws_options)
|
||||
end
|
||||
@@ -77,119 +73,117 @@ module BeEF
|
||||
|
||||
EventMachine.run do
|
||||
EventMachine::WebSocket.start(ws_options) do |ws|
|
||||
begin
|
||||
ws.onopen do |handshake|
|
||||
print_debug("[WebSocket] New #{secure ? 'WebSocketSecure' : 'WebSocket'} channel open.")
|
||||
ws.onopen do |_handshake|
|
||||
print_debug("[WebSocket] New #{secure ? 'WebSocketSecure' : 'WebSocket'} channel open.")
|
||||
end
|
||||
|
||||
ws.onerror do |error|
|
||||
print_error "[WebSocket] Error: #{error}"
|
||||
end
|
||||
|
||||
ws.onclose do |msg|
|
||||
print_debug "[WebSocket] Connection closed: #{msg}"
|
||||
end
|
||||
|
||||
ws.onmessage do |msg, _type|
|
||||
begin
|
||||
msg_hash = JSON.parse(msg)
|
||||
print_debug "[WebSocket] New message: #{msg_hash}" if @@debug
|
||||
rescue StandardError => e
|
||||
print_error "[WebSocket] Failed parsing WebSocket message: #{e.message}"
|
||||
print_error e.backtrace
|
||||
next
|
||||
end
|
||||
|
||||
ws.onerror do |error|
|
||||
print_error "[WebSocket] Error: #{error}"
|
||||
end
|
||||
# new zombie
|
||||
unless msg_hash['cookie'].nil?
|
||||
print_debug('[WebSocket] Browser says hello! WebSocket is running')
|
||||
# insert new connection in activesocket
|
||||
@@activeSocket[(msg_hash['cookie']).to_s] = ws
|
||||
print_debug("[WebSocket] activeSocket content [#{@@activeSocket}]")
|
||||
|
||||
ws.onclose do |msg|
|
||||
print_debug "[WebSocket] Connection closed: #{msg}"
|
||||
end
|
||||
|
||||
ws.onmessage do |msg, type|
|
||||
begin
|
||||
msg_hash = JSON.parse(msg)
|
||||
print_debug "[WebSocket] New message: #{msg_hash}" if @@debug
|
||||
rescue => e
|
||||
print_error "[WebSocket] Failed parsing WebSocket message: #{e.message}"
|
||||
print_error e.backtrace
|
||||
hb_session = msg_hash['cookie']
|
||||
hooked_browser = BeEF::Core::Models::HookedBrowser.where(session: hb_session).first
|
||||
if hooked_browser.nil?
|
||||
print_error '[WebSocket] Fingerprinting not finished yet.'
|
||||
print_more 'ARE rules were not triggered. You may want to trigger them manually via REST API.'
|
||||
next
|
||||
end
|
||||
|
||||
# new zombie
|
||||
unless msg_hash['cookie'].nil?
|
||||
print_debug("[WebSocket] Browser says hello! WebSocket is running")
|
||||
# insert new connection in activesocket
|
||||
@@activeSocket["#{msg_hash["cookie"]}"] = ws
|
||||
print_debug("[WebSocket] activeSocket content [#{@@activeSocket}]")
|
||||
browser_name = BeEF::Core::Models::BrowserDetails.get(hb_session, 'browser.name')
|
||||
browser_version = BeEF::Core::Models::BrowserDetails.get(hb_session, 'browser.version')
|
||||
os_name = BeEF::Core::Models::BrowserDetails.get(hb_session, 'host.os.name')
|
||||
os_version = BeEF::Core::Models::BrowserDetails.get(hb_session, 'host.os.version')
|
||||
BeEF::Core::AutorunEngine::Engine.instance.run(hooked_browser.id, browser_name, browser_version, os_name, os_version)
|
||||
|
||||
hb_session = msg_hash["cookie"]
|
||||
hooked_browser = BeEF::Core::Models::HookedBrowser.where(:session => hb_session).first
|
||||
if hooked_browser.nil?
|
||||
print_error '[WebSocket] Fingerprinting not finished yet.'
|
||||
print_more 'ARE rules were not triggered. You may want to trigger them manually via REST API.'
|
||||
next
|
||||
end
|
||||
|
||||
browser_name = BeEF::Core::Models::BrowserDetails.get(hb_session, 'browser.name')
|
||||
browser_version = BeEF::Core::Models::BrowserDetails.get(hb_session, 'browser.version')
|
||||
os_name = BeEF::Core::Models::BrowserDetails.get(hb_session, 'host.os.name')
|
||||
os_version = BeEF::Core::Models::BrowserDetails.get(hb_session, 'host.os.version')
|
||||
BeEF::Core::AutorunEngine::Engine.instance.run(hooked_browser.id, browser_name, browser_version, os_name, os_version)
|
||||
|
||||
next
|
||||
end
|
||||
|
||||
# polling zombie
|
||||
unless msg_hash['alive'].nil?
|
||||
hooked_browser = BeEF::Core::Models::HookedBrowser.where(:session => msg_hash["alive"]).first
|
||||
|
||||
# This will happen if you reset BeEF database (./beef -x),
|
||||
# and existing zombies try to connect. These zombies will be ignored,
|
||||
# as they are unknown and presumed invalid.
|
||||
#
|
||||
# @todo: consider fixing this. add zombies instead of ignoring them
|
||||
# and report "Hooked browser X appears to have come back online"
|
||||
if hooked_browser.nil?
|
||||
# print_error "Could not find zombie with ID #{msg_hash['alive']}"
|
||||
next
|
||||
end
|
||||
|
||||
hooked_browser.lastseen = Time.new.to_i
|
||||
hooked_browser.count!
|
||||
hooked_browser.save!
|
||||
|
||||
# Check if new modules need to be sent
|
||||
zombie_commands = BeEF::Core::Models::Command.where(:hooked_browser_id => hooked_browser.id, :instructions_sent => false)
|
||||
zombie_commands.each { |command| add_command_instructions(command, hooked_browser) }
|
||||
|
||||
# Check if there are any ARE rules to be triggered. If is_sent=false rules are triggered
|
||||
are_body = ''
|
||||
are_executions = BeEF::Core::Models::Execution.where(:is_sent => false, :session_id => hooked_browser.session)
|
||||
are_executions.each do |are_exec|
|
||||
are_body += are_exec.mod_body
|
||||
are_exec.update(:is_sent => true, :exec_time => Time.new.to_i)
|
||||
end
|
||||
@@activeSocket[hooked_browser.session].send(are_body) unless are_body.empty?
|
||||
|
||||
# @todo antisnatchor:
|
||||
# @todo - re-use the pre_hook_send callback mechanisms to have a generic check for multipl extensions
|
||||
# Check if new forged requests need to be sent (Requester/TunnelingProxy)
|
||||
if @@config.get('beef.extension.requester.loaded')
|
||||
dhook = BeEF::Extension::Requester::API::Hook.new
|
||||
dhook.requester_run(hooked_browser, '')
|
||||
end
|
||||
|
||||
# Check if new XssRays scan need to be started
|
||||
if @@config.get('beef.extension.xssrays.loaded')
|
||||
xssrays = BeEF::Extension::Xssrays::API::Scan.new
|
||||
xssrays.start_scan(hooked_browser, '')
|
||||
end
|
||||
|
||||
next
|
||||
end
|
||||
|
||||
# received request for a specific handler
|
||||
# the zombie is probably trying to return command module results
|
||||
# or call back to a running BeEF extension
|
||||
unless msg_hash['handler'].nil?
|
||||
# Call the handler for websocket cmd response
|
||||
# Base64 decode, parse JSON, and forward
|
||||
execute(msg_hash)
|
||||
next
|
||||
end
|
||||
|
||||
print_error "[WebSocket] Unexpected WebSocket message: #{msg_hash}"
|
||||
next
|
||||
end
|
||||
|
||||
# polling zombie
|
||||
unless msg_hash['alive'].nil?
|
||||
hooked_browser = BeEF::Core::Models::HookedBrowser.where(session: msg_hash['alive']).first
|
||||
|
||||
# This will happen if you reset BeEF database (./beef -x),
|
||||
# and existing zombies try to connect. These zombies will be ignored,
|
||||
# as they are unknown and presumed invalid.
|
||||
#
|
||||
# @todo: consider fixing this. add zombies instead of ignoring them
|
||||
# and report "Hooked browser X appears to have come back online"
|
||||
if hooked_browser.nil?
|
||||
# print_error "Could not find zombie with ID #{msg_hash['alive']}"
|
||||
next
|
||||
end
|
||||
|
||||
hooked_browser.lastseen = Time.new.to_i
|
||||
hooked_browser.count!
|
||||
hooked_browser.save!
|
||||
|
||||
# Check if new modules need to be sent
|
||||
zombie_commands = BeEF::Core::Models::Command.where(hooked_browser_id: hooked_browser.id, instructions_sent: false)
|
||||
zombie_commands.each { |command| add_command_instructions(command, hooked_browser) }
|
||||
|
||||
# Check if there are any ARE rules to be triggered. If is_sent=false rules are triggered
|
||||
are_body = ''
|
||||
are_executions = BeEF::Core::Models::Execution.where(is_sent: false, session_id: hooked_browser.session)
|
||||
are_executions.each do |are_exec|
|
||||
are_body += are_exec.mod_body
|
||||
are_exec.update(is_sent: true, exec_time: Time.new.to_i)
|
||||
end
|
||||
@@activeSocket[hooked_browser.session].send(are_body) unless are_body.empty?
|
||||
|
||||
# @todo antisnatchor:
|
||||
# @todo - re-use the pre_hook_send callback mechanisms to have a generic check for multipl extensions
|
||||
# Check if new forged requests need to be sent (Requester/TunnelingProxy)
|
||||
if @@config.get('beef.extension.requester.loaded')
|
||||
dhook = BeEF::Extension::Requester::API::Hook.new
|
||||
dhook.requester_run(hooked_browser, '')
|
||||
end
|
||||
|
||||
# Check if new XssRays scan need to be started
|
||||
if @@config.get('beef.extension.xssrays.loaded')
|
||||
xssrays = BeEF::Extension::Xssrays::API::Scan.new
|
||||
xssrays.start_scan(hooked_browser, '')
|
||||
end
|
||||
|
||||
next
|
||||
end
|
||||
|
||||
# received request for a specific handler
|
||||
# the zombie is probably trying to return command module results
|
||||
# or call back to a running BeEF extension
|
||||
unless msg_hash['handler'].nil?
|
||||
# Call the handler for websocket cmd response
|
||||
# Base64 decode, parse JSON, and forward
|
||||
execute(msg_hash)
|
||||
next
|
||||
end
|
||||
|
||||
print_error "[WebSocket] Unexpected WebSocket message: #{msg_hash}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue => e
|
||||
rescue StandardError => e
|
||||
print_error "[WebSocket] Error: #{e.message}"
|
||||
raise e
|
||||
end
|
||||
@@ -198,7 +192,7 @@ module BeEF
|
||||
# @note retrieve the right websocket channel given an hooked browser session
|
||||
# @param [String] session the hooked browser session
|
||||
#
|
||||
def getsocket (session)
|
||||
def getsocket(session)
|
||||
!@@activeSocket[session].nil?
|
||||
end
|
||||
|
||||
@@ -207,7 +201,7 @@ module BeEF
|
||||
# @param [String] fn the module to execute
|
||||
# @param [String] session the hooked browser session
|
||||
#
|
||||
def send (fn, session)
|
||||
def send(fn, session)
|
||||
@@activeSocket[session].send(fn)
|
||||
end
|
||||
|
||||
@@ -228,27 +222,25 @@ module BeEF
|
||||
# "jkERa2PIdTtwnwxheXiiGZsm4ukfAD6o84LpgcJBW0g7S8fIh0Uq1yUZxnC0Cr163FxPWCpPN3uOVyPZ"}
|
||||
# => {"handler"=>"/command/test_beef_debug.js", "cid"=>"1", "result"=>"InJlc3VsdD1jYWxsZWQgdGhlIGJlZWYuZGVidWcoKSBmdW5jdGlvbi4gQ2hlY2sgdGhlIGRldmVsb3BlciBjb25zb2xlIGZvciB5b3VyIGRlYnVnIG1lc3NhZ2UuIg==", "status"=>"undefined", "callback"=>"undefined", "bh"=>"jkERa2PIdTtwnwxheXiiGZsm4ukfAD6o84LpgcJBW0g7S8fIh0Uq1yUZxnC0Cr163FxPWCpPN3uOVyPZ"}
|
||||
#
|
||||
def execute (data)
|
||||
if @@debug
|
||||
print_debug data.inspect
|
||||
end
|
||||
def execute(data)
|
||||
print_debug data.inspect if @@debug
|
||||
|
||||
hooked_browser = data['bh']
|
||||
unless BeEF::Filters.is_valid_hook_session_id?(hooked_browser)
|
||||
print_error "[Websocket] BeEF hook is invalid"
|
||||
print_error '[Websocket] BeEF hook is invalid'
|
||||
return
|
||||
end
|
||||
|
||||
command_id = data['cid'].to_s
|
||||
unless BeEF::Filters::nums_only?(command_id)
|
||||
print_error "[Websocket] command_id is invalid"
|
||||
unless BeEF::Filters.nums_only?(command_id)
|
||||
print_error '[Websocket] command_id is invalid'
|
||||
return
|
||||
end
|
||||
command_id = command_id.to_i
|
||||
|
||||
handler = data['handler']
|
||||
if handler.to_s.strip == ''
|
||||
print_error "[Websocket] handler is invalid"
|
||||
print_error '[Websocket] handler is invalid'
|
||||
return
|
||||
end
|
||||
|
||||
@@ -260,20 +252,20 @@ module BeEF
|
||||
when '2'
|
||||
status = 2
|
||||
else
|
||||
print_error "[Websocket] command status is invalid"
|
||||
print_error '[Websocket] command status is invalid'
|
||||
return
|
||||
end
|
||||
|
||||
command_results = {}
|
||||
|
||||
command_results['data'] = JSON.parse(Base64.decode64(data['result']).force_encoding('UTF-8'))
|
||||
|
||||
|
||||
if command_results.empty?
|
||||
print_error "[Websocket] command results are empty"
|
||||
print_error '[Websocket] command results are empty'
|
||||
return
|
||||
end
|
||||
|
||||
command_mod = "beef.module.#{handler.gsub('/command/','').gsub('.js','')}"
|
||||
command_mod = "beef.module.#{handler.gsub('/command/', '').gsub('.js', '')}"
|
||||
command_name = @@config.get("#{command_mod}.class")
|
||||
|
||||
# process results from command module
|
||||
@@ -289,9 +281,7 @@ module BeEF
|
||||
nil
|
||||
)
|
||||
|
||||
if command_obj.respond_to?(:post_execute)
|
||||
command_obj.post_execute
|
||||
end
|
||||
command_obj.post_execute if command_obj.respond_to?(:post_execute)
|
||||
|
||||
BeEF::Core::Models::Command.save_result(
|
||||
hooked_browser,
|
||||
@@ -301,18 +291,18 @@ module BeEF
|
||||
status
|
||||
)
|
||||
else # processing results from extensions, call the right handler
|
||||
data["beefhook"] = hooked_browser
|
||||
data["results"] = command_results['data']
|
||||
data['beefhook'] = hooked_browser
|
||||
data['results'] = command_results['data']
|
||||
|
||||
if MOUNTS.has_key?(handler)
|
||||
if MOUNTS[handler].class == Array and MOUNTS[handler].length == 2
|
||||
if MOUNTS[handler].instance_of?(Array) and MOUNTS[handler].length == 2
|
||||
MOUNTS[handler][0].new(data, MOUNTS[handler][1])
|
||||
else
|
||||
MOUNTS[handler].new(data)
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue => e
|
||||
rescue StandardError => e
|
||||
print_error "Error in BeEF::Core::Websocket: #{e.message}"
|
||||
raise e
|
||||
end
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
module BeEF
|
||||
module Core
|
||||
module Rest
|
||||
|
||||
module RegisterHooksHandler
|
||||
def self.mount_handler(server)
|
||||
server.mount('/api/hooks', BeEF::Core::Rest::HookedBrowsers.new)
|
||||
@@ -64,24 +63,23 @@ module BeEF
|
||||
BeEF::API::Registrar.instance.register(BeEF::Core::Rest::RegisterServerHandler, BeEF::API::Server, 'mount_handler')
|
||||
BeEF::API::Registrar.instance.register(BeEF::Core::Rest::RegisterAutorunHandler, BeEF::API::Server, 'mount_handler')
|
||||
|
||||
|
||||
#
|
||||
# Check the source IP is within the permitted subnet
|
||||
# This is from extensions/admin_ui/controllers/authentication/authentication.rb
|
||||
#
|
||||
def self.permitted_source?(ip)
|
||||
# test if supplied IP address is valid
|
||||
return false unless BeEF::Filters::is_valid_ip?(ip)
|
||||
return false unless BeEF::Filters.is_valid_ip?(ip)
|
||||
|
||||
# get permitted subnets
|
||||
permitted_ui_subnet = BeEF::Core::Configuration.instance.get("beef.restrictions.permitted_ui_subnet")
|
||||
return false if permitted_ui_subnet.nil?
|
||||
return false if permitted_ui_subnet.empty?
|
||||
permitted_ui_subnet = BeEF::Core::Configuration.instance.get('beef.restrictions.permitted_ui_subnet')
|
||||
return false if permitted_ui_subnet.nil?
|
||||
return false if permitted_ui_subnet.empty?
|
||||
|
||||
# test if ip within subnets
|
||||
permitted_ui_subnet.each do |subnet|
|
||||
permitted_ui_subnet.each do |subnet|
|
||||
return true if IPAddr.new(subnet).include?(ip)
|
||||
end
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
@@ -100,18 +98,17 @@ module BeEF
|
||||
# @return <boolean>
|
||||
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
|
||||
|
||||
@@ -8,20 +8,19 @@ module BeEF
|
||||
module Core
|
||||
module Rest
|
||||
class Admin < BeEF::Core::Router::Router
|
||||
|
||||
config = BeEF::Core::Configuration.instance
|
||||
time_since_last_failed_auth = 0
|
||||
|
||||
|
||||
before do
|
||||
# @todo: this code comment is a lie. why is it here?
|
||||
# error 401 unless params[:token] == config.get('beef.api_token')
|
||||
halt 401 if not BeEF::Core::Rest.permitted_source?(request.ip)
|
||||
halt 401 unless BeEF::Core::Rest.permitted_source?(request.ip)
|
||||
|
||||
# halt if requests are inside beef.restrictions.api_attempt_delay
|
||||
if time_since_last_failed_auth != 0
|
||||
halt 401 if not BeEF::Core::Rest.timeout?('beef.restrictions.api_attempt_delay',
|
||||
time_since_last_failed_auth,
|
||||
lambda { |time| time_since_last_failed_auth = time})
|
||||
# halt if requests are inside beef.restrictions.api_attempt_delay
|
||||
if time_since_last_failed_auth != 0 && !BeEF::Core::Rest.timeout?('beef.restrictions.api_attempt_delay',
|
||||
time_since_last_failed_auth,
|
||||
->(time) { time_since_last_failed_auth = time })
|
||||
halt 401
|
||||
end
|
||||
|
||||
headers 'Content-Type' => 'application/json; charset=UTF-8',
|
||||
@@ -36,44 +35,37 @@ module BeEF
|
||||
# Input must be specified in JSON format
|
||||
#
|
||||
# +++ Example: +++
|
||||
#POST /api/admin/login HTTP/1.1
|
||||
#Host: 127.0.0.1:3000
|
||||
#Content-Type: application/json; charset=UTF-8
|
||||
#Content-Length: 18
|
||||
# POST /api/admin/login HTTP/1.1
|
||||
# Host: 127.0.0.1:3000
|
||||
# Content-Type: application/json; charset=UTF-8
|
||||
# Content-Length: 18
|
||||
#
|
||||
#{"username":"beef", "password":"beef"}
|
||||
# {"username":"beef", "password":"beef"}
|
||||
#===response (snip)===
|
||||
#HTTP/1.1 200 OK
|
||||
#Content-Type: application/json; charset=UTF-8
|
||||
#Content-Length: 35
|
||||
# HTTP/1.1 200 OK
|
||||
# Content-Type: application/json; charset=UTF-8
|
||||
# Content-Length: 35
|
||||
#
|
||||
#{"success":"true","token":"122323121"}
|
||||
# {"success":"true","token":"122323121"}
|
||||
#
|
||||
post '/login' do
|
||||
request.body.rewind
|
||||
begin
|
||||
data = JSON.parse request.body.read
|
||||
# check username and password
|
||||
if not (data['username'].eql? config.get('beef.credentials.user') and data['password'].eql? config.get('beef.credentials.passwd') )
|
||||
if not data['password'].eql? "broken_pass"
|
||||
BeEF::Core::Logger.instance.register('Authentication', "User with ip #{request.ip} has failed to authenticate in the application.")
|
||||
end
|
||||
|
||||
# failed attempts
|
||||
time_since_last_failed_auth = Time.now()
|
||||
halt 401
|
||||
else
|
||||
{ "success" => true,
|
||||
"token" => "#{config.get('beef.api_token')}"
|
||||
if data['username'].eql?(config.get('beef.credentials.user')) && data['password'].eql?(config.get('beef.credentials.passwd'))
|
||||
return {
|
||||
'success' => true,
|
||||
'token' => config.get('beef.api_token').to_s
|
||||
}.to_json
|
||||
end
|
||||
rescue => e
|
||||
|
||||
BeEF::Core::Logger.instance.register('Authentication', "User with ip #{request.ip} has failed to authenticate in the application.")
|
||||
time_since_last_failed_auth = Time.now
|
||||
halt 401
|
||||
rescue StandardError
|
||||
error 400
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,12 +8,11 @@ module BeEF
|
||||
module Core
|
||||
module Rest
|
||||
class AutorunEngine < BeEF::Core::Router::Router
|
||||
|
||||
config = BeEF::Core::Configuration.instance
|
||||
|
||||
before do
|
||||
error 401 unless params[:token] == config.get('beef.api_token')
|
||||
halt 401 if not BeEF::Core::Rest.permitted_source?(request.ip)
|
||||
halt 401 unless BeEF::Core::Rest.permitted_source?(request.ip)
|
||||
headers 'Content-Type' => 'application/json; charset=UTF-8',
|
||||
'Pragma' => 'no-cache',
|
||||
'Cache-Control' => 'no-cache',
|
||||
@@ -27,7 +26,7 @@ module BeEF
|
||||
data = JSON.parse request.body.read
|
||||
rloader = BeEF::Core::AutorunEngine::RuleLoader.instance
|
||||
rloader.load(data).to_json
|
||||
rescue => e
|
||||
rescue StandardError => e
|
||||
err = 'Malformed JSON ruleset.'
|
||||
print_error "[ARE] ERROR: #{e.message}"
|
||||
{ 'success' => false, 'error' => err }.to_json
|
||||
@@ -36,96 +35,90 @@ module BeEF
|
||||
|
||||
# Delete a ruleset
|
||||
get '/rule/delete/:rule_id' do
|
||||
begin
|
||||
rule_id = params[:rule_id]
|
||||
rule = BeEF::Core::AutorunEngine::Models::Rule.find(rule_id)
|
||||
rule.destroy
|
||||
{ 'success' => true}.to_json
|
||||
rescue => e
|
||||
err = 'Error getting rule.'
|
||||
print_error "[ARE] ERROR: #{e.message}"
|
||||
{ 'success' => false, 'error' => err }.to_json
|
||||
end
|
||||
rule_id = params[:rule_id]
|
||||
rule = BeEF::Core::AutorunEngine::Models::Rule.find(rule_id)
|
||||
rule.destroy
|
||||
{ 'success' => true }.to_json
|
||||
rescue StandardError => e
|
||||
err = 'Error getting rule.'
|
||||
print_error "[ARE] ERROR: #{e.message}"
|
||||
{ 'success' => false, 'error' => err }.to_json
|
||||
end
|
||||
|
||||
# Trigger a specified rule_id on online hooked browsers. Offline hooked browsers are ignored
|
||||
get '/rule/trigger/:rule_id' do
|
||||
begin
|
||||
rule_id = params[:rule_id]
|
||||
rule_id = params[:rule_id]
|
||||
|
||||
online_hooks = BeEF::Core::Models::HookedBrowser.where('lastseen >= ?', (Time.new.to_i - 15))
|
||||
are = BeEF::Core::AutorunEngine::Engine.instance
|
||||
online_hooks = BeEF::Core::Models::HookedBrowser.where('lastseen >= ?', (Time.new.to_i - 15))
|
||||
are = BeEF::Core::AutorunEngine::Engine.instance
|
||||
|
||||
if online_hooks != nil
|
||||
online_hooks.each do |hb|
|
||||
hb_details = BeEF::Core::Models::BrowserDetails
|
||||
browser_name = hb_details.get(hb.session, 'browser.name')
|
||||
browser_version = hb_details.get(hb.session, 'browser.version')
|
||||
os_name = hb_details.get(hb.session, 'host.os.name')
|
||||
os_version = hb_details.get(hb.session, 'host.os.version')
|
||||
if online_hooks.nil?
|
||||
{ 'success' => false, 'error' => 'There are currently no hooked browsers online.' }.to_json
|
||||
else
|
||||
online_hooks.each do |hb|
|
||||
hb_details = BeEF::Core::Models::BrowserDetails
|
||||
browser_name = hb_details.get(hb.session, 'browser.name')
|
||||
browser_version = hb_details.get(hb.session, 'browser.version')
|
||||
os_name = hb_details.get(hb.session, 'host.os.name')
|
||||
os_version = hb_details.get(hb.session, 'host.os.version')
|
||||
|
||||
match_rules = are.match(browser_name, browser_version, os_name, os_version, rule_id)
|
||||
are.trigger(match_rules, hb.id) if match_rules.length > 0
|
||||
end
|
||||
{ 'success' => true }.to_json
|
||||
else
|
||||
{ 'success' => false, 'error' => 'There are currently no hooked browsers online.' }.to_json
|
||||
match_rules = are.match(browser_name, browser_version, os_name, os_version, rule_id)
|
||||
are.trigger(match_rules, hb.id) if match_rules.length > 0
|
||||
end
|
||||
rescue => e
|
||||
err = 'Malformed JSON ruleset.'
|
||||
print_error "[ARE] ERROR: #{e.message}"
|
||||
{ 'success' => false, 'error' => err }.to_json
|
||||
{ 'success' => true }.to_json
|
||||
end
|
||||
rescue StandardError => e
|
||||
err = 'Malformed JSON ruleset.'
|
||||
print_error "[ARE] ERROR: #{e.message}"
|
||||
{ 'success' => false, 'error' => err }.to_json
|
||||
end
|
||||
|
||||
# Delete a ruleset
|
||||
get '/rule/list/:rule_id' do
|
||||
begin
|
||||
rule_id = params[:rule_id]
|
||||
if rule_id == 'all'
|
||||
result = Array.new
|
||||
rules = BeEF::Core::AutorunEngine::Models::Rule.all
|
||||
rules.each do |rule|
|
||||
{
|
||||
'id' => rule.id,
|
||||
'name'=> rule.name,
|
||||
'author'=> rule.author,
|
||||
'browser'=> rule.browser,
|
||||
'browser_version'=> rule.browser_version,
|
||||
'os'=> rule.os,
|
||||
'os_version'=> rule.os_version,
|
||||
'modules'=> rule.modules,
|
||||
'execution_order'=> rule.execution_order,
|
||||
'execution_delay'=> rule.execution_delay,
|
||||
'chain_mode'=> rule.chain_mode
|
||||
}
|
||||
result.push rule
|
||||
end
|
||||
else
|
||||
result = nil
|
||||
rule = BeEF::Core::AutorunEngine::Models::Rule.get(rule_id)
|
||||
if rule != nil
|
||||
result = {
|
||||
'id' => rule.id,
|
||||
'name'=> rule.name,
|
||||
'author'=> rule.author,
|
||||
'browser'=> rule.browser,
|
||||
'browser_version'=> rule.browser_version,
|
||||
'os'=> rule.os,
|
||||
'os_version'=> rule.os_version,
|
||||
'modules'=> rule.modules,
|
||||
'execution_order'=> rule.execution_order,
|
||||
'execution_delay'=> rule.execution_delay,
|
||||
'chain_mode'=> rule.chain_mode
|
||||
}
|
||||
end
|
||||
rule_id = params[:rule_id]
|
||||
if rule_id == 'all'
|
||||
result = []
|
||||
rules = BeEF::Core::AutorunEngine::Models::Rule.all
|
||||
rules.each do |rule|
|
||||
{
|
||||
'id' => rule.id,
|
||||
'name' => rule.name,
|
||||
'author' => rule.author,
|
||||
'browser' => rule.browser,
|
||||
'browser_version' => rule.browser_version,
|
||||
'os' => rule.os,
|
||||
'os_version' => rule.os_version,
|
||||
'modules' => rule.modules,
|
||||
'execution_order' => rule.execution_order,
|
||||
'execution_delay' => rule.execution_delay,
|
||||
'chain_mode' => rule.chain_mode
|
||||
}
|
||||
result.push rule
|
||||
end
|
||||
else
|
||||
result = nil
|
||||
rule = BeEF::Core::AutorunEngine::Models::Rule.get(rule_id)
|
||||
unless rule.nil?
|
||||
result = {
|
||||
'id' => rule.id,
|
||||
'name' => rule.name,
|
||||
'author' => rule.author,
|
||||
'browser' => rule.browser,
|
||||
'browser_version' => rule.browser_version,
|
||||
'os' => rule.os,
|
||||
'os_version' => rule.os_version,
|
||||
'modules' => rule.modules,
|
||||
'execution_order' => rule.execution_order,
|
||||
'execution_delay' => rule.execution_delay,
|
||||
'chain_mode' => rule.chain_mode
|
||||
}
|
||||
end
|
||||
{ 'success' => true, 'rules' => result}.to_json
|
||||
rescue => e
|
||||
err = 'Error getting rule(s)'
|
||||
print_error "[ARE] ERROR: #{e.message}"
|
||||
{ 'success' => false, 'error' => err }.to_json
|
||||
end
|
||||
{ 'success' => true, 'rules' => result }.to_json
|
||||
rescue StandardError => e
|
||||
err = 'Error getting rule(s)'
|
||||
print_error "[ARE] ERROR: #{e.message}"
|
||||
{ 'success' => false, 'error' => err }.to_json
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,12 +8,11 @@ module BeEF
|
||||
module Core
|
||||
module Rest
|
||||
class BrowserDetails < BeEF::Core::Router::Router
|
||||
|
||||
config = BeEF::Core::Configuration.instance
|
||||
|
||||
before do
|
||||
error 401 unless params[:token] == config.get('beef.api_token')
|
||||
halt 401 if not BeEF::Core::Rest.permitted_source?(request.ip)
|
||||
halt 401 unless BeEF::Core::Rest.permitted_source?(request.ip)
|
||||
headers 'Content-Type' => 'application/json; charset=UTF-8',
|
||||
'Pragma' => 'no-cache',
|
||||
'Cache-Control' => 'no-cache',
|
||||
@@ -24,15 +23,15 @@ module BeEF
|
||||
# @note Get all browser details for the specified session
|
||||
#
|
||||
get '/:session' do
|
||||
hb = BeEF::Core::Models::HookedBrowser.where(:session => params[:session]).first
|
||||
hb = BeEF::Core::Models::HookedBrowser.where(session: params[:session]).first
|
||||
error 404 if hb.nil?
|
||||
|
||||
details = BeEF::Core::Models::BrowserDetails.where(:session_id => hb.session)
|
||||
details = BeEF::Core::Models::BrowserDetails.where(session_id: hb.session)
|
||||
error 404 if details.nil?
|
||||
|
||||
result = []
|
||||
details.each do |d|
|
||||
result << {key: d[:detail_key], value: d[:detail_value] }
|
||||
result << { key: d[:detail_key], value: d[:detail_value] }
|
||||
end
|
||||
|
||||
output = {
|
||||
|
||||
@@ -8,12 +8,11 @@ module BeEF
|
||||
module Core
|
||||
module Rest
|
||||
class Categories < BeEF::Core::Router::Router
|
||||
|
||||
config = BeEF::Core::Configuration.instance
|
||||
|
||||
before do
|
||||
error 401 unless params[:token] == config.get('beef.api_token')
|
||||
halt 401 if not BeEF::Core::Rest.permitted_source?(request.ip)
|
||||
halt 401 unless BeEF::Core::Rest.permitted_source?(request.ip)
|
||||
headers 'Content-Type' => 'application/json; charset=UTF-8',
|
||||
'Pragma' => 'no-cache',
|
||||
'Cache-Control' => 'no-cache',
|
||||
@@ -21,19 +20,18 @@ module BeEF
|
||||
end
|
||||
|
||||
get '/' do
|
||||
categories = BeEF::Modules::get_categories
|
||||
cats = Array.new
|
||||
i = 0
|
||||
# todo add sub-categories support!
|
||||
categories.each do |category|
|
||||
cat = {"id" => i, "name" => category}
|
||||
cats << cat
|
||||
i += 1
|
||||
end
|
||||
cats.to_json
|
||||
categories = BeEF::Modules.get_categories
|
||||
cats = []
|
||||
i = 0
|
||||
# TODO: add sub-categories support!
|
||||
categories.each do |category|
|
||||
cat = { 'id' => i, 'name' => category }
|
||||
cats << cat
|
||||
i += 1
|
||||
end
|
||||
cats.to_json
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,12 +8,11 @@ module BeEF
|
||||
module Core
|
||||
module Rest
|
||||
class HookedBrowsers < BeEF::Core::Router::Router
|
||||
|
||||
config = BeEF::Core::Configuration.instance
|
||||
|
||||
before do
|
||||
error 401 unless params[:token] == config.get('beef.api_token')
|
||||
halt 401 if not BeEF::Core::Rest.permitted_source?(request.ip)
|
||||
halt 401 unless BeEF::Core::Rest.permitted_source?(request.ip)
|
||||
headers 'Content-Type' => 'application/json; charset=UTF-8',
|
||||
'Pragma' => 'no-cache',
|
||||
'Cache-Control' => 'no-cache',
|
||||
@@ -37,50 +36,51 @@ module BeEF
|
||||
end
|
||||
|
||||
output = {
|
||||
'hooked-browsers' => {
|
||||
'online' => online_hooks,
|
||||
'offline' => offline_hooks
|
||||
}
|
||||
'hooked-browsers' => {
|
||||
'online' => online_hooks,
|
||||
'offline' => offline_hooks
|
||||
}
|
||||
}
|
||||
output.to_json
|
||||
end
|
||||
|
||||
get '/:session/delete' do
|
||||
hb = BeEF::Core::Models::HookedBrowser.where(:session => params[:session]).first
|
||||
error 401 unless hb != nil
|
||||
get '/:session/delete' do
|
||||
hb = BeEF::Core::Models::HookedBrowser.where(session: params[:session]).first
|
||||
error 401 if hb.nil?
|
||||
|
||||
details = BeEF::Core::Models::BrowserDetails.where(:session_id => hb.session)
|
||||
details.destroy_all
|
||||
details = BeEF::Core::Models::BrowserDetails.where(session_id: hb.session)
|
||||
details.destroy_all
|
||||
|
||||
logs = BeEF::Core::Models::Log.where(:hooked_browser_id => hb.id)
|
||||
logs.destroy_all
|
||||
logs = BeEF::Core::Models::Log.where(hooked_browser_id: hb.id)
|
||||
logs.destroy_all
|
||||
|
||||
commands = BeEF::Core::Models::Command.where(:hooked_browser_id => hb.id)
|
||||
commands.destroy_all
|
||||
commands = BeEF::Core::Models::Command.where(hooked_browser_id: hb.id)
|
||||
commands.destroy_all
|
||||
|
||||
results = BeEF::Core::Models::Result.where(:hooked_browser_id => hb.id)
|
||||
results.destroy_all
|
||||
results = BeEF::Core::Models::Result.where(hooked_browser_id: hb.id)
|
||||
results.destroy_all
|
||||
|
||||
begin
|
||||
requester = BeEF::Core::Models::Http.where(:hooked_browser_id => hb.id)
|
||||
requester.destroy_all
|
||||
rescue => e
|
||||
#the requester module may not be enabled
|
||||
end
|
||||
begin
|
||||
requester = BeEF::Core::Models::Http.where(hooked_browser_id: hb.id)
|
||||
requester.destroy_all
|
||||
rescue StandardError
|
||||
# @todo why is this error swallowed?
|
||||
# the requester module may not be enabled
|
||||
end
|
||||
|
||||
begin
|
||||
xssraysscans = BeEF::Core::Models::Xssraysscan.where(:hooked_browser_id => hb.id)
|
||||
xssraysscans.destroy_all
|
||||
begin
|
||||
xssraysscans = BeEF::Core::Models::Xssraysscan.where(hooked_browser_id: hb.id)
|
||||
xssraysscans.destroy_all
|
||||
|
||||
xssraysdetails = BeEF::Core::Models::Xssraysdetail.where(:hooked_browser_id => hb.id)
|
||||
xssraysdetails.destroy_all
|
||||
rescue => e
|
||||
#the xssraysscan module may not be enabled
|
||||
end
|
||||
|
||||
hb.destroy
|
||||
end
|
||||
xssraysdetails = BeEF::Core::Models::Xssraysdetail.where(hooked_browser_id: hb.id)
|
||||
xssraysdetails.destroy_all
|
||||
rescue StandardError => e
|
||||
# @todo why is this error swallowed?
|
||||
# the xssraysscan module may not be enabled
|
||||
end
|
||||
|
||||
hb.destroy
|
||||
end
|
||||
|
||||
#
|
||||
# @note returns all zombies
|
||||
@@ -99,7 +99,6 @@ module BeEF
|
||||
output.to_json
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# @note this is basically the same call as /api/hooks, but returns different data structured in arrays rather than objects.
|
||||
# Useful if you need to query the API via jQuery.dataTable < 1.10 which is currently used in PhishingFrenzy
|
||||
@@ -108,7 +107,7 @@ module BeEF
|
||||
online_hooks = hbs_to_array(BeEF::Core::Models::HookedBrowser.where('lastseen >= ?', (Time.new.to_i - 15)))
|
||||
|
||||
output = {
|
||||
'aaData' => online_hooks
|
||||
'aaData' => online_hooks
|
||||
}
|
||||
output.to_json
|
||||
end
|
||||
@@ -121,7 +120,7 @@ module BeEF
|
||||
offline_hooks = hbs_to_array(BeEF::Core::Models::HookedBrowser.where('lastseen <= ?', (Time.new.to_i - 15)))
|
||||
|
||||
output = {
|
||||
'aaData' => offline_hooks
|
||||
'aaData' => offline_hooks
|
||||
}
|
||||
output.to_json
|
||||
end
|
||||
@@ -130,10 +129,10 @@ module BeEF
|
||||
# @note Get all the hooked browser details (plugins enabled, technologies enabled, cookies)
|
||||
#
|
||||
get '/:session' do
|
||||
hb = BeEF::Core::Models::HookedBrowser.where(:session => params[:session]).first
|
||||
error 401 unless hb != nil
|
||||
hb = BeEF::Core::Models::HookedBrowser.where(session: params[:session]).first
|
||||
error 401 if hb.nil?
|
||||
|
||||
details = BeEF::Core::Models::BrowserDetails.where(:session_id => hb.session)
|
||||
details = BeEF::Core::Models::BrowserDetails.where(session_id: hb.session)
|
||||
result = {}
|
||||
details.each do |property|
|
||||
result[property.detail_key] = property.detail_value
|
||||
@@ -149,28 +148,28 @@ module BeEF
|
||||
os_version = body['os_version']
|
||||
arch = body['arch']
|
||||
|
||||
hb = BeEF::Core::Models::HookedBrowser.where(:session => params[:session]).first
|
||||
error 401 unless hb != nil
|
||||
hb = BeEF::Core::Models::HookedBrowser.where(session: params[:session]).first
|
||||
error 401 if hb.nil?
|
||||
|
||||
BeEF::Core::Models::BrowserDetails.where(:session_id => hb.session, :detail_key => 'host.os.name').destroy
|
||||
BeEF::Core::Models::BrowserDetails.where(:session_id => hb.session, :detail_key => 'host.os.version').destroy
|
||||
#BeEF::Core::Models::BrowserDetails.first(:session_id => hb.session, :detail_key => 'Arch').destroy
|
||||
BeEF::Core::Models::BrowserDetails.where(session_id: hb.session, detail_key: 'host.os.name').destroy
|
||||
BeEF::Core::Models::BrowserDetails.where(session_id: hb.session, detail_key: 'host.os.version').destroy
|
||||
# BeEF::Core::Models::BrowserDetails.first(:session_id => hb.session, :detail_key => 'Arch').destroy
|
||||
|
||||
BeEF::Core::Models::BrowserDetails.create(:session_id => hb.session, :detail_key => 'host.os.name', :detail_value => os)
|
||||
BeEF::Core::Models::BrowserDetails.create(:session_id => hb.session, :detail_key => 'host.os.version', :detail_value => os_version)
|
||||
BeEF::Core::Models::BrowserDetails.create(:session_id => hb.session, :detail_key => 'Arch', :detail_value => arch)
|
||||
BeEF::Core::Models::BrowserDetails.create(session_id: hb.session, detail_key: 'host.os.name', detail_value: os)
|
||||
BeEF::Core::Models::BrowserDetails.create(session_id: hb.session, detail_key: 'host.os.version', detail_value: os_version)
|
||||
BeEF::Core::Models::BrowserDetails.create(session_id: hb.session, detail_key: 'Arch', detail_value: arch)
|
||||
|
||||
# TODO if there where any ARE rules defined for this hooked browser,
|
||||
# TODO: if there where any ARE rules defined for this hooked browser,
|
||||
# after updating OS/arch, force a retrigger of the rule.
|
||||
{'success' => true}.to_json
|
||||
{ 'success' => true }.to_json
|
||||
end
|
||||
|
||||
def hb_to_json(hbs)
|
||||
hbs_hash = {}
|
||||
i = 0
|
||||
hbs.each do |hb|
|
||||
hbs_hash[i] = (get_hb_details(hb))
|
||||
i+=1
|
||||
hbs_hash[i] = get_hb_details(hb)
|
||||
i += 1
|
||||
end
|
||||
hbs_hash
|
||||
end
|
||||
@@ -179,24 +178,24 @@ module BeEF
|
||||
details = BeEF::Core::Models::BrowserDetails
|
||||
|
||||
{
|
||||
'id' => hb.id,
|
||||
'session' => hb.session,
|
||||
'name' => details.get(hb.session, 'browser.name'),
|
||||
'version' => details.get(hb.session, 'browser.version'),
|
||||
'platform' => details.get(hb.session, 'browser.platform'),
|
||||
'os' => details.get(hb.session, 'host.os.name'),
|
||||
'os_version' => details.get(hb.session, 'host.os.version'),
|
||||
'hardware' => details.get(hb.session, 'hardware.type'),
|
||||
'ip' => hb.ip,
|
||||
'domain' => details.get(hb.session, 'browser.window.hostname'),
|
||||
'port' => hb.port.to_s,
|
||||
'page_uri' => details.get(hb.session, 'browser.window.uri'),
|
||||
'firstseen' => hb.firstseen,
|
||||
'lastseen' => hb.lastseen,
|
||||
'date_stamp' => details.get(hb.session, 'browser.date.datestamp'),
|
||||
'city' => details.get(hb.session, 'location.city'),
|
||||
'country' => details.get(hb.session, 'location.country'),
|
||||
'country_code' => details.get(hb.session, 'location.country.isocode'),
|
||||
'id' => hb.id,
|
||||
'session' => hb.session,
|
||||
'name' => details.get(hb.session, 'browser.name'),
|
||||
'version' => details.get(hb.session, 'browser.version'),
|
||||
'platform' => details.get(hb.session, 'browser.platform'),
|
||||
'os' => details.get(hb.session, 'host.os.name'),
|
||||
'os_version' => details.get(hb.session, 'host.os.version'),
|
||||
'hardware' => details.get(hb.session, 'hardware.type'),
|
||||
'ip' => hb.ip,
|
||||
'domain' => details.get(hb.session, 'browser.window.hostname'),
|
||||
'port' => hb.port.to_s,
|
||||
'page_uri' => details.get(hb.session, 'browser.window.uri'),
|
||||
'firstseen' => hb.firstseen,
|
||||
'lastseen' => hb.lastseen,
|
||||
'date_stamp' => details.get(hb.session, 'browser.date.datestamp'),
|
||||
'city' => details.get(hb.session, 'location.city'),
|
||||
'country' => details.get(hb.session, 'location.country'),
|
||||
'country_code' => details.get(hb.session, 'location.country.isocode')
|
||||
}
|
||||
end
|
||||
|
||||
@@ -205,32 +204,32 @@ module BeEF
|
||||
hooked_browsers = []
|
||||
hbs.each do |hb|
|
||||
details = BeEF::Core::Models::BrowserDetails
|
||||
# TODO jQuery.dataTables needs fixed array indexes, add emptry string if a value is blank
|
||||
# @todo what does the below TODO comment mean? why do we care about the client side view inside a controller?
|
||||
# TODO: jQuery.dataTables needs fixed array indexes, add emptry string if a value is blank
|
||||
|
||||
pfuid = details.get(hb.session, 'PhishingFrenzyUID') != nil ? details.get(hb.session, 'PhishingFrenzyUID') : 'n/a'
|
||||
bname = details.get(hb.session, 'browser.name') != nil ? details.get(hb.session, 'browser.name') : 'n/a'
|
||||
bversion = details.get(hb.session, 'browser.version') != nil ? details.get(hb.session, 'browser.version') : 'n/a'
|
||||
bplugins = details.get(hb.session, 'browser.plugins') != nil ? details.get(hb.session, 'browser.plugins') : 'n/a'
|
||||
pfuid = details.get(hb.session, 'PhishingFrenzyUID').nil? ? 'n/a' : details.get(hb.session, 'PhishingFrenzyUID')
|
||||
bname = details.get(hb.session, 'browser.name').nil? ? 'n/a' : details.get(hb.session, 'browser.name')
|
||||
bversion = details.get(hb.session, 'browser.version').nil? ? 'n/a' : details.get(hb.session, 'browser.version')
|
||||
bplugins = details.get(hb.session, 'browser.plugins').nil? ? 'n/a' : details.get(hb.session, 'browser.plugins')
|
||||
|
||||
hooked_browsers << [
|
||||
hb.id,
|
||||
hb.ip,
|
||||
pfuid,
|
||||
bname,
|
||||
bversion,
|
||||
details.get(hb.session, 'host.os.name'),
|
||||
details.get(hb.session, 'browser.platform'),
|
||||
details.get(hb.session, 'browser.language'),
|
||||
bplugins,
|
||||
details.get(hb.session, 'location.city'),
|
||||
details.get(hb.session, 'location.country'),
|
||||
details.get(hb.session, 'location.latitude'),
|
||||
details.get(hb.session, 'location.longitude')
|
||||
hb.id,
|
||||
hb.ip,
|
||||
pfuid,
|
||||
bname,
|
||||
bversion,
|
||||
details.get(hb.session, 'host.os.name'),
|
||||
details.get(hb.session, 'browser.platform'),
|
||||
details.get(hb.session, 'browser.language'),
|
||||
bplugins,
|
||||
details.get(hb.session, 'location.city'),
|
||||
details.get(hb.session, 'location.country'),
|
||||
details.get(hb.session, 'location.latitude'),
|
||||
details.get(hb.session, 'location.longitude')
|
||||
]
|
||||
end
|
||||
hooked_browsers
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,12 +8,11 @@ module BeEF
|
||||
module Core
|
||||
module Rest
|
||||
class Logs < BeEF::Core::Router::Router
|
||||
|
||||
config = BeEF::Core::Configuration.instance
|
||||
|
||||
before do
|
||||
error 401 unless params[:token] == config.get('beef.api_token')
|
||||
halt 401 if not BeEF::Core::Rest.permitted_source?(request.ip)
|
||||
halt 401 unless BeEF::Core::Rest.permitted_source?(request.ip)
|
||||
headers 'Content-Type' => 'application/json; charset=UTF-8',
|
||||
'Pragma' => 'no-cache',
|
||||
'Cache-Control' => 'no-cache',
|
||||
@@ -33,10 +32,10 @@ module BeEF
|
||||
#
|
||||
get '/rss' do
|
||||
logs = BeEF::Core::Models::Log.all
|
||||
headers 'Content-Type' => 'text/xml; charset=UTF-8',
|
||||
'Pragma' => 'no-cache',
|
||||
headers 'Content-Type' => 'text/xml; charset=UTF-8',
|
||||
'Pragma' => 'no-cache',
|
||||
'Cache-Control' => 'no-cache',
|
||||
'Expires' => '0'
|
||||
'Expires' => '0'
|
||||
logs_to_rss logs
|
||||
end
|
||||
|
||||
@@ -44,10 +43,10 @@ module BeEF
|
||||
# @note Get hooked browser logs
|
||||
#
|
||||
get '/:session' do
|
||||
hb = BeEF::Core::Models::HookedBrowser.where(:session => params[:session]).first
|
||||
error 401 unless hb != nil
|
||||
hb = BeEF::Core::Models::HookedBrowser.where(session: params[:session]).first
|
||||
error 401 if hb.nil?
|
||||
|
||||
logs = BeEF::Core::Models::Log.where(:hooked_browser_id => hb.id)
|
||||
logs = BeEF::Core::Models::Log.where(hooked_browser_id: hb.id)
|
||||
logs_to_json(logs)
|
||||
end
|
||||
|
||||
@@ -59,19 +58,20 @@ module BeEF
|
||||
|
||||
logs.each do |log|
|
||||
logs_json << {
|
||||
'id' => log.id.to_i,
|
||||
'date' => log.date.to_s,
|
||||
'event' => log.event.to_s,
|
||||
'logtype' => log.logtype.to_s,
|
||||
'hooked_browser_id' => log.hooked_browser_id.to_s
|
||||
'id' => log.id.to_i,
|
||||
'date' => log.date.to_s,
|
||||
'event' => log.event.to_s,
|
||||
'logtype' => log.logtype.to_s,
|
||||
'hooked_browser_id' => log.hooked_browser_id.to_s
|
||||
}
|
||||
end
|
||||
|
||||
{
|
||||
unless logs_json.empty?
|
||||
{
|
||||
'logs_count' => count,
|
||||
'logs' => logs_json
|
||||
}.to_json if not logs_json.empty?
|
||||
|
||||
}.to_json
|
||||
end
|
||||
end
|
||||
|
||||
def logs_to_rss(logs)
|
||||
@@ -91,7 +91,6 @@ module BeEF
|
||||
end
|
||||
rss.to_s
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,12 +8,11 @@ module BeEF
|
||||
module Core
|
||||
module Rest
|
||||
class Modules < BeEF::Core::Router::Router
|
||||
|
||||
config = BeEF::Core::Configuration.instance
|
||||
|
||||
before do
|
||||
error 401 unless params[:token] == config.get('beef.api_token')
|
||||
halt 401 if not BeEF::Core::Rest.permitted_source?(request.ip)
|
||||
halt 401 unless BeEF::Core::Rest.permitted_source?(request.ip)
|
||||
headers 'Content-Type' => 'application/json; charset=UTF-8',
|
||||
'Pragma' => 'no-cache',
|
||||
'Cache-Control' => 'no-cache',
|
||||
@@ -30,24 +29,23 @@ module BeEF
|
||||
i = 0
|
||||
mods.each do |mod|
|
||||
modk = BeEF::Module.get_key_by_database_id(mod.id)
|
||||
next if !BeEF::Module.is_enabled(modk)
|
||||
next unless BeEF::Module.is_enabled(modk)
|
||||
|
||||
mods_hash[i] = {
|
||||
'id' => mod.id,
|
||||
'class' => config.get("beef.module.#{modk}.class"),
|
||||
'name' => config.get("beef.module.#{modk}.name"),
|
||||
'category' => config.get("beef.module.#{modk}.category")
|
||||
'id' => mod.id,
|
||||
'class' => config.get("beef.module.#{modk}.class"),
|
||||
'name' => config.get("beef.module.#{modk}.name"),
|
||||
'category' => config.get("beef.module.#{modk}.category")
|
||||
}
|
||||
i+=1
|
||||
i += 1
|
||||
end
|
||||
mods_hash.to_json
|
||||
end
|
||||
|
||||
get '/search/:mod_name' do
|
||||
mod = BeEF::Core::Models::CommandModule.where(:name => params[:mod_name]).first
|
||||
mod = BeEF::Core::Models::CommandModule.where(name: params[:mod_name]).first
|
||||
result = {}
|
||||
if mod != nil
|
||||
result = {'id' => mod.id}
|
||||
end
|
||||
result = { 'id' => mod.id } unless mod.nil?
|
||||
result.to_json
|
||||
end
|
||||
|
||||
@@ -56,47 +54,47 @@ module BeEF
|
||||
#
|
||||
get '/:mod_id' do
|
||||
cmd = BeEF::Core::Models::CommandModule.find(params[:mod_id])
|
||||
error 404 unless cmd != nil
|
||||
error 404 if cmd.nil?
|
||||
modk = BeEF::Module.get_key_by_database_id(params[:mod_id])
|
||||
error 404 unless modk != nil
|
||||
error 404 if modk.nil?
|
||||
|
||||
#todo check if it's possible to also retrieve the TARGETS supported
|
||||
# TODO: check if it's possible to also retrieve the TARGETS supported
|
||||
{
|
||||
'name' => cmd.name,
|
||||
'description' => config.get("beef.module.#{cmd.name}.description"),
|
||||
'category'=> config.get("beef.module.#{cmd.name}.category"),
|
||||
'options' => BeEF::Module.get_options(modk) #todo => get also payload options..get_payload_options(modk,text)
|
||||
'name' => cmd.name,
|
||||
'description' => config.get("beef.module.#{cmd.name}.description"),
|
||||
'category' => config.get("beef.module.#{cmd.name}.category"),
|
||||
'options' => BeEF::Module.get_options(modk) # TODO: => get also payload options..get_payload_options(modk,text)
|
||||
}.to_json
|
||||
end
|
||||
|
||||
# @note Get the module result for the specific executed command
|
||||
#
|
||||
# Example with the Alert Dialog
|
||||
#GET /api/modules/wiJCKAJybcB6aXZZOj31UmQKhbKXY63aNBeODl9kvkIuYLmYTooeGeRD7Xn39x8zOChcUReM3Bt7K0xj/86/1?token=0a931a461d08b86bfee40df987aad7e9cfdeb050 HTTP/1.1
|
||||
#Host: 127.0.0.1:3000
|
||||
# GET /api/modules/wiJCKAJybcB6aXZZOj31UmQKhbKXY63aNBeODl9kvkIuYLmYTooeGeRD7Xn39x8zOChcUReM3Bt7K0xj/86/1?token=0a931a461d08b86bfee40df987aad7e9cfdeb050 HTTP/1.1
|
||||
# Host: 127.0.0.1:3000
|
||||
#===response (snip)===
|
||||
#HTTP/1.1 200 OK
|
||||
#Content-Type: application/json; charset=UTF-8
|
||||
# HTTP/1.1 200 OK
|
||||
# Content-Type: application/json; charset=UTF-8
|
||||
#
|
||||
#{"date":"1331637093","data":"{\"data\":\"text=michele\"}"}
|
||||
# {"date":"1331637093","data":"{\"data\":\"text=michele\"}"}
|
||||
#
|
||||
get '/:session/:mod_id/:cmd_id' do
|
||||
hb = BeEF::Core::Models::HookedBrowser.where(:session => params[:session]).first
|
||||
error 401 unless hb != nil
|
||||
cmd = BeEF::Core::Models::Command.where(:hooked_browser_id => hb.id,
|
||||
:command_module_id => params[:mod_id], :id => params[:cmd_id]).first
|
||||
error 404 unless cmd != nil
|
||||
results = BeEF::Core::Models::Result.where(:hooked_browser_id => hb.id, :command_id => cmd.id)
|
||||
error 404 unless results != nil
|
||||
hb = BeEF::Core::Models::HookedBrowser.where(session: params[:session]).first
|
||||
error 401 if hb.nil?
|
||||
cmd = BeEF::Core::Models::Command.where(hooked_browser_id: hb.id,
|
||||
command_module_id: params[:mod_id], id: params[:cmd_id]).first
|
||||
error 404 if cmd.nil?
|
||||
results = BeEF::Core::Models::Result.where(hooked_browser_id: hb.id, command_id: cmd.id)
|
||||
error 404 if results.nil?
|
||||
|
||||
results_hash = {}
|
||||
i = 0
|
||||
results.each do |result|
|
||||
results_hash[i] = {
|
||||
'date' => result.date,
|
||||
'data' => result.data
|
||||
'date' => result.date,
|
||||
'data' => result.data
|
||||
}
|
||||
i+=1
|
||||
i += 1
|
||||
end
|
||||
results_hash.to_json
|
||||
end
|
||||
@@ -107,56 +105,56 @@ module BeEF
|
||||
# Input must be specified in JSON format
|
||||
#
|
||||
# +++ Example with the Alert Dialog: +++
|
||||
#POST /api/modules/wiJCKAJybcB6aXZZOj31UmQKhbKXY63aNBeODl9kvkIuYLmYTooeGeRD7Xn39x8zOChcUReM3Bt7K0xj/86?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
|
||||
#Host: 127.0.0.1:3000
|
||||
#Content-Type: application/json; charset=UTF-8
|
||||
#Content-Length: 18
|
||||
# POST /api/modules/wiJCKAJybcB6aXZZOj31UmQKhbKXY63aNBeODl9kvkIuYLmYTooeGeRD7Xn39x8zOChcUReM3Bt7K0xj/86?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
|
||||
# Host: 127.0.0.1:3000
|
||||
# Content-Type: application/json; charset=UTF-8
|
||||
# Content-Length: 18
|
||||
#
|
||||
#{"text":"michele"}
|
||||
# {"text":"michele"}
|
||||
#===response (snip)===
|
||||
#HTTP/1.1 200 OK
|
||||
#Content-Type: application/json; charset=UTF-8
|
||||
#Content-Length: 35
|
||||
# HTTP/1.1 200 OK
|
||||
# Content-Type: application/json; charset=UTF-8
|
||||
# Content-Length: 35
|
||||
#
|
||||
#{"success":"true","command_id":"1"}
|
||||
# {"success":"true","command_id":"1"}
|
||||
#
|
||||
# +++ Example with a Metasploit module (Adobe FlateDecode Stream Predictor 02 Integer Overflow) +++
|
||||
# +++ note that in this case we cannot query BeEF/Metasploit if module execution was successful or not.
|
||||
# +++ this is why there is "command_id":"not_available" in the response
|
||||
#POST /api/modules/wiJCKAJybcB6aXZZOj31UmQKhbKXY63aNBeODl9kvkIuYLmYTooeGeRD7Xn39x8zOChcUReM3Bt7K0xj/236?token=83f13036060fd7d92440432dd9a9b5e5648f8d75 HTTP/1.1
|
||||
#Host: 127.0.0.1:3000
|
||||
#Content-Type: application/json; charset=UTF-8
|
||||
#Content-Length: 81
|
||||
# POST /api/modules/wiJCKAJybcB6aXZZOj31UmQKhbKXY63aNBeODl9kvkIuYLmYTooeGeRD7Xn39x8zOChcUReM3Bt7K0xj/236?token=83f13036060fd7d92440432dd9a9b5e5648f8d75 HTTP/1.1
|
||||
# Host: 127.0.0.1:3000
|
||||
# Content-Type: application/json; charset=UTF-8
|
||||
# Content-Length: 81
|
||||
#
|
||||
#{"SRVPORT":"3992", "URIPATH":"77345345345dg", "PAYLOAD":"generic/shell_bind_tcp"}
|
||||
# {"SRVPORT":"3992", "URIPATH":"77345345345dg", "PAYLOAD":"generic/shell_bind_tcp"}
|
||||
#===response (snip)===
|
||||
#HTTP/1.1 200 OK
|
||||
#Content-Type: application/json; charset=UTF-8
|
||||
#Content-Length: 35
|
||||
# HTTP/1.1 200 OK
|
||||
# Content-Type: application/json; charset=UTF-8
|
||||
# Content-Length: 35
|
||||
#
|
||||
#{"success":"true","command_id":"not_available"}
|
||||
# {"success":"true","command_id":"not_available"}
|
||||
#
|
||||
post '/:session/:mod_id' do
|
||||
hb = BeEF::Core::Models::HookedBrowser.where(:session => params[:session]).first
|
||||
error 401 unless hb != nil
|
||||
hb = BeEF::Core::Models::HookedBrowser.where(session: params[:session]).first
|
||||
error 401 if hb.nil?
|
||||
modk = BeEF::Module.get_key_by_database_id(params[:mod_id])
|
||||
error 404 unless modk != nil
|
||||
error 404 if modk.nil?
|
||||
|
||||
request.body.rewind
|
||||
begin
|
||||
data = JSON.parse request.body.read
|
||||
options = []
|
||||
data.each{|k,v| options.push({'name' => k, 'value' => v})}
|
||||
data.each { |k, v| options.push({ 'name' => k, 'value' => v }) }
|
||||
exec_results = BeEF::Module.execute(modk, params[:session], options)
|
||||
exec_results != nil ? '{"success":"true","command_id":"'+exec_results.to_s+'"}' : '{"success":"false"}'
|
||||
rescue => e
|
||||
exec_results.nil? ? '{"success":"false"}' : '{"success":"true","command_id":"' + exec_results.to_s + '"}'
|
||||
rescue StandardError
|
||||
print_error "Invalid JSON input for module '#{params[:mod_id]}'"
|
||||
error 400 # Bad Request
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
#@note Fire a new command module to multiple hooked browsers.
|
||||
# @note Fire a new command module to multiple hooked browsers.
|
||||
# Returns the command IDs of the launched module, or 0 if firing got issues.
|
||||
# Use "hb_ids":["ALL"] to run on all hooked browsers
|
||||
# Use "hb_ids":["ALL_ONLINE"] to run on all hooked browsers currently online
|
||||
@@ -174,7 +172,7 @@ module BeEF
|
||||
#
|
||||
# curl example (alert module with custom text, 2 hooked browsers)):
|
||||
#
|
||||
#curl -H "Content-Type: application/json; charset=UTF-8" -d '{"mod_id":110,"mod_params":{"text":"mucci?"},"hb_ids":[1,2]}'
|
||||
# curl -H "Content-Type: application/json; charset=UTF-8" -d '{"mod_id":110,"mod_params":{"text":"mucci?"},"hb_ids":[1,2]}'
|
||||
#-X POST http://127.0.0.1:3000/api/modules/multi_browser?token=2316d82702b83a293e2d46a0886a003a6be0a633
|
||||
#
|
||||
post '/multi_browser' do
|
||||
@@ -182,34 +180,35 @@ module BeEF
|
||||
begin
|
||||
body = JSON.parse request.body.read
|
||||
|
||||
modk = BeEF::Module.get_key_by_database_id body["mod_id"]
|
||||
error 404 unless modk != nil
|
||||
modk = BeEF::Module.get_key_by_database_id body['mod_id']
|
||||
error 404 if modk.nil?
|
||||
mod_params = []
|
||||
|
||||
if body["mod_params"] != nil
|
||||
body["mod_params"].each{|k,v|
|
||||
mod_params.push({'name' => k, 'value' => v})
|
||||
}
|
||||
unless body['mod_params'].nil?
|
||||
body['mod_params'].each do |k, v|
|
||||
mod_params.push({ 'name' => k, 'value' => v })
|
||||
end
|
||||
end
|
||||
|
||||
hb_ids = body["hb_ids"]
|
||||
results = Hash.new
|
||||
hb_ids = body['hb_ids']
|
||||
results = {}
|
||||
|
||||
# run on all hooked browsers currently online?
|
||||
if hb_ids.first =~ /\Aall_online\z/i
|
||||
hb_ids = []
|
||||
BeEF::Core::Models::HookedBrowser.where(
|
||||
:lastseen.gte => (Time.new.to_i - 15)).each {|hb| hb_ids << hb.id }
|
||||
:lastseen.gte => (Time.new.to_i - 15)
|
||||
).each { |hb| hb_ids << hb.id }
|
||||
# run on all hooked browsers?
|
||||
elsif hb_ids.first =~ /\Aall\z/i
|
||||
hb_ids = []
|
||||
BeEF::Core::Models::HookedBrowser.all.each {|hb| hb_ids << hb.id }
|
||||
BeEF::Core::Models::HookedBrowser.all.each { |hb| hb_ids << hb.id }
|
||||
end
|
||||
|
||||
# run modules
|
||||
hb_ids.each do |hb_id|
|
||||
hb = BeEF::Core::Models::HookedBrowser.find(hb_id)
|
||||
if hb == nil
|
||||
if hb.nil?
|
||||
results[hb_id] = 0
|
||||
next
|
||||
else
|
||||
@@ -218,8 +217,8 @@ module BeEF
|
||||
end
|
||||
end
|
||||
results.to_json
|
||||
rescue => e
|
||||
print_error "Invalid JSON input passed to endpoint /api/modules/multi_browser"
|
||||
rescue StandardError
|
||||
print_error 'Invalid JSON input passed to endpoint /api/modules/multi_browser'
|
||||
error 400 # Bad Request
|
||||
end
|
||||
end
|
||||
@@ -228,7 +227,7 @@ module BeEF
|
||||
# Returns the command IDs of the launched modules, or 0 if firing got issues.
|
||||
#
|
||||
# POST request body example (for modules that don't need parameters, just pass an empty JSON object like {} )
|
||||
#{ "hb":"vkIwVV3ok5i5vH2f8sxlkoaKqAGKCbZXdWqE9vkHNFBhI8aBBHvtZAGRO2XqFZXxThBlmKlRiVwPeAzj",
|
||||
# { "hb":"vkIwVV3ok5i5vH2f8sxlkoaKqAGKCbZXdWqE9vkHNFBhI8aBBHvtZAGRO2XqFZXxThBlmKlRiVwPeAzj",
|
||||
# "modules": [
|
||||
# { # test_return_long_string module with custom input
|
||||
# "mod_id":99,
|
||||
@@ -248,7 +247,7 @@ module BeEF
|
||||
#
|
||||
# curl example (test_return_long_string and prompt_dialog module with custom inputs)):
|
||||
#
|
||||
#curl -H "Content-Type: application/json; charset=UTF-8" -d '{"hb":"vkIwVV3ok5i5vH2f8sxlkoaKqAGKCbZXdWqE9vkHNFBhI8aBBHvtZAGRO2XqFZXxThBlmKlRiVwPeAzj",
|
||||
# curl -H "Content-Type: application/json; charset=UTF-8" -d '{"hb":"vkIwVV3ok5i5vH2f8sxlkoaKqAGKCbZXdWqE9vkHNFBhI8aBBHvtZAGRO2XqFZXxThBlmKlRiVwPeAzj",
|
||||
# "modules":[{"mod_id":99,"mod_input":[{"repeat":"10"},{"repeat_string":"ABCDE"}]},{"mod_id":116,"mod_input":[{"question":"hooked?"}]},{"mod_id":128,"mod_input":[]}]}'
|
||||
# -X POST http://127.0.0.1:3000/api/modules/multi_module?token=e640483ae9bca2eb904f003f27dd4bc83936eb92
|
||||
#
|
||||
@@ -256,32 +255,32 @@ module BeEF
|
||||
request.body.rewind
|
||||
begin
|
||||
body = JSON.parse request.body.read
|
||||
hb = BeEF::Core::Models::HookedBrowser.where(:session => body["hb"]).first
|
||||
error 401 unless hb != nil
|
||||
hb = BeEF::Core::Models::HookedBrowser.where(session: body['hb']).first
|
||||
error 401 if hb.nil?
|
||||
|
||||
results = Hash.new
|
||||
if body["modules"] != nil
|
||||
body["modules"].each{|mod|
|
||||
mod_id = mod["mod_id"]
|
||||
mod_k = BeEF::Module.get_key_by_database_id mod["mod_id"]
|
||||
if mod_k == nil
|
||||
results = {}
|
||||
unless body['modules'].nil?
|
||||
body['modules'].each do |mod|
|
||||
mod_id = mod['mod_id']
|
||||
mod_k = BeEF::Module.get_key_by_database_id mod['mod_id']
|
||||
if mod_k.nil?
|
||||
results[mod_id] = 0
|
||||
next
|
||||
else
|
||||
mod_params = []
|
||||
mod["mod_input"].each{|input|
|
||||
input.each{|k,v|
|
||||
mod_params.push({'name' => k, 'value' => v})
|
||||
}
|
||||
}
|
||||
mod['mod_input'].each do |input|
|
||||
input.each do |k, v|
|
||||
mod_params.push({ 'name' => k, 'value' => v })
|
||||
end
|
||||
end
|
||||
cmd_id = BeEF::Module.execute(mod_k, hb.session, mod_params)
|
||||
results[mod_id] = cmd_id
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
results.to_json
|
||||
rescue => e
|
||||
print_error "Invalid JSON input passed to endpoint /api/modules/multi"
|
||||
rescue StandardError
|
||||
print_error 'Invalid JSON input passed to endpoint /api/modules/multi'
|
||||
error 400 # Bad Request
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,20 +8,18 @@ module BeEF
|
||||
module Core
|
||||
module Rest
|
||||
class Server < BeEF::Core::Router::Router
|
||||
|
||||
config = BeEF::Core::Configuration.instance
|
||||
http_server = BeEF::Core::Server.instance
|
||||
|
||||
before do
|
||||
error 401 unless params[:token] == config.get('beef.api_token')
|
||||
halt 401 if not BeEF::Core::Rest.permitted_source?(request.ip)
|
||||
halt 401 unless BeEF::Core::Rest.permitted_source?(request.ip)
|
||||
headers 'Content-Type' => 'application/json; charset=UTF-8',
|
||||
'Pragma' => 'no-cache',
|
||||
'Cache-Control' => 'no-cache',
|
||||
'Expires' => '0'
|
||||
end
|
||||
|
||||
|
||||
# @note Binds a local file to a specified path in BeEF's web server
|
||||
# Note: 'local_file' expects a file from the /extensions/social_engineering/droppers directory.
|
||||
# Example usage:
|
||||
@@ -35,17 +33,17 @@ module BeEF
|
||||
mount = data['mount']
|
||||
local_file = data['local_file']
|
||||
|
||||
droppers_dir = File.expand_path('..', __FILE__) + "/../../../../extensions/social_engineering/droppers/"
|
||||
droppers_dir = "#{File.expand_path(__dir__)}/../../../../extensions/social_engineering/droppers/"
|
||||
|
||||
if File.exists?(droppers_dir + local_file) && Dir.entries(droppers_dir).include?(local_file)
|
||||
f_ext = File.extname(local_file).gsub('.','')
|
||||
if File.exist?(droppers_dir + local_file) && Dir.entries(droppers_dir).include?(local_file)
|
||||
f_ext = File.extname(local_file).gsub('.', '')
|
||||
f_ext = nil if f_ext.empty?
|
||||
BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.bind("/extensions/social_engineering/droppers/#{local_file}", mount, f_ext)
|
||||
status 200
|
||||
else
|
||||
halt 400
|
||||
end
|
||||
rescue => e
|
||||
rescue StandardError
|
||||
error 400
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
module BeEF
|
||||
module Core
|
||||
module Router
|
||||
|
||||
module RegisterRouterHandler
|
||||
def self.mount_handler(server)
|
||||
server.mount('/', BeEF::Core::Router::Router.new)
|
||||
@@ -14,7 +13,6 @@ module BeEF
|
||||
end
|
||||
|
||||
BeEF::API::Registrar.instance.register(BeEF::Core::Router::RegisterRouterHandler, BeEF::API::Server, 'mount_handler')
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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 = "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">" +
|
||||
"<html><head>" +
|
||||
"<title>404 Not Found</title>" +
|
||||
"</head><body>" +
|
||||
"<h1>Not Found</h1>" +
|
||||
"<p>The requested URL was not found on this server.</p>" +
|
||||
"<hr>" +
|
||||
"<address>Apache/2.2.3 (CentOS)</address>" +
|
||||
("<script src='#{config.get("beef.http.hook_file")}'></script>" if config.get("beef.http.web_server_imitation.hook_404")).to_s +
|
||||
"</body></html>"
|
||||
IIS_HEADER = {"Server" => "Microsoft-IIS/6.0",
|
||||
"X-Powered-By" => "ASP.NET",
|
||||
"Content-Type" => "text/html; charset=UTF-8"}
|
||||
IIS_BODY = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" +
|
||||
"<HTML><HEAD><TITLE>The page cannot be found</TITLE>" +
|
||||
"<META HTTP-EQUIV=\"Content-Type\" Content=\"text/html; charset=Windows-1252\">" +
|
||||
"<STYLE type=\"text/css\">" +
|
||||
" BODY { font: 8pt/12pt verdana } " +
|
||||
" H1 { font: 13pt/15pt verdana }" +
|
||||
" H2 { font: 8pt/12pt verdana }" +
|
||||
" A:link { color: red }" +
|
||||
" A:visited { color: maroon }" +
|
||||
"</STYLE>" +
|
||||
"</HEAD><BODY><TABLE width=500 border=0 cellspacing=10><TR><TD>" +
|
||||
"<h1>The page cannot be found</h1>" +
|
||||
"The page you are looking for might have been removed, had its name changed, or is temporarily unavailable." +
|
||||
"<hr>" +
|
||||
"<p>Please try the following:</p>" +
|
||||
"<ul>" +
|
||||
"<li>Make sure that the Web site address displayed in the address bar of your browser is spelled and formatted correctly.</li>" +
|
||||
"<li>If you reached this page by clicking a link, contact" +
|
||||
" the Web site administrator to alert them that the link is incorrectly formatted." +
|
||||
"</li>" +
|
||||
"<li>Click the <a href=\"javascript:history.back(1)\">Back</a> button to try another link.</li>" +
|
||||
"</ul>" +
|
||||
"<h2>HTTP Error 404 - File or directory not found.<br>Internet Information Services (IIS)</h2>" +
|
||||
"<hr>" +
|
||||
"<p>Technical Information (for support personnel)</p>" +
|
||||
"<ul>" +
|
||||
"<li>Go to <a href=\"http://go.microsoft.com/fwlink/?linkid=8180\">Microsoft Product Support Services</a> and perform a title search for the words <b>HTTP</b> and <b>404</b>.</li>" +
|
||||
"<li>Open <b>IIS Help</b>, which is accessible in IIS Manager (inetmgr)," +
|
||||
"and search for topics titled <b>Web Site Setup</b>, <b>Common Administrative Tasks</b>, and <b>About Custom Error Messages</b>.</li>" +
|
||||
"</ul>" +
|
||||
"</TD></TR></TABLE>" +
|
||||
("<script src='#{config.get("beef.http.hook_file")}'></script>" if config.get("beef.http.web_server_imitation.hook_404")).to_s +
|
||||
"</BODY></HTML>"
|
||||
NGINX_HEADER = {"Server" => "nginx",
|
||||
"Content-Type" => "text/html"}
|
||||
NGINX_BODY = "<html>\n"+
|
||||
"<head><title>404 Not Found</title></head>\n" +
|
||||
"<body bgcolor=\"white\">\n" +
|
||||
"<center><h1>404 Not Found</h1></center>\n" +
|
||||
"<hr><center>nginx</center>\n" +
|
||||
("<script src='#{config.get("beef.http.hook_file")}'></script>" if config.get("beef.http.web_server_imitation.hook_404")).to_s +
|
||||
"</body>\n" +
|
||||
"</html>\n"
|
||||
|
||||
APACHE_HEADER = { 'Server' => 'Apache/2.2.3 (CentOS)',
|
||||
'Content-Type' => 'text/html; charset=UTF-8' }.freeze
|
||||
APACHE_BODY = '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">' \
|
||||
'<html><head>' \
|
||||
'<title>404 Not Found</title>' \
|
||||
'</head><body>' \
|
||||
'<h1>Not Found</h1>' \
|
||||
'<p>The requested URL was not found on this server.</p>' \
|
||||
'<hr>' \
|
||||
'<address>Apache/2.2.3 (CentOS)</address>' +
|
||||
("<script src='#{config.get('beef.http.hook_file')}'></script>" if config.get('beef.http.web_server_imitation.hook_404')).to_s +
|
||||
'</body></html>'
|
||||
IIS_HEADER = { 'Server' => 'Microsoft-IIS/6.0',
|
||||
'X-Powered-By' => 'ASP.NET',
|
||||
'Content-Type' => 'text/html; charset=UTF-8' }.freeze
|
||||
IIS_BODY = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">' \
|
||||
'<HTML><HEAD><TITLE>The page cannot be found</TITLE>' \
|
||||
'<META HTTP-EQUIV="Content-Type" Content="text/html; charset=Windows-1252">' \
|
||||
'<STYLE type="text/css">' \
|
||||
' BODY { font: 8pt/12pt verdana } ' \
|
||||
' H1 { font: 13pt/15pt verdana }' \
|
||||
' H2 { font: 8pt/12pt verdana }' \
|
||||
' A:link { color: red }' \
|
||||
' A:visited { color: maroon }' \
|
||||
'</STYLE>' \
|
||||
'</HEAD><BODY><TABLE width=500 border=0 cellspacing=10><TR><TD>' \
|
||||
'<h1>The page cannot be found</h1>' \
|
||||
'The page you are looking for might have been removed, had its name changed, or is temporarily unavailable.' \
|
||||
'<hr>' \
|
||||
'<p>Please try the following:</p>' \
|
||||
'<ul>' \
|
||||
'<li>Make sure that the Web site address displayed in the address bar of your browser is spelled and formatted correctly.</li>' \
|
||||
'<li>If you reached this page by clicking a link, contact' \
|
||||
' the Web site administrator to alert them that the link is incorrectly formatted.' \
|
||||
'</li>' \
|
||||
'<li>Click the <a href="javascript:history.back(1)">Back</a> button to try another link.</li>' \
|
||||
'</ul>' \
|
||||
'<h2>HTTP Error 404 - File or directory not found.<br>Internet Information Services (IIS)</h2>' \
|
||||
'<hr>' \
|
||||
'<p>Technical Information (for support personnel)</p>' \
|
||||
'<ul>' \
|
||||
'<li>Go to <a href="http://go.microsoft.com/fwlink/?linkid=8180">Microsoft Product Support Services</a> and perform a title search for the words <b>HTTP</b> and <b>404</b>.</li>' \
|
||||
'<li>Open <b>IIS Help</b>, which is accessible in IIS Manager (inetmgr),' \
|
||||
'and search for topics titled <b>Web Site Setup</b>, <b>Common Administrative Tasks</b>, and <b>About Custom Error Messages</b>.</li>' \
|
||||
'</ul>' \
|
||||
'</TD></TR></TABLE>' +
|
||||
("<script src='#{config.get('beef.http.hook_file')}'></script>" if config.get('beef.http.web_server_imitation.hook_404')).to_s +
|
||||
'</BODY></HTML>'
|
||||
NGINX_HEADER = { 'Server' => 'nginx',
|
||||
'Content-Type' => 'text/html' }.freeze
|
||||
NGINX_BODY = "<html>\n" \
|
||||
"<head><title>404 Not Found</title></head>\n" \
|
||||
"<body bgcolor=\"white\">\n" \
|
||||
"<center><h1>404 Not Found</h1></center>\n" \
|
||||
"<hr><center>nginx</center>\n" +
|
||||
("<script src='#{config.get('beef.http.hook_file')}'></script>" if config.get('beef.http.web_server_imitation.hook_404')).to_s +
|
||||
"</body>\n" \
|
||||
"</html>\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,11 +7,9 @@
|
||||
module BeEF
|
||||
module Core
|
||||
module Router
|
||||
|
||||
#@note This is the main Router parent class.
|
||||
#@note All the HTTP handlers registered on BeEF will extend this class.
|
||||
# @note This is the main Router parent class.
|
||||
# @note All the HTTP handlers registered on BeEF will extend this class.
|
||||
class Router < Sinatra::Base
|
||||
|
||||
config = BeEF::Core::Configuration.instance
|
||||
configure do
|
||||
set :show_exceptions, false
|
||||
@@ -19,240 +17,239 @@ module BeEF
|
||||
|
||||
# @note Override default 404 HTTP response
|
||||
not_found do
|
||||
if config.get("beef.http.web_server_imitation.enable")
|
||||
type = config.get("beef.http.web_server_imitation.type")
|
||||
if config.get('beef.http.web_server_imitation.enable')
|
||||
type = config.get('beef.http.web_server_imitation.type')
|
||||
case type
|
||||
when "apache"
|
||||
#response body
|
||||
BeEF::Core::Router::APACHE_BODY
|
||||
when "iis"
|
||||
#response body
|
||||
BeEF::Core::Router::IIS_BODY
|
||||
when "nginx"
|
||||
#response body
|
||||
BeEF::Core::Router::NGINX_BODY
|
||||
else
|
||||
"Not Found."
|
||||
when 'apache'
|
||||
# response body
|
||||
BeEF::Core::Router::APACHE_BODY
|
||||
when 'iis'
|
||||
# response body
|
||||
BeEF::Core::Router::IIS_BODY
|
||||
when 'nginx'
|
||||
# response body
|
||||
BeEF::Core::Router::NGINX_BODY
|
||||
else
|
||||
'Not Found.'
|
||||
end
|
||||
else
|
||||
"Not Found."
|
||||
'Not Found.'
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
# @note Override Server HTTP response header
|
||||
if config.get("beef.http.web_server_imitation.enable")
|
||||
type = config.get("beef.http.web_server_imitation.type")
|
||||
if config.get('beef.http.web_server_imitation.enable')
|
||||
type = config.get('beef.http.web_server_imitation.type')
|
||||
case type
|
||||
when "apache"
|
||||
headers BeEF::Core::Router::APACHE_HEADER
|
||||
when "iis"
|
||||
headers BeEF::Core::Router::IIS_HEADER
|
||||
when "nginx"
|
||||
headers BeEF::Core::Router::NGINX_HEADER
|
||||
else
|
||||
headers "Server" => '',
|
||||
"Content-Type" => "text/html"
|
||||
print_error "You have an error in beef.http.web_server_imitation.type!"
|
||||
print_more "Supported values are: apache, iis, nginx."
|
||||
when 'apache'
|
||||
headers BeEF::Core::Router::APACHE_HEADER
|
||||
when 'iis'
|
||||
headers BeEF::Core::Router::IIS_HEADER
|
||||
when 'nginx'
|
||||
headers BeEF::Core::Router::NGINX_HEADER
|
||||
else
|
||||
headers 'Server' => '',
|
||||
'Content-Type' => 'text/html'
|
||||
print_error 'You have an error in beef.http.web_server_imitation.type!'
|
||||
print_more 'Supported values are: apache, iis, nginx.'
|
||||
end
|
||||
end
|
||||
|
||||
# @note If CORS is enabled, expose the appropriate headers
|
||||
if config.get("beef.http.restful_api.allow_cors")
|
||||
allowed_domains = config.get("beef.http.restful_api.cors_allowed_domains")
|
||||
if config.get('beef.http.restful_api.allow_cors')
|
||||
allowed_domains = config.get('beef.http.restful_api.cors_allowed_domains')
|
||||
|
||||
# Responses to preflight OPTIONS requests need to respond with hTTP 200
|
||||
# and be able to handle requests with a JSON content-type
|
||||
if request.request_method == 'OPTIONS'
|
||||
headers "Access-Control-Allow-Origin" => allowed_domains,
|
||||
"Access-Control-Allow-Methods" => "POST, GET",
|
||||
"Access-Control-Allow-Headers" => "Content-Type"
|
||||
headers 'Access-Control-Allow-Origin' => allowed_domains,
|
||||
'Access-Control-Allow-Methods' => 'POST, GET',
|
||||
'Access-Control-Allow-Headers' => 'Content-Type'
|
||||
halt 200
|
||||
end
|
||||
|
||||
headers "Access-Control-Allow-Origin" => allowed_domains,
|
||||
"Access-Control-Allow-Methods" => "POST, GET"
|
||||
headers 'Access-Control-Allow-Origin' => allowed_domains,
|
||||
'Access-Control-Allow-Methods' => 'POST, GET'
|
||||
end
|
||||
end
|
||||
|
||||
# @note Default root page
|
||||
get "/" do
|
||||
if config.get("beef.http.web_server_imitation.enable")
|
||||
bp = config.get "beef.extension.admin_ui.base_path"
|
||||
type = config.get("beef.http.web_server_imitation.type")
|
||||
get '/' do
|
||||
if config.get('beef.http.web_server_imitation.enable')
|
||||
bp = config.get 'beef.extension.admin_ui.base_path'
|
||||
type = config.get('beef.http.web_server_imitation.type')
|
||||
case type
|
||||
when "apache"
|
||||
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">" +
|
||||
"<head>" +
|
||||
"<title>Apache HTTP Server Test Page powered by CentOS</title>" +
|
||||
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />" +
|
||||
"<style type=\"text/css\">" +
|
||||
"body {" +
|
||||
"background-color: #fff; " +
|
||||
"color: #000;" +
|
||||
"font-size: 0.9em;" +
|
||||
"font-family: sans-serif,helvetica;" +
|
||||
"margin: 0;" +
|
||||
"padding: 0; " +
|
||||
"} " +
|
||||
":link { " +
|
||||
"color: #0000FF; " +
|
||||
"} " +
|
||||
":visited { " +
|
||||
"color: #0000FF; " +
|
||||
"} " +
|
||||
"a:hover { " +
|
||||
"color: #3399FF; " +
|
||||
"} " +
|
||||
"h1 { " +
|
||||
" text-align: center; " +
|
||||
" margin: 0; " +
|
||||
" padding: 0.6em 2em 0.4em; " +
|
||||
" background-color: #3399FF;" +
|
||||
" color: #ffffff; " +
|
||||
" font-weight: normal; " +
|
||||
" font-size: 1.75em; " +
|
||||
" border-bottom: 2px solid #000; " +
|
||||
"} " +
|
||||
"h1 strong {" +
|
||||
"font-weight: bold; " +
|
||||
"} " +
|
||||
"h2 { " +
|
||||
" font-size: 1.1em;" +
|
||||
"font-weight: bold; " +
|
||||
"} " +
|
||||
".content { " +
|
||||
" padding: 1em 5em; " +
|
||||
"} " +
|
||||
".content-columns { " +
|
||||
" /* Setting relative positioning allows for " +
|
||||
" absolute positioning for sub-classes */ " +
|
||||
" position: relative; " +
|
||||
" padding-top: 1em; " +
|
||||
"} " +
|
||||
".content-column-left { " +
|
||||
" /* Value for IE/Win; will be overwritten for other browsers */" +
|
||||
" width: 47%; " +
|
||||
" padding-right: 3%; " +
|
||||
" float: left; " +
|
||||
" padding-bottom: 2em; " +
|
||||
"} " +
|
||||
".content-column-right { " +
|
||||
" /* Values for IE/Win; will be overwritten for other browsers */" +
|
||||
" width: 47%; " +
|
||||
" padding-left: 3%; " +
|
||||
" float: left; " +
|
||||
" padding-bottom: 2em; " +
|
||||
"} " +
|
||||
".content-columns>.content-column-left, .content-columns>.content-column-right {" +
|
||||
" /* Non-IE/Win */" +
|
||||
"} " +
|
||||
"img { " +
|
||||
" border: 2px solid #fff; " +
|
||||
" padding: 2px; " +
|
||||
" margin: 2px; " +
|
||||
"} " +
|
||||
"a:hover img { " +
|
||||
" border: 2px solid #3399FF; " +
|
||||
"} " +
|
||||
"</style> " +
|
||||
"</head> " +
|
||||
"<body> " +
|
||||
"<h1>Apache 2 Test Page<br><font size=\"-1\"><strong>powered by</font> CentOS</strong></h1>" +
|
||||
"<div class=\"content\">" +"<div class=\"content-middle\">" +
|
||||
"<p>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.</p>" +
|
||||
"</div>" +
|
||||
"<hr />" +
|
||||
"<div class=\"content-columns\">" +
|
||||
"<div class=\"content-column-left\"> " +
|
||||
"<h2>If you are a member of the general public:</h2>" +
|
||||
"<p>The fact that you are seeing this page indicates that the website you just visited is either experiencing problems or is undergoing routine maintenance.</p>" +
|
||||
"<p>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.</p> " +
|
||||
"<p>For example, if you experienced problems while visiting www.example.com, you should send e-mail to \"webmaster@example.com\".</p>" +
|
||||
"</div>" +
|
||||
"<div class=\"content-column-right\">" +
|
||||
"<h2>If you are the website administrator:</h2>" +
|
||||
"<p>You may now add content to the directory <tt>/var/www/html/</tt>. 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 <tt>/etc/httpd/conf.d/welcome.conf</tt>.</p>" +
|
||||
"<p>You are free to use the images below on Apache and CentOS Linux powered HTTP servers. Thanks for using Apache and CentOS!</p>" +
|
||||
"<p><a href=\"http://httpd.apache.org/\"><img src=\"#{bp}/media/images/icons/apache_pb.gif\" alt=\"[ Powered by Apache ]\"/></a> <a href=\"http://www.centos.org/\"><img src=\"#{bp}/media/images/icons/powered_by_rh.png\" alt=\"[ Powered by CentOS Linux ]\" width=\"88\" height=\"31\" /></a></p>" +
|
||||
"</div>" +
|
||||
"</div>" +
|
||||
"</div>" +
|
||||
" <div class=\"content\">" +
|
||||
"<div class=\"content-middle\"><h2>About CentOS:</h2><b>The Community ENTerprise Operating System</b> (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.</p>" +
|
||||
"<p>For information on CentOS please visit the <a href=\"http://www.centos.org/\">CentOS website</a>.</p>" +
|
||||
"<p><h2>Note:</h2><p>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. <b>If you have issues with the content of this site, contact the owner of the domain, not the CentOS project.</b>" +
|
||||
"<p>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.</p> " +
|
||||
"<p>For example, if this website is www.example.com, you would find the owner of the example.com domain at the following WHOIS server:</p>" +
|
||||
"<p><a href=\"http://www.internic.net/whois.html\">http://www.internic.net/whois.html</a></p>" +
|
||||
"</div>" +
|
||||
"</div>" +
|
||||
("<script src='#{config.get("beef.http.hook_file")}'></script>" if config.get("beef.http.web_server_imitation.hook_root")).to_s +
|
||||
"</body>" +
|
||||
"</html>"
|
||||
when "iis"
|
||||
"<html>" +
|
||||
"<head>" +
|
||||
"<meta HTTP-EQUIV=\"Content-Type\" Content=\"text/html; charset=Windows-1252\">" +
|
||||
"<title ID=titletext>Under Construction</title>" +
|
||||
"</head>" +
|
||||
"<body bgcolor=white>" +
|
||||
"<table>" +
|
||||
"<tr>" +
|
||||
"<td ID=tableProps width=70 valign=top align=center>" +
|
||||
"<img ID=pagerrorImg src=\"#{bp}/media/images/icons/pagerror.gif\" width=36 height=48>" +
|
||||
"<td ID=tablePropsWidth width=400>" +
|
||||
"<h1 ID=errortype style=\"font:14pt/16pt verdana; color:#4e4e4e\">" +
|
||||
"<P ID=Comment1><!--Problem--><P ID=\"errorText\">Under Construction</h1>" +
|
||||
"<P ID=Comment2><!--Probable causes:<--><P ID=\"errordesc\"><font style=\"font:9pt/12pt verdana; color:black\">" +
|
||||
"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." +
|
||||
"<P ID=term1>Please try this site again later. If you still experience the problem, try contacting the Web site administrator." +
|
||||
"<hr size=1 color=\"blue\">" +
|
||||
"<P ID=message1>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." +
|
||||
"<h5 ID=head1>To access IIS Help</h5>" +
|
||||
"<ol>" +
|
||||
"<li ID=bullet1>Click <b>Start</b>, and then click <b>Run</b>." +
|
||||
"<li ID=bullet2>In the <b>Open</b> text box, type <b>inetmgr</b>. IIS Manager appears." +
|
||||
"<li ID=bullet3>From the <b>Help</b> menu, click <b>Help Topics</b>." +
|
||||
"<li ID=bullet4>Click <b>Internet Information Services</b>.</ol>" +
|
||||
"</td>" +
|
||||
"</tr>" +
|
||||
"</table>" +
|
||||
("<script src='#{config.get("beef.http.hook_file")}'></script>" if config.get("beef.http.web_server_imitation.hook_root")).to_s +
|
||||
"</body>" +
|
||||
"</html>"
|
||||
when "nginx"
|
||||
"<!DOCTYPE html>\n" +
|
||||
"<html>\n" +
|
||||
"<head>\n" +
|
||||
"<title>Welcome to nginx!</title>\n" +
|
||||
"<style>\n" +
|
||||
" body {\n" +
|
||||
" width: 35em;\n" +
|
||||
" margin: 0 auto;\n" +
|
||||
" font-family: Tahoma, Verdana, Arial, sans-serif;\n" +
|
||||
" }\n" +
|
||||
"</style>\n" +
|
||||
"</head>\n" +
|
||||
"<body>\n" +
|
||||
"<h1>Welcome to nginx!</h1>\n" +
|
||||
"<p>If you see this page, the nginx web server is successfully installed and\n" +
|
||||
"working. Further configuration is required.</p>\n\n" +
|
||||
"<p>For online documentation and support please refer to\n" +
|
||||
"<a href=\"http://nginx.org/\">nginx.org</a>.<br/>\n" +
|
||||
"Commercial support is available at\n" +
|
||||
"<a href=\"http://nginx.com/\">nginx.com</a>.</p>\n\n" +
|
||||
"<p><em>Thank you for using nginx.</em></p>\n" +
|
||||
("<script src='#{config.get("beef.http.hook_file")}'></script>" if config.get("beef.http.web_server_imitation.hook_root")).to_s +
|
||||
"</body>\n" +
|
||||
"</html>\n"
|
||||
else
|
||||
""
|
||||
when 'apache'
|
||||
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">' \
|
||||
'<head>' \
|
||||
'<title>Apache HTTP Server Test Page powered by CentOS</title>' \
|
||||
'<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />' \
|
||||
'<style type="text/css">' \
|
||||
'body {' \
|
||||
'background-color: #fff; ' \
|
||||
'color: #000;' \
|
||||
'font-size: 0.9em;' \
|
||||
'font-family: sans-serif,helvetica;' \
|
||||
'margin: 0;' \
|
||||
'padding: 0; ' \
|
||||
'} ' \
|
||||
':link { ' \
|
||||
'color: #0000FF; ' \
|
||||
'} ' \
|
||||
':visited { ' \
|
||||
'color: #0000FF; ' \
|
||||
'} ' \
|
||||
'a:hover { ' \
|
||||
'color: #3399FF; ' \
|
||||
'} ' \
|
||||
'h1 { ' \
|
||||
"\ttext-align: center; " \
|
||||
"\tmargin: 0; " \
|
||||
"\tpadding: 0.6em 2em 0.4em; " \
|
||||
"\tbackground-color: #3399FF;" \
|
||||
"\tcolor: #ffffff; " \
|
||||
"\tfont-weight: normal; " \
|
||||
"\tfont-size: 1.75em; " \
|
||||
"\tborder-bottom: 2px solid #000; " \
|
||||
'} ' \
|
||||
'h1 strong {' \
|
||||
'font-weight: bold; ' \
|
||||
'} ' \
|
||||
'h2 { ' \
|
||||
"\tfont-size: 1.1em;" \
|
||||
'font-weight: bold; ' \
|
||||
'} ' \
|
||||
'.content { ' \
|
||||
"\tpadding: 1em 5em; " \
|
||||
'} ' \
|
||||
'.content-columns { ' \
|
||||
"\t/* Setting relative positioning allows for " \
|
||||
"\tabsolute positioning for sub-classes */ " \
|
||||
"\tposition: relative; " \
|
||||
"\tpadding-top: 1em; " \
|
||||
'} ' \
|
||||
'.content-column-left { ' \
|
||||
"\t/* Value for IE/Win; will be overwritten for other browsers */" \
|
||||
"\twidth: 47%; " \
|
||||
"\tpadding-right: 3%; " \
|
||||
"\tfloat: left; " \
|
||||
"\tpadding-bottom: 2em; " \
|
||||
'} ' \
|
||||
'.content-column-right { ' \
|
||||
"\t/* Values for IE/Win; will be overwritten for other browsers */" \
|
||||
"\twidth: 47%; " \
|
||||
"\tpadding-left: 3%; " \
|
||||
"\tfloat: left; " \
|
||||
"\tpadding-bottom: 2em; " \
|
||||
'} ' \
|
||||
'.content-columns>.content-column-left, .content-columns>.content-column-right {' \
|
||||
"\t/* Non-IE/Win */" \
|
||||
'} ' \
|
||||
'img { ' \
|
||||
"\tborder: 2px solid #fff; " \
|
||||
"\tpadding: 2px; " \
|
||||
"\tmargin: 2px; " \
|
||||
'} ' \
|
||||
'a:hover img { ' \
|
||||
"\tborder: 2px solid #3399FF; " \
|
||||
'} ' \
|
||||
'</style> ' \
|
||||
'</head> ' \
|
||||
'<body> ' \
|
||||
'<h1>Apache 2 Test Page<br><font size="-1"><strong>powered by</font> CentOS</strong></h1>' \
|
||||
'<div class="content">' + '<div class="content-middle">' \
|
||||
'<p>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.</p>' \
|
||||
'</div>' \
|
||||
'<hr />' \
|
||||
'<div class="content-columns">' \
|
||||
'<div class="content-column-left"> ' \
|
||||
'<h2>If you are a member of the general public:</h2>' \
|
||||
'<p>The fact that you are seeing this page indicates that the website you just visited is either experiencing problems or is undergoing routine maintenance.</p>' \
|
||||
"<p>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.</p> " \
|
||||
'<p>For example, if you experienced problems while visiting www.example.com, you should send e-mail to "webmaster@example.com".</p>' \
|
||||
'</div>' \
|
||||
'<div class="content-column-right">' \
|
||||
'<h2>If you are the website administrator:</h2>' \
|
||||
'<p>You may now add content to the directory <tt>/var/www/html/</tt>. 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 <tt>/etc/httpd/conf.d/welcome.conf</tt>.</p>' \
|
||||
'<p>You are free to use the images below on Apache and CentOS Linux powered HTTP servers. Thanks for using Apache and CentOS!</p>' \
|
||||
"<p><a href=\"http://httpd.apache.org/\"><img src=\"#{bp}/media/images/icons/apache_pb.gif\" alt=\"[ Powered by Apache ]\"/></a> <a href=\"http://www.centos.org/\"><img src=\"#{bp}/media/images/icons/powered_by_rh.png\" alt=\"[ Powered by CentOS Linux ]\" width=\"88\" height=\"31\" /></a></p>" \
|
||||
'</div>' \
|
||||
'</div>' \
|
||||
'</div>' \
|
||||
' <div class="content">' \
|
||||
'<div class="content-middle"><h2>About CentOS:</h2><b>The Community ENTerprise Operating System</b> (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.</p>' \
|
||||
'<p>For information on CentOS please visit the <a href="http://www.centos.org/">CentOS website</a>.</p>' \
|
||||
'<p><h2>Note:</h2><p>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. <b>If you have issues with the content of this site, contact the owner of the domain, not the CentOS project.</b>' \
|
||||
"<p>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.</p> " \
|
||||
'<p>For example, if this website is www.example.com, you would find the owner of the example.com domain at the following WHOIS server:</p>' \
|
||||
'<p><a href="http://www.internic.net/whois.html">http://www.internic.net/whois.html</a></p>' \
|
||||
'</div>' \
|
||||
'</div>' +
|
||||
("<script src='#{config.get('beef.http.hook_file')}'></script>" if config.get('beef.http.web_server_imitation.hook_root')).to_s +
|
||||
'</body>' \
|
||||
'</html>'
|
||||
when 'iis'
|
||||
'<html>' \
|
||||
'<head>' \
|
||||
'<meta HTTP-EQUIV="Content-Type" Content="text/html; charset=Windows-1252">' \
|
||||
'<title ID=titletext>Under Construction</title>' \
|
||||
'</head>' \
|
||||
'<body bgcolor=white>' \
|
||||
'<table>' \
|
||||
'<tr>' \
|
||||
'<td ID=tableProps width=70 valign=top align=center>' \
|
||||
"<img ID=pagerrorImg src=\"#{bp}/media/images/icons/pagerror.gif\" width=36 height=48>" \
|
||||
'<td ID=tablePropsWidth width=400>' \
|
||||
'<h1 ID=errortype style="font:14pt/16pt verdana; color:#4e4e4e">' \
|
||||
'<P ID=Comment1><!--Problem--><P ID="errorText">Under Construction</h1>' \
|
||||
'<P ID=Comment2><!--Probable causes:<--><P ID="errordesc"><font style="font:9pt/12pt verdana; color:black">' \
|
||||
'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.' \
|
||||
'<P ID=term1>Please try this site again later. If you still experience the problem, try contacting the Web site administrator.' \
|
||||
'<hr size=1 color="blue">' \
|
||||
'<P ID=message1>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.' \
|
||||
'<h5 ID=head1>To access IIS Help</h5>' \
|
||||
'<ol>' \
|
||||
'<li ID=bullet1>Click <b>Start</b>, and then click <b>Run</b>.' \
|
||||
'<li ID=bullet2>In the <b>Open</b> text box, type <b>inetmgr</b>. IIS Manager appears.' \
|
||||
'<li ID=bullet3>From the <b>Help</b> menu, click <b>Help Topics</b>.' \
|
||||
'<li ID=bullet4>Click <b>Internet Information Services</b>.</ol>' \
|
||||
'</td>' \
|
||||
'</tr>' \
|
||||
'</table>' +
|
||||
("<script src='#{config.get('beef.http.hook_file')}'></script>" if config.get('beef.http.web_server_imitation.hook_root')).to_s +
|
||||
'</body>' \
|
||||
'</html>'
|
||||
when 'nginx'
|
||||
"<!DOCTYPE html>\n" \
|
||||
"<html>\n" \
|
||||
"<head>\n" \
|
||||
"<title>Welcome to nginx!</title>\n" \
|
||||
"<style>\n" \
|
||||
" body {\n" \
|
||||
" width: 35em;\n" \
|
||||
" margin: 0 auto;\n" \
|
||||
" font-family: Tahoma, Verdana, Arial, sans-serif;\n" \
|
||||
" }\n" \
|
||||
"</style>\n" \
|
||||
"</head>\n" \
|
||||
"<body>\n" \
|
||||
"<h1>Welcome to nginx!</h1>\n" \
|
||||
"<p>If you see this page, the nginx web server is successfully installed and\n" \
|
||||
"working. Further configuration is required.</p>\n\n" \
|
||||
"<p>For online documentation and support please refer to\n" \
|
||||
"<a href=\"http://nginx.org/\">nginx.org</a>.<br/>\n" \
|
||||
"Commercial support is available at\n" \
|
||||
"<a href=\"http://nginx.com/\">nginx.com</a>.</p>\n\n" \
|
||||
"<p><em>Thank you for using nginx.</em></p>\n" +
|
||||
("<script src='#{config.get('beef.http.hook_file')}'></script>" if config.get('beef.http.web_server_imitation.hook_root')).to_s +
|
||||
"</body>\n" \
|
||||
"</html>\n"
|
||||
else
|
||||
''
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -50,11 +50,11 @@ module BeEF
|
||||
# argument type checking
|
||||
raise TypeError, '"url" needs to be a string' unless url.string?
|
||||
|
||||
if args.nil?
|
||||
@mounts[url] = http_handler_class
|
||||
else
|
||||
@mounts[url] = http_handler_class, *args
|
||||
end
|
||||
@mounts[url] = if args.nil?
|
||||
http_handler_class
|
||||
else
|
||||
[http_handler_class, *args]
|
||||
end
|
||||
print_debug "Server: mounted handler '#{url}'"
|
||||
end
|
||||
|
||||
@@ -65,6 +65,7 @@ module BeEF
|
||||
#
|
||||
def unmount(url)
|
||||
raise TypeError, '"url" needs to be a string' unless url.string?
|
||||
|
||||
@mounts.delete url
|
||||
end
|
||||
|
||||
@@ -80,7 +81,7 @@ module BeEF
|
||||
#
|
||||
def prepare
|
||||
# Create http handler for the javascript hook file
|
||||
mount(@configuration.get("beef.http.hook_file").to_s, BeEF::Core::Handlers::HookedBrowsers.new)
|
||||
mount(@configuration.get('beef.http.hook_file').to_s, BeEF::Core::Handlers::HookedBrowsers.new)
|
||||
|
||||
# Create handler for the initialization checks (Browser Details)
|
||||
mount('/init', BeEF::Core::Handlers::BrowserDetails)
|
||||
@@ -93,7 +94,7 @@ module BeEF
|
||||
|
||||
return if @http_server
|
||||
|
||||
# Set the logging level of Thin to match the config
|
||||
# Set the logging level of Thin to match the config
|
||||
Thin::Logging.silent = true
|
||||
if @configuration.get('beef.http.debug') == true
|
||||
Thin::Logging.silent = false
|
||||
@@ -104,7 +105,8 @@ module BeEF
|
||||
@http_server = Thin::Server.new(
|
||||
@configuration.get('beef.http.host'),
|
||||
@configuration.get('beef.http.port'),
|
||||
@rack_app)
|
||||
@rack_app
|
||||
)
|
||||
|
||||
# Configure SSL/TLS
|
||||
return unless @configuration.get('beef.http.https.enable') == true
|
||||
@@ -116,18 +118,14 @@ module BeEF
|
||||
end
|
||||
|
||||
cert_key = @configuration.get 'beef.http.https.key'
|
||||
unless cert_key.start_with? '/'
|
||||
cert_key = File.expand_path cert_key, $root_dir
|
||||
end
|
||||
cert_key = File.expand_path cert_key, $root_dir unless cert_key.start_with? '/'
|
||||
unless File.exist? cert_key
|
||||
print_error "Error: #{cert_key} does not exist"
|
||||
exit 1
|
||||
end
|
||||
|
||||
cert = @configuration.get 'beef.http.https.cert'
|
||||
unless cert.start_with? '/'
|
||||
cert = File.expand_path cert, $root_dir
|
||||
end
|
||||
cert = File.expand_path cert, $root_dir unless cert.start_with? '/'
|
||||
unless File.exist? cert
|
||||
print_error "Error: #{cert} does not exist"
|
||||
exit 1
|
||||
@@ -135,9 +133,9 @@ module BeEF
|
||||
|
||||
@http_server.ssl = true
|
||||
@http_server.ssl_options = {
|
||||
:private_key_file => cert_key,
|
||||
:cert_chain_file => cert,
|
||||
:verify_peer => false
|
||||
private_key_file: cert_key,
|
||||
cert_chain_file: cert,
|
||||
verify_peer: false
|
||||
}
|
||||
|
||||
if Digest::SHA256.hexdigest(File.read(cert)).eql?('978f761fc30cbd174eab0c6ffd2d235849260c0589a99262f136669224c8d82a') ||
|
||||
@@ -145,10 +143,10 @@ module BeEF
|
||||
print_warning 'Warning: Default SSL cert/key in use.'
|
||||
print_more 'Use the generate-certificate utility to generate a new certificate.'
|
||||
end
|
||||
rescue => e
|
||||
rescue StandardError => e
|
||||
print_error "Failed to prepare HTTP server: #{e.message}"
|
||||
print_error e.backtrace
|
||||
exit 1
|
||||
print_error e.backtrace
|
||||
exit 1
|
||||
end
|
||||
|
||||
#
|
||||
@@ -161,6 +159,7 @@ module BeEF
|
||||
rescue RuntimeError => e
|
||||
# port is in use
|
||||
raise unless e.message.include? 'no acceptor'
|
||||
|
||||
print_error "Another process is already listening on port #{@configuration.get('beef.http.port')}, or you're trying to bind BeEF to an invalid IP."
|
||||
print_error 'Is BeEF already running? Exiting...'
|
||||
exit 127
|
||||
|
||||
144
core/module.rb
144
core/module.rb
@@ -5,7 +5,6 @@
|
||||
#
|
||||
module BeEF
|
||||
module Module
|
||||
|
||||
# Checks to see if module key is in configuration
|
||||
# @param [String] mod module key
|
||||
# @return [Boolean] if the module key exists in BeEF's configuration
|
||||
@@ -37,14 +36,14 @@ module BeEF
|
||||
# Gets all module options
|
||||
# @param [String] mod module key
|
||||
# @return [Hash] a hash of all the module options
|
||||
# @note API Fire: get_options
|
||||
# @note API Fire: get_options
|
||||
def self.get_options(mod)
|
||||
if BeEF::API::Registrar.instance.matched? BeEF::API::Module, 'get_options', [mod]
|
||||
options = BeEF::API::Registrar.instance.fire BeEF::API::Module, 'get_options', mod
|
||||
mo = []
|
||||
options.each do |o|
|
||||
unless o[:data].is_a?(Array)
|
||||
print_debug "API Warning: return result for BeEF::Module.get_options() was not an array."
|
||||
print_debug 'API Warning: return result for BeEF::Module.get_options() was not an array.'
|
||||
next
|
||||
end
|
||||
mo += o[:data]
|
||||
@@ -68,9 +67,10 @@ module BeEF
|
||||
# Gets all module payload options
|
||||
# @param [String] mod module key
|
||||
# @return [Hash] a hash of all the module options
|
||||
# @note API Fire: get_options
|
||||
def self.get_payload_options(mod,payload)
|
||||
# @note API Fire: get_options
|
||||
def self.get_payload_options(mod, payload)
|
||||
return [] unless BeEF::API::Registrar.instance.matched?(BeEF::API::Module, 'get_payload_options', [mod, nil])
|
||||
|
||||
BeEF::API::Registrar.instance.fire(BeEF::API::Module, 'get_payload_options', mod, payload)
|
||||
end
|
||||
|
||||
@@ -104,8 +104,8 @@ module BeEF
|
||||
# API call for post-soft-load module
|
||||
BeEF::API::Registrar.instance.fire(BeEF::API::Module, 'post_soft_load', mod)
|
||||
true
|
||||
rescue => e
|
||||
print_error "There was a problem soft loading the module '#{mod}'"
|
||||
rescue StandardError => e
|
||||
print_error "There was a problem soft loading the module '#{mod}': #{e.message}"
|
||||
false
|
||||
end
|
||||
|
||||
@@ -143,7 +143,7 @@ module BeEF
|
||||
# API call for post-hard-load module
|
||||
BeEF::API::Registrar.instance.fire(BeEF::API::Module, 'post_hard_load', mod)
|
||||
true
|
||||
rescue => e
|
||||
rescue StandardError => e
|
||||
BeEF::Core::Configuration.instance.set("#{mod_str}.loaded", false)
|
||||
print_error "There was a problem loading the module '#{mod}'"
|
||||
print_debug "Hard load module syntax error: #{e}"
|
||||
@@ -155,6 +155,7 @@ module BeEF
|
||||
# @return [Boolean] if already hard loaded then true otherwise (see #hard_load)
|
||||
def self.check_hard_load(mod)
|
||||
return true if is_loaded mod
|
||||
|
||||
hard_load mod
|
||||
end
|
||||
|
||||
@@ -162,7 +163,7 @@ module BeEF
|
||||
# @param [Integer] id module database ID
|
||||
# @return [String] module key
|
||||
def self.get_key_by_database_id(id)
|
||||
ret = BeEF::Core::Configuration.instance.get('beef.module').select do |k, v|
|
||||
ret = BeEF::Core::Configuration.instance.get('beef.module').select do |_k, v|
|
||||
v.key?('db') && v['db']['id'].to_i == id.to_i
|
||||
end
|
||||
ret.is_a?(Array) ? ret.first.first : ret.keys.first
|
||||
@@ -172,7 +173,7 @@ module BeEF
|
||||
# @param [Class] c module class
|
||||
# @return [String] module key
|
||||
def self.get_key_by_class(c)
|
||||
ret = BeEF::Core::Configuration.instance.get('beef.module').select do |k, v|
|
||||
ret = BeEF::Core::Configuration.instance.get('beef.module').select do |_k, v|
|
||||
v.key?('class') && v['class'].to_s.eql?(c.to_s)
|
||||
end
|
||||
ret.is_a?(Array) ? ret.first.first : ret.keys.first
|
||||
@@ -180,7 +181,7 @@ module BeEF
|
||||
|
||||
# Checks to see if module class exists
|
||||
# @param [String] mod module key
|
||||
# @return [Boolean] returns whether or not the class exists
|
||||
# @return [Boolean] returns whether or not the class exists
|
||||
def self.exists?(mod)
|
||||
kclass = BeEF::Core::Command.const_get mod.capitalize
|
||||
kclass.is_a? Class
|
||||
@@ -204,7 +205,7 @@ module BeEF
|
||||
return nil unless opts.is_a? Hash
|
||||
|
||||
unless opts.key? 'browser'
|
||||
print_error "BeEF::Module.support() was passed a hash without a valid browser constant"
|
||||
print_error 'BeEF::Module.support() was passed a hash without a valid browser constant'
|
||||
return nil
|
||||
end
|
||||
|
||||
@@ -217,30 +218,26 @@ module BeEF
|
||||
# if k == BeEF::Core::Constants::CommandModule::VERIFIED_NOT_WORKING
|
||||
# rating += 1
|
||||
# end
|
||||
results << {'rating' => 2, 'const' => k}
|
||||
results << { 'rating' => 2, 'const' => k }
|
||||
end
|
||||
when Hash
|
||||
break if opts['browser'] != v.keys.first && v.keys.first != BeEF::Core::Constants::Browsers::ALL
|
||||
|
||||
subv = v[v.keys.first]
|
||||
rating = 1
|
||||
|
||||
# version check
|
||||
if opts.key?('ver')
|
||||
if subv.key?('min_ver')
|
||||
if subv['min_ver'].is_a?(Integer) && opts['ver'].to_i >= subv['min_ver']
|
||||
rating += 1
|
||||
else
|
||||
break
|
||||
end
|
||||
break unless subv['min_ver'].is_a?(Integer) && opts['ver'].to_i >= subv['min_ver']
|
||||
rating += 1
|
||||
end
|
||||
if subv.key?('max_ver')
|
||||
if (subv['max_ver'].is_a?(Integer) && opts['ver'].to_i <= subv['max_ver']) || subv['max_ver'] == 'latest'
|
||||
rating += 1
|
||||
else
|
||||
break
|
||||
end
|
||||
break unless (subv['max_ver'].is_a?(Integer) && opts['ver'].to_i <= subv['max_ver']) || subv['max_ver'] == 'latest'
|
||||
rating += 1
|
||||
end
|
||||
end
|
||||
|
||||
# os check
|
||||
if opts.key?('os') && subv.key?('os')
|
||||
match = false
|
||||
@@ -271,18 +268,16 @@ module BeEF
|
||||
# if k == BeEF::Core::Constants::CommandModule::VERIFIED_NOT_WORKING
|
||||
# rating += 1
|
||||
# end
|
||||
results << {'rating' => rating, 'const' => k}
|
||||
results << { 'rating' => rating, 'const' => k }
|
||||
end
|
||||
end
|
||||
|
||||
next unless v.eql? BeEF::Core::Constants::Browsers::ALL
|
||||
|
||||
rating = 1
|
||||
if k == BeEF::Core::Constants::CommandModule::VERIFIED_NOT_WORKING
|
||||
rating = 1
|
||||
end
|
||||
rating = 1 if k == BeEF::Core::Constants::CommandModule::VERIFIED_NOT_WORKING
|
||||
|
||||
results << {'rating' => rating, 'const' => k}
|
||||
results << { 'rating' => rating, 'const' => k }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -290,9 +285,7 @@ module BeEF
|
||||
|
||||
result = {}
|
||||
results.each do |r|
|
||||
if result == {} || r['rating'] > result['rating']
|
||||
result = {'rating' => r['rating'], 'const' => r['const']}
|
||||
end
|
||||
result = { 'rating' => r['rating'], 'const' => r['const'] } if result == {} || r['rating'] > result['rating']
|
||||
end
|
||||
|
||||
result['const']
|
||||
@@ -308,38 +301,37 @@ module BeEF
|
||||
|
||||
targets = {}
|
||||
target_config.each do |k, v|
|
||||
begin
|
||||
next unless BeEF::Core::Constants::CommandModule.const_defined? "VERIFIED_#{k.upcase}"
|
||||
key = BeEF::Core::Constants::CommandModule.const_get "VERIFIED_#{k.upcase}"
|
||||
targets[key] = [] unless targets.key? key
|
||||
browser = nil
|
||||
next unless BeEF::Core::Constants::CommandModule.const_defined? "VERIFIED_#{k.upcase}"
|
||||
|
||||
case v
|
||||
when String
|
||||
browser = match_target_browser v
|
||||
key = BeEF::Core::Constants::CommandModule.const_get "VERIFIED_#{k.upcase}"
|
||||
targets[key] = [] unless targets.key? key
|
||||
browser = nil
|
||||
|
||||
case v
|
||||
when String
|
||||
browser = match_target_browser v
|
||||
targets[key] << browser if browser
|
||||
when Array
|
||||
v.each do |c|
|
||||
browser = match_target_browser c
|
||||
targets[key] << browser if browser
|
||||
when Array
|
||||
v.each do |c|
|
||||
browser = match_target_browser c
|
||||
targets[key] << browser if browser
|
||||
end
|
||||
when Hash
|
||||
v.each do |k, c|
|
||||
browser = match_target_browser k
|
||||
next unless browser
|
||||
end
|
||||
when Hash
|
||||
v.each do |k, c|
|
||||
browser = match_target_browser k
|
||||
next unless browser
|
||||
|
||||
case c
|
||||
when TrueClass
|
||||
targets[key] << browser
|
||||
when Hash
|
||||
details = match_target_browser_spec c
|
||||
targets[key] << {browser => details} if details
|
||||
end
|
||||
case c
|
||||
when TrueClass
|
||||
targets[key] << browser
|
||||
when Hash
|
||||
details = match_target_browser_spec c
|
||||
targets[key] << { browser => details } if details
|
||||
end
|
||||
end
|
||||
rescue NameError
|
||||
print_error "Module '#{mod}' configuration has invalid target status defined '#{k}'"
|
||||
end
|
||||
rescue NameError
|
||||
print_error "Module '#{mod}' configuration has invalid target status defined '#{k}'"
|
||||
end
|
||||
|
||||
BeEF::Core::Configuration.instance.clear "#{mod_str}.target"
|
||||
@@ -351,7 +343,7 @@ module BeEF
|
||||
# @param [String] v user defined browser
|
||||
# @return [Constant] a BeEF browser constant
|
||||
def self.match_target_browser(v)
|
||||
unless v.class == String
|
||||
unless v.instance_of?(String)
|
||||
print_error 'Invalid datatype passed to BeEF::Module.match_target_browser()'
|
||||
return false
|
||||
end
|
||||
@@ -366,24 +358,20 @@ module BeEF
|
||||
|
||||
# Translates complex browser target configuration
|
||||
# @note Takes a complex user defined browser hash and converts it to applicable BeEF constants
|
||||
# @param [Hash] v user defined browser hash
|
||||
# @param [Hash] v user defined browser hash
|
||||
# @return [Hash] BeEF constants hash
|
||||
def self.match_target_browser_spec(v)
|
||||
unless v.class == Hash
|
||||
unless v.instance_of?(Hash)
|
||||
print_error 'Invalid datatype passed to BeEF::Module.match_target_browser_spec()'
|
||||
return {}
|
||||
end
|
||||
|
||||
browser = {}
|
||||
if v.key?('max_ver') && (v['max_ver'].is_a?(Integer) || v['max_ver'].is_a?(Float) || v['max_ver'] == 'latest')
|
||||
browser['max_ver'] = v['max_ver']
|
||||
end
|
||||
browser['max_ver'] = v['max_ver'] if v.key?('max_ver') && (v['max_ver'].is_a?(Integer) || v['max_ver'].is_a?(Float) || v['max_ver'] == 'latest')
|
||||
|
||||
if v.key?('min_ver') && (v['min_ver'].is_a?(Integer) || v['min_ver'].is_a?(Float))
|
||||
browser['min_ver'] = v['min_ver']
|
||||
end
|
||||
browser['min_ver'] = v['min_ver'] if v.key?('min_ver') && (v['min_ver'].is_a?(Integer) || v['min_ver'].is_a?(Float))
|
||||
|
||||
return browser unless v.key? 'os'
|
||||
return browser unless v.key?('os')
|
||||
|
||||
case v['os']
|
||||
when String
|
||||
@@ -405,7 +393,7 @@ module BeEF
|
||||
# @param [String] v user defined OS string
|
||||
# @return [Constant] BeEF OS Constant
|
||||
def self.match_target_os(v)
|
||||
unless v.class == String
|
||||
unless v.instance_of?(String)
|
||||
print_error 'Invalid datatype passed to BeEF::Module.match_target_os()'
|
||||
return false
|
||||
end
|
||||
@@ -418,13 +406,13 @@ module BeEF
|
||||
false
|
||||
end
|
||||
|
||||
# Executes a module
|
||||
# Executes a module
|
||||
# @param [String] mod module key
|
||||
# @param [String] hbsession hooked browser session
|
||||
# @param [Array] opts array of module execute options (see #get_options)
|
||||
# @return [Integer] the command_id associated to the module execution when info is persisted. nil if there are errors.
|
||||
# @note The return value of this function does not specify if the module was successful, only that it was executed within the framework
|
||||
def self.execute(mod, hbsession, opts=[])
|
||||
def self.execute(mod, hbsession, opts = [])
|
||||
unless is_present(mod) && is_enabled(mod)
|
||||
print_error "Module not found '#{mod}'. Failed to execute module."
|
||||
return nil
|
||||
@@ -445,15 +433,13 @@ module BeEF
|
||||
|
||||
check_hard_load mod
|
||||
command_module = get_definition(mod).new(mod)
|
||||
if command_module.respond_to?(:pre_execute)
|
||||
command_module.pre_execute
|
||||
end
|
||||
command_module.pre_execute if command_module.respond_to?(:pre_execute)
|
||||
merge_options(mod, [])
|
||||
c = BeEF::Core::Models::Command.create(
|
||||
:data => merge_options(mod, opts).to_json,
|
||||
:hooked_browser_id => hb.id,
|
||||
:command_module_id => BeEF::Core::Configuration.instance.get("beef.module.#{mod}.db.id"),
|
||||
:creationdate => Time.new.to_i
|
||||
data: merge_options(mod, opts).to_json,
|
||||
hooked_browser_id: hb.id,
|
||||
command_module_id: BeEF::Core::Configuration.instance.get("beef.module.#{mod}.db.id"),
|
||||
creationdate: Time.new.to_i
|
||||
)
|
||||
c.id
|
||||
end
|
||||
@@ -471,9 +457,7 @@ module BeEF
|
||||
defaults.each do |v|
|
||||
mer = nil
|
||||
opts.each do |o|
|
||||
if v.key?('name') && o.key?('name') && v['name'] == o['name']
|
||||
mer = v.deep_merge o
|
||||
end
|
||||
mer = v.deep_merge o if v.key?('name') && o.key?('name') && v['name'] == o['name']
|
||||
end
|
||||
|
||||
mer.nil? ? merged.push(v) : merged.push(mer)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -13,4 +13,3 @@ require 'core/ruby/object'
|
||||
require 'core/ruby/string'
|
||||
require 'core/ruby/print'
|
||||
require 'core/ruby/hash'
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4,16 +4,14 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
class Module
|
||||
|
||||
# Returns the classes in the current ObjectSpace where this module has been mixed in according to Module#included_modules.
|
||||
# @return [Array] An array of classes
|
||||
def included_in_classes
|
||||
classes = []
|
||||
ObjectSpace.each_object(Class) { |k| classes << k if k.included_modules.include?(self) }
|
||||
|
||||
classes.reverse.inject([]) do |unique_classes, klass|
|
||||
classes.reverse.each_with_object([]) do |klass, unique_classes|
|
||||
unique_classes << klass unless unique_classes.collect { |k| k.to_s }.include?(klass.to_s)
|
||||
unique_classes
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,9 +21,8 @@ class Module
|
||||
modules = []
|
||||
ObjectSpace.each_object(Module) { |k| modules << k if k.included_modules.include?(self) }
|
||||
|
||||
modules.reverse.inject([]) do |unique_modules, klass|
|
||||
modules.reverse.each_with_object([]) do |klass, unique_modules|
|
||||
unique_modules << klass unless unique_modules.collect { |k| k.to_s }.include?(klass.to_s)
|
||||
unique_modules
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user