Complicate the matching process in the name of memory

and loading speed. Use optional match_details param
to find matching vuln instances.
unstable
HD Moore 2012-06-17 00:07:00 -05:00
parent 7d9d6f11e5
commit be9b7a88fb
2 changed files with 146 additions and 60 deletions

View File

@ -1453,10 +1453,10 @@ class DBManager
wspace = opts.delete(:workspace) || workspace
exploited_at = opts[:exploited_at] || opts["exploited_at"]
details = opts.delete(:details)
rids = nil
rids = opts.delete(:ref_ids)
if opts[:refs]
rids = []
rids ||= []
opts[:refs].each do |r|
if (r.respond_to?(:ctx_id)) and (r.respond_to?(:ctx_val))
r = "#{r.ctx_id}-#{r.ctx_val}"
@ -1476,16 +1476,6 @@ class DBManager
ret = {}
=begin
if host
host.updated_at = host.created_at
host.state = HostState::Alive
host.save!
else
host = get_host(:workspace => wspace, :address => addr)
end
=end
# Truncate the info field at the maximum field length
if info
info = info[0,65535]
@ -1498,46 +1488,73 @@ class DBManager
vuln = nil
# Identify the associated service
service = nil
service = opts.delete(:service)
# Treat port zero as no service
if opts[:port].to_i > 0
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
if service or opts[:port].to_i > 0
service = host.services.find_or_create_by_port_and_proto(opts[:port], proto)
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.find_or_create_by_port_and_proto(opts[:port].to_i, proto)
end
# Try to find an existing vulnerability with the same service & references
# If there are multiple matches, choose the one with the most matches
vuln = find_vuln_by_refs(rids, host, service) if rids
# 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
vuln = find_vuln_by_refs(rids, host) if rids
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 info and name !~ /^NEXPOSE-/
vuln = host.vulns.find_or_initialize_by_name_and_info(name, info)
if service
vuln = service.vulns.find_by_name(name)
else
vuln = host.vulns.find_or_initialize_by_name(name)
vuln = host.vulns.find_by_name(name)
end
vuln.service = service
end
unless vuln
# Overwrite the name and information
vuln.name = name
vuln.info = info.to_s if info
vinf = {
:host_id => host.id,
:name => name,
:info => info
}
vinf[:service_id] = service.id if service
vuln = Mdm::Vuln.create(vinf)
end
end
# Set the exploited_at value if provided
vuln.exploited_at = exploited_at if exploited_at
@ -1562,6 +1579,8 @@ class DBManager
def find_vuln_by_refs(refs, host, service=nil)
vuln = nil
# Try to find an existing vulnerability with the same service & references
# If there are multiple matches, choose the one with the most matches
if service
@ -1580,6 +1599,35 @@ class DBManager
vuln = host.vulns.find(:all, :include => [:refs], :conditions => { 'service_id' => nil, 'refs.id' => refs_ids }).sort { |a,b|
( refs_ids - a.refs.map{|x| x.id } ).length <=> ( refs_ids - b.refs.map{|x| x.id } ).length
}.first
return vuln
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.find(:first, :include => [:vuln_details], :conditions => crit)
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.find(:first, :include => [:vuln_details], :conditions => crit)
return vuln
end
def get_vuln(wspace, host, service, name, data='')

View File

