lib/msf/core/db_manager/vuln.rb
module Msf::DBManager::Vuln
#
# This method iterates the vulns table calling the supplied block with the
# vuln instance of each entry.
#
def each_vuln(wspace=framework.db.workspace, &block)
::ApplicationRecord.connection_pool.with_connection {
wspace.vulns.each do |vulns|
block.call(vulns)
end
}
end
#
# Find or create a vuln matching this service/name
#
def find_or_create_vuln(opts)
report_vuln(opts)
end
def find_vuln_by_details(details_map, host, service=nil)
# Create a modified version of the criteria in order to match against
# the joined version of the fields
crit = {}
details_map.each_pair do |k,v|
crit[ "vuln_details.#{k}" ] = v
end
vuln = nil
if service
other_vulns = service.vulns.includes(:vuln_details).where(crit).to_a
vuln = other_vulns.empty? ? nil : other_vulns.first
end
# Return if we matched based on service
return vuln if vuln
# Prevent matches against other services
crit["vulns.service_id"] = nil if service
other_vulns = host.vulns.includes(:vuln_details).where(crit).to_a
other_vulns.empty? ? nil : other_vulns.first
end
def find_vuln_by_refs(refs, host, service=nil)
ref_ids = refs.find_all { |ref| ref.name.starts_with? 'CVE-'}
relation = host.vulns.joins(:refs)
if !service.try(:id).nil?
return relation.where(service_id: service.try(:id), refs: { id: ref_ids}).first
end
return relation.where(refs: { id: ref_ids}).first
end
def get_vuln(wspace, host, service, name, data='')
raise RuntimeError, "Not workspace safe: #{caller.inspect}"
::ApplicationRecord.connection_pool.with_connection {
vuln = nil
if (service)
vuln = ::Mdm::Vuln.find.where("name = ? and service_id = ? and host_id = ?", name, service.id, host.id).order("vulns.id DESC").first()
else
vuln = ::Mdm::Vuln.find.where("name = ? and host_id = ?", name, host.id).first()
end
return vuln
}
end
#
# Find a vulnerability matching this name
#
def has_vuln?(name)
::ApplicationRecord.connection_pool.with_connection {
Mdm::Vuln.find_by_name(name)
}
end
#
# opts MUST contain
# +:host+:: the host where this vulnerability resides
# +:name+:: the friendly name for this vulnerability (title)
#
# opts can contain
# +:info+:: a human readable description of the vuln, free-form text
# +:refs+:: an array of Ref objects or string names of references
# +:details+:: a hash with :key pointed to a find criteria hash and the rest containing VulnDetail fields
# +:sname+:: the name of the service this vulnerability relates to, used to associate it or create it.
#
def report_vuln(opts)
return if not active
raise ArgumentError.new("Missing required option :host") if opts[:host].nil?
raise ArgumentError.new("Deprecated data column for vuln, use .info instead") if opts[:data]
name = opts[:name] || return
info = opts[:info]
::ApplicationRecord.connection_pool.with_connection {
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
opts = opts.clone()
opts.delete(:workspace)
exploited_at = opts[:exploited_at] || opts["exploited_at"]
details = opts.delete(:details)
rids = opts.delete(:ref_ids)
if opts[:refs]
rids ||= []
opts[:refs].each do |r|
if r.instance_of?(Mdm::Module::Ref)
str = r.name
elsif (r.respond_to?(:ctx_id)) and (r.respond_to?(:ctx_val))
str = "#{r.ctx_id}-#{r.ctx_val}"
elsif (r.is_a?(Hash) and r[:ctx_id] and r[:ctx_val])
str = "#{r[:ctx_id]}-#{r[:ctx_val]}"
elsif r.is_a?(String)
str = r
end
rids << find_or_create_ref(:name => str) unless str.nil?
end
end
host = nil
addr = nil
if opts[:host].kind_of? ::Mdm::Host
host = opts[:host]
else
host = report_host({:workspace => wspace, :host => opts[:host]})
addr = Msf::Util::Host.normalize_host(opts[:host])
end
ret = {}
# Truncate the info field at the maximum field length
if info
info = info[0,65535]
end
# Truncate the name field at the maximum field length
name = name[0,255]
# Placeholder for the vuln object
vuln = nil
# Identify the associated service
service = opts.delete(:service)
# Treat port zero as no service
if service or opts[:port].to_i > 0
if not service
proto = nil
case opts[:proto].to_s.downcase # Catch incorrect usages, as in report_note
when 'tcp','udp'
proto = opts[:proto]
sname = opts[:sname]
when 'dns','snmp','dhcp'
proto = 'udp'
sname = opts[:proto]
else
proto = 'tcp'
sname = opts[:proto]
end
services = host.services.where(port: opts[:port].to_i, proto: proto)
services = services.where(name: sname) if sname.present?
service = services.first_or_create
end
# Try to find an existing vulnerability with the same service & references
# If there are multiple matches, choose the one with the most matches
# If a match is found on a vulnerability with no associated service,
# update that vulnerability with our service information. This helps
# prevent dupes of the same vuln found by both local patch and
# service detection.
if rids and rids.length > 0
vuln = find_vuln_by_refs(rids, host, service)
vuln.service = service if vuln
end
else
# Try to find an existing vulnerability with the same host & references
# If there are multiple matches, choose the one with the most matches
if rids and rids.length > 0
vuln = find_vuln_by_refs(rids, host)
end
end
# Try to match based on vuln_details records
if not vuln and opts[:details_match]
vuln = find_vuln_by_details(opts[:details_match], host, service)
if vuln && service && vuln.service.nil?
vuln.service = service
end
end
# No matches, so create a new vuln record
unless vuln
if service
vuln = service.vulns.find_by_name(name)
else
vuln = host.vulns.find_by_name(name)
end
unless vuln
vinf = {
:host_id => host.id,
:name => name,
:info => info
}
vinf[:service_id] = service.id if service
vuln = Mdm::Vuln.create(vinf)
begin
framework.events.on_db_vuln(vuln) if vuln
rescue ::Exception => e
wlog("Exception in on_db_vuln event handler: #{e.class}: #{e}")
wlog("Call Stack\n#{e.backtrace.join("\n")}")
end
end
end
# Set the exploited_at value if provided
vuln.exploited_at = exploited_at if exploited_at
# Merge the references
if rids
vuln.refs << (rids - vuln.refs)
end
# Finalize
if vuln.changed?
msf_assign_timestamps(opts, vuln)
vuln.save!
end
# Handle vuln_details parameters
report_vuln_details(vuln, details) if details
vuln
}
end
#
# This methods returns a list of all vulnerabilities in the database
#
def vulns(opts)
::ApplicationRecord.connection_pool.with_connection {
# If we have the ID, there is no point in creating a complex query.
if opts[:id] && !opts[:id].to_s.empty?
return Array.wrap(Mdm::Vuln.find(opts[:id]))
end
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
opts = opts.clone()
opts.delete(:workspace)
search_term = opts.delete(:search_term)
if search_term && !search_term.empty?
column_search_conditions = Msf::Util::DBManager.create_all_column_search_conditions(Mdm::Vuln, search_term)
wspace.vulns.includes(:host).where(opts).where(column_search_conditions)
else
wspace.vulns.includes(:host).where(opts)
end
}
end
# Update the attributes of a Vuln entry with the values in opts.
# The values in opts should match the attributes to update.
#
# @param opts [Hash] Hash containing the updated values. Key should match the attribute to update. Must contain :id of record to update.
# @return [Mdm::Vuln] The updated Mdm::Vuln object.
def update_vuln(opts)
::ApplicationRecord.connection_pool.with_connection {
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework, false)
opts = opts.clone()
opts.delete(:workspace)
opts[:workspace] = wspace if wspace
v = Mdm::Vuln.find(opts.delete(:id))
v.update!(opts)
v
}
end
# Deletes Vuln entries based on the IDs passed in.
#
# @param opts[:ids] [Array] Array containing Integers corresponding to the IDs of the Vuln entries to delete.
# @return [Array] Array containing the Mdm::Vuln objects that were successfully deleted.
def delete_vuln(opts)
raise ArgumentError.new("The following options are required: :ids") if opts[:ids].nil?
::ApplicationRecord.connection_pool.with_connection {
deleted = []
opts[:ids].each do |vuln_id|
vuln = Mdm::Vuln.find(vuln_id)
begin
deleted << vuln.destroy
rescue # refs suck
elog("Forcibly deleting #{vuln}")
deleted << vuln.delete
end
end
return deleted
}
end
end