mirror of
https://github.com/beefproject/beef
synced 2026-06-08 13:15:56 +00:00
Phase 4: Remove Server-Side Core
This commit is contained in:
@@ -266,11 +266,6 @@ BeEF::Core::GeoIp.instance
|
|||||||
#
|
#
|
||||||
BeEF::API::Registrar.instance.fire(BeEF::API::Server, 'pre_http_start', http_hook_server)
|
BeEF::API::Registrar.instance.fire(BeEF::API::Server, 'pre_http_start', http_hook_server)
|
||||||
|
|
||||||
#
|
|
||||||
# @note Load any ARE (Autorun Rule Engine) rules scanning the <beef_root>/arerules/enabled directory
|
|
||||||
#
|
|
||||||
BeEF::Core::AutorunEngine::RuleLoader.instance.load_directory
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# @note Start the WebSocket server
|
# @note Start the WebSocket server
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -29,11 +29,6 @@ require 'core/main/network_stack/handlers/raw'
|
|||||||
require 'core/main/network_stack/assethandler'
|
require 'core/main/network_stack/assethandler'
|
||||||
require 'core/main/network_stack/api'
|
require 'core/main/network_stack/api'
|
||||||
|
|
||||||
# @note Include the autorun engine
|
|
||||||
require 'core/main/autorun_engine/parser'
|
|
||||||
require 'core/main/autorun_engine/engine'
|
|
||||||
require 'core/main/autorun_engine/rule_loader'
|
|
||||||
|
|
||||||
## @note Include helpers
|
## @note Include helpers
|
||||||
require 'core/module'
|
require 'core/module'
|
||||||
require 'core/modules'
|
require 'core/modules'
|
||||||
@@ -49,7 +44,6 @@ require 'core/main/rest/handlers/categories'
|
|||||||
require 'core/main/rest/handlers/logs'
|
require 'core/main/rest/handlers/logs'
|
||||||
require 'core/main/rest/handlers/admin'
|
require 'core/main/rest/handlers/admin'
|
||||||
require 'core/main/rest/handlers/server'
|
require 'core/main/rest/handlers/server'
|
||||||
require 'core/main/rest/handlers/autorun_engine'
|
|
||||||
require 'core/main/rest/api'
|
require 'core/main/rest/api'
|
||||||
|
|
||||||
## @note Include Websocket
|
## @note Include Websocket
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ require 'core/main/models/command'
|
|||||||
require 'core/main/models/result'
|
require 'core/main/models/result'
|
||||||
require 'core/main/models/optioncache'
|
require 'core/main/models/optioncache'
|
||||||
require 'core/main/models/browserdetails'
|
require 'core/main/models/browserdetails'
|
||||||
require 'core/main/models/rule'
|
|
||||||
require 'core/main/models/execution'
|
|
||||||
require 'core/main/models/legacybrowseruseragents'
|
require 'core/main/models/legacybrowseruseragents'
|
||||||
|
|
||||||
# @note Include the constants
|
# @note Include the constants
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
class RemoveAutorunTables < ActiveRecord::Migration[6.0]
|
||||||
|
def up
|
||||||
|
drop_table :executions if table_exists?(:executions)
|
||||||
|
drop_table :rules if table_exists?(:rules)
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
# Cannot recreate these tables - ARE functionality has been removed
|
||||||
|
raise ActiveRecord::IrreversibleMigration
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,589 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
|
||||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
|
||||||
# See the file 'doc/COPYING' for copying permission
|
|
||||||
#
|
|
||||||
module BeEF
|
|
||||||
module Core
|
|
||||||
module AutorunEngine
|
|
||||||
class Engine
|
|
||||||
include Singleton
|
|
||||||
|
|
||||||
def initialize
|
|
||||||
@config = BeEF::Core::Configuration.instance
|
|
||||||
|
|
||||||
@result_poll_interval = @config.get('beef.autorun.result_poll_interval')
|
|
||||||
@result_poll_timeout = @config.get('beef.autorun.result_poll_timeout')
|
|
||||||
@continue_after_timeout = @config.get('beef.autorun.continue_after_timeout')
|
|
||||||
|
|
||||||
@debug_on = @config.get('beef.debug')
|
|
||||||
|
|
||||||
@VERSION = ['<', '<=', '==', '>=', '>', 'ALL']
|
|
||||||
@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::Models::Rule database table
|
|
||||||
# If one or more Rule-sets do match, trigger the module chain specified
|
|
||||||
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
|
|
||||||
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 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)
|
|
||||||
|
|
||||||
execution_order = JSON.parse(rule.execution_order)
|
|
||||||
execution_delay = JSON.parse(rule.execution_delay)
|
|
||||||
chain_mode = rule.chain_mode
|
|
||||||
|
|
||||||
unless %w[sequential nested-forward].include?(chain_mode)
|
|
||||||
print_error("[ARE] Invalid chain mode '#{chain_mode}' for rule")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
mods_bodies = []
|
|
||||||
mods_codes = []
|
|
||||||
mods_conditions = []
|
|
||||||
|
|
||||||
# this ensures that if both rule A and rule B call the same module in sequential mode,
|
|
||||||
# execution will be correct preventing wrapper functions to be called with equal names.
|
|
||||||
rule_token = SecureRandom.hex(5)
|
|
||||||
|
|
||||||
modules.each do |cmd_mod|
|
|
||||||
mod = BeEF::Core::Models::CommandModule.where(name: cmd_mod['name']).first
|
|
||||||
options = []
|
|
||||||
replace_input = false
|
|
||||||
cmd_mod['options'].each do |k, v|
|
|
||||||
options.push({ 'name' => k, 'value' => v })
|
|
||||||
replace_input = true if v == '<<mod_input>>'
|
|
||||||
end
|
|
||||||
|
|
||||||
command_body = prepare_command(mod, options, hb_id, replace_input, rule_token)
|
|
||||||
|
|
||||||
mods_bodies.push(command_body)
|
|
||||||
mods_codes.push(cmd_mod['code'])
|
|
||||||
mods_conditions.push(cmd_mod['condition'])
|
|
||||||
end
|
|
||||||
|
|
||||||
# Depending on the chosen chain mode (sequential or nested/forward), prepare the appropriate wrapper
|
|
||||||
case chain_mode
|
|
||||||
when 'nested-forward'
|
|
||||||
wrapper = prepare_nested_forward_wrapper(mods_bodies, mods_codes, mods_conditions, execution_order, rule_token)
|
|
||||||
when 'sequential'
|
|
||||||
wrapper = prepare_sequential_wrapper(mods_bodies, execution_order, execution_delay, rule_token)
|
|
||||||
else
|
|
||||||
# we should never get here. chain mode is validated earlier.
|
|
||||||
print_error("[ARE] Invalid chain mode '#{chain_mode}'")
|
|
||||||
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,
|
|
||||||
mod_successful: 0,
|
|
||||||
rule_token: rule_token,
|
|
||||||
mod_body: wrapper,
|
|
||||||
is_sent: false,
|
|
||||||
rule_id: rule_id
|
|
||||||
)
|
|
||||||
are_exec.save!
|
|
||||||
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)
|
|
||||||
# ex.: setTimeout(module_one(), 0);
|
|
||||||
# setTimeout(module_two(), 2000);
|
|
||||||
# setTimeout(module_three(), 3000);
|
|
||||||
# Note: no result status is checked here!! Useful if you just want to launch a bunch of modules without caring
|
|
||||||
# what their status will be (for instance, a bunch of XSRFs on a set of targets)
|
|
||||||
def prepare_sequential_wrapper(mods, order, delay, rule_token)
|
|
||||||
wrapper = ''
|
|
||||||
delayed_exec = ''
|
|
||||||
c = 0
|
|
||||||
while c < mods.length
|
|
||||||
delayed_exec += %| setTimeout(function(){#{mods[order[c]][:mod_name]}_#{rule_token}();}, #{delay[c]}); |
|
|
||||||
mod_body = mods[order[c]][:mod_body].to_s.gsub("#{mods[order[c]][:mod_name]}_mod_output", "#{mods[order[c]][:mod_name]}_#{rule_token}_mod_output")
|
|
||||||
wrapped_mod = "#{mod_body}\n"
|
|
||||||
wrapper += wrapped_mod
|
|
||||||
c += 1
|
|
||||||
end
|
|
||||||
wrapper += delayed_exec
|
|
||||||
print_more "Final Modules Wrapper:\n #{wrapper}" if @debug_on
|
|
||||||
wrapper
|
|
||||||
end
|
|
||||||
|
|
||||||
# Wraps module bodies in their own function, then start to execute them from the first, polling for
|
|
||||||
# command execution status/results (with configurable polling interval and timeout).
|
|
||||||
# Launch order is also taken care of.
|
|
||||||
# - nested forward chain with status checks (setInterval to wait for command to return from async operations)
|
|
||||||
# ex.: module_one()
|
|
||||||
# if condition
|
|
||||||
# module_two(module_one_output)
|
|
||||||
# if condition
|
|
||||||
# module_three(module_two_output)
|
|
||||||
#
|
|
||||||
# Note: command result status is checked, and you can properly chain input into output, having also
|
|
||||||
# the flexibility of slightly mangling it to adapt to module needs.
|
|
||||||
# Note: Useful in situations where you want to launch 2 modules, where the second one will execute only
|
|
||||||
# if the first once return with success. Also, the second module has the possibility of mangling first
|
|
||||||
# module output and use it as input for some of its module inputs.
|
|
||||||
def prepare_nested_forward_wrapper(mods, code, conditions, order, rule_token)
|
|
||||||
wrapper = ''
|
|
||||||
delayed_exec = ''
|
|
||||||
delayed_exec_footers = []
|
|
||||||
c = 0
|
|
||||||
|
|
||||||
while c < mods.length
|
|
||||||
i = if mods.length == 1
|
|
||||||
c
|
|
||||||
else
|
|
||||||
c + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
code_snippet = ''
|
|
||||||
mod_input = ''
|
|
||||||
if code[c] != 'null' && code[c] != ''
|
|
||||||
code_snippet = code[c]
|
|
||||||
mod_input = 'mod_input'
|
|
||||||
end
|
|
||||||
|
|
||||||
conditions[i] = true if conditions[i].nil? || conditions[i] == ''
|
|
||||||
|
|
||||||
if c == 0
|
|
||||||
# this is the first wrapper to prepare
|
|
||||||
delayed_exec += %|
|
|
||||||
function #{mods[order[c]][:mod_name]}_#{rule_token}_f(){
|
|
||||||
#{mods[order[c]][:mod_name]}_#{rule_token}();
|
|
||||||
|
|
||||||
// TODO add timeout to prevent infinite loops
|
|
||||||
function isResReady(mod_result, start){
|
|
||||||
if (mod_result === null && parseInt(((new Date().getTime()) - start)) < #{@result_poll_timeout}){
|
|
||||||
// loop
|
|
||||||
}else{
|
|
||||||
// module return status/data is now available
|
|
||||||
clearInterval(resultReady);
|
|
||||||
if (mod_result === null && #{@continue_after_timeout}){
|
|
||||||
var mod_result = [];
|
|
||||||
mod_result[0] = 1; //unknown status
|
|
||||||
mod_result[1] = '' //empty result
|
|
||||||
}
|
|
||||||
var status = mod_result[0];
|
|
||||||
if(#{conditions[i]}){
|
|
||||||
#{mods[order[i]][:mod_name]}_#{rule_token}_can_exec = true;
|
|
||||||
#{mods[order[c]][:mod_name]}_#{rule_token}_mod_output = mod_result[1];
|
|
||||||
|
|
|
||||||
|
|
||||||
delayed_exec_footer = %|
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var start = (new Date()).getTime();
|
|
||||||
var resultReady = setInterval(function(){var start = (new Date()).getTime(); isResReady(#{mods[order[c]][:mod_name]}_#{rule_token}_mod_output, start);},#{@result_poll_interval});
|
|
||||||
}
|
|
||||||
#{mods[order[c]][:mod_name]}_#{rule_token}_f();
|
|
||||||
|
|
|
||||||
|
|
||||||
delayed_exec_footers.push(delayed_exec_footer)
|
|
||||||
|
|
||||||
elsif c < mods.length - 1
|
|
||||||
code_snippet = code_snippet.to_s.gsub(mods[order[c - 1]][:mod_name], "#{mods[order[c - 1]][:mod_name]}_#{rule_token}")
|
|
||||||
|
|
||||||
# this is one of the wrappers in the middle of the chain
|
|
||||||
delayed_exec += %|
|
|
||||||
function #{mods[order[c]][:mod_name]}_#{rule_token}_f(){
|
|
||||||
if(#{mods[order[c]][:mod_name]}_#{rule_token}_can_exec){
|
|
||||||
#{code_snippet}
|
|
||||||
#{mods[order[c]][:mod_name]}_#{rule_token}(#{mod_input});
|
|
||||||
function isResReady(mod_result, start){
|
|
||||||
if (mod_result === null && parseInt(((new Date().getTime()) - start)) < #{@result_poll_timeout}){
|
|
||||||
// loop
|
|
||||||
}else{
|
|
||||||
// module return status/data is now available
|
|
||||||
clearInterval(resultReady);
|
|
||||||
if (mod_result === null && #{@continue_after_timeout}){
|
|
||||||
var mod_result = [];
|
|
||||||
mod_result[0] = 1; //unknown status
|
|
||||||
mod_result[1] = '' //empty result
|
|
||||||
}
|
|
||||||
var status = mod_result[0];
|
|
||||||
if(#{conditions[i]}){
|
|
||||||
#{mods[order[i]][:mod_name]}_#{rule_token}_can_exec = true;
|
|
||||||
#{mods[order[c]][:mod_name]}_#{rule_token}_mod_output = mod_result[1];
|
|
||||||
|
|
|
||||||
|
|
||||||
delayed_exec_footer = %|
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var start = (new Date()).getTime();
|
|
||||||
var resultReady = setInterval(function(){ isResReady(#{mods[order[c]][:mod_name]}_#{rule_token}_mod_output, start);},#{@result_poll_interval});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#{mods[order[c]][:mod_name]}_#{rule_token}_f();
|
|
||||||
|
|
|
||||||
|
|
||||||
delayed_exec_footers.push(delayed_exec_footer)
|
|
||||||
else
|
|
||||||
code_snippet = code_snippet.to_s.gsub(mods[order[c - 1]][:mod_name], "#{mods[order[c - 1]][:mod_name]}_#{rule_token}")
|
|
||||||
# this is the last wrapper to prepare
|
|
||||||
delayed_exec += %|
|
|
||||||
function #{mods[order[c]][:mod_name]}_#{rule_token}_f(){
|
|
||||||
if(#{mods[order[c]][:mod_name]}_#{rule_token}_can_exec){
|
|
||||||
#{code_snippet}
|
|
||||||
#{mods[order[c]][:mod_name]}_#{rule_token}(#{mod_input});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#{mods[order[c]][:mod_name]}_#{rule_token}_f();
|
|
||||||
|
|
|
||||||
end
|
|
||||||
mod_body = mods[order[c]][:mod_body].to_s.gsub("#{mods[order[c]][:mod_name]}_mod_output", "#{mods[order[c]][:mod_name]}_#{rule_token}_mod_output")
|
|
||||||
wrapped_mod = "#{mod_body}\n"
|
|
||||||
wrapper += wrapped_mod
|
|
||||||
c += 1
|
|
||||||
end
|
|
||||||
wrapper += delayed_exec + delayed_exec_footers.reverse.join("\n")
|
|
||||||
print_more "Final Modules Wrapper:\n #{delayed_exec + delayed_exec_footers.reverse.join("\n")}" if @debug_on
|
|
||||||
wrapper
|
|
||||||
end
|
|
||||||
|
|
||||||
# prepare the command module (compiling the Erubis templating stuff), eventually obfuscate it,
|
|
||||||
# and store it in the database.
|
|
||||||
# Returns the raw module body after template substitution.
|
|
||||||
def prepare_command(mod, options, hb_id, replace_input, rule_token)
|
|
||||||
config = BeEF::Core::Configuration.instance
|
|
||||||
begin
|
|
||||||
command = BeEF::Core::Models::Command.new(
|
|
||||||
data: options.to_json,
|
|
||||||
hooked_browser_id: hb_id,
|
|
||||||
command_module_id: BeEF::Core::Configuration.instance.get("beef.module.#{mod.name}.db.id"),
|
|
||||||
creationdate: Time.new.to_i,
|
|
||||||
instructions_sent: true
|
|
||||||
)
|
|
||||||
command.save!
|
|
||||||
|
|
||||||
command_module = BeEF::Core::Models::CommandModule.find(mod.id)
|
|
||||||
if command_module.path.match(/^Dynamic/)
|
|
||||||
# metasploit and similar integrations
|
|
||||||
command_module = BeEF::Modules::Commands.const_get(command_module.path.split('/').last.capitalize).new
|
|
||||||
else
|
|
||||||
# normal modules always here
|
|
||||||
key = BeEF::Module.get_key_by_database_id(mod.id)
|
|
||||||
command_module = BeEF::Core::Command.const_get(config.get("beef.module.#{key}.class")).new(key)
|
|
||||||
end
|
|
||||||
|
|
||||||
hb = BeEF::HBManager.get_by_id(hb_id)
|
|
||||||
hb_session = hb.session
|
|
||||||
command_module.command_id = command.id
|
|
||||||
command_module.session_id = hb_session
|
|
||||||
command_module.build_datastore(command.data)
|
|
||||||
command_module.pre_send
|
|
||||||
|
|
||||||
build_missing_beefjs_components(command_module.beefjs_components) unless command_module.beefjs_components.empty?
|
|
||||||
|
|
||||||
if config.get('beef.extension.evasion.enable')
|
|
||||||
evasion = BeEF::Extension::Evasion::Evasion.instance
|
|
||||||
command_body = evasion.obfuscate(command_module.output) + "\n\n"
|
|
||||||
else
|
|
||||||
command_body = command_module.output + "\n\n"
|
|
||||||
end
|
|
||||||
|
|
||||||
# @note prints the event to the console
|
|
||||||
print_more "Preparing JS for command id [#{command.id}], module [#{mod.name}]"
|
|
||||||
|
|
||||||
mod_input = replace_input ? 'mod_input' : ''
|
|
||||||
result = %|
|
|
||||||
var #{mod.name}_#{rule_token} = function(#{mod_input}){
|
|
||||||
#{clean_command_body(command_body, replace_input)}
|
|
||||||
};
|
|
||||||
var #{mod.name}_#{rule_token}_can_exec = false;
|
|
||||||
var #{mod.name}_#{rule_token}_mod_output = null;
|
|
||||||
|
|
|
||||||
|
|
||||||
{ mod_name: mod.name, mod_body: result }
|
|
||||||
rescue StandardError => e
|
|
||||||
print_error e.message
|
|
||||||
print_debug e.backtrace.join("\n")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Removes the beef.execute wrapper in order that modules are executed in the ARE wrapper, rather than
|
|
||||||
# using the default behavior of adding the module to an array and execute it at polling time.
|
|
||||||
#
|
|
||||||
# Also replace <<mod_input>> with mod_input variable if needed for chaining module output/input
|
|
||||||
def clean_command_body(command_body, replace_input)
|
|
||||||
cmd_body = command_body.lines.map(&:chomp)
|
|
||||||
wrapper_start_index, wrapper_end_index = nil
|
|
||||||
|
|
||||||
cmd_body.each_with_index do |line, index|
|
|
||||||
if line.to_s =~ /^(beef|[a-zA-Z]+)\.execute\(function\(\)/
|
|
||||||
wrapper_start_index = index
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
print_error '[ARE] Could not find module start index' if wrapper_start_index.nil?
|
|
||||||
|
|
||||||
cmd_body.reverse.each_with_index do |line, index|
|
|
||||||
if line.include?('});')
|
|
||||||
wrapper_end_index = index
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
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 <<mod_input>> should be replaced with a variable name (depending if the variable is a string or number)
|
|
||||||
return cleaned_cmd_body unless replace_input
|
|
||||||
|
|
||||||
if cleaned_cmd_body.include?('"<<mod_input>>"')
|
|
||||||
cleaned_cmd_body.gsub('"<<mod_input>>"', 'mod_input')
|
|
||||||
elsif cleaned_cmd_body.include?('\'<<mod_input>>\'')
|
|
||||||
cleaned_cmd_body.gsub('\'<<mod_input>>\'', 'mod_input')
|
|
||||||
elsif cleaned_cmd_body.include?('<<mod_input>>')
|
|
||||||
cleaned_cmd_body.gsub('\'<<mod_input>>\'', 'mod_input')
|
|
||||||
else
|
|
||||||
cleaned_cmd_body
|
|
||||||
end
|
|
||||||
rescue StandardError => e
|
|
||||||
print_error "[ARE] There is likely a problem with the module's command.js parsing. Check Engine.clean_command_body. #{e.message}"
|
|
||||||
end
|
|
||||||
|
|
||||||
# compare versions
|
|
||||||
def compare_versions(ver_a, cond, ver_b)
|
|
||||||
return true if cond == 'ALL'
|
|
||||||
return true if cond == '==' && ver_a == ver_b
|
|
||||||
return true if cond == '<=' && ver_a <= ver_b
|
|
||||||
return true if cond == '<' && ver_a < ver_b
|
|
||||||
return true if cond == '>=' && ver_a >= ver_b
|
|
||||||
return true if cond == '>' && ver_a > ver_b
|
|
||||||
|
|
||||||
false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
|
||||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
|
||||||
# See the file 'doc/COPYING' for copying permission
|
|
||||||
#
|
|
||||||
module BeEF
|
|
||||||
module Core
|
|
||||||
module AutorunEngine
|
|
||||||
class Parser
|
|
||||||
include Singleton
|
|
||||||
|
|
||||||
def initialize
|
|
||||||
@config = BeEF::Core::Configuration.instance
|
|
||||||
end
|
|
||||||
|
|
||||||
BROWSER = %w[FF C IE S O ALL]
|
|
||||||
OS = %w[Linux Windows OSX Android iOS BlackBerry ALL]
|
|
||||||
VERSION = ['<', '<=', '==', '>=', '>', 'ALL', 'Vista', 'XP']
|
|
||||||
CHAIN_MODE = %w[sequential nested-forward]
|
|
||||||
MAX_VER_LEN = 15
|
|
||||||
|
|
||||||
def parse(name, author, browser, browser_version, os, os_version, modules, execution_order, execution_delay, chain_mode)
|
|
||||||
raise ArgumentError, "Invalid rule name: #{name}" unless BeEF::Filters.is_non_empty_string?(name)
|
|
||||||
raise ArgumentError, "Invalid author name: #{author}" unless BeEF::Filters.is_non_empty_string?(author)
|
|
||||||
raise ArgumentError, "Invalid chain_mode definition: #{chain_mode}" unless CHAIN_MODE.include?(chain_mode)
|
|
||||||
raise ArgumentError, "Invalid os definition: #{os}" unless OS.include?(os)
|
|
||||||
|
|
||||||
unless modules.size == execution_delay.size
|
|
||||||
raise ArgumentError, "Number of execution_delay values (#{execution_delay.size}) must be consistent with number of modules (#{modules.size})"
|
|
||||||
end
|
|
||||||
execution_delay.each { |delay| raise TypeError, "Invalid execution_delay value: #{delay}. Values must be Integers." unless delay.is_a?(Integer) }
|
|
||||||
|
|
||||||
unless modules.size == execution_order.size
|
|
||||||
raise ArgumentError, "Number of execution_order values (#{execution_order.size}) must be consistent with number of modules (#{modules.size})"
|
|
||||||
end
|
|
||||||
execution_order.each { |order| raise TypeError, "Invalid execution_order value: #{order}. Values must be Integers." unless order.is_a?(Integer) }
|
|
||||||
|
|
||||||
# if multiple browsers were specified, check each browser
|
|
||||||
if browser.is_a?(Array)
|
|
||||||
browser.each do |b|
|
|
||||||
raise ArgumentError, "Invalid browser definition: #{browser}" unless BROWSER.include?(b)
|
|
||||||
end
|
|
||||||
# else, if only one browser was specified, check browser and browser version
|
|
||||||
else
|
|
||||||
raise ArgumentError, "Invalid browser definition: #{browser}" unless BROWSER.include?(browser)
|
|
||||||
|
|
||||||
if browser_version != 'ALL' && !(VERSION.include?(browser_version[0, 2].gsub(/\s+/, '')) &&
|
|
||||||
BeEF::Filters.is_valid_browserversion?(browser_version[2..-1].gsub(/\s+/, '')) && browser_version.length < MAX_VER_LEN)
|
|
||||||
raise ArgumentError, "Invalid browser_version definition: #{browser_version}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if os_version != 'ALL' && !(VERSION.include?(os_version[0, 2].gsub(/\s+/, '')) &&
|
|
||||||
BeEF::Filters.is_valid_osversion?(os_version[2..-1].gsub(/\s+/, '')) && os_version.length < MAX_VER_LEN)
|
|
||||||
return ArgumentError, "Invalid os_version definition: #{os_version}"
|
|
||||||
end
|
|
||||||
|
|
||||||
# check if module names, conditions and options are ok
|
|
||||||
modules.each do |cmd_mod|
|
|
||||||
mod = BeEF::Core::Models::CommandModule.where(name: cmd_mod['name']).first
|
|
||||||
|
|
||||||
raise "The specified module name (#{cmd_mod['name']}) does not exist" if mod.nil?
|
|
||||||
|
|
||||||
modk = BeEF::Module.get_key_by_database_id(mod.id)
|
|
||||||
mod_options = BeEF::Module.get_options(modk)
|
|
||||||
|
|
||||||
opt_count = 0
|
|
||||||
mod_options.each do |opt|
|
|
||||||
if opt['name'] != cmd_mod['options'].keys[opt_count]
|
|
||||||
raise ArgumentError, "The specified option (#{cmd_mod['options'].keys[opt_count]}) for module (#{cmd_mod['name']}) was not specified"
|
|
||||||
end
|
|
||||||
|
|
||||||
opt_count += 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,220 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
|
||||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
|
||||||
# See the file 'doc/COPYING' for copying permission
|
|
||||||
#
|
|
||||||
module BeEF
|
|
||||||
module Core
|
|
||||||
module AutorunEngine
|
|
||||||
class RuleLoader
|
|
||||||
include Singleton
|
|
||||||
|
|
||||||
def initialize
|
|
||||||
@config = BeEF::Core::Configuration.instance
|
|
||||||
@debug_on = @config.get('beef.debug')
|
|
||||||
end
|
|
||||||
|
|
||||||
# Load an ARE rule set
|
|
||||||
# @param [Hash] ARE ruleset as JSON
|
|
||||||
# @return [Hash] {"success": Boolean, "rule_id": Integer, "error": String}
|
|
||||||
def load_rule_json(data)
|
|
||||||
name = data['name'] || ''
|
|
||||||
author = data['author'] || ''
|
|
||||||
browser = data['browser'] || 'ALL'
|
|
||||||
browser_version = data['browser_version'] || 'ALL'
|
|
||||||
os = data['os'] || 'ALL'
|
|
||||||
os_version = data['os_version'] || 'ALL'
|
|
||||||
modules = data['modules']
|
|
||||||
execution_order = data['execution_order']
|
|
||||||
execution_delay = data['execution_delay']
|
|
||||||
chain_mode = data['chain_mode'] || 'sequential'
|
|
||||||
|
|
||||||
begin
|
|
||||||
BeEF::Core::AutorunEngine::Parser.instance.parse(
|
|
||||||
name,
|
|
||||||
author,
|
|
||||||
browser,
|
|
||||||
browser_version,
|
|
||||||
os,
|
|
||||||
os_version,
|
|
||||||
modules,
|
|
||||||
execution_order,
|
|
||||||
execution_delay,
|
|
||||||
chain_mode
|
|
||||||
)
|
|
||||||
rescue => e
|
|
||||||
print_error("[ARE] Error loading ruleset (#{name}): #{e.message}")
|
|
||||||
return { 'success' => false, 'error' => e.message }
|
|
||||||
end
|
|
||||||
|
|
||||||
existing_rule = BeEF::Core::Models::Rule.where(
|
|
||||||
name: name,
|
|
||||||
author: author,
|
|
||||||
browser: browser,
|
|
||||||
browser_version: browser_version,
|
|
||||||
os: os,
|
|
||||||
os_version: os_version,
|
|
||||||
modules: modules.to_json,
|
|
||||||
execution_order: execution_order.to_s,
|
|
||||||
execution_delay: execution_delay.to_s,
|
|
||||||
chain_mode: chain_mode
|
|
||||||
).first
|
|
||||||
|
|
||||||
unless existing_rule.nil?
|
|
||||||
msg = "Duplicate rule already exists in the database (ID: #{existing_rule.id})"
|
|
||||||
print_info("[ARE] Skipping ruleset (#{name}): #{msg}")
|
|
||||||
return { 'success' => false, 'error' => msg }
|
|
||||||
end
|
|
||||||
|
|
||||||
are_rule = BeEF::Core::Models::Rule.new(
|
|
||||||
name: name,
|
|
||||||
author: author,
|
|
||||||
browser: browser,
|
|
||||||
browser_version: browser_version,
|
|
||||||
os: os,
|
|
||||||
os_version: os_version,
|
|
||||||
modules: modules.to_json,
|
|
||||||
execution_order: execution_order.to_s,
|
|
||||||
execution_delay: execution_delay.to_s,
|
|
||||||
chain_mode: chain_mode
|
|
||||||
)
|
|
||||||
are_rule.save
|
|
||||||
|
|
||||||
print_info("[ARE] Ruleset (#{name}) parsed and stored successfully.")
|
|
||||||
|
|
||||||
if @debug_on
|
|
||||||
print_more "Target Browser: #{browser} (#{browser_version})"
|
|
||||||
print_more "Target OS: #{os} (#{os_version})"
|
|
||||||
print_more 'Modules to run:'
|
|
||||||
modules.each do |mod|
|
|
||||||
print_more "(*) Name: #{mod['name']}"
|
|
||||||
print_more "(*) Condition: #{mod['condition']}"
|
|
||||||
print_more "(*) Code: #{mod['code']}"
|
|
||||||
print_more '(*) Options:'
|
|
||||||
mod['options'].each do |key, value|
|
|
||||||
print_more "\t#{key}: (#{value})"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
print_more "Exec order: #{execution_order}"
|
|
||||||
print_more "Exec delay: #{exec_delay}"
|
|
||||||
end
|
|
||||||
|
|
||||||
{ 'success' => true, 'rule_id' => are_rule.id }
|
|
||||||
rescue TypeError, ArgumentError => e
|
|
||||||
print_error("[ARE] Failed to load ruleset (#{name}): #{e.message}")
|
|
||||||
{ 'success' => false, 'error' => e.message }
|
|
||||||
end
|
|
||||||
|
|
||||||
# Update an ARE rule set.
|
|
||||||
# @param [Hash] ARE rule ID.
|
|
||||||
# @param [Hash] ARE ruleset as JSON
|
|
||||||
# @return [Hash] {"success": Boolean, "rule_id": Integer, "error": String}
|
|
||||||
def update_rule_json(id, data)
|
|
||||||
# Quite similar in implementation to load_rule_json. Might benefit from a refactor.
|
|
||||||
name = data['name'] || ''
|
|
||||||
author = data['author'] || ''
|
|
||||||
browser = data['browser'] || 'ALL'
|
|
||||||
browser_version = data['browser_version'] || 'ALL'
|
|
||||||
os = data['os'] || 'ALL'
|
|
||||||
os_version = data['os_version'] || 'ALL'
|
|
||||||
modules = data['modules']
|
|
||||||
execution_order = data['execution_order']
|
|
||||||
execution_delay = data['execution_delay']
|
|
||||||
chain_mode = data['chain_mode'] || 'sequential'
|
|
||||||
|
|
||||||
begin
|
|
||||||
BeEF::Core::AutorunEngine::Parser.instance.parse(
|
|
||||||
name,
|
|
||||||
author,
|
|
||||||
browser,
|
|
||||||
browser_version,
|
|
||||||
os,
|
|
||||||
os_version,
|
|
||||||
modules,
|
|
||||||
execution_order,
|
|
||||||
execution_delay,
|
|
||||||
chain_mode
|
|
||||||
)
|
|
||||||
rescue => e
|
|
||||||
print_error("[ARE] Error updating ruleset (#{name}): #{e.message}")
|
|
||||||
return { 'success' => false, 'error' => e.message }
|
|
||||||
end
|
|
||||||
|
|
||||||
existing_rule = BeEF::Core::Models::Rule.where(
|
|
||||||
name: name,
|
|
||||||
author: author,
|
|
||||||
browser: browser,
|
|
||||||
browser_version: browser_version,
|
|
||||||
os: os,
|
|
||||||
os_version: os_version,
|
|
||||||
modules: modules.to_json,
|
|
||||||
execution_order: execution_order.to_s,
|
|
||||||
execution_delay: execution_delay.to_s,
|
|
||||||
chain_mode: chain_mode
|
|
||||||
).first
|
|
||||||
|
|
||||||
unless existing_rule.nil?
|
|
||||||
msg = "Duplicate rule already exists in the database (ID: #{existing_rule.id})"
|
|
||||||
print_info("[ARE] Skipping ruleset (#{name}): #{msg}")
|
|
||||||
return { 'success' => false, 'error' => msg }
|
|
||||||
end
|
|
||||||
old_are_rule = BeEF::Core::Models::Rule.find_by(id: id)
|
|
||||||
|
|
||||||
old_are_rule.update(
|
|
||||||
name: name,
|
|
||||||
author: author,
|
|
||||||
browser: browser,
|
|
||||||
browser_version: browser_version,
|
|
||||||
os: os,
|
|
||||||
os_version: os_version,
|
|
||||||
modules: modules.to_json,
|
|
||||||
execution_order: execution_order.to_s,
|
|
||||||
execution_delay: execution_delay.to_s,
|
|
||||||
chain_mode: chain_mode
|
|
||||||
)
|
|
||||||
|
|
||||||
print_info("[ARE] Ruleset (#{name}) updated successfully.")
|
|
||||||
|
|
||||||
if @debug_on
|
|
||||||
print_more "Target Browser: #{browser} (#{browser_version})"
|
|
||||||
print_more "Target OS: #{os} (#{os_version})"
|
|
||||||
print_more 'Modules to run:'
|
|
||||||
modules.each do |mod|
|
|
||||||
print_more "(*) Name: #{mod['name']}"
|
|
||||||
print_more "(*) Condition: #{mod['condition']}"
|
|
||||||
print_more "(*) Code: #{mod['code']}"
|
|
||||||
print_more '(*) Options:'
|
|
||||||
mod['options'].each do |key, value|
|
|
||||||
print_more "\t#{key}: (#{value})"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
print_more "Exec order: #{execution_order}"
|
|
||||||
print_more "Exec delay: #{exec_delay}"
|
|
||||||
end
|
|
||||||
|
|
||||||
{ 'success' => true }
|
|
||||||
rescue TypeError, ArgumentError => e
|
|
||||||
print_error("[ARE] Failed to update ruleset (#{name}): #{e.message}")
|
|
||||||
{ 'success' => false, 'error' => e.message }
|
|
||||||
end
|
|
||||||
|
|
||||||
# Load an ARE ruleset from file
|
|
||||||
# @param [String] JSON ARE ruleset file path
|
|
||||||
def load_rule_file(json_rule_path)
|
|
||||||
rule_file = File.open(json_rule_path, 'r:UTF-8', &:read)
|
|
||||||
self.load_rule_json(JSON.parse(rule_file))
|
|
||||||
rescue => e
|
|
||||||
print_error("[ARE] Failed to load ruleset from #{json_rule_path}: #{e.message}")
|
|
||||||
end
|
|
||||||
|
|
||||||
# Load all JSON ARE rule files from arerules/enabled/ directory
|
|
||||||
def load_directory
|
|
||||||
Dir.glob("#{$root_dir}/arerules/enabled/**/*.json") do |rule_file|
|
|
||||||
print_debug("[ARE] Processing ruleset file: #{rule_file}")
|
|
||||||
load_rule_file(rule_file)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -557,10 +557,6 @@ module BeEF
|
|||||||
BeEF::Core::Models::NetworkHost.create(hooked_browser_id: session_id, ip: '127.0.0.1', hostname: 'localhost',
|
BeEF::Core::Models::NetworkHost.create(hooked_browser_id: session_id, ip: '127.0.0.1', hostname: 'localhost',
|
||||||
os: BeEF::Core::Models::BrowserDetails.get(session_id, 'host.os.name'))
|
os: BeEF::Core::Models::BrowserDetails.get(session_id, 'host.os.name'))
|
||||||
end
|
end
|
||||||
|
|
||||||
# check if any ARE rules shall be triggered only if the channel is != WebSockets (XHR). If the channel
|
|
||||||
# is WebSockets, then ARe rules are triggered after channel is established.
|
|
||||||
BeEF::Core::AutorunEngine::Engine.instance.find_and_run_all_matching_rules_for_zombie(zombie.id) unless config.get('beef.http.websocket.enable')
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_param(query, key)
|
def get_param(query, key)
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
|
||||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
|
||||||
# See the file 'doc/COPYING' for copying permission
|
|
||||||
#
|
|
||||||
|
|
||||||
module BeEF
|
|
||||||
module Core
|
|
||||||
module Models # @note Stored info about the execution of the ARE on hooked browsers.
|
|
||||||
class Execution < BeEF::Core::Model
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
|
||||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
|
||||||
# See the file 'doc/COPYING' for copying permission
|
|
||||||
#
|
|
||||||
|
|
||||||
module BeEF
|
|
||||||
module Core
|
|
||||||
module Models
|
|
||||||
# @note Table stores the rules for the Distributed Engine.
|
|
||||||
class Rule < BeEF::Core::Model
|
|
||||||
has_many :executions
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -108,12 +108,9 @@ module BeEF
|
|||||||
hooked_browser = BeEF::Core::Models::HookedBrowser.where(session: hb_session).first
|
hooked_browser = BeEF::Core::Models::HookedBrowser.where(session: hb_session).first
|
||||||
if hooked_browser.nil?
|
if hooked_browser.nil?
|
||||||
print_error '[WebSocket] Fingerprinting not finished yet.'
|
print_error '[WebSocket] Fingerprinting not finished yet.'
|
||||||
print_more 'ARE rules were not triggered. You may want to trigger them manually via REST API.'
|
|
||||||
next
|
next
|
||||||
end
|
end
|
||||||
|
|
||||||
BeEF::Core::AutorunEngine::Engine.instance.find_and_run_all_matching_rules_for_zombie(hooked_browser.id)
|
|
||||||
|
|
||||||
next
|
next
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -140,15 +137,6 @@ module BeEF
|
|||||||
zombie_commands = BeEF::Core::Models::Command.where(hooked_browser_id: hooked_browser.id, instructions_sent: false)
|
zombie_commands = BeEF::Core::Models::Command.where(hooked_browser_id: hooked_browser.id, instructions_sent: false)
|
||||||
zombie_commands.each { |command| add_command_instructions(command, hooked_browser) }
|
zombie_commands.each { |command| add_command_instructions(command, hooked_browser) }
|
||||||
|
|
||||||
# Check if there are any ARE rules to be triggered. If is_sent=false rules are triggered
|
|
||||||
are_body = ''
|
|
||||||
are_executions = BeEF::Core::Models::Execution.where(is_sent: false, session_id: hooked_browser.session)
|
|
||||||
are_executions.each do |are_exec|
|
|
||||||
are_body += are_exec.mod_body
|
|
||||||
are_exec.update(is_sent: true, exec_time: Time.new.to_i)
|
|
||||||
end
|
|
||||||
@@activeSocket[hooked_browser.session].send(are_body) unless are_body.empty?
|
|
||||||
|
|
||||||
# @todo antisnatchor:
|
# @todo antisnatchor:
|
||||||
# @todo - re-use the pre_hook_send callback mechanisms to have a generic check for multipl extensions
|
# @todo - re-use the pre_hook_send callback mechanisms to have a generic check for multipl extensions
|
||||||
# Check if new forged requests need to be sent (Requester/TunnelingProxy)
|
# Check if new forged requests need to be sent (Requester/TunnelingProxy)
|
||||||
|
|||||||
@@ -1,157 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
|
||||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
|
||||||
# See the file 'doc/COPYING' for copying permission
|
|
||||||
#
|
|
||||||
|
|
||||||
module BeEF
|
|
||||||
module Core
|
|
||||||
module Rest
|
|
||||||
class AutorunEngine < BeEF::Core::Router::Router
|
|
||||||
config = BeEF::Core::Configuration.instance
|
|
||||||
|
|
||||||
before do
|
|
||||||
error 401 unless params[:token] == config.get('beef.api_token')
|
|
||||||
halt 401 unless BeEF::Core::Rest.permitted_source?(request.ip)
|
|
||||||
headers 'Content-Type' => 'application/json; charset=UTF-8',
|
|
||||||
'Pragma' => 'no-cache',
|
|
||||||
'Cache-Control' => 'no-cache',
|
|
||||||
'Expires' => '0'
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
|
||||||
# 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
|
|
||||||
data = JSON.parse request.body.read
|
|
||||||
rloader = BeEF::Core::AutorunEngine::RuleLoader.instance
|
|
||||||
rloader.load_rule_json(data).to_json
|
|
||||||
rescue StandardError => e
|
|
||||||
print_error "Internal error while adding Autorun rule: #{e.message}"
|
|
||||||
{ 'success' => false, 'error' => e.message }.to_json
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
|
||||||
# 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
|
|
||||||
|
|
||||||
#
|
|
||||||
# Update a ruleset
|
|
||||||
#
|
|
||||||
patch '/rule/:rule_id' do
|
|
||||||
rule_id = params[:rule_id]
|
|
||||||
rule = BeEF::Core::Models::Rule.find(rule_id)
|
|
||||||
raise InvalidParameterError, 'id' if rule.nil?
|
|
||||||
data = JSON.parse request.body.read
|
|
||||||
rloader = BeEF::Core::AutorunEngine::RuleLoader.instance
|
|
||||||
rloader.update_rule_json(rule_id, data)
|
|
||||||
|
|
||||||
{ 'success' => true }.to_json
|
|
||||||
rescue InvalidParameterError => e
|
|
||||||
print_error e.message
|
|
||||||
halt 400
|
|
||||||
rescue StandardError => e
|
|
||||||
print_error "Internal error while updating 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))
|
|
||||||
|
|
||||||
if online_hooks.nil?
|
|
||||||
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
|
|
||||||
msg = "Could not trigger rules: #{e.message}"
|
|
||||||
print_error "[ARE] #{msg}"
|
|
||||||
{ 'success' => false, 'error' => msg }.to_json
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
|
||||||
# Run a specified rule on the specified hooked browser.
|
|
||||||
#
|
|
||||||
get '/run/:rule_id/:hb_id' do
|
|
||||||
rule_id = params[:rule_id]
|
|
||||||
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
|
|
||||||
msg = "Could not trigger rule: #{e.message}"
|
|
||||||
print_error "[ARE] #{msg}"
|
|
||||||
{ 'success' => false, 'error' => msg }.to_json
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
|
||||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
|
||||||
# See the file 'doc/COPYING' for copying permission
|
|
||||||
#
|
|
||||||
|
|
||||||
require 'rest-client'
|
|
||||||
require 'json'
|
|
||||||
require_relative '../../../../spec_helper'
|
|
||||||
require_relative '../../../../support/constants'
|
|
||||||
require_relative '../../../../support/beef_test'
|
|
||||||
|
|
||||||
RSpec.describe 'AutoRunEngine Test', run_on_browserstack: true do
|
|
||||||
before(:all) do
|
|
||||||
@__ar_config_snapshot = SpecActiveRecordConnection.snapshot
|
|
||||||
@config = BeEF::Core::Configuration.instance
|
|
||||||
|
|
||||||
# Grab DB file and regenerate if requested
|
|
||||||
print_info 'Loading database'
|
|
||||||
db_file = @config.get('beef.database.file')
|
|
||||||
print_info 'Resetting the database for BeEF.'
|
|
||||||
|
|
||||||
if ENV['RESET_DB']
|
|
||||||
File.delete(db_file) if File.exist?(db_file)
|
|
||||||
end
|
|
||||||
|
|
||||||
@config.set('beef.credentials.user', 'beef')
|
|
||||||
@config.set('beef.credentials.passwd', 'beef')
|
|
||||||
@username = @config.get('beef.credentials.user')
|
|
||||||
@password = @config.get('beef.credentials.passwd')
|
|
||||||
|
|
||||||
# Load BeEF extensions and modules
|
|
||||||
# Always load Extensions, as previous changes to the config from other tests may affect
|
|
||||||
# whether or not this test passes.
|
|
||||||
print_info 'Loading in BeEF::Extensions'
|
|
||||||
BeEF::Extensions.load
|
|
||||||
|
|
||||||
# Check if modules already loaded. No need to reload.
|
|
||||||
if @config.get('beef.module').nil?
|
|
||||||
print_info 'Loading in BeEF::Modules'
|
|
||||||
BeEF::Modules.load
|
|
||||||
else
|
|
||||||
print_info 'Modules already loaded'
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
# Load up DB and migrate if necessary
|
|
||||||
ActiveRecord::Base.logger = nil
|
|
||||||
OTR::ActiveRecord.configure_from_hash!(adapter: 'sqlite3', database: db_file)
|
|
||||||
# otr-activerecord require you to manually establish the connection with the following line
|
|
||||||
#Also a check to confirm that the correct Gem version is installed to require it, likely easier for old systems.
|
|
||||||
if Gem.loaded_specs['otr-activerecord'].version > Gem::Version.create('1.4.2')
|
|
||||||
OTR::ActiveRecord.establish_connection!
|
|
||||||
end
|
|
||||||
ActiveRecord::Migrator.migrations_paths = [File.join('core', 'main', 'ar-migrations')]
|
|
||||||
MUTEX.synchronize do
|
|
||||||
context = ActiveRecord::MigrationContext.new(ActiveRecord::Migrator.migrations_paths)
|
|
||||||
if context.needs_migration?
|
|
||||||
ActiveRecord::Migrator.new(:up, context.migrations, context.schema_migration, context.internal_metadata).migrate
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
BeEF::Core::Migration.instance.update_db!
|
|
||||||
|
|
||||||
# add AutoRunEngine rule
|
|
||||||
test_rule = { 'name' => 'Display an alert', 'author' => 'mgeeky', 'browser' => 'ALL', 'browser_version' => 'ALL', 'os' => 'ALL', 'os_version' => 'ALL', 'modules' => [{ 'name' => 'alert_dialog', 'condition' => nil, 'options' => { 'text' => "You've been BeEFed ;>" } }], 'execution_order' => [0], 'execution_delay' => [0], 'chain_mode' => 'sequential' }
|
|
||||||
|
|
||||||
BeEF::Core::AutorunEngine::RuleLoader.instance.load_directory
|
|
||||||
# are_engine.R
|
|
||||||
|
|
||||||
# Spawn HTTP Server
|
|
||||||
print_info 'Starting HTTP Hook Server'
|
|
||||||
http_hook_server = BeEF::Core::Server.instance
|
|
||||||
|
|
||||||
# Generate a token for the server to respond with
|
|
||||||
@token = BeEF::Core::Crypto.api_token
|
|
||||||
|
|
||||||
# ***** IMPORTANT: close any and all AR/OTR connections before forking *****
|
|
||||||
disconnect_all_active_record!
|
|
||||||
|
|
||||||
# Initiate server start-up
|
|
||||||
@pid = fork do
|
|
||||||
http_hook_server.prepare
|
|
||||||
BeEF::API::Registrar.instance.fire(BeEF::API::Server, 'pre_http_start', http_hook_server)
|
|
||||||
http_hook_server.start
|
|
||||||
end
|
|
||||||
|
|
||||||
begin
|
|
||||||
@caps = CONFIG['common_caps'].merge(CONFIG['browser_caps'][TASK_ID])
|
|
||||||
@caps['name'] = self.class.description || ENV['name'] || 'no-name'
|
|
||||||
@caps['browserstack.local'] = true
|
|
||||||
@caps['browserstack.localIdentifier'] = ENV['BROWSERSTACK_LOCAL_IDENTIFIER']
|
|
||||||
|
|
||||||
@driver = Selenium::WebDriver.for(:remote,
|
|
||||||
url: "http://#{CONFIG['user']}:#{CONFIG['key']}@#{CONFIG['server']}/wd/hub",
|
|
||||||
options: @caps)
|
|
||||||
# Hook new victim
|
|
||||||
print_info 'Hooking a new victim, waiting a few seconds...'
|
|
||||||
wait = Selenium::WebDriver::Wait.new(timeout: 30) # seconds
|
|
||||||
|
|
||||||
@driver.navigate.to VICTIM_URL.to_s
|
|
||||||
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
sleep 1 until wait.until { @driver.execute_script('return window.beef.session.get_hook_session_id().length') > 0 }
|
|
||||||
|
|
||||||
@session = @driver.execute_script('return window.beef.session.get_hook_session_id()')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
after(:all) do
|
|
||||||
server_teardown(@driver, @pid, @pids)
|
|
||||||
disconnect_all_active_record!
|
|
||||||
SpecActiveRecordConnection.restore!(@__ar_config_snapshot)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'AutoRunEngine is working' do
|
|
||||||
expect(@session).not_to be_nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
Reference in New Issue
Block a user