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
1 change: 1 addition & 0 deletions lib/msf/core.rb
Expand Up @@ -51,6 +51,7 @@ module Msf
require 'msf/core/plugin_manager'
require 'msf/core/session'
require 'msf/core/session_manager'
require 'msf/core/analyze'



Expand Down
96 changes: 96 additions & 0 deletions lib/msf/core/analyze.rb
@@ -0,0 +1,96 @@
class Msf::Analyze

def initialize(framework)
@framework = framework
end

def host(eval_host)
suggested_modules = {}

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

unless eval_host.vulns
return {}
end

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

# finds all modules that have references matching those found on host vulns with service data
found_modules = mrefs.values_at(*(vuln_refs.map { |x| x.name.upcase } & mrefs.keys)).map { |x| x.values }.flatten.uniq
found_modules.each do |fnd_mod|
# next if exploit_filter_by_service(fnd_mod, vuln.service)
jmartin-tech marked this conversation as resolved.
Show resolved Hide resolved
next unless exploit_matches_host_os(fnd_mod, eval_host)
end

suggested_modules[:modules] = found_modules

suggested_modules
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
jmartin-tech marked this conversation as resolved.
Show resolved Hide resolved

set = mod.platform.split(',').map{ |x| x.downcase }
return true if set.length == 0
jmartin-tech marked this conversation as resolved.
Show resolved Hide resolved

# 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
jmartin-tech marked this conversation as resolved.
Show resolved Hide resolved
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
7 changes: 7 additions & 0 deletions lib/msf/core/framework.rb
Expand Up @@ -80,6 +80,7 @@ def initialize(options={})
self.jobs = Rex::JobContainer.new
self.plugins = PluginManager.new(self)
self.uuid_db = Rex::JSONHashFile.new(::File.join(Msf::Config.config_directory, "payloads.json"))
self.analyze = Analyze.new(self)
self.browser_profiles = Hash.new

# Configure the thread factory
Expand Down Expand Up @@ -196,6 +197,11 @@ def version
# different contexts.
#
attr_reader :browser_profiles
#
# The framework instance's analysis utility. Provide method to analyze
# framework objects to offer related objects/actions available.
#
attr_reader :analyze

#
# The framework instance's data service proxy
Expand Down Expand Up @@ -272,6 +278,7 @@ def search(match, logger: nil)
attr_writer :db # :nodoc:
attr_writer :uuid_db # :nodoc:
attr_writer :browser_profiles # :nodoc:
attr_writer :analyze # :nodoc:

private

Expand Down
43 changes: 43 additions & 0 deletions lib/msf/core/rpc/v10/rpc_db.rb
Expand Up @@ -673,6 +673,49 @@ def rpc_get_host(xopts)
}
end

# Returns analysis of module suggestions for known data about a host.
#
# @param [Hash] xopts Options (:addr, :address, :host are the same thing, and you only need one):
# @option xopts [String] :addr Host address.
# @option xopts [String] :address Same as :addr.
# @option xopts [String] :host Same as :address.
# @raise [Msf::RPC::ServerException] You might get one of these errors:
# * 500 ActiveRecord::ConnectionNotEstablished. Try: rpc.call('console.create').
# * 500 Database not loaded. Try: rpc.call('console.create')
# * 500 Invalid workspace.
# @return [Hash] A hash that contains the following:
# * 'host' [Array<Hash>] Each hash in the array contains the following:
# * 'address' [String] Address.
# * 'modules' [Array<Hash>] Each hash in the array modules contains the following:
# * 'mtype' []String] Module type.
jmartin-tech marked this conversation as resolved.
Show resolved Hide resolved
# * 'nmame' [String] Module name. For example: 'windows/wlan/wlan_profile'
jmartin-tech marked this conversation as resolved.
Show resolved Hide resolved
# @example Here's how you would use this from the client:
# rpc.call('db.analyze_host', {:host => ip})
def rpc_analyze_host(xopts)
::ActiveRecord::Base.connection_pool.with_connection {
_opts, _wspace = init_db_opts_workspace(xopts)

ret = {}
ret[:host] = []
opts = fix_options(xopts)
h = self.framework.db.get_host(opts)
h_result = self.framework.analyze.host(h)
host_detail = {}
host_detail[:address] = host
jmartin-tech marked this conversation as resolved.
Show resolved Hide resolved
# for now only modules can be returned, in future maybe process whole result map
unless h_result[:modules].empty?
host_detail[:modules] = []
h_result[:modules].each do |mod|
jmartin-tech marked this conversation as resolved.
Show resolved Hide resolved
mod_detail = {}
mod_detail[:mtype] = mod.mtype
mod_detail[:mname] = mod.full_name
end
end
ret[:host] << host_detail
ret
}
end


# Reports a new host to the database.
#
Expand Down
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
66 changes: 66 additions & 0 deletions lib/msf/ui/console/command_dispatcher/db/analyze.rb
@@ -0,0 +1,66 @@
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 = []
suggested_modules = {}
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

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
host_result = framework.analyze.host(eval_host)
found_modules = host_result[:modules]
found_modules.each do |fnd_mod|
print_status(fnd_mod.full_name)
reported_module = true
end

suggested_modules[eval_host.address] = found_modules

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


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

end