Files
metasploit-gs/lib/rex/parser/foundstone_nokogiri.rb
T
sinn3r d45cdd61aa Resolve #4507 - respond_to? + send = evil
Since Ruby 2.1, the respond_to? method is more strict because it does
not check protected methods. So when you use send(), clearly you're
ignoring this type of access control. The patch is meant to preserve
this behavior to avoid potential breakage.

Resolve #4507
2015-01-02 13:29:17 -06:00

343 lines
10 KiB
Ruby

# -*- coding: binary -*-
require "rex/parser/nokogiri_doc_mixin"
module Rex
module Parser
# If Nokogiri is available, define Template document class.
load_nokogiri && class FoundstoneDocument < Nokogiri::XML::SAX::Document
include NokogiriDocMixin
def start_document
@report_type_ok = true # Optimistic
end
# Triggered every time a new element is encountered. We keep state
# ourselves with the @state variable, turning things on when we
# get here (and turning things off when we exit in end_element()).
def start_element(name=nil,attrs=[])
attrs = normalize_attrs(attrs)
block = @block
return unless @report_type_ok
@state[:current_tag][name] = true
case name
when "ReportInfo"
check_for_correct_report_type(attrs,&block)
when "Host"
record_host(attrs)
when "Service"
record_service(attrs)
when "Port", "Protocol", "Banner"
@state[:has_text] = true
when "Vuln" # under VulnsFound, ignore risk 0 things
record_vuln(attrs)
when "Risk" # for Vuln
@state[:has_text] = true
when "CVE" # Under Vuln
@state[:has_text] = true
end
end
# When we exit a tag, this is triggered.
def end_element(name=nil)
block = @block
return unless @report_type_ok
case name
when "Host" # Wrap it up
collect_host_data
host_object = report_host &block
if host_object
db.report_import_note(@args[:wspace],host_object)
report_fingerprint(host_object)
report_services(host_object)
report_vulns(host_object)
end
# Reset the state once we close a host
@state.delete_if {|k| k != :current_tag}
@report_data = {:wspace => args[:wspace]}
when "Port"
@state[:has_text] = false
collect_port
when "Protocol"
@state[:has_text] = false
collect_protocol
when "Banner"
collect_banner
@state[:has_text] = false
when "Service"
collect_service
when "Vuln"
collect_vuln
when "Risk"
@state[:has_text] = false
collect_risk
when "CVE"
@state[:has_text] = false
collect_cve
end
@state[:current_tag].delete name
end
# Nothing technically stopping us from parsing this as well,
# but saving this for later
def check_for_correct_report_type(attrs,&block)
report_type = attr_hash(attrs)["ReportType"]
if report_type == "Network Inventory"
@report_type_ok = true
else
if report_type == "Risk Data"
msg = "The Foundstone/Mcafee report type '#{report_type}' is not currently supported"
msg << ",\nso no data will be imported. Please use the 'Network Inventory' report instead."
else
msg = ".\nThe Foundstone/Macafee report type '#{report_type}' is unsupported."
end
db.emit(:warning,msg,&block) if block
@report_type_ok = false
end
end
def collect_risk
return unless in_tag("VulnsFound")
return unless in_tag("HostData")
return unless in_tag("Host")
risk = @text.to_s.to_i
@state[:vuln][:risk] = risk
@text = nil
end
def collect_cve
return unless in_tag("VulnsFound")
return unless in_tag("HostData")
return unless in_tag("Host")
cve = @text.to_s
@state[:vuln][:cves] ||= []
@state[:vuln][:cves] << cve unless cve == "CVE-MAP-NOMATCH"
@text = nil
end
# Determines if we should keep the vuln or not. Note that
# we cannot tie them to a service.
def collect_vuln
return unless in_tag("VulnsFound")
return unless in_tag("HostData")
return unless in_tag("Host")
return if @state[:vuln][:risk] == 0
@report_data[:vulns] ||= []
vuln_hash = {}
vuln_hash[:name] = @state[:vuln]["VulnName"]
refs = []
refs << "FID-#{@state[:vuln]["id"]}"
if @state[:vuln][:cves]
@state[:vuln][:cves].each {|cve| refs << cve}
end
vuln_hash[:refs] = refs
@report_data[:vulns] << vuln_hash
end
# These are per host.
def record_vuln(attrs)
return unless in_tag("VulnsFound")
return unless in_tag("HostData")
return unless in_tag("Host")
@state[:vulns] ||= []
@state[:vuln] = attr_hash(attrs) # id and VulnName
end
def record_service(attrs)
return unless in_tag("ServicesFound")
return unless in_tag("Host")
@state[:service] = attr_hash(attrs)
end
def collect_port
return unless in_tag("Service")
return unless in_tag("ServicesFound")
return unless in_tag("Host")
return if @text.nil? || @text.empty?
@state[:service][:port] = @text.strip
@text = nil
end
def collect_protocol
return unless in_tag("Service")
return unless in_tag("ServicesFound")
return unless in_tag("Host")
return if @text.nil? || @text.empty?
@state[:service][:proto] = @text.strip
@text = nil
end
def collect_banner
return unless in_tag("Service")
return unless in_tag("ServicesFound")
return unless in_tag("Host")
return if @text.nil? || @text.empty?
banner = normalize_foundstone_banner(@state[:service]["ServiceName"],@text)
unless banner.nil? || banner.empty?
@state[:service][:banner] = banner
end
@text = nil
end
def collect_service
return unless in_tag("ServicesFound")
return unless in_tag("Host")
return unless @state[:service][:port]
@report_data[:ports] ||= []
port_hash = {}
port_hash[:port] = @state[:service][:port]
port_hash[:proto] = @state[:service][:proto]
port_hash[:info] = @state[:service][:banner]
port_hash[:name] = db.nmap_msf_service_map(@state[:service]["ServiceName"])
@report_data[:ports] << port_hash
end
def record_host(attrs)
return unless in_tag("HostData")
@state[:host] = attr_hash(attrs)
end
def collect_host_data
@report_data[:host] = @state[:host]["IPAddress"]
if @state[:host]["NBName"] && !@state[:host]["NBName"].empty?
@report_data[:name] = @state[:host]["NBName"]
elsif @state[:host]["DNSName"] && !@state[:host]["DNSName"].empty?
@report_data[:name] = @state[:host]["DNSName"]
end
if @state[:host]["OSName"] && !@state[:host]["OSName"].empty?
@report_data[:os_fingerprint] = @state[:host]["OSName"]
end
@report_data[:state] = Msf::HostState::Alive
@report_data[:mac] = @state[:mac] if @state[:mac]
end
def report_host(&block)
return unless in_tag("HostData")
if host_is_okay
db.emit(:address,@report_data[:host],&block) if block
host_info = @report_data.merge(:workspace => @args[:wspace])
db_report(:host,host_info)
end
end
def report_fingerprint(host_object)
fp_note = {
:workspace => host_object.workspace,
:host => host_object,
:type => 'host.os.foundstone_fingerprint',
:data => {:os => @report_data[:os_fingerprint] }
}
db_report(:note, fp_note)
end
def report_services(host_object)
return unless in_tag("HostData")
return unless host_object.kind_of? ::Mdm::Host
return unless @report_data[:ports]
return if @report_data[:ports].empty?
@report_data[:ports].each do |svc|
db_report(:service, svc.merge(:host => host_object))
end
end
def report_vulns(host_object)
return unless in_tag("HostData")
return unless host_object.kind_of? ::Mdm::Host
return unless @report_data[:vulns]
return if @report_data[:vulns].empty?
@report_data[:vulns].each do |vuln|
db_report(:vuln, vuln.merge(:host => host_object))
end
end
# Foundstone's banners are pretty free-form
# and often not just banners. Clean them up
# for the :info field, delegate off for other
# protocol data we can use.
def normalize_foundstone_banner(service,banner)
return "" if(banner.nil? || banner.strip.empty?)
if first_line_only? service
return (first_line banner)
elsif needs_more_processing? service
return process_service(service,banner)
else
return (first_line banner)
end
end
# Services where we only care about the first
# line of the banner tag.
def first_line_only?(service)
svcs = %w{
vnc ftp ftps smtp oracle-tns nntp ssh ntp
}
9.times {|i| svcs << "vnc-#{i}"}
svcs.include? service
end
# Services where we need to do more processing
# before handing the banner back.
def needs_more_processing?(service)
svcs = %w{
microsoft-ds loc-srv http https sunrpc netbios-ns
}
svcs.include? service
end
def first_line(str)
str.split("\n").first.to_s.strip
end
# XXX: Actually implement more of these
def process_service(service,banner)
meth = "process_service_#{service.gsub("-","_")}"
if self.respond_to?(meth, true)
self.send meth, banner
else
return (first_line banner)
end
end
# XXX: Register a proper netbios note as the regular
# scanner does.
def process_service_netbios_ns(banner)
mac_regex = /[0-9A-Fa-f:]{17}/
@state[:mac] = banner[mac_regex]
first_line banner
end
# XXX: Make this behave more like the smb scanner
def process_service_microsoft_ds(banner)
lm_regex = /Native LAN Manager/
lm_banner = nil
banner.each_line { |line|
if line[lm_regex]
lm_banner = line
break
end
}
lm_banner || first_line(banner)
end
def process_service_http(banner)
server = nil
banner.each_line do |line|
if line =~ /^Server:\s+(.*)/
server = $1
break
end
end
server || first_line(banner)
end
alias :process_service_https :process_service_http
alias :process_service_rtsp :process_service_http
end
end
end