Skip to content

Commit

Permalink
Land #11191, add analyze command
Browse files Browse the repository at this point in the history
  • Loading branch information
mkienow-r7 authored and jmartin-tech committed Feb 20, 2019
1 parent d0d3903 commit 7b2c625
Show file tree
Hide file tree
Showing 6 changed files with 225 additions and 2 deletions.
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)
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 @@ -192,6 +193,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 db manager. The db manager
# maintains the database db and handles db events
Expand Down Expand Up @@ -268,6 +274,7 @@ def search(match, logger: nil)
attr_writer :db # :nodoc:
attr_writer :uuid_db # :nodoc:
attr_writer :browser_profiles # :nodoc:
attr_writer :analyze # :nodoc:
end

class FrameworkEventSubscriber
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|
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
7 changes: 5 additions & 2 deletions lib/msf/ui/console/command_dispatcher/db.rb
Expand Up @@ -3,6 +3,7 @@
require 'rexml/document'
require 'rex/parser/nmap_xml'
require 'msf/core/db_export'
require 'msf/ui/console/command_dispatcher/db/analyze'

module Msf
module Ui
Expand All @@ -15,7 +16,8 @@ class Db

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

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

#
# The dispatcher's name.
#
Expand Down Expand Up @@ -43,7 +45,8 @@ def commands
"db_import" => "Import a scan result file (filetype will be auto-detected)",
"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"
"db_rebuild_cache" => "Rebuilds the database-stored module cache",
"analyze" => "Analyze database information about a specific address or address range",
}

# Always include commands that only make sense when connected.
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
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

0 comments on commit 7b2c625

Please sign in to comment.