Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add analyze command #11191

Merged
merged 9 commits into from Feb 20, 2019
4 changes: 4 additions & 0 deletions lib/msf/ui/console/command_dispatcher/db.rb
Expand Up @@ -4,6 +4,7 @@
require 'rexml/document'
require 'rex/parser/nmap_xml'
require 'msf/core/db_export'
require 'msf/ui/console/command_dispatcher/db/analyze'
require 'metasploit/framework/data_service'
require 'metasploit/framework/data_service/remote/http/core'

Expand All @@ -18,6 +19,7 @@ class Db

include Msf::Ui::Console::CommandDispatcher
include Msf::Ui::Console::CommandDispatcher::Common
include Msf::Ui::Console::CommandDispatcher::Analyze

DB_CONFIG_PATH = 'framework/database'

Expand Down Expand Up @@ -51,11 +53,13 @@ def commands
"db_export" => "Export a file containing the contents of the database",
"db_nmap" => "Executes nmap and records the output automatically",
"db_rebuild_cache" => "Rebuilds the database-stored module cache",
"analyze" => "Analyze database information about a specific address or address range",
jmartin-tech marked this conversation as resolved.
Show resolved Hide resolved
}

# Always include commands that only make sense when connected.
# This avoids the problem of them disappearing unexpectedly if the
# database dies or times out. See #1923

base.merge(more)
end

Expand Down
132 changes: 132 additions & 0 deletions lib/msf/ui/console/command_dispatcher/db/analyze.rb
@@ -0,0 +1,132 @@
module Msf::Ui::Console::CommandDispatcher::Analyze

def cmd_analyze(*args)
if !active?
jmartin-tech marked this conversation as resolved.
Show resolved Hide resolved
print_error "Not currently connected to a data service for analysis."
return
end

host_ranges = []

while (arg = args.shift)
case arg
when '-h','help'
cmd_analyze_help
return
else
(arg_host_range(arg, host_ranges))
end
end

host_ids = []
each_host_range_chunk(host_ranges) do |host_search|
break if !host_search.nil? && host_search.empty?
eval_hosts_ids = framework.db.hosts(address: host_search).map(&:id)
if eval_hosts_ids
host_ids.push(eval_hosts_ids)
end
end

if host_ids.empty?
print_status("No host found for #{host_ranges}.")
else

mrefs, _mports, _mservs = Msf::Modules::Metadata::Cache.instance.all_remote_exploit_maps

host_ids.each do |id|
eval_host = framework.db.hosts(id: id).first
jmartin-tech marked this conversation as resolved.
Show resolved Hide resolved
next unless eval_host
print_status("Analyzing #{eval_host.address}...")
unless eval_host.vulns
print_status("No suggestions for #{eval_host.address}.")
next
end

reported_module = false
vuln_refs = []
eval_host.vulns.each do |vuln|
next if vuln.service.nil?
vuln_refs.push(*vuln.refs)
end

found_modules = mrefs.values_at(*(vuln_refs.map { |x| x.name.upcase } & mrefs.keys)).map { |x| x.values }.flatten.uniq
jmartin-tech marked this conversation as resolved.
Show resolved Hide resolved
found_modules.each do |fnd_mod|
# next if exploit_filter_by_service(fnd_mod, vuln.service)
next unless exploit_matches_host_os(fnd_mod, eval_host)
print_status(fnd_mod.full_name)
reported_module = true
end

print_status("No suggestions for #{eval_host.address}.") unless reported_module
end
end
end


def cmd_analyze_help
print_line "Usage: analyze [addr1 addr2 ...]"
print_line
end

private

# Tests for various service conditions by comparing the module's full_name (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.
def exploit_filter_by_service(mod, serv)

# Filter out Unix vs Windows exploits for SMB services
return true if (mod.full_name =~ /\/samba/ and serv.info.to_s =~ /windows/i)
return true if (mod.full_name =~ /\/windows/ and serv.info.to_s =~ /samba|unix|vxworks|qnx|netware/i)
return true if (mod.full_name =~ /\/netware/ and serv.info.to_s =~ /samba|unix|vxworks|qnx/i)

# Filter out IIS exploits for non-Microsoft services
return true if (mod.full_name =~ /\/iis\/|\/isapi\// and (serv.info.to_s !~ /microsoft|asp/i))

# Filter out Apache exploits for non-Apache services
return true if (mod.full_name =~ /\/apache/ and serv.info.to_s !~ /apache|ibm/i)

false
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." 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 exploit_matches_host_os(mod, host)
hos = host.os_name
return true if not hos
return true if hos.length == 0

set = mod.platform.split(',').map{ |x| x.downcase }
return true if set.length == 0

# Special cases
return true if set.include?("unix") and hos !~ /windows/i

# Skip archaic old HPUX bugs if we have a solid match against another OS
if set.include?("unix") and set.include?("hpux") and mod.refname.index("hpux") and hos =~ /linux|irix|solaris|aix|bsd/i
return false
end

# Skip AIX bugs if we have a solid match against another OS
if set.include?("unix") and set.include?("aix") and mod.refname.index("aix") and hos =~ /linux|irix|solaris|hpux|bsd/i
return false
end

# Skip IRIX bugs if we have a solid match against another OS
if set.include?("unix") and set.include?("irix") and mod.refname.index("irix") and hos =~ /linux|solaris|hpux|aix|bsd/i
return false
end

return true if set.include?("php")

set.each do |mos|
return true if hos.downcase.index(mos)
end

false
end
end