class System attr_accessor :name attr_accessor :attributes # (basebox selection) attr_accessor :module_selectors # (filters) attr_accessor :module_selections # (after resolution) attr_accessor :num_actioned_module_conflicts # Initalizes System object # @param [Object] name of the system # @param [Object] attributes such as base box selection # @param [Object] module_selectors these are modules that define filters for selecting the actual modules to use def initialize(name, attributes, module_selectors) self.name = name self.attributes = attributes self.module_selectors = module_selectors self.module_selections = [] self.num_actioned_module_conflicts = 0 end # selects from the available modules, based on the selection filters that have been specified # @param [Object] available_modules all available modules (vulnerabilities, services, bases) # @return [Object] the list of selected modules def resolve_module_selection(available_modules) retry_count = 0 begin selected_modules = [] self.num_actioned_module_conflicts = 0 # for each module specified in the scenario module_selectors.each do |module_filter| selected_modules += select_modules(module_filter.module_type, module_filter.attributes, available_modules, selected_modules) end selected_modules rescue RuntimeError=>e # When the scenario fails to be resolved # bruteforce conflict resolution (could be more intelligent) Print.err 'Failed to resolve scenario.' if self.num_actioned_module_conflicts > 0 Print.err "During scenario generation #{num_actioned_module_conflicts} module conflict(s) occured..." else Print.err 'No conflicts, but failed to resolve scenario -- this is a sign there is something wrong in the config (scenario / modules)' Print.err 'Please review the scenario -- something is wrong.' exit end if retry_count < RETRIES_LIMIT Print.err "Re-attempting to resolve scenario (##{retry_count + 1})..." sleep 1 retry_count += 1 retry else Print.err "Tried re-randomising #{RETRIES_LIMIT} times. Still no joy." Print.err 'Please review the scenario -- something is wrong.' exit end end end # returns a list containing a module (plus dependencies recursively) of the module type with the required attributes # modules are selected from the list of available modules and will be checked against previously selected modules for conflicts # raises an exception when unable to resolve and the retry limit has not been reached def select_modules(module_type, required_attributes, available_modules, previously_selected_modules) # select based on selected type, access, cve... search_list = available_modules.clone # shuffle order of available vulnerabilities search_list.shuffle! # remove any module that is not the type of module we are adding (vulnerabilty/service) if module_type != 'any' search_list.delete_if{|x| x.module_type != module_type } end # filter to those that satisfy the attribute filters search_list.delete_if{|module_for_possible_exclusion| !module_for_possible_exclusion.matches_attributes_requirement(required_attributes) } Print.verbose "Filtered to modules matching: #{required_attributes.inspect} ~= (n=#{search_list.size})" # remove non-options due to conflicts search_list.delete_if{|module_for_possible_exclusion| check_conflicts_with_list(module_for_possible_exclusion, previously_selected_modules) } if search_list.length == 0 raise 'failed' Print.err 'Could not find a matching module. Please check the scenario specification' else # use from the top of the randomised list selected = search_list[0] # add any modules that the selected module requires dependencies = select_required_modules(selected, available_modules, previously_selected_modules + [selected]) end selected_modules = dependencies + [selected] Print.std "Selected module: #{selected.printable_name}" selected_modules end def check_conflicts_with_list(module_for_possible_exclusion, selected_modules) found_conflict = false selected_modules.each do |prev_selected| if module_for_possible_exclusion.conflicts_with(prev_selected) || prev_selected.conflicts_with(module_for_possible_exclusion) Print.verbose "Excluding incompatible module: #{module_for_possible_exclusion.module_path} (conflicts with #{prev_selected.module_path})" self.num_actioned_module_conflicts += 1 found_conflict = true end end found_conflict end # for a single dependency # returns a module that satisfies the requirement from a list of modules provided # returns nil when the requirement cannot be satisfied def resolve_dependency(required, provided_modules) provided_modules.each do |possibly_add| if possibly_add.matches_attributes_requirement(required) return possibly_add end end # couldn't satisfy requirement! return nil end # returns a list of modules that satisfies all dependencies for the given module # returns an empty list if there are no requirements def select_required_modules(required_by, available_modules, selected_modules) modules_to_add = [] if required_by.requires.size > 0 Print.verbose "Resolving dependencies for #{required_by.printable_name}" end required_by.requires.each do |required| Print.verbose "Resolving dependency: #{required.inspect}" # checking whether dependency is satisfied by previously selected modules existing = resolve_dependency(required, selected_modules) if existing != nil Print.verbose "Dependency satisfied by previously selected module: #{existing.printable_name}" else Print.verbose 'Adding required modules...' modules_to_add += select_modules('any', required, available_modules, modules_to_add + selected_modules) end end modules_to_add end end