331 lines
8.9 KiB
Ruby
331 lines
8.9 KiB
Ruby
# -*- coding: binary -*-
|
|
require "rex/parser/nokogiri_doc_mixin"
|
|
|
|
module Rex
|
|
module Parser
|
|
|
|
# If Nokogiri is available, define Nexpose document class.
|
|
load_nokogiri && class NexposeSimpleDocument < Nokogiri::XML::SAX::Document
|
|
|
|
include NokogiriDocMixin
|
|
|
|
attr_reader :text
|
|
|
|
# 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
|
|
@state[:current_tag][name] = true
|
|
case name
|
|
when "device"
|
|
record_device(attrs)
|
|
when "service"
|
|
record_service(attrs)
|
|
when "fingerprint"
|
|
record_service_fingerprint(attrs)
|
|
record_host_fingerprint(attrs)
|
|
when "description"
|
|
@state[:has_text] = true
|
|
record_host_fingerprint_data(name,attrs)
|
|
when "vendor", "family", "product", "version", "architecture"
|
|
@state[:has_text] = true
|
|
record_host_fingerprint_data(name,attrs)
|
|
when "vulnerability"
|
|
record_service_vuln(attrs)
|
|
record_host_vuln(attrs)
|
|
when "id"
|
|
@state[:has_text] = true
|
|
record_service_vuln_id(attrs)
|
|
record_host_vuln_id(attrs)
|
|
end
|
|
end
|
|
|
|
# When we exit a tag, this is triggered.
|
|
def end_element(name=nil)
|
|
block = @block
|
|
case name
|
|
when "device" # Wrap it up
|
|
collect_device_data
|
|
host_object = report_host &block
|
|
report_services(host_object)
|
|
report_host_fingerprint(host_object)
|
|
report_vulns(host_object)
|
|
# Reset the state once we close a host
|
|
@state.delete_if {|k| k != :current_tag}
|
|
@report_data = {:wspace => @args[:wspace]}
|
|
when "description"
|
|
@state[:has_text] = false
|
|
collect_service_fingerprint_description
|
|
collect_host_fingerprint_data(name)
|
|
@text = nil
|
|
when "vendor", "family", "product", "version", "architecture"
|
|
@state[:has_text] = false
|
|
collect_host_fingerprint_data(name)
|
|
@text = nil
|
|
when "service"
|
|
collect_service_data
|
|
when "id"
|
|
@state[:has_text] = false
|
|
collect_service_vuln_id
|
|
collect_host_vuln_id
|
|
@text = nil
|
|
when "vulnerability"
|
|
collect_service_vuln
|
|
collect_host_vuln
|
|
@state[:references] = nil
|
|
end
|
|
@state[:current_tag].delete name
|
|
end
|
|
|
|
def report_vulns(host_object)
|
|
vuln_count = 0
|
|
block = @block
|
|
return unless host_object.kind_of? ::Mdm::Host
|
|
return unless @report_data[:vulns]
|
|
@report_data[:vulns].each do |vuln|
|
|
if vuln[:refs]
|
|
vuln[:refs] << vuln[:name]
|
|
else
|
|
vuln[:refs] = [vuln[:name]]
|
|
end
|
|
vuln[:refs].uniq!
|
|
data = {
|
|
:workspace => host_object.workspace,
|
|
:host => host_object,
|
|
:name => vuln[:name],
|
|
:info => vuln[:info],
|
|
:refs => vuln[:refs]
|
|
}
|
|
if vuln[:port] && vuln[:proto]
|
|
data[:port] = vuln[:port]
|
|
data[:proto] = vuln[:proto]
|
|
end
|
|
db_report(:vuln,data)
|
|
end
|
|
|
|
end
|
|
|
|
def collect_host_vuln_id
|
|
return unless in_tag("device")
|
|
return unless in_tag("vulnerability")
|
|
return if in_tag("service")
|
|
return unless @state[:host_vuln_id]
|
|
@state[:references] ||= []
|
|
ref = normalize_ref( @state[:host_vuln_id]["type"], @text )
|
|
@state[:references] << ref if ref
|
|
@state[:host_vuln_id] = nil
|
|
@text = nil
|
|
end
|
|
|
|
def collect_service_vuln_id
|
|
return unless in_tag("device")
|
|
return unless in_tag("vulnerability")
|
|
return unless in_tag("service")
|
|
return unless @state[:service_vuln_id]
|
|
@state[:references] ||= []
|
|
ref = normalize_ref( @state[:service_vuln_id]["type"], @text )
|
|
@state[:references] << ref if ref
|
|
@state[:service_vuln_id] = nil
|
|
@text = nil
|
|
end
|
|
|
|
def collect_service_vuln
|
|
return unless in_tag("device")
|
|
return unless in_tag("vulnerability")
|
|
return unless in_tag("service")
|
|
@report_data[:vulns] ||= []
|
|
return unless actually_vulnerable(@state[:service_vuln])
|
|
return if @state[:service]["port"].to_i == 0
|
|
vid = @state[:service_vuln]["id"].to_s.downcase
|
|
vuln = {
|
|
:name => "NEXPOSE-#{vid}",
|
|
:info => vid,
|
|
:refs => @state[:references],
|
|
:port => @state[:service]["port"].to_i,
|
|
:proto => @state[:service]["protocol"]
|
|
}
|
|
@report_data[:vulns] << vuln
|
|
end
|
|
|
|
def collect_host_vuln
|
|
return unless in_tag("vulnerability")
|
|
return unless in_tag("device")
|
|
return if in_tag("service")
|
|
@report_data[:vulns] ||= []
|
|
return unless actually_vulnerable(@state[:host_vuln])
|
|
vid = @state[:host_vuln]["id"].to_s.downcase
|
|
vuln = {
|
|
:name => "NEXPOSE-#{vid}",
|
|
:info => vid,
|
|
:refs => @state[:references]
|
|
}
|
|
@report_data[:vulns] << vuln
|
|
end
|
|
|
|
def record_host_vuln_id(attrs)
|
|
return unless in_tag("device")
|
|
return if in_tag("service")
|
|
@state[:host_vuln_id] = attr_hash(attrs)
|
|
end
|
|
|
|
def record_host_vuln(attrs)
|
|
return unless in_tag("device")
|
|
return if in_tag("service")
|
|
@state[:host_vuln] = attr_hash(attrs)
|
|
end
|
|
|
|
def record_service_vuln_id(attrs)
|
|
return unless in_tag("device")
|
|
return unless in_tag("service")
|
|
@state[:service_vuln_id] = attr_hash(attrs)
|
|
end
|
|
|
|
def record_service_vuln(attrs)
|
|
return unless in_tag("device")
|
|
return unless in_tag("service")
|
|
@state[:service_vuln] = attr_hash(attrs)
|
|
end
|
|
|
|
def actually_vulnerable(vuln)
|
|
vuln_result = vuln["resultCode"]
|
|
vuln_result =~ /^V[VE]$/
|
|
end
|
|
|
|
def record_device(attrs)
|
|
attrs.each do |k,v|
|
|
next unless k == "address"
|
|
@state[:address] = v
|
|
end
|
|
end
|
|
|
|
def record_host_fingerprint(attrs)
|
|
return unless in_tag("device")
|
|
return if in_tag("service")
|
|
@state[:host_fingerprint] = attr_hash(attrs)
|
|
end
|
|
|
|
def collect_device_data
|
|
return unless in_tag("device")
|
|
@report_data[:host] = @state[:address]
|
|
@report_data[:state] = Msf::HostState::Alive # always
|
|
end
|
|
|
|
def record_host_fingerprint_data(name, attrs)
|
|
return unless in_tag("device")
|
|
return if in_tag("service")
|
|
return unless in_tag("fingerprint")
|
|
@state[:host_fingerprint] ||= {}
|
|
@state[:host_fingerprint].merge! attr_hash(attrs)
|
|
end
|
|
|
|
def collect_host_fingerprint_data(name)
|
|
return unless in_tag("device")
|
|
return if in_tag("service")
|
|
return unless in_tag("fingerprint")
|
|
return unless @text
|
|
@report_data[:host_fingerprint] ||= {}
|
|
@report_data[:host_fingerprint].merge!(@state[:host_fingerprint])
|
|
@report_data[:host_fingerprint][name] = @text.to_s.strip
|
|
@text = nil
|
|
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] ) )
|
|
if host_object
|
|
db.report_import_note(host_object.workspace, host_object)
|
|
end
|
|
host_object
|
|
end
|
|
end
|
|
|
|
def report_host_fingerprint(host_object)
|
|
return unless host_object.kind_of? ::Mdm::Host
|
|
return unless @report_data[:host_fingerprint].kind_of? Hash
|
|
@report_data[:host_fingerprint].reject! {|k,v| v.nil? || v.empty?}
|
|
return if @report_data[:host_fingerprint].empty?
|
|
note = {
|
|
:workspace => host_object.workspace,
|
|
:host => host_object,
|
|
:type => "host.os.nexpose_fingerprint"
|
|
}
|
|
data = {
|
|
:desc => @report_data[:host_fingerprint]["description"],
|
|
:vendor => @report_data[:host_fingerprint]["vendor"],
|
|
:family => @report_data[:host_fingerprint]["family"],
|
|
:product => @report_data[:host_fingerprint]["product"],
|
|
:version => @report_data[:host_fingerprint]["version"],
|
|
:arch => @report_data[:host_fingerprint]["architecture"]
|
|
}
|
|
db_report(:note, note.merge(:data => data))
|
|
end
|
|
|
|
def record_service(attrs)
|
|
return unless in_tag("device")
|
|
@state[:service] = attr_hash(attrs)
|
|
end
|
|
|
|
def record_service_fingerprint(attrs)
|
|
return unless in_tag("device")
|
|
return unless in_tag("service")
|
|
@state[:service][:fingerprint] = attr_hash(attrs)
|
|
end
|
|
|
|
def collect_service_data
|
|
return unless in_tag("device")
|
|
port_hash = {}
|
|
@report_data[:ports] ||= []
|
|
@state[:service].each do |k,v|
|
|
case k
|
|
when "protocol"
|
|
port_hash[:proto] = v
|
|
when "port"
|
|
port_hash[:port] = v
|
|
when "name"
|
|
sname = v.to_s.downcase.split("(")[0].strip
|
|
if sname == "<unknown>"
|
|
port_hash[:name] = nil
|
|
else
|
|
port_hash[:name] = db.nmap_msf_service_map(sname)
|
|
end
|
|
end
|
|
end
|
|
if @state[:service_fingerprint]
|
|
port_hash[:info] = "#{@state[:service_fingerprint]}"
|
|
end
|
|
@report_data[:ports] << port_hash.clone
|
|
@state.delete :service_fingerprint
|
|
@state.delete :service
|
|
@report_data[:ports]
|
|
end
|
|
|
|
def collect_service_fingerprint_description
|
|
return unless in_tag("device")
|
|
return unless in_tag("service")
|
|
return unless in_tag("fingerprint")
|
|
return unless @text
|
|
@state[:service_fingerprint] = @text.to_s.strip
|
|
@text = nil
|
|
end
|
|
|
|
def report_services(host_object)
|
|
return unless host_object.kind_of? ::Mdm::Host
|
|
return unless @report_data[:ports]
|
|
return if @report_data[:ports].empty?
|
|
reported = []
|
|
@report_data[:ports].each do |svc|
|
|
reported << db_report(:service, svc.merge(:host => host_object))
|
|
end
|
|
reported
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
end
|
|
|