@ -33,6 +33,7 @@ module Rex
when "name"
@state[:has_text] = true
when "endpoint"
@state.delete(:cached_service_object)
record_service(attrs)
when "service"
record_service_info(attrs)
@ -168,10 +169,29 @@ module Rex
vuln_instances = @report_data[:vuln][:matches].size
db.emit(:vuln, [refs.last,vuln_instances], &block) if block
# Save some time by creating the refs up front
rids = refs.uniq.map{|x| db.find_or_create_ref(:name => x) }
vuln_ids = @report_data[:vuln][:matches].map{ |v| v[0] }
vdet_ids = @report_data[:vuln][:matches].map{ |v| v[1] }
vdet_info = { :title => @report_data[:vuln]["title"] }
refs = refs.uniq.map{|x| db.find_or_create_ref(:name => x) }
# Assign title and references to all vuln_ids
# Mass update fails due to the join table || ::Mdm::Vuln.where(:id => vuln_ids).update_all({ :name => @report_data[:vuln]["title"], :refs => refs } )
vuln_ids.each do |vid|
vuln = ::Mdm::Vuln.find(vid)
next unless vuln
vuln.name = @report_data[:vuln]["title"]
if refs.length > 0
vuln.refs += refs
end
if vuln.changed?
vuln.save!
end
end
# Mass update vulnerability details across the database based on conditions
vdet_info = { :title => @report_data[:vuln]["title"] }
vdet_info[:description] = @report_data[:vuln_description] unless @report_data[:vuln_description].to_s.empty?
vdet_info[:solution] = @report_data[:vuln_solution] unless @report_data[:vuln_solution].to_s.empty?
vdet_info[:nx_tags] = @report_data[:vuln_tags].sort.uniq.join(", ") if ( @report_data[:vuln_tags].kind_of?(::Array) and @report_data[:vuln_tags].length > 0 )
@ -187,12 +207,7 @@ module Rex
vdet_info[ "nx_#{tf}".to_sym ] = ts
end
@report_data[:vuln][:matches].each do |vinfo|
vinfo[:name] = @report_data[:vuln]["title"]
vinfo[:ref_ids] = rids
vinfo[:details].merge(vdet_info)
db.report_vuln(vinfo)
end
::Mdm::VulnDetail.where(:id => vdet_ids).update_all(vdet_info)
@report_data[:vuln] = nil
@ -230,10 +245,17 @@ module Rex
:name => "NEXPOSE-" + @state[:test][:id].downcase,
:host => @state[:cached_host_object] || @state[:address]
}
vuln_info[:port] = @state[:test][:port] if @state[:test][:port]
vuln_info[:proto] = @state[:test][:protocol] if @state[:test][:protocol]
if in_tag("endpoint") and @state[:test][:port]
# Verify this port actually has some relation to our tracked state
# since it may not due to greedy vulnerability matching
if @state[:cached_service_object] and @state[:cached_service_object].port.to_i == @state[:test][:port].to_i
vuln_info[:service] = @state[:cached_service_object]
else
vuln_info[:port] = @state[:test][:port]
vuln_info[:proto] = @state[:test][:protocol] if @state[:test][:protocol]
end
end
# This hash feeds a vuln_details row for this vulnerability
vdet = { :src => 'nexpose', :nx_vuln_id => @state[:test][:id] }
@ -269,18 +291,34 @@ module Rex
# Configure the find key for vuln_details
vdet[:key] = vkey
# Store the details on the vuln hash
vuln_info[:details] = vdet
# Pass this key to the vuln hash to find existing entries
# that may have been renamed (re-import nexpose vulns)
vuln_info[:details_match] = vkey
# Record this test information for future correlation that
# brings in title, risk, description, solution, etc.
# XXX: This can be a memory hog, but a two-step update
# process can result in duplicate vulns due to the
# renaming step (nothing to match on otherwise)
@tests[ @state[:test][:id].downcase ] ||= []
@tests[ @state[:test][:id].downcase ] << vuln_info
::ActiveRecord::Base.connection_pool.with_connection {
# Report the vulnerability
vuln = db.report_vuln(vuln_info)
if vuln
# Report the vulnerability details
detail = db.report_vuln_details(vuln, vdet)
# Cache returned host and service objects if necessary
@state[:cached_host_object] ||= vuln.host
# The vuln.service may be found via greedy matching
if in_tag("endpoint") and vuln.service
@state[:cached_service_object] ||= vuln.service
end
# Record the ID of this vuln for a future mass update that
# brings in title, risk, description, solution, etc
@tests[ @state[:test][:id].downcase ] ||= []
@tests[ @state[:test][:id].downcase ] << [ vuln.id, detail.id ]
end
}
@state[:test] = nil
end