AutorunEngine: RuleLoader: Skip enabled rule file if rule is already in database (#2782)
* AutorunEngine: Engine: store rule execution rule ID * AutorunEngine: RuleLoader: Skip enabled rule file if rule is already in database
This commit is contained in:
@@ -45,6 +45,11 @@ module BeEF
|
||||
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 = []
|
||||
@@ -76,9 +81,9 @@ module BeEF
|
||||
when 'sequential'
|
||||
wrapper = prepare_sequential_wrapper(mods_bodies, execution_order, execution_delay, rule_token)
|
||||
else
|
||||
wrapper = nil
|
||||
print_error 'Chain mode looks wrong!'
|
||||
# TODO: catch error, which should never happen as values are checked way before ;-)
|
||||
# we should never get here. chain mode is validated earlier.
|
||||
print_error("[ARE] Invalid chain mode '#{chain_mode}'")
|
||||
next
|
||||
end
|
||||
|
||||
are_exec = BeEF::Core::Models::Execution.new(
|
||||
@@ -88,9 +93,10 @@ module BeEF
|
||||
rule_token: rule_token,
|
||||
mod_body: wrapper,
|
||||
is_sent: false,
|
||||
id: rule_id
|
||||
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
|
||||
|
||||
@@ -18,68 +18,63 @@ module BeEF
|
||||
VERSION = ['<', '<=', '==', '>=', '>', 'ALL', 'Vista', 'XP']
|
||||
CHAIN_MODE = %w[sequential nested-forward]
|
||||
MAX_VER_LEN = 15
|
||||
# Parse a JSON ARE file and returns an Hash with the value mappings
|
||||
def parse(name, author, browser, browser_version, os, os_version, modules, exec_order, exec_delay, chain_mode)
|
||||
success = [true]
|
||||
|
||||
return [false, 'Illegal chain_mode definition'] unless CHAIN_MODE.include?(chain_mode)
|
||||
return [false, 'Illegal rule name'] unless BeEF::Filters.is_non_empty_string?(name)
|
||||
return [false, 'Illegal author name'] unless BeEF::Filters.is_non_empty_string?(author)
|
||||
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|
|
||||
return [false, 'Illegal browser definition'] unless BROWSER.include?(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
|
||||
return [false, 'Illegal browser definition'] unless BROWSER.include?(browser)
|
||||
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)
|
||||
return [false, 'Illegal browser_version definition']
|
||||
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 [false, 'Illegal os_version definition']
|
||||
return ArgumentError, "Invalid os_version definition: #{os_version}"
|
||||
end
|
||||
|
||||
return [false, 'Illegal os definition'] unless OS.include?(os)
|
||||
|
||||
# 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
|
||||
if mod.nil?
|
||||
return [false, "The specified module name (#{cmd_mod['name']}) does not exist"]
|
||||
else
|
||||
|
||||
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]
|
||||
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
|
||||
else
|
||||
return [false, "The specified option (#{cmd_mod['options'].keys[opt_count]
|
||||
}) for module (#{cmd_mod['name']}) does not exist"]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
exec_order.each { |order| return [false, 'execution_order values must be Integers'] unless order.is_a?(Integer) }
|
||||
exec_delay.each { |delay| return [false, 'execution_delay values must be Integers'] unless delay.is_a?(Integer) }
|
||||
|
||||
return [false, 'execution_order and execution_delay values must be consistent with modules numbers'] unless
|
||||
modules.size == exec_order.size && modules.size == exec_delay.size
|
||||
|
||||
success
|
||||
rescue StandardError => e
|
||||
print_error e.message.to_s
|
||||
print_debug e.backtrace.join("\n").to_s
|
||||
[false, 'Something went wrong.']
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -14,29 +14,78 @@ module BeEF
|
||||
@debug_on = @config.get('beef.debug')
|
||||
end
|
||||
|
||||
# this expects parsed JSON as input
|
||||
def load(data)
|
||||
name = data['name']
|
||||
author = data['author']
|
||||
# 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']
|
||||
exec_order = data['execution_order']
|
||||
exec_delay = data['execution_delay']
|
||||
chain_mode = data['chain_mode']
|
||||
execution_order = data['execution_order']
|
||||
execution_delay = data['execution_delay']
|
||||
chain_mode = data['chain_mode'] || 'sequential'
|
||||
|
||||
parser_result = BeEF::Core::AutorunEngine::Parser.instance.parse(
|
||||
name, author, browser, browser_version, os, os_version, modules, exec_order, exec_delay, chain_mode
|
||||
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 parser_result.length == 1 && parser_result.first
|
||||
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 Trigger:'
|
||||
print_more 'Modules to run:'
|
||||
modules.each do |mod|
|
||||
print_more "(*) Name: #{mod['name']}"
|
||||
print_more "(*) Condition: #{mod['condition']}"
|
||||
@@ -46,44 +95,30 @@ module BeEF
|
||||
print_more "\t#{key}: (#{value})"
|
||||
end
|
||||
end
|
||||
print_more "Exec order: #{exec_order}"
|
||||
print_more "Exec order: #{execution_order}"
|
||||
print_more "Exec delay: #{exec_delay}"
|
||||
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: exec_order,
|
||||
execution_delay: exec_delay,
|
||||
chain_mode: chain_mode
|
||||
)
|
||||
are_rule.save
|
||||
|
||||
{ 'success' => true, 'rule_id' => are_rule.id }
|
||||
else
|
||||
print_error "[ARE] Ruleset (#{name}): ERROR. " + parser_result.last
|
||||
{ 'success' => false, 'error' => parser_result.last }
|
||||
end
|
||||
rescue StandardError => e
|
||||
err = 'Malformed JSON ruleset.'
|
||||
print_error "[ARE] Ruleset (#{name}): ERROR. #{e} #{e.backtrace}"
|
||||
{ 'success' => false, 'error' => err }
|
||||
rescue TypeError, ArgumentError => e
|
||||
print_error("[ARE] Failed to load ruleset (#{name}): #{e.message}")
|
||||
{ 'success' => false, 'error' => e.message }
|
||||
end
|
||||
|
||||
def load_file(json_rule_path)
|
||||
# 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 JSON.parse(rule_file)
|
||||
rescue StandardError => e
|
||||
print_error "[ARE] Failed to load ruleset from #{json_rule_path}: #{e.message}"
|
||||
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|
|
||||
print_debug "[ARE] Processing rule: #{rule}"
|
||||
load_file rule
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user