More cleanup related to vuln schema

unstable
HD Moore 2012-06-07 04:42:16 -05:00
parent 42c3bedfad
commit 49b3c9b0e8
6 changed files with 231 additions and 103 deletions

0
data/exploits/CVE-2011-3400/CVE-2011-3400.vsd Normal file → Executable file
View File

View File

@ -1,35 +0,0 @@
class AddVulnDetails < ActiveRecord::Migration
def self.up
create_table :vuln_details do |t|
t.integer :vuln_id # Vuln table reference
t.float :cvss_score # 0.0 to 10.0
t.string :cvss_vector # Ex: (AV:N/AC:L/Au:N/C:C/I:C/A:C)(AV:N/AC:L/Au:N/C:C/I:C/A:C)
t.string :src # Source application ("nexpose")
t.string :title # Short identifier
t.text :description # Plain text or HTML (trusted)
t.text :solution # Plain text or HTML (trusted)
t.binary :proof # Should be UTF-8, but may not be, sanitize on output
# Technically this duplicates vuln.info, but that field
# is poorly managed / handled today. Eventually we will
# replace vuln.info
# Nexpose-specific fields
t.integer :nx_console_id # NexposeConsole local table reference
t.integer :nx_device_id # Reference from the Nexpose side
t.string :nx_vuln_id # 'jre-java-update-flaw'
t.float :nx_severity # 0-10
t.float :nx_pci_severity # 0-10
t.timestamp :nx_published # Normalized from "20081205T000000000"
t.timestamp :nx_added # Normalized from "20081205T000000000"
t.timestamp :nx_modified # Normalized from "20081205T000000000"
t.text :nx_tags # Comma separated
end
end
def self.down
drop_table :vuln_details
end
end

View File

@ -0,0 +1,16 @@
class ExpandDetails < ActiveRecord::Migration
def self.up
add_column :vuln_details, :nx_vuln_status, :text
add_column :vuln_details, :nx_proof_key, :text
add_column :vuln_details, :src, :string
add_column :host_details, :src, :string
end
def self.down
remove_column :vuln_details, :nx_vuln_status
remove_column :vuln_details, :nx_proof_key
remove_column :vuln_details, :src
remove_column :host_details, :src
end
end

View File

