Merge in nCircle support from Dave Lassalle, handle file reads more consistently

git-svn-id: file:///home/svn/framework3/trunk@10902 4d416f70-5f16-0410-b530-b9f4589650da
unstable
HD Moore 2010-11-04 21:44:16 +00:00
parent 13a93d41f6
commit 652764ebd3
4 changed files with 464 additions and 25 deletions

View File

@ -3,6 +3,8 @@ require 'rex/parser/nexpose_xml'
require 'rex/parser/retina_xml'
require 'rex/parser/netsparker_xml'
require 'rex/parser/nessus_xml'
require 'rex/parser/ip360_xml'
require 'rex/parser/ip360_aspl_xml'
require 'rex/socket'
require 'zip'
require 'uri'
@ -1883,8 +1885,11 @@ class DBManager
@import_filedata = {}
@import_filedata[:filename] = filename
f = File.open(filename, 'rb')
data = ""
::File.open(filename, 'rb') do |f|
data = f.read(f.stat.size)
end
if data[0,4] == "PK\x03\x04"
data = Zip::ZipFile.open(filename)
end
@ -1976,6 +1981,9 @@ class DBManager
when /netsparker/
@import_filedata[:type] = "NetSparker XML"
return :netsparker_xml
when /audits/
@import_filedata[:type] = "IP360 XML v3"
return :ip360_xml_v3
else
# Give up if we haven't hit the root tag in the first few lines
break if line_count > 10
@ -2131,8 +2139,10 @@ class DBManager
filename = args[:filename]
wspace = args[:wspace] || workspace
f = File.open(filename, 'rb')
data = ""
::File.open(filename, 'rb') do |f|
data = f.read(f.stat.size)
end
import_nexpose_simplexml(args.merge(:data => data))
end
@ -2141,8 +2151,10 @@ class DBManager
filename = args[:filename]
wspace = args[:wspace] || workspace
f = File.open(filename, 'rb')
data = ""
::File.open(filename, 'rb') do |f|
data = f.read(f.stat.size)
end
import_msf_xml(args.merge(:data => data))
end
@ -2707,8 +2719,10 @@ class DBManager
filename = args[:filename]
wspace = args[:wspace] || workspace
f = File.open(filename, 'rb')
data = ""
::File.open(filename, 'rb') do |f|
data = f.read(f.stat.size)
end
import_nexpose_rawxml(args.merge(:data => data))
end
@ -2880,8 +2894,10 @@ class DBManager
filename = args[:filename]
wspace = args[:wspace] || workspace
f = File.open(filename, 'rb')
data = ""
::File.open(filename, 'rb') do |f|
data = f.read(f.stat.size)
end
import_retina_xml(args.merge(:data => data))
end
@ -2962,8 +2978,10 @@ class DBManager
filename = args[:filename]
wspace = args[:wspace] || workspace
f = File.open(filename, 'rb')
data = ""
::File.open(filename, 'rb') do |f|
data = f.read(f.stat.size)
end
import_netsparker_xml(args.merge(:data => data))
end
@ -3345,8 +3363,10 @@ class DBManager
filename = args[:filename]
wspace = args[:wspace] || workspace
f = File.open(filename, 'rb')
data = ""
::File.open(filename, 'rb') do |f|
data = f.read(f.stat.size)
end
import_nmap_xml(args.merge(:data => data))
end
@ -3499,8 +3519,10 @@ class DBManager
filename = args[:filename]
wspace = args[:wspace] || workspace
f = File.open(filename, 'rb')
data = ""
::File.open(filename, 'rb') do |f|
data = f.read(f.stat.size)
end
import_nessus_nbe(args.merge(:data => data))
end
@ -3582,6 +3604,20 @@ class DBManager
raise DBImportError.new("No OpenVAS XML support. Please submit a patch to msfdev[at]metasploit.com")
end
#
# Import IP360 XML v3 output
#
def import_ip360_xml_file(args={})
filename = args[:filename]
wspace = args[:wspace] || workspace
data = ""
::File.open(filename, 'rb') do |f|
data = f.read(f.stat.size)
end
import_ip360_xml_v3(args.merge(:data => data))
end
#
# Import Nessus XML v1 and v2 output
#
@ -3591,8 +3627,10 @@ class DBManager
filename = args[:filename]
wspace = args[:wspace] || workspace
f = File.open(filename, 'rb')
data = ""
::File.open(filename, 'rb') do |f|
data = f.read(f.stat.size)
end
if data.index("NessusClientData_v2")
import_nessus_xml_v2(args.merge(:data => data))
@ -3757,6 +3795,125 @@ class DBManager
end
#
# Import IP360's xml output
#
def import_ip360_xml_v3(args={}, &block)
data = args[:data]
wspace = args[:wspace] || workspace
bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
# @aspl = {'vulns' => {'name' => { }, 'cve' => { }, 'bid' => { } }
# 'oses' => {'name' } }
aspl_path = File.join(Msf::Config.data_directory, "data", "ncircle", "ip360.aspl")
if not ::File.exist?(aspl_path)
raise DBImportError.new("The nCircle IP360 ASPL file is not present.\n Download ASPL from nCircle VNE | Administer | Support | Resources, unzip it, and save it as " + aspl_path)
end
if not ::File.readable?(aspl_path)
raise DBImportError.new("Could not read the IP360 ASPL XML file provided at " + aspl_path)
end
# parse nCircle ASPL file
aspl = ""
::File.open(aspl_path, "rb") do |f|
aspl = f.read(f.stat.size)
end
@asplhash = nil
parser = Rex::Parser::IP360ASPLXMLStreamParser.new
parser.on_found_aspl = Proc.new { |asplh|
@asplhash = asplh
}
REXML::Document.parse_stream(aspl, parser)
#@host = {'hname' => nil, 'addr' => nil, 'mac' => nil, 'os' => nil, 'hid' => nil,
# 'vulns' => ['vuln' => {'vulnid' => nil, 'port' => nil, 'proto' => nil } ],
# 'apps' => ['app' => {'appid' => nil, 'svcid' => nil, 'port' => nil, 'proto' => nil } ],
# 'shares' => []
# }
# nCircle has some quotes escaped which causes the parser to break
# we don't need these lines so just replace \" with "
data.gsub!(/\\"/,'"')
# parse nCircle Scan Output
parser = Rex::Parser::IP360XMLStreamParser.new
parser.on_found_host = Proc.new { |host|
addr = host['addr'] || host['hname']
next unless ipv4_validator(addr) # Catches SCAN-ERROR, among others.
if bl.include? addr
next
else
yield(:address,addr) if block
end
os = host['os']
yield(:os, os) if block
if os
report_note(
:workspace => wspace,
:host => addr,
:type => 'host.os.ip360_fingerprint',
:data => {
:os => @asplhash['oses'][os].to_s.strip
}
)
end
hname = host['hname']
if hname
report_host(
:workspace => wspace,
:host => addr,
:name => hname.to_s.strip
)
end
mac = host['mac']
if mac
report_host(
:workspace => wspace,
:host => addr,
:mac => mac.to_s.strip.upcase
)
end
host['apps'].each do |item|
port = item['port'].to_s
proto = item['proto'].to_s
handle_ip360_v3_svc(wspace, addr, port, proto, hname)
end
host['vulns'].each do |item|
vulnid = item['vulnid'].to_s
port = item['port'].to_s
proto = item['proto'] || "tcp"
vulnname = @asplhash['vulns']['name'][vulnid]
cves = @asplhash['vulns']['cve'][vulnid]
bids = @asplhash['vulns']['bid'][vulnid]
yield(:port, port) if block
handle_ip360_v3_vuln(wspace, addr, port, proto, hname, vulnid, vulnname, cves, bids)
end
yield(:end, hname) if block
}
REXML::Document.parse_stream(data, parser)
end
#
# Import Qualys' xml output
#
@ -3764,8 +3921,10 @@ class DBManager
filename = args[:filename]
wspace = args[:wspace] || workspace
f = File.open(filename, 'rb')
data = ""
::File.open(filename, 'rb') do |f|
data = f.read(f.stat.size)
end
import_qualys_xml(args.merge(:data => data))
end
@ -3852,8 +4011,10 @@ class DBManager
filename = args[:filename]
wspace = args[:wspace] || workspace
f = File.open(filename, 'rb')
data = ""
::File.open(filename, 'rb') do |f|
data = f.read(f.stat.size)
end
import_ip_list(args.merge(:data => data))
end
@ -3876,8 +4037,11 @@ class DBManager
def import_amap_log_file(args={})
filename = args[:filename]
wspace = args[:wspace] || workspace
f = File.open(filename, 'rb')
data = ""
::File.open(filename, 'rb') do |f|
data = f.read(f.stat.size)
end
case import_filetype_detect(data)
when :amap_log
import_amap_log(args.merge(:data => data))
@ -4101,6 +4265,65 @@ protected
report_vuln(vuln)
end
#
# IP360 v3 vuln
#
def handle_ip360_v3_svc(wspace,addr,port,proto,hname)
report_host(:workspace => wspace, :host => addr, :state => Msf::HostState::Alive)
info = { :workspace => wspace, :host => addr, :port => port, :proto => proto }
if hname != "unknown" and hname[-1,1] != "?"
info[:name] = hname
end
if port.to_i != 0
report_service(info)
end
end #handle_ip360_v3_svc
#
# IP360 v3 vuln
#
def handle_ip360_v3_vuln(wspace,addr,port,proto,hname,vulnid,vulnname,cves,bids)
report_host(:workspace => wspace, :host => addr, :state => Msf::HostState::Alive)
info = { :workspace => wspace, :host => addr, :port => port, :proto => proto }
if hname != "unknown" and hname[-1,1] != "?"
info[:name] = hname
end
if port.to_i != 0
report_service(info)
end
refs = []
cves.split(/,/).each do |cve|
refs.push(cve.to_s)
end if cves
bids.split(/,/).each do |bid|
refs.push('BID-' + bid.to_s)
end if bids
description = nil # not working yet
vuln = {
:workspace => wspace,
:host => addr,
:name => vulnname,
:info => description ? description : "",
:refs => refs
}
if port.to_i != 0
vuln[:port] = port
vuln[:proto] = proto
end
report_vuln(vuln)
end #handle_ip360_v3_vuln
#
# Qualys report parsing/handling

View File

@ -69,6 +69,7 @@ class Db
"db_import_amap_log" => "Import a THC-Amap scan results file (-o )",
"db_import_nessus_nbe" => "Import a Nessus scan result file (NBE)",
"db_import_nessus_xml" => "Import a Nessus scan result file (NESSUS)",
"db_import_ip360_xml" => "Import an IP360 scan result file (XML)",
"db_import_nmap_xml" => "Import a Nmap scan results file (-oX)",
"db_import_qualys_xml" => "Import a Qualys scan results file (XML)",
"db_import_msfe_xml" => "Import a Metasploit Express report (XML)",
@ -1236,6 +1237,26 @@ class Db
framework.db.import_nessus_xml_file(:filename => args[0])
end
#
# Import IP360 XML files
#
def cmd_db_import_ip360_xml(*args)
return unless active?
if (not (args and args.length == 1))
print_status("Usage: db_import_ip360_xml <ip360.xml>")
return
end
warn_about_db_import(args[0])
if (not File.readable?(args[0]))
print_status("Could not read the IP360 Scan file provided")
return
end
framework.db.import_ip360_xml_file(:filename => args[0])
end
#
# Import Nmap data from a file
#

View File

@ -0,0 +1,102 @@
require 'rexml/document'
require 'rex/ui'
module Rex
module Parser
class IP360ASPLXMLStreamParser
@vulnid = nil
@appid = nil
@location = nil
attr_accessor :on_found_aspl
def initialize(&block)
reset_state
on_found_aspl = block if block
end
def reset_state
@aspl = {'vulns' => {'name' => { }, 'cve' => { }, 'bid' => { } },
'oses' => {'name' => { } } }
@state = :generic_state
end
def tag_start(name, attributes)
case name
when "vulns"
@location = "vulns"
when "vuln"
@vulnid = attributes['id'].strip
when "name"
@state = :is_name
when "advisories"
@c = ""
@cfirst = 1
@b = ""
@bfirst = 1
@x = Hash.new
when "publisher"
@state = :is_pub
when "id"
@state = :is_refid
when "operatingSystems"
@location = "os"
when "operatingSystem"
@osid = attributes['id'].strip
end
end
def text(str)
case @state
when :is_name
@aspl['vulns']['name'][@vulnid] = str if @location == "vulns"
@aspl['oses'][@osid] = str if @location == "os"
when :is_pub
@x['pub'] = str
when :is_refid
@x['refid'] = str
end
end
def tag_end(name)
case name
when "ontology"
on_found_aspl.call(@aspl) if on_found_aspl
reset_state
when "advisory"
if (@x['pub'] =~ /CVE/)
if (@cfirst == 0)
@c += ","
end
@c += @x['refid']
@cfirst = 0
elsif (@x['pub'] =~ /BugTraq/)
if (@bfirst == 0)
@b += ","
end
@b += @x['refid']
@bfirst = 0
end
when "advisories"
@aspl['vulns']['cve'][@vulnid] = @c
@aspl['vulns']['bid'][@vulnid] = @b
@c = ""
@b = ""
end
@state = :generic_state
end
# We don't need these methods, but they're necessary to keep REXML happy
#
def xmldecl(version, encoding, standalone); end
def cdata; end
def comment(str); end
def instruction(name, instruction); end
def attlist; end
end
end
end

View File

@ -0,0 +1,93 @@
require 'rexml/document'
require 'rex/ui'
module Rex
module Parser
class IP360XMLStreamParser
attr_accessor :on_found_host
def initialize(&block)
reset_state
on_found_host = block if block
end
def reset_state
@host = {'hname' => nil, 'hid' => nil, 'addr' => nil, 'mac' => nil, 'os' => nil,
'vulns' => ['vuln' => {'vulnid' => nil, 'port' => nil, 'proto' => nil} ],
'apps' => ['app' => {'appid' => nil, 'svcid' => nil, 'port' => nil, 'proto' => nil } ],
}
@state = :generic_state
end
def tag_start(name, attributes)
case name
when "host"
@host['hid'] = attributes['persistent_id']
when "ip"
@state = :is_ip
when "dnsName"
@state = :is_fqdn
when "macAddress"
@state = :is_mac
when "os"
@host['os'] = attributes['id']
when "vulnerability"
@x = Hash.new
@x['vulnid'] = attributes['id']
when "port"
@state = :is_port
when "protocol"
@state = :is_proto
when "application"
@y = Hash.new
@y['appid'] = attributes['application_id']
@y['svcid'] = attributes['svcid']
@y['port'] = attributes['port']
@y['proto'] = attributes['protocol']
@host['apps'].push @y
end
end
def text(str)
case @state
when :is_fqdn
@host['hname'] = str
when :is_ip
@host['addr'] = str
when :is_mac
@host['mac'] = str
when :is_port
@x['port'] = str
when :is_proto
@x['proto'] = str
end
end
def tag_end(name)
case name
when "host"
on_found_host.call(@host) if on_found_host
reset_state
when "vulnerability"
@host['vulns'].push @x
end
@state = :generic_state
end
def cdata(d)
#do nothing
end
# We don't need these methods, but they're necessary to keep REXML happy
#
def xmldecl(version, encoding, standalone); end
def comment(str); end
def instruction(name, instruction); end
def attlist; end
end
end
end