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
89 changes: 89 additions & 0 deletions lib/msf/core/analyze.rb
@@ -0,0 +1,89 @@
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 hos.nil? || hos.empty?

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

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

if set.include?("unix")
# Skip archaic old HPUX bugs if we have a solid match against another OS
return false if set.include?("hpux") and mod.refname.index("hpux") and hos =~ /linux|irix|solaris|aix|bsd/i
# Skip AIX bugs if we have a solid match against another OS
return false if set.include?("aix") and mod.refname.index("aix") and hos =~ /linux|irix|solaris|hpux|bsd/i
# Skip IRIX bugs if we have a solid match against another OS
return false if set.include?("irix") and mod.refname.index("irix") and hos =~ /linux|solaris|hpux|aix|bsd/i
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
53 changes: 53 additions & 0 deletions lib/msf/core/rpc/v10/rpc_db.rb
Expand Up @@ -622,6 +622,7 @@ def rpc_add_workspace(wspace)
# Returns information about a host.
#
# @param [Hash] xopts Options (:addr, :address, :host are the same thing, and you only need one):
# @option xopts [String] :workspace Name of the workspace.
# @option xopts [String] :addr Host address.
# @option xopts [String] :address Same as :addr.
# @option xopts [String] :host Same as :address.
Expand Down Expand Up @@ -673,10 +674,57 @@ 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] :workspace Name of the workspace.
# @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.
# * 'mname' [String] Module name. For example: 'windows/wlan/wlan_profile'
# @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)
return ret unless h
h_result = self.framework.analyze.host(h)
host_detail = {}
host_detail[:address] = h.address
# 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.type
mod_detail[:mname] = mod.full_name
host_detail[:modules] << mod_detail
end
end
ret[:host] << host_detail
ret
}
end


# Reports a new host to the database.
#
# @param [Hash] xopts Information to report about the host. See below:
# @option xopts [String] :workspace Name of the workspace.
# @option xopts [String] :host IP address. You msut supply this.
# @option xopts [String] :state One of the Msf::HostState constants. (See Most::HostState Documentation)
# @option xopts [String] :os_name Something like "Windows", "Linux", or "Mac OS X".
Expand Down Expand Up @@ -711,6 +759,7 @@ def rpc_report_host(xopts)
# Reports a service to the database.
#
# @param [Hash] xopts Information to report about the service. See below:
# @option xopts [String] :workspace Name of the workspace.
# @option xopts [String] :host Required. The host where this service is running.
# @option xopts [String] :port Required. The port where this service listens.
# @option xopts [String] :proto Required. The transport layer protocol (e.g. tcp, udp).
Expand Down Expand Up @@ -805,6 +854,7 @@ def rpc_get_service(xopts)
# Returns a note.
#
# @param [Hash] xopts Options.
# @option xopts [String] :workspace Workspace name.
# @option xopts [String] :addr Host address.
# @option xopts [String] :address Same as :addr.
# @option xopts [String] :host Same as :address.
Expand Down Expand Up @@ -899,6 +949,7 @@ def rpc_get_client(xopts)
# Reports a client connection.
#
# @param [Hash] xopts Information about the client.
# @option xopts [String] :workspace Name of the workspace.
# @option xopts [String] :ua_string Required. User-Agent string.
# @option xopts [String] :host Required. Host IP.
# @option xopts [String] :ua_name One of the Msf::HttpClients constants. (See Msf::HttpClient Documentation.)
Expand Down Expand Up @@ -970,6 +1021,7 @@ def rpc_report_note(xopts)
# Returns notes from the database.
#
# @param [Hash] xopts Filters for the search. See below:
# @option xopts [String] :workspace Name of the workspace.
# @option xopts [String] :address Host address.
# @option xopts [String] :names Names (separated by ',').
# @option xopts [String] :ntype Note type.
Expand Down Expand Up @@ -1612,6 +1664,7 @@ def rpc_get_vuln(xopts)
# Returns browser clients information.
#
# @param [Hash] xopts Filters that narrow down the search.
# @option xopts [String] :workspace Name of the workspace.
# @option xopts [String] :ua_name User-Agent name.
# @option xopts [String] :ua_ver Browser version.
# @option xopts [Array] :addresses Addresses.
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
70 changes: 70 additions & 0 deletions lib/msf/ui/console/command_dispatcher/db/analyze.rb
@@ -0,0 +1,70 @@
module Msf::Ui::Console::CommandDispatcher::Analyze

def cmd_analyze(*args)
unless active?
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_ranges.push(nil) if host_ranges.empty?

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
eval_hosts_ids.each do |eval_id|
host_ids.push(eval_id)
end
end
end

if host_ids.empty?
print_status("No existing hosts stored to analyze.")
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