@ -1345,7 +1345,9 @@ class DBManager
# Identify the associated service
service = nil
if opts[:port]
# 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'
@ -1362,18 +1364,22 @@ class DBManager
# Try to find an existing vulnerability with the same service & references
# If there are multiple matches, choose the one with the most matches
refs_ids = rids.map{|x| x.id }
vuln = service.vulns.find(:all, :include => [:refs], :conditions => { '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
if rids
refs_ids = rids.map{|x| x.id }
vuln = service.vulns.find(:all, :include => [:refs], :conditions => { '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
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
refs_ids = rids.map{|x| x.id }
vuln = service.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
if rids
refs_ids = rids.map{|x| x.id }
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
end
end
# No matches, so create a new vuln record
@ -1398,16 +1404,16 @@ class DBManager
vuln.refs << (rids - vuln.refs)
end
# Handle vuln_details parameters
if details
report_vuln_details(vuln, details)
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
@ -1454,35 +1460,44 @@ class DBManager
#
def report_vuln_details(vuln, details)
::ActiveRecord::Base.connection_pool.with_connection {
detail = ::Mdm::VulnDetail.where(( details.delete(:key) || {} ).merge(:vuln_id => vuln.id))
detail = ::Mdm::VulnDetail.where(( details.delete(:key) || {} ).merge(:vuln_id => vuln.id)).first
if detail
details.each_pair do |k,v|
detail[k] = v
end
detail.save! if detail.changed?
detail
else
detail = ::Mdm::VulnDetail.create(details.merge(:vuln_id => vuln.id))
end
}
end
#
# Update vuln_details records en-masse based on specific criteria
# Note that this *can* update data across workspaces
#
def update_vuln_details(details)
criteria = details.delete(:key) || {}
::Mdm::VulnDetail.update(key, details)
end
#
# Populate the host_details table with additional
# information, matched by a specific criteria
#
def report_host_details(vuln, details)
def report_host_details(host, details)
::ActiveRecord::Base.connection_pool.with_connection {
detail = ::Mdm::HostDetail.where(( details.delete(:key) || {} ).merge(:host_id => host.id))
detail = ::Mdm::HostDetail.where(( details.delete(:key) || {} ).merge(:host_id => host.id)).first
if detail
details.each_pair do |k,v|
detail[k] = v
end
detail.save! if detail.changed?
detail
else
detail = ::Mdm::HostDetail.create(details.merge(:host_id => host.id))
end
}
end

View File

@ -1,4 +1,5 @@
require "rex/parser/nokogiri_doc_mixin"
require "date"
module Rex
module Parser
@ -19,7 +20,7 @@ module Rex
@state[:current_tag][name] = true
case name
when "nodes" # There are two main sections, nodes and VulnerabilityDefinitions
@tests = []
@tests = {}
when "node"
record_host(attrs)
when "name"
@ -33,6 +34,7 @@ module Rex
when "os"
record_os_fingerprint(attrs)
when "test" # All the vulns tested for
@state[:has_text] = true
record_host_test(attrs)
record_service_test(attrs)
when "vulnerability"
@ -40,6 +42,14 @@ module Rex
when "reference"
@state[:has_text] = true
record_reference(attrs)
when "description"
@state[:has_text] = true
when "solution"
@state[:has_text] = true
when "tag"
@state[:has_text] = true
when "tags"
@state[:tags] = []
end
end
@ -58,12 +68,16 @@ module Rex
when "name"
collect_hostname
@state[:has_text] = false
@text = nil
when "endpoint"
collect_service_data
@state.delete(:cached_service_object)
when "os"
collect_os_fingerprints
when "test"
save_test
report_test(&block)
@state[:has_text] = false
@text = nil
when "vulnerability"
collect_vuln_info
report_vuln(&block)
@ -72,6 +86,20 @@ module Rex
@state[:has_text] = false
collect_reference
@text = nil
when "description"
@state[:has_text] = false
collect_vuln_description
@text = nil
when "solution"
@state[:has_text] = false
collect_vuln_solution
@text = nil
when "tag"
@state[:has_text] = false
collect_tag
@text = nil
when "tags"
@state.delete(:tags)
end
@state[:current_tag].delete name
end
@ -86,6 +114,29 @@ module Rex
@state[:ref] = nil
end
def collect_vuln_description
return unless in_tag("description")
return unless in_tag("vulnerability")
return unless @state[:vuln]
@report_data[:vuln_description] = @text.to_s.strip
end
def collect_vuln_solution
return unless in_tag("solution")
return unless in_tag("vulnerability")
return unless @state[:vuln]
@report_data[:vuln_solution] = @text.to_s.strip
end
def collect_tag
return unless in_tag("tag")
return unless in_tag("tags")
return unless in_tag("vulnerability")
return unless @state[:vuln]
@report_data[:vuln_tags] ||= []
@report_data[:vuln_tags] << @text.to_s.strip
end
def collect_vuln_info
return unless in_tag("VulnerabilityDefinitions")
return unless in_tag("vulnerability")
@ -101,49 +152,53 @@ module Rex
return unless in_tag("VulnerabilityDefinitions")
return unless @report_data[:vuln]
return unless @report_data[:vuln][:matches].kind_of? Array
::ActiveRecord::Base.connection_pool.with_connection {
refs = normalize_references(@report_data[:vuln][:refs])
refs << "NEXPOSE-#{report_data[:vuln]["id"]}"
vuln_instances = @report_data[:vuln][:matches].size
db.emit(:vuln, [refs.last,vuln_instances], &block) if block
data = {
:workspace => @args[:wspace],
:name => refs.last,
:info => @report_data[:vuln]["title"],
:refs => refs.uniq
}
hosts_keys = {}
@report_data[:vuln][:matches].each do |match|
host_data = data.dup
host_data[:host] = match[:host]
host_data[:port] = match[:port] if match[:port]
host_data[:proto] = match[:protocol] if match[:protocol]
db_report(:vuln, host_data)
if match[:key]
hosts_keys[host_data[:host]] ||= []
hosts_keys[host_data[:host]] << match[:key]
end
end
report_key_note(hosts_keys,data)
@report_data[:vuln] = nil
end
def report_key_note(hosts_keys,data)
return if hosts_keys.empty?
hosts_keys.each do |key_host,key_values|
key_note = {
:workspace => @args[:wspace],
:host => key_host,
:type => "host.vuln.nexpose_keys",
:data => {},
:update => :unique_data
}
key_values.each do |key_value|
key_note[:data][data[:name]] ||= []
next if key_note[:data][data[:name]].include? key_value
key_note[:data][data[:name]] << key_value
vuln_ids = @report_data[:vuln][:matches].map{ |v| v[0] }
vdet_ids = @report_data[:vuln][:matches].map{ |v| v[1] }
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"]
vuln.refs = refs
if vuln.changed?
vuln.save!
end
db_report(:note, key_note)
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].join(", ") if ( @report_data[:vuln_tags].kind_of?(::Array) and @report_data[:vuln_tags].length > 0 )
vdet_info[:nx_severity] = @report_data[:vuln]["severity"].to_f if @report_data[:vuln]["severity"]
vdet_info[:nx_pci_severity] = @report_data[:vuln]["pciSeverity"].to_f if @report_data[:vuln]["pciSeverity"]
vdet_info[:cvss_score] = @report_data[:vuln]["cvssScore"].to_f if @report_data[:vuln]["cvssScore"]
vdet_info[:cvss_vector] = @report_data[:vuln]["cvssVector"] if @report_data[:vuln]["cvssVector"]
%W{ published added modified }.each do |tf|
next if not @report_data[:vuln][tf]
ts = DateTime.parse(@report_data[:vuln][tf]) rescue nil
next if not ts
vdet_info[ "nx_#{tf}".to_sym ] = ts
end
::Mdm::VulnDetail.where(:id => vdet_ids).update_all(vdet_info)
@report_data[:vuln] = nil
}
end
def record_reference(attrs)
@ -155,22 +210,82 @@ module Rex
def record_vuln(attrs)
return unless in_tag("VulnerabilityDefinitions")
vuln = attr_hash(attrs)
matching_tests = @tests.select {|x| x[:id] == vuln["id"].downcase}
matching_tests = @tests[ vuln["id"].downcase ]
return unless matching_tests
return if matching_tests.empty?
@state[:vuln] = vuln
@state[:vuln][:matches] = matching_tests
end
def save_test
# XML Export 2.0 includes additional test keys:
# <test id="unix-unowned-files-or-dirs" status="vulnerable-exploited" scan-id="6381" vulnerable-since="20120322T124352665" pci-compliance-status="pass">
def report_test
return unless in_tag("nodes")
return unless in_tag("node")
return unless @state[:test]
test = { :id => @state[:test][:id]}
test[:host] = @state[:address]
test[:port] = @state[:test][:port] if @state[:test][:port]
test[:protocol] = @state[:test][:protocol] if @state[:test][:protocol]
test[:key] = @state[:test][:key] if @state[:test][:key]
@tests << test
vuln_info = {
:workspace => @args[:wspace],
# This name will be overwritten during the vuln definition
# parsing via mass-update.
:name => "NEXPOSE-" + @state[:test][:id].downcase,
:host => @state[:cached_host_object] || @state[:address]
}
if @state[:cached_service_object]
vuln_info[:service] = @state[:cached_service_object]
else
vuln_info[:port] = @state[:test][:port] if @state[:test][:port]
vuln_info[:protocol] = @state[:test][:protocol] if @state[:test][:protocol]
end
# This hash feeds a vuln_details row for this vulnerability
vdet = { :src => 'nexpose', :nx_vuln_id => @state[:test][:id] }
# This hash defines the matching criteria to overwrite an existing entry
vkey = { :src => 'nexpose', :nx_vuln_id => @state[:test][:id] }
if @state[:device_id]
vdet[:nx_device_id] = @state[:device_id]
vkey[:nx_device_id] = @state[:device_id]
end
if @state[:test][:key]
vdet[:nx_proof_key] = @state[:test][:key]
vkey[:nx_proof_key] = @state[:test][:key]
end
vdet[:nx_console_id] = @console_id if @console_id
vdet[:nx_vuln_status] = @state[:test][:status] if @state[:test][:status]
proof = @text.to_s.strip
vuln_info[:info] = proof
vdet[:proof] = proof
# Configure the find key for vuln_details
vdet[:key] = vkey
::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
@state[:cached_service_object] ||= vuln.service if vuln.service
# 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
@ -321,6 +436,7 @@ module Rex
:protocol => @state[:service]["protocol"],
}
@state[:test][:key] = test["key"] if test["key"]
@state[:test][:status] = test["status"] if test["status"]
end
def record_host(attrs)
@ -330,6 +446,7 @@ module Rex
@state[:host_is_alive] = true
@state[:address] = host_attrs["address"]
@state[:mac] = host_attrs["hardware-address"] if host_attrs["hardware-address"]
@state[:device_id] = host_attrs["device-id"] if host_attrs["device-id"]
end
end
@ -345,15 +462,26 @@ module Rex
@report_data[:mac] = @state[:mac]
end
end
@report_data[:device_id] = @state[:device_id] if @state[:device_id]
end
def report_host(&block)
if host_is_okay
db.emit(:address,@report_data[:host],&block) if block
host_object = db_report(:host, @report_data.merge(
:workspace => @args[:wspace] ) )
device_id = @report_data.delete(:device_id)
host_object = db_report(:host, @report_data.merge(:workspace => @args[:wspace] ) )
if host_object
db.report_import_note(host_object.workspace, host_object)
if device_id
detail = {
:key => { :src => 'nexpose' },
:src => 'nexpose',
:nx_device_id => device_id
}
detail[:nx_console_id] = @nx_console_id if @nx_console_id
db.report_host_details(host_object, detail)
end
end
host_object
end

View File

@ -149,7 +149,11 @@ module Parser
[:host, :host_name, :mac, :workspace]
when :vuln
::Mdm::Vuln.new.attribute_names.map {|x| x.to_sym} |
[:host, :refs, :workspace, :port, :proto]
[:host, :refs, :workspace, :port, :proto, :details, :exploited_at]
when :vuln_details
::Mdm::VulnDetails.new.attribute_names.map {|x| x.to_sym} | [ :key ]
when :host_details
::Mdm::HostDetails.new.attribute_names.map {|x| x.to_sym} | [ :key ]
when :note, :web_site, :web_page, :web_form, :web_vuln
# These guys don't complain
[:anything]