302 lines
8.8 KiB
Ruby
302 lines
8.8 KiB
Ruby
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)
|
|
::ActiveRecord::Base.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
|
|
vuln = service.vulns.includes(:vuln_details).where(crit).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
|
|
vuln = host.vulns.includes(:vuln_details).where(crit).first
|
|
|
|
return vuln
|
|
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}"
|
|
::ActiveRecord::Base.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)
|
|
::ActiveRecord::Base.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
|
|
#
|
|
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]
|
|
|
|
::ActiveRecord::Base.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]}"
|
|
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]
|
|
when 'dns','snmp','dhcp'
|
|
proto = 'udp'
|
|
sname = opts[:proto]
|
|
else
|
|
proto = 'tcp'
|
|
sname = opts[:proto]
|
|
end
|
|
|
|
service = host.services.where(port: opts[:port].to_i, proto: proto).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 and service and not vuln.service
|
|
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_import_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)
|
|
::ActiveRecord::Base.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)
|
|
::ActiveRecord::Base.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?
|
|
|
|
::ActiveRecord::Base.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
|