add a generic db_import command that auto-detects filetype, move import parsing into msf/core/db. fixes 750
git-svn-id: file:///home/svn/framework3/trunk@8085 4d416f70-5f16-0410-b530-b9f4589650da
This commit is contained in:
parent
1bd6872c6f
commit
30b897b6cd
|
@ -880,6 +880,480 @@ class DBManager
|
|||
ActiveRecord::Base.connection.select_all(sqlquery)
|
||||
end
|
||||
|
||||
|
||||
##
|
||||
#
|
||||
# Import methods
|
||||
#
|
||||
##
|
||||
|
||||
#
|
||||
# Generic importer that automatically determines the file type being
|
||||
# imported. Since this looks for vendor-specific strings in the given
|
||||
# file, there shouldn't be any false detections, but no guarantees.
|
||||
#
|
||||
def import_file(filename)
|
||||
f = File.open(filename, 'r')
|
||||
data = f.read(f.stat.size)
|
||||
import(data)
|
||||
end
|
||||
def import(data)
|
||||
firstline = data[0, data.index("\n")]
|
||||
if (firstline.index("<NeXposeSimpleXML"))
|
||||
return import_nexpose_simplexml(data)
|
||||
elsif (firstline.index("<?xml"))
|
||||
# it's xml, check for root tags we can handle
|
||||
line_count = 0
|
||||
data.each_line { |line|
|
||||
line =~ /<([a-zA-Z-]+)[ >]/
|
||||
case $1
|
||||
when "nmaprun"
|
||||
return import_nmap_xml(data)
|
||||
when "openvas-report"
|
||||
return import_openvas_xml(data)
|
||||
when "NessusClientData"
|
||||
return import_nessus_xml(data)
|
||||
else
|
||||
# Give up if we haven't hit the root tag in the first few lines
|
||||
break if line_count > 10
|
||||
end
|
||||
line_count += 1
|
||||
}
|
||||
elsif (firstline.index("timestamps|||scan_start"))
|
||||
# then it's a nessus nbe
|
||||
return import_nessus_nbe(data)
|
||||
elsif (firstline.index("# amap v"))
|
||||
# then it's an amap mlog
|
||||
return import_amap_mlog(data)
|
||||
end
|
||||
raise RuntimeError.new("Could not automatically determine file type")
|
||||
end
|
||||
|
||||
#
|
||||
# Nexpose Simple XML
|
||||
#
|
||||
# XXX At some point we'll want to make this a stream parser for dealing
|
||||
# with large results files
|
||||
#
|
||||
def import_nexpose_simplexml_file(filename)
|
||||
f = File.open(filename, 'r')
|
||||
data = f.read(f.stat.size)
|
||||
import_nexpose_simplexml(data)
|
||||
end
|
||||
def import_nexpose_simplexml(data)
|
||||
if data.kind_of? REXML::Document
|
||||
doc = data
|
||||
else
|
||||
doc = REXML::Document.new(data)
|
||||
end
|
||||
p doc.root
|
||||
doc.elements.each('/NeXposeSimpleXML/devices/device') do |dev|
|
||||
addr = dev.attributes['address'].to_s
|
||||
desc = ''
|
||||
dev.elements.each('fingerprint/description') do |fdesc|
|
||||
desc = fdesc.text.to_s.strip
|
||||
end
|
||||
|
||||
host = framework.db.find_or_create_host(:host => addr, :state => Msf::HostState::Alive)
|
||||
next if not host
|
||||
|
||||
# Load vulnerabilities not associated with a service
|
||||
dev.elements.each('vulnerabilities/vulnerability') do |vuln|
|
||||
vid = vuln.attributes['id'].to_s.downcase
|
||||
rids = []
|
||||
refs = process_nexpose_data_sxml_refs(vuln)
|
||||
next if not refs
|
||||
vuln = framework.db.find_or_create_vuln(
|
||||
:host => host,
|
||||
:name => 'NEXPOSE-' + vid,
|
||||
:data => vid)
|
||||
refs.each { |r| rids << framework.db.find_or_create_ref(:name => r) }
|
||||
vuln.refs << (rids - vuln.refs)
|
||||
end
|
||||
|
||||
# Load the services
|
||||
dev.elements.each('services/service') do |svc|
|
||||
sname = svc.attributes['name'].to_s
|
||||
sprot = svc.attributes['protocol'].to_s.downcase
|
||||
sport = svc.attributes['port'].to_s.to_i
|
||||
|
||||
name = sname.split('(')[0].strip
|
||||
if(sname.downcase != '<unknown>')
|
||||
serv = framework.db.find_or_create_service(:host => host, :proto => sprot, :port => sport, :name => name)
|
||||
else
|
||||
serv = framework.db.find_or_create_service(:host => host, :proto => sprot, :port => sport)
|
||||
end
|
||||
|
||||
# Load vulnerabilities associated with this service
|
||||
svc.elements.each('vulnerabilities/vulnerability') do |vuln|
|
||||
vid = vuln.attributes['id'].to_s.downcase
|
||||
rids = []
|
||||
refs = process_nexpose_data_sxml_refs(vuln)
|
||||
next if not refs
|
||||
vuln = framework.db.find_or_create_vuln(:host => host, :service => serv, :name => 'NEXPOSE-' + vid, :data => vid)
|
||||
refs.each { |r| rids << framework.db.find_or_create_ref(:name => r) }
|
||||
vuln.refs << (rids - vuln.refs)
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# Nexpose Raw XML
|
||||
#
|
||||
# XXX At some point we'll want to make this a stream parser for dealing
|
||||
# with large results files
|
||||
#
|
||||
def import_nexpose_rawxml_file(filename)
|
||||
f = File.open(filename, 'r')
|
||||
data = f.read(f.stat.size)
|
||||
import_nexpose_rawxml(data)
|
||||
end
|
||||
def import_nexpose_rawxml(data)
|
||||
doc = REXML::Document.new(data)
|
||||
doc.elements.each('/NexposeReport/nodes/node') do |host|
|
||||
addr = host.attributes['address']
|
||||
xhost = addr
|
||||
refs = {}
|
||||
|
||||
# os based vuln
|
||||
host.elements['tests'].elements.each('test') do |vuln|
|
||||
if vuln.attributes['status'] == 'vulnerable-exploited' or vuln.attributes['status'] == 'vulnerable-version'
|
||||
dhost = framework.db.find_or_create_host(:host => addr)
|
||||
next if not dhost
|
||||
|
||||
vid = vuln.attributes['id'].to_s
|
||||
nexpose_vuln_lookup(doc,vid,refs,dhost)
|
||||
nexpose_vuln_lookup(doc,vid.upcase,refs,dhost)
|
||||
end
|
||||
end
|
||||
|
||||
# skip if no endpoints
|
||||
next unless host.elements['endpoints']
|
||||
|
||||
# parse the ports and add the vulns
|
||||
host.elements['endpoints'].elements.each('endpoint') do |port|
|
||||
prot = port.attributes['protocol']
|
||||
pnum = port.attributes['port']
|
||||
stat = port.attributes['status']
|
||||
next if not port.elements['services']
|
||||
name = port.elements['services'].elements['service'].attributes['name'].downcase
|
||||
|
||||
next if not port.elements['services'].elements['service'].elements['fingerprints']
|
||||
prod = port.elements['services'].elements['service'].elements['fingerprints'].elements['fingerprint'].attributes['product']
|
||||
vers = port.elements['services'].elements['service'].elements['fingerprints'].elements['fingerprint'].attributes['version']
|
||||
vndr = port.elements['services'].elements['service'].elements['fingerprints'].elements['fingerprint'].attributes['vendor']
|
||||
|
||||
next if stat != 'open'
|
||||
|
||||
dhost = framework.db.find_or_create_host(:host => addr, :state => Msf::HostState::Alive)
|
||||
next if not dhost
|
||||
|
||||
if name != "unknown"
|
||||
service = framework.db.find_or_create_service(:host => dhost, :proto => prot.downcase, :port => pnum.to_i, :name => name)
|
||||
else
|
||||
service = framework.db.find_or_create_service(:host => dhost, :proto => prot.downcase, :port => pnum.to_i)
|
||||
end
|
||||
|
||||
port.elements['services'].elements['service'].elements['tests'].elements.each('test') do |vuln|
|
||||
if vuln.attributes['status'] == 'vulnerable-exploited' or vuln.attributes['status'] == 'vulnerable-version'
|
||||
vid = vuln.attributes['id'].to_s
|
||||
# TODO, improve the vuln_lookup check so case of the vuln_id doesnt matter
|
||||
nexpose_vuln_lookup(doc,vid,refs,dhost,service)
|
||||
nexpose_vuln_lookup(doc,vid.upcase,refs,dhost,service)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Import Nmap's -oX xml output
|
||||
#
|
||||
def import_nmap_xml_file(filename)
|
||||
f = File.open(filename, 'r')
|
||||
data = f.read(f.stat.size)
|
||||
import_nmap_xml(data)
|
||||
end
|
||||
def import_nmap_xml(data)
|
||||
# Use a stream parser instead of a tree parser so we can deal with
|
||||
# huge results files without running out of memory.
|
||||
parser = Rex::Parser::NmapXMLStreamParser.new
|
||||
|
||||
# Whenever the parser pulls a host out of the nmap results, store
|
||||
# it, along with any associated services, in the database.
|
||||
parser.on_found_host = Proc.new { |h|
|
||||
data = {}
|
||||
if (h["addrs"].has_key?("ipv4"))
|
||||
data[:host] = h["addrs"]["ipv4"]
|
||||
elsif (h["addrs"].has_key?("ipv6"))
|
||||
data[:host] = h["addrs"]["ipv6"]
|
||||
else
|
||||
# Can't report it if it doesn't have an IP
|
||||
return
|
||||
end
|
||||
if (h["addrs"].has_key?("mac"))
|
||||
data[:mac] = h["addrs"]["mac"]
|
||||
end
|
||||
data[:state] = (h["status"] == "up" ? Msf::HostState::Alive : Msf::HostState::Dead)
|
||||
host = framework.db.find_or_create_host(data)
|
||||
|
||||
# Put all the ports, regardless of state, into the db.
|
||||
h["ports"].each { |p|
|
||||
extra = ""
|
||||
extra << p["product"] + " " if p["product"]
|
||||
extra << p["version"] + " " if p["version"]
|
||||
extra << p["extrainfo"] + " " if p["extrainfo"]
|
||||
|
||||
data = {}
|
||||
data[:proto] = p["protocol"].downcase
|
||||
data[:port] = p["portid"].to_i
|
||||
data[:state] = p["state"]
|
||||
data[:host] = host
|
||||
data[:info] = extra if not extra.empty?
|
||||
if p["name"] != "unknown"
|
||||
data[:name] = p["name"]
|
||||
end
|
||||
framework.db.report_service(data)
|
||||
}
|
||||
}
|
||||
|
||||
REXML::Document.parse_stream(data, parser)
|
||||
end
|
||||
|
||||
#
|
||||
# Import Nessus NBE files
|
||||
#
|
||||
def import_nessus_nbe_file(filename)
|
||||
f = File.open(filename, 'r')
|
||||
data = f.read(f.stat.size)
|
||||
import_nessus_nbe(data)
|
||||
end
|
||||
def import_nessus_nbe(data)
|
||||
data.each_line do |line|
|
||||
r = line.split('|')
|
||||
next if r[0] != 'results'
|
||||
addr = r[2]
|
||||
port = r[3]
|
||||
nasl = r[4]
|
||||
type = r[5]
|
||||
data = r[6]
|
||||
|
||||
# Match the NBE types with the XML severity ratings
|
||||
case type
|
||||
# log messages don't actually have any data, they are just
|
||||
# complaints about not being able to perform this or that test
|
||||
# because such-and-such was missing
|
||||
when "Log Message"; next
|
||||
when "Security Hole"; severity = 3
|
||||
when "Security Warning"; severity = 2
|
||||
when "Security Note"; severity = 1
|
||||
# a severity 0 means there's no extra data, it's just an open port
|
||||
else; severity = 0
|
||||
end
|
||||
handle_nessus(addr, port, nasl, severity, data)
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Of course they had to change the nessus format.
|
||||
#
|
||||
def import_openvas_xml(filename)
|
||||
raise NotImplementedError.new("No openvas XML support. Patches welcome")
|
||||
end
|
||||
|
||||
#
|
||||
# Import Nessus XML v1 output
|
||||
#
|
||||
# Old versions of openvas exported this as well
|
||||
#
|
||||
def import_nessus_xml_file(filename)
|
||||
f = File.open(filename, 'r')
|
||||
data = f.read(f.stat.size)
|
||||
import_nessus_xml(data)
|
||||
end
|
||||
def import_nessus_xml(data)
|
||||
if(data.index("NessusClientData_v2"))
|
||||
raise RuntimeError.new("The v2 .nessus format is not currently supported (patches welcome).")
|
||||
end
|
||||
|
||||
doc = REXML::Document.new(file_contents)
|
||||
doc.elements.each('/NessusClientData/Report/ReportHost') do |host|
|
||||
addr = host.elements['HostName'].text
|
||||
|
||||
host.elements.each('ReportItem') do |item|
|
||||
nasl = item.elements['pluginID'].text
|
||||
port = item.elements['port'].text
|
||||
data = item.elements['data'].text
|
||||
severity = item.elements['severity'].text
|
||||
|
||||
handle_nessus(addr, port, nasl, severity, data)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def import_nessus_xml_file(filename)
|
||||
f = File.open(filename, 'r')
|
||||
data = f.read(f.stat.size)
|
||||
import_nessus_xml(data)
|
||||
end
|
||||
def import_amap_mlog(data)
|
||||
data.each_line do |line|
|
||||
next if line =~ /^#/
|
||||
r = line.split(':')
|
||||
next if r.length < 6
|
||||
|
||||
addr = r[0]
|
||||
port = r[1].to_i
|
||||
proto = r[2].downcase
|
||||
status = r[3]
|
||||
name = r[5]
|
||||
next if status != "open"
|
||||
|
||||
host = find_or_create_host(:host => addr, :state => Msf::HostState::Alive)
|
||||
next if not host
|
||||
info = {
|
||||
:host => host,
|
||||
:proto => proto,
|
||||
:port => port
|
||||
}
|
||||
if name != "unidentified"
|
||||
info[:name] = name
|
||||
end
|
||||
service = find_or_create_service(info)
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
#
|
||||
# This holds all of the shared parsing/handling used by the
|
||||
# Nessus NBE and NESSUS methods
|
||||
#
|
||||
def handle_nessus(addr, port, nasl, severity, data)
|
||||
# The port section looks like:
|
||||
# http (80/tcp)
|
||||
p = port.match(/^([^\(]+)\((\d+)\/([^\)]+)\)/)
|
||||
return if not p
|
||||
|
||||
host = find_or_create_host(:host => addr, :state => Msf::HostState::Alive)
|
||||
return if not host
|
||||
|
||||
info = { :host => host, :port => p[2].to_i, :proto => p[3].downcase }
|
||||
name = p[1].strip
|
||||
if name != "unknown"
|
||||
info[:name] = name
|
||||
end
|
||||
service = find_or_create_service(info)
|
||||
|
||||
return if not nasl
|
||||
|
||||
data.gsub!("\\n", "\n")
|
||||
|
||||
refs = []
|
||||
|
||||
if (data =~ /^CVE : (.*)$/)
|
||||
$1.gsub(/C(VE|AN)\-/, '').split(',').map { |r| r.strip }.each do |r|
|
||||
refs.push('CVE-' + r)
|
||||
end
|
||||
end
|
||||
|
||||
if (data =~ /^BID : (.*)$/)
|
||||
$1.split(',').map { |r| r.strip }.each do |r|
|
||||
refs.push('BID-' + r)
|
||||
end
|
||||
end
|
||||
|
||||
if (data =~ /^Other references : (.*)$/)
|
||||
$1.split(',').map { |r| r.strip }.each do |r|
|
||||
ref_id, ref_val = r.split(':')
|
||||
ref_val ? refs.push(ref_id + '-' + ref_val) : refs.push(ref_id)
|
||||
end
|
||||
end
|
||||
|
||||
nss = 'NSS-' + nasl.to_s
|
||||
|
||||
vuln = find_or_create_vuln(
|
||||
:host => host,
|
||||
:service => service,
|
||||
:name => nss,
|
||||
:data => data)
|
||||
|
||||
rids = []
|
||||
refs.each do |r|
|
||||
rids << find_or_create_ref(:name => r)
|
||||
end
|
||||
|
||||
vuln.refs << (rids - vuln.refs)
|
||||
end
|
||||
|
||||
def process_nexpose_data_sxml_refs(vuln)
|
||||
refs = []
|
||||
vid = vuln.attributes['id'].to_s.downcase
|
||||
vry = vuln.attributes['resultCode'].to_s.upcase
|
||||
|
||||
# Only process vuln-exploitable and vuln-version statuses
|
||||
return if vry !~ /^V[VE]$/
|
||||
|
||||
refs = []
|
||||
vuln.elements.each('id') do |ref|
|
||||
rtyp = ref.attributes['type'].to_s.upcase
|
||||
rval = ref.text.to_s.strip
|
||||
case rtyp
|
||||
when 'CVE'
|
||||
refs << rval.gsub('CAN', 'CVE')
|
||||
when 'MS' # obsolete?
|
||||
refs << "MSB-MS-#{rval}"
|
||||
else
|
||||
refs << "#{rtyp}-#{rval}"
|
||||
end
|
||||
end
|
||||
|
||||
refs << "NEXPOSE-#{vid}"
|
||||
refs
|
||||
end
|
||||
|
||||
#
|
||||
# NeXpose vuln lookup
|
||||
#
|
||||
def nexpose_vuln_lookup(doc, vid, refs, host, serv=nil)
|
||||
doc.elements.each("/NexposeReport/VulnerabilityDefinitions/vulnerability[@id = '#{vid}']]") do |vulndef|
|
||||
|
||||
title = vulndef.attributes['title']
|
||||
pciSeverity = vulndef.attributes['pciSeverity']
|
||||
cvss_score = vulndef.attributes['cvssScore']
|
||||
cvss_vector = vulndef.attributes['cvssVector']
|
||||
|
||||
vulndef.elements['references'].elements.each('reference') do |ref|
|
||||
if ref.attributes['source'] == 'BID'
|
||||
refs[ 'BID-' + ref.text ] = true
|
||||
elsif ref.attributes['source'] == 'CVE'
|
||||
# ref.text is CVE-$ID
|
||||
refs[ ref.text ] = true
|
||||
elsif ref.attributes['source'] == 'MS'
|
||||
refs[ 'MSB-MS-' + ref.text ] = true
|
||||
end
|
||||
end
|
||||
|
||||
refs[ 'NEXPOSE-' + vid.downcase ] = true
|
||||
|
||||
vuln = find_or_create_vuln(
|
||||
:host => host,
|
||||
:service => serv,
|
||||
:name => 'NEXPOSE-' + vid.downcase,
|
||||
:data => title)
|
||||
|
||||
rids = []
|
||||
refs.keys.each do |r|
|
||||
rids << find_or_create_ref(:name => r)
|
||||
end
|
||||
|
||||
vuln.refs << (rids - vuln.refs)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -55,6 +55,7 @@ class Db
|
|||
"db_del_host" => "Delete one or more hosts from the database",
|
||||
"db_del_port" => "Delete one port from the database",
|
||||
"db_autopwn" => "Automatically exploit everything",
|
||||
"db_import" => "Import a scan result file (filetype will be auto-detected)",
|
||||
"db_import_amap_mlog" => "Import a THC-Amap scan results file (-o -m)",
|
||||
"db_import_nessus_nbe" => "Import a Nessus scan result file (NBE)",
|
||||
"db_import_nessus_xml" => "Import a Nessus scan result file (NESSUS)",
|
||||
|
@ -824,64 +825,19 @@ class Db
|
|||
# EOM
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# This holds all of the shared parsing/handling used by the
|
||||
# Nessus NBE and NESSUS methods
|
||||
# Generic import that automatically detects the file type
|
||||
#
|
||||
def handle_nessus(addr, port, nasl, data)
|
||||
p = port.match(/^([^\(]+)\((\d+)\/([^\)]+)\)/)
|
||||
return if not p
|
||||
|
||||
host = framework.db.find_or_create_host(:host => addr, :state => Msf::HostState::Alive)
|
||||
return if not host
|
||||
|
||||
info = { :host => host, :proto => p[3].downcase, :port => p[2].to_i }
|
||||
name = p[1].strip
|
||||
if name != "unknown"
|
||||
info[:name] = name
|
||||
def cmd_db_import(*args)
|
||||
if (not (args and args.length == 1))
|
||||
print_error("Usage: db_import <filename>")
|
||||
return
|
||||
end
|
||||
service = framework.db.find_or_create_service(info)
|
||||
|
||||
return if not nasl
|
||||
|
||||
data.gsub!("\\n", "\n")
|
||||
|
||||
refs = {}
|
||||
|
||||
if (data =~ /^CVE : (.*)$/)
|
||||
$1.gsub(/C(VE|AN)\-/, '').split(',').map { |r| r.strip }.each do |r|
|
||||
refs[ 'CVE-' + r ] = true
|
||||
end
|
||||
if (not File.readable?(args[0]))
|
||||
print_error("Could not read the file")
|
||||
return
|
||||
end
|
||||
|
||||
if (data =~ /^BID : (.*)$/)
|
||||
$1.split(',').map { |r| r.strip }.each do |r|
|
||||
refs[ 'BID-' + r ] = true
|
||||
end
|
||||
end
|
||||
|
||||
if (data =~ /^Other references : (.*)$/)
|
||||
$1.split(',').map { |r| r.strip }.each do |r|
|
||||
ref_id, ref_val = r.split(':')
|
||||
ref_val ? refs[ ref_id + '-' + ref_val ] = true : refs[ ref_id ] = true
|
||||
end
|
||||
end
|
||||
|
||||
nss = 'NSS-' + nasl.to_s
|
||||
|
||||
vuln = framework.db.find_or_create_vuln(
|
||||
:host => host,
|
||||
:service => service,
|
||||
:name => nss,
|
||||
:data => data)
|
||||
|
||||
rids = []
|
||||
refs.keys.each do |r|
|
||||
rids << framework.db.find_or_create_ref(:name => r)
|
||||
end
|
||||
|
||||
vuln.refs << (rids - vuln.refs)
|
||||
framework.db.import_file(args[0])
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -889,7 +845,7 @@ class Db
|
|||
#
|
||||
def cmd_db_import_nessus_nbe(*args)
|
||||
if (not (args and args.length == 1))
|
||||
print_status("Usage: db_import_nessus_nbe [nessus.nbe]")
|
||||
print_status("Usage: db_import_nessus_xml <nessus.nbe>")
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -897,19 +853,7 @@ class Db
|
|||
print_status("Could not read the NBE file")
|
||||
return
|
||||
end
|
||||
|
||||
fd = File.open(args[0], 'r')
|
||||
fd.each_line do |line|
|
||||
r = line.split('|')
|
||||
next if r[0] != 'results'
|
||||
addr = r[2]
|
||||
port = r[3]
|
||||
nasl = r[4]
|
||||
data = r[6]
|
||||
|
||||
handle_nessus(addr, port, nasl, data)
|
||||
end
|
||||
fd.close
|
||||
framework.db.import_nessus_nbe_file(args[0])
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -917,7 +861,7 @@ class Db
|
|||
#
|
||||
def cmd_db_import_nessus_xml(*args)
|
||||
if (not (args and args.length == 1))
|
||||
print_status("Usage: db_import_nessus_xml [nessus.nessus]")
|
||||
print_status("Usage: db_import_nessus_xml <nessus.nessus>")
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -925,28 +869,7 @@ class Db
|
|||
print_status("Could not read the NESSUS file")
|
||||
return
|
||||
end
|
||||
|
||||
fd = File.open(args[0], 'r')
|
||||
data = fd.read(fd.stat.size)
|
||||
fd.close
|
||||
|
||||
if(data.index("NessusClientData_v2"))
|
||||
print_status("The v2 .nessus format is not currently supported (patches welcome).")
|
||||
return
|
||||
end
|
||||
|
||||
doc = REXML::Document.new(data)
|
||||
doc.elements.each('/NessusClientData/Report/ReportHost') do |host|
|
||||
addr = host.elements['HostName'].text
|
||||
|
||||
host.elements.each('ReportItem') do |item|
|
||||
nasl = item.elements['pluginID'].text
|
||||
port = item.elements['port'].text
|
||||
data = item.elements['data'].text
|
||||
|
||||
handle_nessus(addr, port, nasl, data)
|
||||
end
|
||||
end
|
||||
framework.db.import_nessus_xml_file(args[0])
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -954,11 +877,15 @@ class Db
|
|||
#
|
||||
def cmd_db_import_nmap_xml(*args)
|
||||
if (not (args and args.length == 1))
|
||||
print_status("Usage: db_import_nmap_xml [nmap.xml]")
|
||||
print_error("Usage: db_import_nmap_xml <nmap.xml>")
|
||||
return
|
||||
end
|
||||
|
||||
load_nmap_xml(args[0])
|
||||
if (not File.readable?(args[0]))
|
||||
print_status("Could not read the NESSUS file")
|
||||
return
|
||||
end
|
||||
framework.db.import_nmap_xml_file(args[0])
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -974,7 +901,7 @@ class Db
|
|||
Rex::FileUtils.find_full_path("nmap") ||
|
||||
Rex::FileUtils.find_full_path("nmap.exe")
|
||||
|
||||
if(not nmap)
|
||||
if (not nmap)
|
||||
print_error("The nmap executable could not be found")
|
||||
return
|
||||
end
|
||||
|
@ -1001,62 +928,7 @@ class Db
|
|||
# end
|
||||
|
||||
::File.unlink(fo.path)
|
||||
load_nmap_xml(fd.path)
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# Process Nmap XML data
|
||||
#
|
||||
def load_nmap_xml(filename)
|
||||
if (not File.readable?(filename) or File.size(filename) < 1)
|
||||
print_status("Could not read the XML file")
|
||||
return
|
||||
end
|
||||
|
||||
# Use a stream parser instead of a tree parser so we can deal with
|
||||
# huge results files without running out of memory.
|
||||
parser = Rex::Parser::NmapXMLStreamParser.new
|
||||
|
||||
# Whenever the parser pulls a host out of the nmap results, store
|
||||
# it, along with any associated services, in the database.
|
||||
parser.on_found_host = Proc.new { |h|
|
||||
data = {}
|
||||
if (h["addrs"].has_key?("ipv4"))
|
||||
data[:host] = h["addrs"]["ipv4"]
|
||||
elsif (h["addrs"].has_key?("ipv6"))
|
||||
data[:host] = h["addrs"]["ipv6"]
|
||||
else
|
||||
# Can't report it if it doesn't have an IP
|
||||
return
|
||||
end
|
||||
if (h["addrs"].has_key?("mac"))
|
||||
data[:mac] = h["addrs"]["mac"]
|
||||
end
|
||||
data[:state] = (h["status"] == "up" ? Msf::HostState::Alive : Msf::HostState::Dead)
|
||||
host = framework.db.find_or_create_host(data)
|
||||
|
||||
# Put all the ports, regardless of state, into the db.
|
||||
h["ports"].each { |p|
|
||||
extra = ""
|
||||
extra << p["product"] + " " if p["product"]
|
||||
extra << p["version"] + " " if p["version"]
|
||||
extra << p["extrainfo"] + " " if p["extrainfo"]
|
||||
|
||||
data = {}
|
||||
data[:proto] = p["protocol"].downcase
|
||||
data[:port] = p["portid"].to_i
|
||||
data[:state] = p["state"]
|
||||
data[:host] = host
|
||||
data[:info] = extra if not extra.empty?
|
||||
if p["name"] != "unknown"
|
||||
data[:name] = p["name"]
|
||||
end
|
||||
framework.db.report_service(data)
|
||||
}
|
||||
}
|
||||
|
||||
REXML::Document.parse_stream(File.new(filename), parser)
|
||||
framework.db.import_nmap_xml_file(fd.path)
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -1073,37 +945,7 @@ class Db
|
|||
return
|
||||
end
|
||||
|
||||
fd = File.open(args[0], 'r')
|
||||
|
||||
fd.each_line do |line|
|
||||
line.sub!(/#.*/, "")
|
||||
|
||||
r = line.split(':')
|
||||
next if r.length < 6
|
||||
|
||||
addr = r[0]
|
||||
port = r[1].to_i
|
||||
proto = r[2].downcase
|
||||
status = r[3]
|
||||
name = r[5]
|
||||
|
||||
next if status != "open"
|
||||
|
||||
host = framework.db.find_or_create_host(:host => addr, :state => Msf::HostState::Alive)
|
||||
next if not host
|
||||
|
||||
info = {
|
||||
:host => host,
|
||||
:proto => proto,
|
||||
:port => port
|
||||
}
|
||||
if name != "unidentified"
|
||||
info[:name] = name
|
||||
end
|
||||
service = framework.db.find_or_create_service(info)
|
||||
end
|
||||
|
||||
fd.close
|
||||
framework.db.import_amap_mlog_file(args[0])
|
||||
end
|
||||
|
||||
#
|
||||
|
|
|
@ -22,8 +22,6 @@ class Plugin::Nexpose < Msf::Plugin
|
|||
'nexpose_exhaustive' => "Launch a scan covering all TCP ports and all authorized safe checks",
|
||||
'nexpose_dos' => "Launch a scan that includes checks that can crash services and devices (caution)",
|
||||
|
||||
'nexpose_import_raw' => "Import a Raw XML file from NeXpose (premium editions)",
|
||||
'nexpose_import_simple' => "Import a Simple XML file from any version of NeXpose",
|
||||
'nexpose_disconnect' => "Disconnect from an active NeXpose instance",
|
||||
|
||||
# TODO:
|
||||
|
@ -368,93 +366,17 @@ class Plugin::Nexpose < Msf::Plugin
|
|||
@nsc = nil
|
||||
end
|
||||
|
||||
def cmd_nexpose_import_raw(*args)
|
||||
if ! (args[0] and args[0] != "-h")
|
||||
print_status("Usage: nexpose_import_raw <filename>")
|
||||
return
|
||||
end
|
||||
|
||||
process_nexpose_data_rxml(::File.read(args[0], ::File.size(args[0])))
|
||||
end
|
||||
|
||||
def cmd_nexpose_import_simple(*args)
|
||||
if ! (args[0] and args[0] != "-h")
|
||||
print_status("Usage: nexpose_import_simple <filename>")
|
||||
return
|
||||
end
|
||||
|
||||
process_nexpose_data_sxml(::File.read(args[0], ::File.size(args[0])))
|
||||
end
|
||||
|
||||
def process_nexpose_data(fmt, data)
|
||||
case fmt
|
||||
when 'raw-xml'
|
||||
process_nexpose_data_rxml(data)
|
||||
framework.db.import_nexpose_rawxml(data)
|
||||
when 'ns-xml'
|
||||
process_nexpose_data_sxml(data)
|
||||
framework.db.import_nexpose_simplexml(data)
|
||||
else
|
||||
print_error("Unsupported NeXpose data format: #{fmt}")
|
||||
end
|
||||
end
|
||||
|
||||
def process_nexpose_data_rxml(data)
|
||||
doc = REXML::Document.new(data)
|
||||
doc.elements.each('/NexposeReport/nodes/node') do |host|
|
||||
addr = host.attributes['address']
|
||||
xhost = addr
|
||||
refs = {}
|
||||
|
||||
# os based vuln
|
||||
host.elements['tests'].elements.each('test') do |vuln|
|
||||
if vuln.attributes['status'] == 'vulnerable-exploited' or vuln.attributes['status'] == 'vulnerable-version'
|
||||
dhost = framework.db.find_or_create_host(:host => addr)
|
||||
next if not dhost
|
||||
|
||||
vid = vuln.attributes['id'].to_s
|
||||
nexpose_vuln_lookup(doc,vid,refs,dhost)
|
||||
nexpose_vuln_lookup(doc,vid.upcase,refs,dhost)
|
||||
end
|
||||
end
|
||||
|
||||
# skip if no endpoints
|
||||
next unless host.elements['endpoints']
|
||||
|
||||
# parse the ports and add the vulns
|
||||
host.elements['endpoints'].elements.each('endpoint') do |port|
|
||||
prot = port.attributes['protocol']
|
||||
pnum = port.attributes['port']
|
||||
stat = port.attributes['status']
|
||||
next if not port.elements['services']
|
||||
name = port.elements['services'].elements['service'].attributes['name'].downcase
|
||||
|
||||
next if not port.elements['services'].elements['service'].elements['fingerprints']
|
||||
prod = port.elements['services'].elements['service'].elements['fingerprints'].elements['fingerprint'].attributes['product']
|
||||
vers = port.elements['services'].elements['service'].elements['fingerprints'].elements['fingerprint'].attributes['version']
|
||||
vndr = port.elements['services'].elements['service'].elements['fingerprints'].elements['fingerprint'].attributes['vendor']
|
||||
|
||||
next if stat != 'open'
|
||||
|
||||
dhost = framework.db.find_or_create_host(:host => addr, :state => Msf::HostState::Alive)
|
||||
next if not dhost
|
||||
|
||||
if name != "unknown"
|
||||
service = framework.db.find_or_create_service(:host => dhost, :proto => prot.downcase, :port => pnum.to_i, :name => name)
|
||||
else
|
||||
service = framework.db.find_or_create_service(:host => dhost, :proto => prot.downcase, :port => pnum.to_i)
|
||||
end
|
||||
|
||||
port.elements['services'].elements['service'].elements['tests'].elements.each('test') do |vuln|
|
||||
if vuln.attributes['status'] == 'vulnerable-exploited' or vuln.attributes['status'] == 'vulnerable-version'
|
||||
vid = vuln.attributes['id'].to_s
|
||||
# TODO, improve the vuln_lookup check so case of the vuln_id doesnt matter
|
||||
nexpose_vuln_lookup(doc,vid,refs,dhost,service)
|
||||
nexpose_vuln_lookup(doc,vid.upcase,refs,dhost,service)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# NeXpose vuln lookup
|
||||
#
|
||||
|
@ -494,84 +416,6 @@ class Plugin::Nexpose < Msf::Plugin
|
|||
end
|
||||
end
|
||||
|
||||
def process_nexpose_data_sxml(data)
|
||||
doc = REXML::Document.new(data)
|
||||
doc.elements.each('/NeXposeSimpleXML/devices/device') do |dev|
|
||||
addr = dev.attributes['address'].to_s
|
||||
desc = ''
|
||||
dev.elements.each('fingerprint/description') do |fdesc|
|
||||
desc = fdesc.text.to_s.strip
|
||||
end
|
||||
|
||||
host = framework.db.find_or_create_host(:host => addr, :state => Msf::HostState::Alive)
|
||||
next if not host
|
||||
|
||||
# Load vulnerabilities not associated with a service
|
||||
dev.elements.each('vulnerabilities/vulnerability') do |vuln|
|
||||
vid = vuln.attributes['id'].to_s.downcase
|
||||
rids = []
|
||||
refs = process_nexpose_data_sxml_refs(vuln)
|
||||
next if not refs
|
||||
vuln = framework.db.find_or_create_vuln(
|
||||
:host => host,
|
||||
:name => 'NEXPOSE-' + vid,
|
||||
:data => vid)
|
||||
refs.each { |r| rids << framework.db.find_or_create_ref(:name => r) }
|
||||
vuln.refs << (rids - vuln.refs)
|
||||
end
|
||||
|
||||
# Load the services
|
||||
dev.elements.each('services/service') do |svc|
|
||||
sname = svc.attributes['name'].to_s
|
||||
sprot = svc.attributes['protocol'].to_s.downcase
|
||||
sport = svc.attributes['port'].to_s.to_i
|
||||
|
||||
name = sname.split('(')[0].strip
|
||||
if(sname.downcase != '<unknown>')
|
||||
serv = framework.db.find_or_create_service(:host => host, :proto => sprot, :port => sport, :name => name)
|
||||
else
|
||||
serv = framework.db.find_or_create_service(:host => host, :proto => sprot, :port => sport)
|
||||
end
|
||||
|
||||
# Load vulnerabilities associated with this service
|
||||
svc.elements.each('vulnerabilities/vulnerability') do |vuln|
|
||||
vid = vuln.attributes['id'].to_s.downcase
|
||||
rids = []
|
||||
refs = process_nexpose_data_sxml_refs(vuln)
|
||||
next if not refs
|
||||
vuln = framework.db.find_or_create_vuln(:host => host, :service => serv, :name => 'NEXPOSE-' + vid, :data => vid)
|
||||
refs.each { |r| rids << framework.db.find_or_create_ref(:name => r) }
|
||||
vuln.refs << (rids - vuln.refs)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def process_nexpose_data_sxml_refs(vuln)
|
||||
refs = []
|
||||
vid = vuln.attributes['id'].to_s.downcase
|
||||
vry = vuln.attributes['resultCode'].to_s.upcase
|
||||
|
||||
# Only process vuln-exploitable and vuln-version statuses
|
||||
return if vry !~ /^V[VE]$/
|
||||
|
||||
refs = []
|
||||
vuln.elements.each('id') do |ref|
|
||||
rtyp = ref.attributes['type'].to_s.upcase
|
||||
rval = ref.text.to_s.strip
|
||||
case rtyp
|
||||
when 'CVE'
|
||||
refs << rval.gsub('CAN', 'CVE')
|
||||
when 'MS' # obsolete?
|
||||
refs << "MSB-MS-#{rval}"
|
||||
else
|
||||
refs << "#{rtyp}-#{rval}"
|
||||
end
|
||||
end
|
||||
|
||||
refs << "NEXPOSE-#{vid}"
|
||||
refs
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
|
|
Loading…
Reference in New Issue