diff --git a/arerules/alert.json b/arerules/alert.json index fa28a55b0..0f6e537db 100644 --- a/arerules/alert.json +++ b/arerules/alert.json @@ -1,9 +1,5 @@ {"name": "Display an alert", "author": "mgeeky", - "browser": "ALL", - "browser_version": "ALL", - "os": "ALL", - "os_version": "ALL", "modules": [ {"name": "alert_dialog", "condition": null, diff --git a/arerules/coinhive_miner.json b/arerules/coinhive_miner.json index 3c5fad3e8..50a2655a4 100644 --- a/arerules/coinhive_miner.json +++ b/arerules/coinhive_miner.json @@ -1,9 +1,5 @@ {"name": "Start CoinHive JavaScript miner", "author": "bcoles", - "browser": "ALL", - "browser_version": "ALL", - "os": "ALL", - "os_version": "ALL", "modules": [ {"name": "coinhive_miner", "condition": null, diff --git a/arerules/confirm_close_tab.json b/arerules/confirm_close_tab.json index 6ca76158e..c3a4a65a7 100644 --- a/arerules/confirm_close_tab.json +++ b/arerules/confirm_close_tab.json @@ -1,9 +1,5 @@ {"name": "Confirm Close Tab", "author": "mgeeky", - "browser": "ALL", - "browser_version": "ALL", - "os": "ALL", - "os_version": "ALL", "modules": [ {"name": "confirm_close_tab", "condition": null, diff --git a/arerules/ff_osx_extension-dropper.json b/arerules/ff_osx_extension-dropper.json index 2f4bb35f1..4c6b388af 100644 --- a/arerules/ff_osx_extension-dropper.json +++ b/arerules/ff_osx_extension-dropper.json @@ -2,7 +2,6 @@ "name": "Firefox Extension Dropper", "author": "antisnatchor", "browser": "FF", - "browser_version": "ALL", "os": "OSX", "os_version": ">= 10.8", "modules": [{ @@ -17,4 +16,4 @@ "execution_order": [0], "execution_delay": [0], "chain_mode": "sequential" -} \ No newline at end of file +} diff --git a/arerules/get_cookie.json b/arerules/get_cookie.json index fc53c5f4e..42e7cf433 100644 --- a/arerules/get_cookie.json +++ b/arerules/get_cookie.json @@ -1,10 +1,6 @@ { "name": "Get Cookie", "author": "@benichmt1", - "browser": "ALL", - "browser_version": "ALL", - "os": "ALL", - "os_version": "ALL", "modules": [ {"name": "get_cookie", "condition": null, diff --git a/arerules/ie_win_htapowershell.json b/arerules/ie_win_htapowershell.json index a9fd9b22a..d0c477698 100644 --- a/arerules/ie_win_htapowershell.json +++ b/arerules/ie_win_htapowershell.json @@ -2,7 +2,6 @@ "name": "HTA PowerShell", "author": "antisnatchor", "browser": "IE", - "browser_version": "ALL", "os": "Windows", "os_version": ">= 7", "modules": [ diff --git a/arerules/lan_cors_scan.json b/arerules/lan_cors_scan.json index 924519f98..2d75b561f 100644 --- a/arerules/lan_cors_scan.json +++ b/arerules/lan_cors_scan.json @@ -1,9 +1,6 @@ {"name": "LAN CORS Scan", "author": "bcoles", "browser": ["FF", "C"], - "browser_version": "ALL", - "os": "ALL", - "os_version": "ALL", "modules": [ {"name": "get_internal_ip_webrtc", "condition": null, diff --git a/arerules/lan_cors_scan_common.json b/arerules/lan_cors_scan_common.json index f734376f9..51f994e04 100644 --- a/arerules/lan_cors_scan_common.json +++ b/arerules/lan_cors_scan_common.json @@ -1,9 +1,5 @@ {"name": "LAN CORS Scan (Common IPs)", "author": "bcoles", - "browser": "ALL", - "browser_version": "ALL", - "os": "ALL", - "os_version": "ALL", "modules": [ {"name": "cross_origin_scanner_cors", "condition": null, diff --git a/arerules/lan_fingerprint.json b/arerules/lan_fingerprint.json index 7d9eb679c..b9a6313a4 100644 --- a/arerules/lan_fingerprint.json +++ b/arerules/lan_fingerprint.json @@ -1,9 +1,6 @@ {"name": "LAN Fingerprint", "author": "bcoles", "browser": ["FF", "C"], - "browser_version": "ALL", - "os": "ALL", - "os_version": "ALL", "modules": [ {"name": "get_internal_ip_webrtc", "condition": null, diff --git a/arerules/lan_fingerprint_common.json b/arerules/lan_fingerprint_common.json index f0b8479c8..39d94d7f2 100644 --- a/arerules/lan_fingerprint_common.json +++ b/arerules/lan_fingerprint_common.json @@ -1,9 +1,5 @@ {"name": "LAN Fingerprint (Common IPs)", "author": "antisnatchor", - "browser": "ALL", - "browser_version": "ALL", - "os": "ALL", - "os_version": "ALL", "modules": [ {"name": "internal_network_fingerprinting", "condition": null, diff --git a/arerules/lan_flash_scan.json b/arerules/lan_flash_scan.json index b023e0a66..2cf38eb2a 100644 --- a/arerules/lan_flash_scan.json +++ b/arerules/lan_flash_scan.json @@ -1,9 +1,6 @@ {"name": "LAN Flash Scan", "author": "bcoles", "browser": ["FF", "C"], - "browser_version": "ALL", - "os": "ALL", - "os_version": "ALL", "modules": [ {"name": "get_internal_ip_webrtc", "condition": null, diff --git a/arerules/lan_flash_scan_common.json b/arerules/lan_flash_scan_common.json index 7726a5472..859febf95 100644 --- a/arerules/lan_flash_scan_common.json +++ b/arerules/lan_flash_scan_common.json @@ -1,9 +1,6 @@ {"name": "LAN Flash Scan (Common IPs)", "author": "bcoles", "browser": ["FF", "C"], - "browser_version": "ALL", - "os": "ALL", - "os_version": "ALL", "modules": [ {"name": "cross_origin_scanner_flash", "condition": null, diff --git a/arerules/lan_http_scan.json b/arerules/lan_http_scan.json index c1ab7666d..ce900f506 100644 --- a/arerules/lan_http_scan.json +++ b/arerules/lan_http_scan.json @@ -1,9 +1,6 @@ {"name": "LAN HTTP Scan", "author": "bcoles", "browser": ["FF", "C"], - "browser_version": "ALL", - "os": "ALL", - "os_version": "ALL", "modules": [ {"name": "get_internal_ip_webrtc", "condition": null, diff --git a/arerules/lan_http_scan_common.json b/arerules/lan_http_scan_common.json index 87ebd5e02..238a04983 100644 --- a/arerules/lan_http_scan_common.json +++ b/arerules/lan_http_scan_common.json @@ -1,9 +1,5 @@ {"name": "LAN HTTP Scan (Common IPs)", "author": "bcoles", - "browser": "ALL", - "browser_version": "ALL", - "os": "ALL", - "os_version": "ALL", "modules": [ {"name": "get_http_servers", "condition": null, diff --git a/arerules/lan_ping_sweep.json b/arerules/lan_ping_sweep.json index a89ce7def..8f58ccc01 100644 --- a/arerules/lan_ping_sweep.json +++ b/arerules/lan_ping_sweep.json @@ -1,9 +1,6 @@ {"name": "LAN Ping Sweep", "author": "bcoles", "browser": "FF", - "browser_version": "ALL", - "os": "ALL", - "os_version": "ALL", "modules": [ {"name": "get_internal_ip_webrtc", "condition": null, diff --git a/arerules/lan_ping_sweep_common.json b/arerules/lan_ping_sweep_common.json index 3702ecbb7..4c21b7f3d 100644 --- a/arerules/lan_ping_sweep_common.json +++ b/arerules/lan_ping_sweep_common.json @@ -1,9 +1,6 @@ {"name": "LAN Ping Sweep (Common IPs)", "author": "bcoles", "browser": "FF", - "browser_version": "ALL", - "os": "ALL", - "os_version": "ALL", "modules": [ {"name": "ping_sweep", "condition": null, diff --git a/arerules/lan_port_scan.json b/arerules/lan_port_scan.json index 39bd9159c..47d72e25f 100644 --- a/arerules/lan_port_scan.json +++ b/arerules/lan_port_scan.json @@ -1,9 +1,5 @@ {"name": "LAN Port Scan", "author": "aburro & aussieklutz", - "browser": "ALL", - "browser_version": "ALL", - "os": "ALL", - "os_version": "ALL", "modules": [ {"name": "get_internal_ip_webrtc", "condition": null, diff --git a/arerules/lan_sw_port_scan.json b/arerules/lan_sw_port_scan.json index 6856d106e..2405dc778 100644 --- a/arerules/lan_sw_port_scan.json +++ b/arerules/lan_sw_port_scan.json @@ -1,9 +1,5 @@ {"name": "LAN SW Port Scan", "author": "aburro & aussieklutz", - "browser": "ALL", - "browser_version": "ALL", - "os": "ALL", - "os_version": "ALL", "modules": [ {"name": "get_internal_ip_webrtc", "condition": null, diff --git a/arerules/man_in_the_browser.json b/arerules/man_in_the_browser.json index ec18616d5..31f9c370e 100644 --- a/arerules/man_in_the_browser.json +++ b/arerules/man_in_the_browser.json @@ -1,9 +1,5 @@ {"name": "Perform Man-In-The-Browser", "author": "mgeeky", - "browser": "ALL", - "browser_version": "ALL", - "os": "ALL", - "os_version": "ALL", "modules": [ {"name": "man_in_the_browser", "condition": null, diff --git a/arerules/raw_javascript.json b/arerules/raw_javascript.json index 313223496..6e7bca3e7 100644 --- a/arerules/raw_javascript.json +++ b/arerules/raw_javascript.json @@ -1,10 +1,6 @@ { "name": "Raw JavaScript", "author": "wade@bindshell.net", - "browser": "ALL", - "browser_version": "ALL", - "os": "ALL", - "os_version": "ALL", "modules": [ {"name": "raw_javascript", "condition": null, diff --git a/arerules/record_snapshots.json b/arerules/record_snapshots.json index 76054d771..9d0178699 100644 --- a/arerules/record_snapshots.json +++ b/arerules/record_snapshots.json @@ -1,9 +1,5 @@ {"name": "Collects multiple snapshots of the webpage within Same-Origin", "author": "mgeeky", - "browser": ["FF", "C", "O", "IE", "S"], - "browser_version": "ALL", - "os": "ALL", - "os_version": "ALL", "modules": [ {"name": "spyder_eye", "condition": null, diff --git a/arerules/win_fake_malware.json b/arerules/win_fake_malware.json index 194f98618..f0daf9580 100644 --- a/arerules/win_fake_malware.json +++ b/arerules/win_fake_malware.json @@ -2,10 +2,7 @@ { "name": "Windows Fake Malware", "author": "bcoles", - "browser": "ALL", - "browser_version": "ALL", "os": "Windows", - "os_version": "ALL", "modules": [ { "name": "blockui", diff --git a/core/main/autorun_engine/engine.rb b/core/main/autorun_engine/engine.rb index ce73fb2aa..903536597 100644 --- a/core/main/autorun_engine/engine.rb +++ b/core/main/autorun_engine/engine.rb @@ -19,24 +19,231 @@ module BeEF @debug_on = @config.get('beef.debug') @VERSION = ['<', '<=', '==', '>=', '>', 'ALL'] - @VERSION_STR = %w[XP Vista] + @VERSION_STR = %w[XP Vista 7] + end + + # Checks if there are any ARE rules to be triggered for the specified hooked browser. + # + # Returns an array with rule IDs that matched and should be triggered. + # if rule_id is specified, checks will be executed only against the specified rule (useful + # for dynamic triggering of new rulesets ar runtime) + def find_matching_rules_for_zombie(browser, browser_version, os, os_version) + rules = BeEF::Core::Models::Rule.all + + return if rules.nil? + return if rules.empty? + + # TODO: handle cases where there are multiple ARE rules for the same hooked browser. + # maybe rules need to have priority or something? + + print_info '[ARE] Checking if any defined rules should be triggered on target.' + + match_rules = [] + rules.each do |rule| + next unless zombie_matches_rule?(browser, browser_version, os, os_version, rule) + + match_rules.push(rule.id) + print_more("Hooked browser and OS match rule: #{rule.name}.") + end + + print_more("Found [#{match_rules.length}/#{rules.length}] ARE rules matching the hooked browser.") + + match_rules + end + + # @return [Boolean] + # Note: browser version checks are supporting only major versions, ex: C 43, IE 11 + # Note: OS version checks are supporting major/minor versions, ex: OSX 10.10, Windows 8.1 + def zombie_matches_rule?(browser, browser_version, os, os_version, rule) + return false if rule.nil? + + unless zombie_browser_matches_rule?(browser, browser_version, rule) + print_debug("Browser version check -> (hook) #{browser_version} #{rule.browser_version} (rule) : does not match") + return false + end + + print_debug("Browser version check -> (hook) #{browser_version} #{rule.browser_version} (rule) : matched") + + unless zombie_os_matches_rule?(os, os_version, rule) + print_debug("OS version check -> (hook) #{os_version} #{rule.os_version} (rule): does not match") + return false + end + + print_debug("OS version check -> (hook) #{os_version} #{rule.os_version} (rule): matched") + + true + rescue StandardError => e + print_error e.message + print_debug e.backtrace.join("\n") + end + + # @return [Boolean] + # TODO: This should be updated to support matching multiple OS (like the browser check below) + def zombie_os_matches_rule?(os, os_version, rule) + return false if rule.nil? + + return false unless rule.os == 'ALL' || os == rule.os + + # check if the OS versions match + os_ver_rule_cond = rule.os_version.split(' ').first + + return true if os_ver_rule_cond == 'ALL' + + return false unless @VERSION.include?(os_ver_rule_cond) || @VERSION_STR.include?(os_ver_rule_cond) + + os_ver_rule_maj = rule.os_version.split(' ').last.split('.').first + os_ver_rule_min = rule.os_version.split(' ').last.split('.').last + + if os_ver_rule_maj == 'XP' + os_ver_rule_maj = 5 + os_ver_rule_min = 0 + elsif os_ver_rule_maj == 'Vista' + os_ver_rule_maj = 6 + os_ver_rule_min = 0 + elsif os_ver_rule_maj == '7' + os_ver_rule_maj = 6 + os_ver_rule_min = 0 + end + + # Most of the times Linux/*BSD OS doesn't return any version + # (TODO: improve OS detection on these operating systems) + if !os_version.nil? && !@VERSION_STR.include?(os_version) + os_ver_hook_maj = os_version.split('.').first + os_ver_hook_min = os_version.split('.').last + + # the following assignments to 0 are need for later checks like: + # 8.1 >= 7, because if the version doesn't have minor versions, maj/min are the same + os_ver_hook_min = 0 if os_version.split('.').length == 1 + os_ver_rule_min = 0 if rule.os_version.split('.').length == 1 + else + # XP is Windows 5.0 and Vista is Windows 6.0. Easier for comparison later on. + # TODO: BUG: This will fail horribly if the target OS is Windows 7 or newer, + # as no version normalization is performed. + # TODO: Update this for every OS since Vista/7 ... + if os_version == 'XP' + os_ver_hook_maj = 5 + os_ver_hook_min = 0 + elsif os_version == 'Vista' + os_ver_hook_maj = 6 + os_ver_hook_min = 0 + elsif os_version == '7' + os_ver_hook_maj = 6 + os_ver_hook_min = 0 + end + end + + if !os_version.nil? || rule.os_version != 'ALL' + os_major_version_match = compare_versions(os_ver_hook_maj.to_s, os_ver_rule_cond, os_ver_rule_maj.to_s) + os_minor_version_match = compare_versions(os_ver_hook_min.to_s, os_ver_rule_cond, os_ver_rule_min.to_s) + return false unless (os_major_version_match && os_minor_version_match) + end + + true + rescue StandardError => e + print_error e.message + print_debug e.backtrace.join("\n") + end + + # @return [Boolean] + def zombie_browser_matches_rule?(browser, browser_version, rule) + return false if rule.nil? + + b_ver_cond = rule.browser_version.split(' ').first + + return false unless @VERSION.include?(b_ver_cond) + + b_ver = rule.browser_version.split(' ').last + + return false unless BeEF::Filters.is_valid_browserversion?(b_ver) + + # check if rule specifies multiple browsers + if rule.browser =~ /\A[A-Z]+\Z/ + return false unless rule.browser == 'ALL' || browser == rule.browser + + # check if the browser version matches + browser_version_match = compare_versions(browser_version.to_s, b_ver_cond, b_ver.to_s) + return false unless browser_version_match + else + browser_match = false + rule.browser.gsub(/[^A-Z,]/i, '').split(',').each do |b| + if b == browser || b == 'ALL' + browser_match = true + break + end + end + return false unless browser_match + end + + true + rescue StandardError => e + print_error e.message + print_debug e.backtrace.join("\n") end # Check if the hooked browser type/version and OS type/version match any Rule-sets - # stored in the BeEF::Core::AutorunEngine::Models::Rule database table + # stored in the BeEF::Core::Models::Rule database table # If one or more Rule-sets do match, trigger the module chain specified - def run(hb_id, browser_name, browser_version, os_name, os_version) + def find_and_run_all_matching_rules_for_zombie(hb_id) + return if hb_id.nil? + + hb_details = BeEF::Core::Models::BrowserDetails + browser_name = hb_details.get(hb_id, 'browser.name') + browser_version = hb_details.get(hb_id, 'browser.version') + os_name = hb_details.get(hb_id, 'host.os.name') + os_version = hb_details.get(hb_id, 'host.os.version') + are = BeEF::Core::AutorunEngine::Engine.instance - match_rules = are.match(browser_name, browser_version, os_name, os_version) - are.trigger(match_rules, hb_id) if !match_rules.nil? && match_rules.length > 0 + rules = are.find_matching_rules_for_zombie(browser_name, browser_version, os_name, os_version) + + return if rules.nil? + return if rules.empty? + + are.run_rules_on_zombie(rules, hb_id) end + # Run the specified rule IDs on the specified zombie ID + # only if the rules match. + def run_matching_rules_on_zombie(rule_ids, hb_id) + return if rule_ids.nil? + return if hb_id.nil? + + rule_ids = [rule_ids.to_i] if rule_ids.is_a?(String) + + hb_details = BeEF::Core::Models::BrowserDetails + browser_name = hb_details.get(hb_id, 'browser.name') + browser_version = hb_details.get(hb_id, 'browser.version') + os_name = hb_details.get(hb_id, 'host.os.name') + os_version = hb_details.get(hb_id, 'host.os.version') + + are = BeEF::Core::AutorunEngine::Engine.instance + rules = are.find_matching_rules_for_zombie(browser_name, browser_version, os_name, os_version) + + return if rules.nil? + return if rules.empty? + + new_rules = [] + rules.each do |rule| + new_rules << rule if rule_ids.include?(rule) + end + + return if new_rules.empty? + + are.run_rules_on_zombie(new_rules, hb_id) + end + + # Run the specified rule IDs on the specified zombie ID + # regardless of whether the rules match. # Prepare and return the JavaScript of the modules to be sent. # It also updates the rules ARE execution table with timings - def trigger(rule_ids, hb_id) + def run_rules_on_zombie(rule_ids, hb_id) + return if rule_ids.nil? + return if hb_id.nil? + hb = BeEF::HBManager.get_by_id(hb_id) hb_session = hb.session + rule_ids = [rule_ids] if rule_ids.is_a?(Integer) + rule_ids.each do |rule_id| rule = BeEF::Core::Models::Rule.find(rule_id) modules = JSON.parse(rule.modules) @@ -86,6 +293,8 @@ module BeEF next end + print_more "Triggering rules #{rule_ids} on HB #{hb_id}" + are_exec = BeEF::Core::Models::Execution.new( session_id: hb_session, mod_count: modules.length, @@ -96,12 +305,11 @@ module BeEF rule_id: rule_id ) are_exec.save! - - # Once Engine.check() verified that the hooked browser match a Rule, trigger the Rule ;-) - print_more "Triggering ruleset #{rule_ids} on HB #{hb_id}" end end + private + # Wraps module bodies in their own function, using setTimeout to trigger them with an eventual delay. # Launch order is also taken care of. # - sequential chain with delays (setTimeout stuff) @@ -345,20 +553,18 @@ module BeEF print_error '[ARE] Could not find module end index' if wrapper_end_index.nil? cleaned_cmd_body = cmd_body.slice(wrapper_start_index..-(wrapper_end_index + 1)).join("\n") + print_error '[ARE] No command to send' if cleaned_cmd_body.eql?('') # check if <> should be replaced with a variable name (depending if the variable is a string or number) - if replace_input - if cleaned_cmd_body.include?('"<>"') - final_cmd_body = cleaned_cmd_body.gsub('"<>"', 'mod_input') - elsif cleaned_cmd_body.include?('\'<>\'') - final_cmd_body = cleaned_cmd_body.gsub('\'<>\'', 'mod_input') - elsif cleaned_cmd_body.include?('<>') - final_cmd_body = cleaned_cmd_body.gsub('\'<>\'', 'mod_input') - else - return cleaned_cmd_body - end - final_cmd_body + return cleaned_cmd_body unless replace_input + + if cleaned_cmd_body.include?('"<>"') + cleaned_cmd_body.gsub('"<>"', 'mod_input') + elsif cleaned_cmd_body.include?('\'<>\'') + cleaned_cmd_body.gsub('\'<>\'', 'mod_input') + elsif cleaned_cmd_body.include?('<>') + cleaned_cmd_body.gsub('\'<>\'', 'mod_input') else cleaned_cmd_body end @@ -366,129 +572,6 @@ module BeEF print_error "[ARE] There is likely a problem with the module's command.js parsing. Check Engine.clean_command_body. #{e.message}" end - # Checks if there are any ARE rules to be triggered for the specified hooked browser - # - # Note: browser version checks are supporting only major versions, ex: C 43, IE 11 - # Note: OS version checks are supporting major/minor versions, ex: OSX 10.10, Windows 8.1 - # - # Returns an array with rule IDs that matched and should be triggered. - # if rule_id is specified, checks will be executed only against the specified rule (useful - # for dynamic triggering of new rulesets ar runtime) - def match(browser, browser_version, os, os_version, rule_id = nil) - match_rules = [] - rules = if rule_id.nil? - BeEF::Core::Models::Rule.all - else - [BeEF::Core::Models::Rule.find(rule_id)] - end - return nil if rules.nil? - return nil unless rules.length > 0 - - print_info '[ARE] Checking if any defined rules should be triggered on target.' - # TODO: handle cases where there are multiple ARE rules for the same hooked browser. - # TODO the above works well, but maybe rules need to have priority or something? - rules.each do |rule| - browser_match = false - os_match = false - - b_ver_cond = rule.browser_version.split(' ').first - b_ver = rule.browser_version.split(' ').last - - os_ver_rule_cond = rule.os_version.split(' ').first - os_ver_rule_maj = rule.os_version.split(' ').last.split('.').first - os_ver_rule_min = rule.os_version.split(' ').last.split('.').last - - # Most of the times Linux/*BSD OS doesn't return any version - # (TODO: improve OS detection on these operating systems) - if !os_version.nil? && !@VERSION_STR.include?(os_version) - os_ver_hook_maj = os_version.split('.').first - os_ver_hook_min = os_version.split('.').last - - # the following assignments to 0 are need for later checks like: - # 8.1 >= 7, because if the version doesn't have minor versions, maj/min are the same - os_ver_hook_min = 0 if os_version.split('.').length == 1 - os_ver_rule_min = 0 if rule.os_version.split('.').length == 1 - else - # most probably Windows XP or Vista. the following is a hack as Microsoft had the brilliant idea - # to switch from strings to numbers in OS versioning. To prevent rewriting code later on, - # we say that XP is Windows 5.0 and Vista is Windows 6.0. Easier for comparison later on. - if os_version == 'XP' - os_ver_hook_maj = 5 - os_ver_hook_min = 0 - end - if os_version == 'Vista' - os_ver_hook_maj = 6 - os_ver_hook_min = 0 - end - end - - if os_ver_rule_maj == 'XP' - os_ver_rule_maj = 5 - os_ver_rule_min = 0 - end - if os_ver_rule_maj == 'Vista' - os_ver_rule_maj = 6 - os_ver_rule_min = 0 - end - - next unless @VERSION.include?(b_ver_cond) - next unless BeEF::Filters.is_valid_browserversion?(b_ver) - - next unless @VERSION.include?(os_ver_rule_cond) || @VERSION_STR.include?(os_ver_rule_cond) - - # os_ver without checks as it can be very different or even empty, for instance on linux/bsd) - - # skip rule unless the browser matches - browser_match = false - # check if rule specifies multiple browsers - if rule.browser =~ /\A[A-Z]+\Z/ - next unless rule.browser == 'ALL' || browser == rule.browser - - # check if the browser version matches - browser_version_match = compare_versions(browser_version.to_s, b_ver_cond, b_ver.to_s) - browser_match = if browser_version_match - true - else - false - end - print_more "Browser version check -> (hook) #{browser_version} #{rule.browser_version} (rule) : #{browser_version_match}" - else - rule.browser.gsub(/[^A-Z,]/i, '').split(',').each do |b| - browser_match = true if b == browser || b == 'ALL' - end - # else, only one browser - end - next unless browser_match - - # skip rule unless the OS matches - next unless rule.os == 'ALL' || os == rule.os - - # check if the OS versions match - if !os_version.nil? || rule.os_version != 'ALL' - os_major_version_match = compare_versions(os_ver_hook_maj.to_s, os_ver_rule_cond, os_ver_rule_maj.to_s) - os_minor_version_match = compare_versions(os_ver_hook_min.to_s, os_ver_rule_cond, os_ver_rule_min.to_s) - else - # os_version_match = true if (browser doesn't return an OS version || rule OS version is ALL ) - os_major_version_match = true - os_minor_version_match = true - end - - os_match = true if os_ver_rule_cond == 'ALL' || (os_major_version_match && os_minor_version_match) - print_more "OS version check -> (hook) #{os_version} #{rule.os_version} (rule): #{os_major_version_match && os_minor_version_match}" - - if browser_match && os_match - print_more "Hooked browser and OS type/version MATCH rule: #{rule.name}." - match_rules.push(rule.id) - end - rescue StandardError => e - print_error e.message - print_debug e.backtrace.join("\n") - end - print_more "Found [#{match_rules.length}/#{rules.length}] ARE rules matching the hooked browser type/version." - - match_rules - end - # compare versions def compare_versions(ver_a, cond, ver_b) return true if cond == 'ALL' diff --git a/core/main/handlers/browserdetails.rb b/core/main/handlers/browserdetails.rb index d9ed6cf78..d0a6f6b08 100644 --- a/core/main/handlers/browserdetails.rb +++ b/core/main/handlers/browserdetails.rb @@ -28,11 +28,13 @@ module BeEF # validate hook session value session_id = get_param(@data, 'beefhook') + print_debug "[INIT] Processing Browser Details for session #{session_id}" unless BeEF::Filters.is_valid_hook_session_id?(session_id) - (err_msg 'session id is invalid' - return) + err_msg 'session id is invalid' + return end + hooked_browser = HB.where(session: session_id).first return unless hooked_browser.nil? # browser is already registered with framework @@ -567,7 +569,7 @@ module BeEF # check if any ARE rules shall be triggered only if the channel is != WebSockets (XHR). If the channel # is WebSockets, then ARe rules are triggered after channel is established. - BeEF::Core::AutorunEngine::Engine.instance.run(zombie.id, browser_name, browser_version, os_name, os_version) unless config.get('beef.http.websocket.enable') + BeEF::Core::AutorunEngine::Engine.instance.find_and_run_all_matching_rules_for_zombie(zombie.id) unless config.get('beef.http.websocket.enable') end def get_param(query, key) diff --git a/core/main/models/legacybrowseruseragents.rb b/core/main/models/legacybrowseruseragents.rb index 6ffff9bc2..7a28a3b05 100644 --- a/core/main/models/legacybrowseruseragents.rb +++ b/core/main/models/legacybrowseruseragents.rb @@ -10,14 +10,13 @@ module BeEF # Objects stores known 'legacy' browser User Agents. # # This table is used to determine if a hooked browser is a 'legacy' - # browser and therefore which version of the hook file to generate and use + # browser. # # TODO: make it an actual table # module LegacyBrowserUserAgents def self.user_agents [ - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:100.0) Gecko/20100101 Firefox/100.0' ] end end diff --git a/core/main/network_stack/websocket/websocket.rb b/core/main/network_stack/websocket/websocket.rb index 65fa36807..4fbc8d8a8 100644 --- a/core/main/network_stack/websocket/websocket.rb +++ b/core/main/network_stack/websocket/websocket.rb @@ -24,6 +24,8 @@ module BeEF MOUNTS = BeEF::Core::Server.instance.mounts def initialize + return unless @@config.get('beef.websocket.enable') + secure = @@config.get('beef.http.websocket.secure') # @note Start a WSS server socket @@ -110,11 +112,7 @@ module BeEF next end - browser_name = BeEF::Core::Models::BrowserDetails.get(hb_session, 'browser.name') - browser_version = BeEF::Core::Models::BrowserDetails.get(hb_session, 'browser.version') - os_name = BeEF::Core::Models::BrowserDetails.get(hb_session, 'host.os.name') - os_version = BeEF::Core::Models::BrowserDetails.get(hb_session, 'host.os.version') - BeEF::Core::AutorunEngine::Engine.instance.run(hooked_browser.id, browser_name, browser_version, os_name, os_version) + BeEF::Core::AutorunEngine::Engine.instance.find_and_run_all_matching_rules_for_zombie(hooked_browser.id) next end diff --git a/core/main/rest/handlers/autorun_engine.rb b/core/main/rest/handlers/autorun_engine.rb index fcbf4df8f..af9e00628 100644 --- a/core/main/rest/handlers/autorun_engine.rb +++ b/core/main/rest/handlers/autorun_engine.rb @@ -19,106 +19,117 @@ module BeEF 'Expires' => '0' end - # Add a new ruleset. Returne the rule_id if request was successful + # + # Get all rules + # + get '/rules' do + rules = BeEF::Core::Models::Rule.all + { + 'success' => true, + 'count' => rules.length, + 'rules' => rules.to_json + }.to_json + rescue StandardError => e + print_error("Internal error while retrieving Autorun rules: #{e.message}") + halt 500 + end + + # Returns a specific rule by ID + get '/rule/:rule_id' do + rule_id = params[:rule_id] + + rule = BeEF::Core::Models::Rule.find(rule_id) + raise InvalidParameterError, 'id' if rule.nil? + + halt 404 if rule.empty? + + rule.to_json + rescue InvalidParameterError => e + print_error e.message + halt 400 + rescue StandardError => e + print_error "Internal error while retrieving Autorun rule with id #{rule_id} (#{e.message})" + halt 500 + end + + # + # Add a new ruleset. Return the rule_id if request was successful. + # @return [Integer] rule ID + # post '/rule/add' do request.body.rewind - begin - data = JSON.parse request.body.read - rloader = BeEF::Core::AutorunEngine::RuleLoader.instance - rloader.load(data).to_json - rescue StandardError => e - err = 'Malformed JSON ruleset.' - print_error "[ARE] ERROR: #{e.message}" - { 'success' => false, 'error' => err }.to_json - end - end - - # Delete a ruleset - get '/rule/delete/:rule_id' do - rule_id = params[:rule_id] - rule = BeEF::Core::AutorunEngine::Models::Rule.find(rule_id) - rule.destroy - { 'success' => true }.to_json + data = JSON.parse request.body.read + rloader = BeEF::Core::AutorunEngine::RuleLoader.instance + rloader.load_rule_json(data).to_json rescue StandardError => e - err = 'Error getting rule.' - print_error "[ARE] ERROR: #{e.message}" - { 'success' => false, 'error' => err }.to_json + print_error "Internal error while adding Autorun rule: #{e.message}" + { 'success' => false, 'error' => e.message }.to_json end - # Trigger a specified rule_id on online hooked browsers. Offline hooked browsers are ignored - get '/rule/trigger/:rule_id' do + # + # Delete a ruleset + # + delete '/rule/:rule_id' do + rule_id = params[:rule_id] + rule = BeEF::Core::Models::Rule.find(rule_id) + raise InvalidParameterError, 'id' if rule.nil? + rule.destroy + + { 'success' => true }.to_json + rescue InvalidParameterError => e + print_error e.message + halt 400 + rescue StandardError => e + print_error "Internal error while deleting Autorun rule: #{e.message}" + { 'success' => false, 'error' => e.message }.to_json + end + + # + # Run a specified rule on all online hooked browsers (if the zombie matches the rule). + # Offline hooked browsers are ignored + # + get '/run/:rule_id' do rule_id = params[:rule_id] online_hooks = BeEF::Core::Models::HookedBrowser.where('lastseen >= ?', (Time.new.to_i - 15)) - are = BeEF::Core::AutorunEngine::Engine.instance if online_hooks.nil? - { 'success' => false, 'error' => 'There are currently no hooked browsers online.' }.to_json - else - online_hooks.each do |hb| - hb_details = BeEF::Core::Models::BrowserDetails - browser_name = hb_details.get(hb.session, 'browser.name') - browser_version = hb_details.get(hb.session, 'browser.version') - os_name = hb_details.get(hb.session, 'host.os.name') - os_version = hb_details.get(hb.session, 'host.os.version') - - match_rules = are.match(browser_name, browser_version, os_name, os_version, rule_id) - are.trigger(match_rules, hb.id) if match_rules.length > 0 - end - { 'success' => true }.to_json + return { 'success' => false, 'error' => 'There are currently no hooked browsers online.' }.to_json end + + are = BeEF::Core::AutorunEngine::Engine.instance + online_hooks.each do |hb| + are.run_matching_rules_on_zombie(rule_id, hb.id) + end + + { 'success' => true }.to_json rescue StandardError => e - err = 'Malformed JSON ruleset.' - print_error "[ARE] ERROR: #{e.message}" - { 'success' => false, 'error' => err }.to_json + msg = "Could not trigger rules: #{e.message}" + print_error "[ARE] #{msg}" + { 'success' => false, 'error' => msg }.to_json end - # Delete a ruleset - get '/rule/list/:rule_id' do + # + # Run a specified rule on the specified hooked browser. + # + get '/run/:rule_id/:hb_id' do rule_id = params[:rule_id] - if rule_id == 'all' - result = [] - rules = BeEF::Core::AutorunEngine::Models::Rule.all - rules.each do |rule| - { - 'id' => rule.id, - 'name' => rule.name, - 'author' => rule.author, - 'browser' => rule.browser, - 'browser_version' => rule.browser_version, - 'os' => rule.os, - 'os_version' => rule.os_version, - 'modules' => rule.modules, - 'execution_order' => rule.execution_order, - 'execution_delay' => rule.execution_delay, - 'chain_mode' => rule.chain_mode - } - result.push rule - end - else - result = nil - rule = BeEF::Core::AutorunEngine::Models::Rule.get(rule_id) - unless rule.nil? - result = { - 'id' => rule.id, - 'name' => rule.name, - 'author' => rule.author, - 'browser' => rule.browser, - 'browser_version' => rule.browser_version, - 'os' => rule.os, - 'os_version' => rule.os_version, - 'modules' => rule.modules, - 'execution_order' => rule.execution_order, - 'execution_delay' => rule.execution_delay, - 'chain_mode' => rule.chain_mode - } - end - end - { 'success' => true, 'rules' => result }.to_json + hb_id = params[:hb_id] + + raise InvalidParameterError, 'rule_id' if rule_id.nil? + raise InvalidParameterError, 'hb_id' if hb_id.nil? + + are = BeEF::Core::AutorunEngine::Engine.instance + are.run_matching_rules_on_zombie(rule_id, hb_id) + + { 'success' => true }.to_json + rescue InvalidParameterError => e + print_error e.message + halt 400 rescue StandardError => e - err = 'Error getting rule(s)' - print_error "[ARE] ERROR: #{e.message}" - { 'success' => false, 'error' => err }.to_json + msg = "Could not trigger rule: #{e.message}" + print_error "[ARE] #{msg}" + { 'success' => false, 'error' => msg }.to_json end end end diff --git a/tools/rest_api_examples/autorun b/tools/rest_api_examples/autorun new file mode 100644 index 000000000..d38b96b5f --- /dev/null +++ b/tools/rest_api_examples/autorun @@ -0,0 +1,102 @@ +#!/usr/bin/env ruby +# browser-details - Example BeEF RESTful API script +# Retrieves all Autorun rules, adds a rule, runs it on all online browsers, then deletes it +# Refer to the wiki for info: https://github.com/beefproject/beef/wiki/BeEF-RESTful-API +## +require 'rest-client' +require 'json' +require 'optparse' +require 'pp' +require './lib/string' # colored strings +require './lib/print' # print wrappers +require './lib/beef_rest_api' + +if ARGV.length == 0 + puts "#{$0}:" + puts "| Example BeEF RESTful API script" + puts "| Use --help for help" + puts "|_ Use verbose mode (-v) and debug mode (-d) for more output" + exit 1 +end + +# API config +proto = 'http' +host = '127.0.0.1' +port = '3000' +user = 'beef' +pass = 'beef' + +# Command line options +@debug = false +@verbose = false +OptionParser.new do |opts| + opts.on('-h', '--help', 'Shows this help screen') do + puts opts + exit 1 + end + opts.on('--host HOST', "Set BeEF host (default: #{host})") do |h| + host = h + end + opts.on('--port PORT', "Set BeEF port (default: #{port})") do |p| + port = p + end + opts.on('--user USERNAME', "Set BeEF username (default: #{user})") do |u| + user = u + end + opts.on('--pass PASSWORD', "Set BeEF password (default: #{pass})") do |p| + pass = p + end + opts.on('--ssl', 'Use HTTPS') do + proto = 'https' + end + opts.on('-v', '--verbose', 'Enable verbose output') do + @verbose = true + end + opts.on('-d', '--debug', 'Enable debug output') do + @debug = true + end +end.parse! + +@api = BeefRestAPI.new proto, host, port, user, pass + +# Retrieve the RESTful API token +print_status "Authenticating to: #{proto}://#{host}:#{port}" +@api.auth + +# Retrieve BeEF version +@api.version + +print_status("Retrieving Autorun rules") +rules = @api.autorun_rules +print_debug(rules) + +print_status("Adding a rule") + +res = @api.autorun_add_rule({ + "name": "Say Hello", + "author": "REST API", + "modules": [ + { + "name": "alert_dialog", + "options": { + "text":"Hello from REST API" + } + } + ], + "execution_order": [0], + "execution_delay": [0] +}) + +print_debug(res) + +rule_id = res['rule_id'] + +unless rule_id.nil? + print_status "Running rule #{rule_id} on all browsers" + res = @api.autorun_run_rule_on_all_browsers(rule_id) + print_debug(res) + + print_status("Deleting rule #{rule_id}") + res = @api.autorun_delete_rule(rule_id) + print_debug(res) +end diff --git a/tools/rest_api_examples/lib/beef_rest_api.rb b/tools/rest_api_examples/lib/beef_rest_api.rb index 46bae859a..b3705c089 100644 --- a/tools/rest_api_examples/lib/beef_rest_api.rb +++ b/tools/rest_api_examples/lib/beef_rest_api.rb @@ -420,20 +420,25 @@ end # add a rule def dns_add_rule(dns_pattern, dns_resource, dns_response) dns_response = [dns_response] if dns_response.is_a?(String) - begin - print_verbose "Adding DNS rule [pattern: #{dns_pattern}, resource: #{dns_resource}, response: #{dns_response}]" - response = RestClient.post "#{@url}dns/rule?token=#{@token}", { - 'pattern' => dns_pattern, - 'resource' => dns_resource, - 'response' => dns_response }.to_json, - :content_type => :json, - :accept => :json - details = JSON.parse(response.body) - print_good "Added rule [id: #{details['id']}]" - details - rescue => e - print_error "Could not add DNS rule: #{e.message}" + print_verbose "Adding DNS rule [pattern: #{dns_pattern}, resource: #{dns_resource}, response: #{dns_response}]" + response = RestClient.post "#{@url}dns/rule?token=#{@token}", { + 'pattern' => dns_pattern, + 'resource' => dns_resource, + 'response' => dns_response }.to_json, + :content_type => :json, + :accept => :json + details = JSON.parse(response.body) + rule_id = details['id'] + + if rule_id.nil? + print_error("Could not add DNS rule: #{details['error']}") + return details end + + print_good "Added rule [id: #{details['id']}]" + details +rescue => e + print_error "Could not add DNS rule: #{e.message}" end # get rule details @@ -451,14 +456,78 @@ end # delete a rule def dns_delete_rule(id) - begin - response = RestClient.delete "#{@url}dns/rule/#{id}?token=#{@token}" - details = JSON.parse(response.body) - print_good "Deleted rule [id: #{id}]" - details - rescue => e - print_error "Could not delete DNS rule: #{e.message}" + response = RestClient.delete "#{@url}dns/rule/#{id}?token=#{@token}" + details = JSON.parse(response.body) + print_good "Deleted rule [id: #{id}]" + details +rescue => e + print_error "Could not delete DNS rule: #{e.message}" +end + + +################################################################################ +### Autorun +################################################################################ + +def autorun_rules + print_verbose "Retrieving Autorun rules" + response = RestClient.get "#{@url}autorun/rules", {:params => {:token => @token}} + details = JSON.parse(response.body) + print_good("Retrieved #{details['count']} rules") + details +rescue => e + print_error("Could not retrieve Autorun rules: #{e.message}") +end + +def autorun_delete_rule(id) + print_verbose "Deleting Autorun rule with ID: #{id}" + response = RestClient.delete "#{@url}autorun/rule/#{id}?token=#{@token}" + details = JSON.parse(response.body) + print_good("Deleted rule [id: #{id}]") + details +rescue => e + print_error("Could not delete Autorun rule: #{e.message}") +end + +def autorun_add_rule(data) + print_verbose "Adding Autorun rule: #{data}" + response = RestClient.post "#{@url}autorun/rule/add?token=#{@token}", + data.to_json, + :content_type => :json, + :accept => :json + details = JSON.parse(response.body) + rule_id = details['rule_id'] + + if rule_id.nil? + print_error("Could not add Autorun rule: #{details['error']}") + return details end + + print_good("Added rule [id: #{details['id']}]") + details +rescue => e + print_error("Could not add Autorun rule: #{e.message}") +end + +def autorun_run_rule_on_all_browsers(rule_id) + print_verbose "Running Autorun rule #{rule_id} on all browsers" + response = RestClient.get "#{@url}autorun/run/#{rule_id}", {:params => {:token => @token}} + details = JSON.parse(response.body) + print_debug details + print_good('Done') + details +rescue => e + print_error "Could not run Autorun rule #{rule_id}: #{e.message}" +end + +def autorun_run_rule_on_browser(rule_id, hb_id) + print_verbose "Running Autorun rule #{rule_id} on browser #{hb_id}" + response = RestClient.get "#{@url}autorun/run/#{rule_id}/#{hb_id}", {:params => {:token => @token}} + details = JSON.parse(response.body) + print_good('Done') + details +rescue => e + print_error "Could not run Autorun rule #{rule_id}: #{e.message}" end