114 lines
3.9 KiB
Ruby
114 lines
3.9 KiB
Ruby
class Msf::Analyze
|
|
|
|
def initialize(framework)
|
|
@framework = framework
|
|
end
|
|
|
|
def host(eval_host, payloads: nil)
|
|
unless eval_host.vulns
|
|
return {}
|
|
end
|
|
|
|
# Group related vulns
|
|
vuln_families = group_vulns(eval_host.vulns)
|
|
|
|
# finds all modules that have references matching those found on host vulns with service data
|
|
suggested_modules = suggest_modules_for_vulns(eval_host, vuln_families, payloads: payloads)
|
|
|
|
{results: suggested_modules}
|
|
end
|
|
|
|
private
|
|
|
|
def group_vulns(vulns)
|
|
return [] if vulns.empty?
|
|
|
|
vulns = vulns.map do |vuln|
|
|
[vuln, Set.new(vuln.refs.map {|r| r.name.upcase})]
|
|
end
|
|
grouped_vulns = Hash.new
|
|
|
|
vulns.each_index do |ii|
|
|
vuln, refs = vulns[ii]
|
|
grouped_vulns[vuln] ||= [Set.new, Set.new]
|
|
grouped_vulns[vuln][0] << vuln
|
|
grouped_vulns[vuln][1].merge(refs)
|
|
|
|
vulns[(ii+1)..-1].each do |candidate_match, candidate_refs|
|
|
# TODO: measure if sorting the refs ahead of time and doing a O(n + m)
|
|
# walk here is faster
|
|
if candidate_refs.intersect? refs
|
|
if grouped_vulns[candidate_match]
|
|
# For merging two different transitive sets that overlap, we need
|
|
# to merge the individual grouping elements and then use those
|
|
# cells inside both the grouping arrays so that all the
|
|
# already-visited vulns are rolled into the big new set
|
|
grouped_vulns[candidate_match][0] = grouped_vulns[candidate_match][0].merge(grouped_vulns[vuln][0])
|
|
grouped_vulns[candidate_match][1] = grouped_vulns[candidate_match][1].merge(grouped_vulns[vuln][1])
|
|
grouped_vulns[vuln][0] = grouped_vulns[candidate_match][0]
|
|
grouped_vulns[vuln][1] = grouped_vulns[candidate_match][1]
|
|
else
|
|
# Whoever was initialized first has the canonical set
|
|
grouped_vulns[candidate_match] = grouped_vulns[vuln]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
vuln_families = grouped_vulns.values
|
|
vuln_families.uniq!
|
|
vuln_families
|
|
end
|
|
|
|
def suggest_modules_for_vulns(eval_host, vuln_families, payloads: nil)
|
|
mrefs, _mports, _mservs = Msf::Modules::Metadata::Cache.instance.all_exploit_maps
|
|
suggested_modules = []
|
|
|
|
evaluated_module_targets = Set.new
|
|
to_evaluate_with_defaults = Array.new
|
|
vuln_families.each do |vulns, refs|
|
|
found_modules = mrefs.values_at(*refs).compact.reduce(:+)
|
|
next unless found_modules
|
|
|
|
services = vulns.map(&:service).compact
|
|
found_modules.each do |fnd_mod|
|
|
if services.any?
|
|
services.each do |svc|
|
|
port = svc.port
|
|
next if evaluated_module_targets.include?([fnd_mod, port])
|
|
|
|
creds = @framework.db.creds(svcs: [svc.name], workspace: eval_host.workspace)
|
|
r = Result.new(mod: fnd_mod, host: eval_host, datastore: {'rport': port},
|
|
available_creds: creds, payloads: payloads, framework: @framework)
|
|
if r.match?
|
|
suggested_modules << r.evaluate
|
|
end
|
|
evaluated_module_targets << [fnd_mod, port]
|
|
end
|
|
else
|
|
# Only have the default port to go off of, at least for this vuln,
|
|
# prefer using the service data if available on a different vuln
|
|
# entry
|
|
port = fnd_mod.rport
|
|
to_evaluate_with_defaults << [fnd_mod, port]
|
|
end
|
|
end
|
|
end
|
|
|
|
to_evaluate_with_defaults.each do |fnd_mod, port|
|
|
next if evaluated_module_targets.include?([fnd_mod, port])
|
|
|
|
creds = @framework.db.creds(port: port, workspace: eval_host.workspace) if port
|
|
r = Result.new(mod: fnd_mod, host: eval_host, datastore: {'rport': port},
|
|
available_creds: creds, payloads: payloads, framework: @framework)
|
|
|
|
if r.match?
|
|
suggested_modules << r.evaluate
|
|
end
|
|
evaluated_module_targets << [fnd_mod, port]
|
|
end
|
|
|
|
suggested_modules
|
|
end
|
|
end
|