class Msf::Analyze::Result attr_reader :datastore attr_reader :host attr_reader :invalid attr_reader :missing attr_reader :mod attr_reader :required def initialize(host:, mod:, framework:, available_creds: nil, payloads: nil, datastore: nil) @host = host @mod = mod @required = [] @missing = [] @invalid = [] @datastore = datastore&.transform_keys(&:downcase) || Hash.new @available_creds = available_creds @wanted_payloads = payloads @framework = framework determine_likely_compatibility end def evaluate(with: @datastore, payloads: @wanted_payloads) @datastore = with @wanted_payloads = payloads determine_prerequisites self end # Returns state for module readiness. # # @return :sym the stateful result one of: # * :READY_FOR_TEST, :REQUIRES_CRED, :REUSE_PREVIOUS_OPTIONS, :MISSING_REQUIRED_OPTION, :MISSING_PAYLOAD, :REQUIRES_SESSION, :NEEDS_TARGET_ACTION, :INVALID_OPTION, :NOT_APPLICABLE # # | State | Detailed Reason | # |-------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| # | READY_FOR_TEST | Ready for Test - All required options have defaults | # | REQUIRES_CRED | Requires DB Credentials - Required options have defaults except credential values - if db contains known credentials for required fields validation is possible | # | REUSE_PREVIOUS_OPTIONS | Reuse Previous Options- Taken as an analysis option, process existing module runs to gather options set for same module on other hosts | # | MISSING_REQUIRED_OPTION | Missing Required Options - Some options are not available requiring manual configuration | # | MISSING_PAYLOAD | Missing Compatible Payload - Known host details and payload restrictions exclude all payloads | # | REQUIRES_SESSION | Requires Session - Modules that require an existing session can cannot be executed as first entry point on targets | # | NEEDS_TARGET_ACTION | Needs target action - Module that either start a service and need the target to respond in a way that may require user interaction. (Browser exploit, needs target reboot....) | # | INVALID_OPTION | Options used in Result evaluation are invalid | # | NOT_APPLICABLE | Module is not applicable to the host | def state if ready_for_test? || (@missing.empty? && @invalid.empty?) :READY_FOR_TEST # TODO: result eval can look for previous attempts to determine :REUSE_PREVIOUS_OPTIONS state else unless @missing.empty? if @missing.include?(:credential) :REQUIRES_CRED elsif @missing.include?(:payload_match) :MISSING_PAYLOAD elsif @missing.include?(:session) :REQUIRES_SESSION elsif @missing.include?(:os_match) :NOT_APPLICABLE # TODO: result eval check for module stance to determine :NEEDS_TARGET_ACTION state? else :MISSING_REQUIRED_OPTION end else :INVALID_OPTION end end end # Returns state for module readiness. # @return :String detailed sentence form description of result evaluation. def description if ready_for_test? "ready for testing" elsif @missing.empty? && @invalid.empty? # TODO? confirm vuln match in this class "has matching reference" else if missing_message.empty? || invalid_message.empty? missing_message + invalid_message else [missing_message, invalid_message].join(', ') end end end def match? !@missing.include? :os_match end def ready_for_test? @prerequisites_evaluated && @missing.empty? && @invalid.empty? end private def determine_likely_compatibility if matches_host_os? @datastore['rhost'] = @host.address else @missing << :os_match end if @mod.post_auth? unless @mod.default_cred? || has_service_cred? || has_datastore_cred? @missing << :credential end end end def determine_prerequisites mod_detail = @framework.modules.create(@mod.fullname) if mod_detail.nil? @required << :module_not_loadable return end @mod = mod_detail if @mod.respond_to?(:session_types) && @mod.session_types @required << :session if s = @host.sessions.alive.detect { |sess| matches_session?(sess) } @datastore['session'] = s.local_id.to_s else @missing << :session end end @mod.options.each_pair do |name, opt| @required << name if opt.required? && !opt.default.nil? end @datastore.each_pair do |k, v| @mod.datastore[k] = v end target_idx = @mod.respond_to?(:auto_targeted_index) ? @mod.auto_targeted_index(@host) : nil if target_idx @datastore['target'] = target_idx @mod.datastore['target'] = target_idx end # Must come after the target so we know we match the target we want. # TODO: feed available payloads into target selection if @wanted_payloads if p = @wanted_payloads.find { |p| @mod.is_payload_compatible?(p) } @datastore['payload'] = p else @missing << :payload_match end end @mod.validate rescue Msf::OptionValidateError => e unset_options = [] bad_options = [] e.options.each do |opt| if @mod.datastore[opt].nil? unset_options << opt else bad_options << opt end end @missing.concat unset_options @invalid.concat bad_options ensure @prerequisites_evaluated = true end def matches_session?(session) session.stype == 'meterpreter' || !!@mod.session_types&.include?(session.stype) end def required_sessions_list return "meterpreter" unless @mod.session_types&.any? @mod.session_types.join(' or ') end def has_service_cred? @available_creds&.any? end def has_datastore_cred? !!(@datastore['username'] && @datastore['password']) end # Determines if an exploit (mod, an instantiated module) is suitable for the host (host) # defined operating system. Returns true if the host.os isn't defined, if the module's target # OS isn't defined, if the module's OS is "unix" and the host's OS is not "windows," or if # the module's target is "php", "python", or "java." Or, of course, in the event the host.os # actually matches. This is a fail-open gate; if there's a doubt, assume the module will work # on this target. def matches_host_os? hos = @host.os_name&.downcase return true if hos.nil? || hos.empty? set = @mod.platform.split(',').map{ |x| x.downcase } return true if set.empty? # Special cases if set.include?('unix') # Skip archaic old HPUX bugs if we have a solid match against another OS return false if set.include?("hpux") && mod.refname.include?("hpux") && !hos.include?("hpux") # Skip AIX bugs if we have a solid match against another OS return false if set.include?("aix") && mod.refname.include?("aix") && !hos.include?("aix") # Skip IRIX bugs if we have a solid match against another OS return false if set.include?("irix") && mod.refname.include?("irix") && !hos.include?("irix") return true if !hos.include?('windows') end return true if set.include?("php") return true if set.include?("python") return true if set.include?("java") set.each do |mos| return true if hos.include?(mos) end false end def missing_message @missing.map do |m| case m when :module_not_loadable "module not loadable" when :os_match "operating system does not match" when :session, "SESSION" "open #{required_sessions_list} session required" when :credential "credentials are required" when :payload_match "none of the requested payloads match" when String "option #{m.inspect} needs to be set" end end.uniq.join(', ') end def invalid_message @invalid.map do |o| case o when String "option #{o.inspect} is currently invalid" end end.join(', ') end =begin # Tests for various service conditions by comparing the module's fullname (which # is basically a pathname) to the intended target service record. The service.info # column is tested against a regex in most/all cases and "false" is returned in the # event of a match between an incompatible module and service fingerprint. # TODO: fix and integrate def exploit_filter_by_service(mod, serv) # Filter out Unix vs Windows exploits for SMB services return true if (mod.fullname =~ /\/samba/ and serv.info.to_s =~ /windows/i) return true if (mod.fullname =~ /\/windows/ and serv.info.to_s =~ /samba|unix|vxworks|qnx|netware/i) return true if (mod.fullname =~ /\/netware/ and serv.info.to_s =~ /samba|unix|vxworks|qnx/i) # Filter out IIS exploits for non-Microsoft services return true if (mod.fullname =~ /\/iis\/|\/isapi\// and (serv.info.to_s !~ /microsoft|asp/i)) # Filter out Apache exploits for non-Apache services return true if (mod.fullname =~ /\/apache/ and serv.info.to_s !~ /apache|ibm/i) false end =end end