405 lines
12 KiB
Ruby
405 lines
12 KiB
Ruby
# -*- coding: binary -*-
|
|
require "rex/parser/nokogiri_doc_mixin"
|
|
|
|
module Rex
|
|
module Parser
|
|
|
|
# If Nokogiri is available, define Nmap document class.
|
|
load_nokogiri && class NmapDocument < Nokogiri::XML::SAX::Document
|
|
|
|
include NokogiriDocMixin
|
|
|
|
def determine_port_state(v)
|
|
case v
|
|
when "open"
|
|
Msf::ServiceState::Open
|
|
when "closed"
|
|
Msf::ServiceState::Closed
|
|
when "filtered"
|
|
Msf::ServiceState::Filtered
|
|
else
|
|
Msf::ServiceState::Unknown
|
|
end
|
|
end
|
|
|
|
# Compare OS fingerprinting data
|
|
def better_os_match(orig_hash,new_hash)
|
|
return false unless new_hash.has_key? "accuracy"
|
|
return true unless orig_hash.has_key? "accuracy"
|
|
new_hash["accuracy"].to_i > orig_hash["accuracy"].to_i
|
|
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
|
|
@state[:current_tag][name] = true
|
|
case name
|
|
when "status"
|
|
record_host_status(attrs)
|
|
when "address"
|
|
record_address(attrs)
|
|
when "osclass"
|
|
record_host_osclass(attrs)
|
|
when "osmatch"
|
|
record_host_osmatch(attrs)
|
|
when "uptime"
|
|
record_host_uptime(attrs)
|
|
when "hostname"
|
|
record_hostname(attrs)
|
|
when "port"
|
|
record_port(attrs)
|
|
when "state"
|
|
record_port_state(attrs)
|
|
when "service"
|
|
record_port_service(attrs)
|
|
when "script" # Not actually used in import?
|
|
record_port_script(attrs)
|
|
record_host_script(attrs)
|
|
# Ignoring post scripts completely
|
|
when "trace"
|
|
record_host_trace(attrs)
|
|
when "hop"
|
|
record_host_hop(attrs)
|
|
end
|
|
end
|
|
|
|
# When we exit a tag, this is triggered.
|
|
def end_element(name=nil)
|
|
block = @block
|
|
case name
|
|
when "os"
|
|
collect_os_data
|
|
@state[:os] = {}
|
|
when "port"
|
|
collect_port_data
|
|
@state[:port] = {}
|
|
when "host" # Roll everything up now
|
|
collect_host_data
|
|
host_object = report_host &block
|
|
if host_object
|
|
db.report_import_note(@args[:wspace],host_object)
|
|
report_services(host_object,&block)
|
|
report_fingerprint(host_object)
|
|
report_uptime(host_object)
|
|
report_traceroute(host_object)
|
|
end
|
|
@state.delete_if {|k| k != :current_tag}
|
|
@report_data = {:wspace => @args[:wspace]}
|
|
end
|
|
@state[:current_tag].delete name
|
|
end
|
|
|
|
# We can certainly get fancier with self.send() magic, but
|
|
# leaving this pretty simple for now.
|
|
|
|
def record_host_hop(attrs)
|
|
return unless in_tag("host")
|
|
return unless in_tag("trace")
|
|
hops = attr_hash(attrs)
|
|
hops["name"] = hops.delete "host"
|
|
@state[:trace][:hops] << hops
|
|
end
|
|
|
|
def record_host_trace(attrs)
|
|
return unless in_tag("host")
|
|
@state[:trace] = attr_hash(attrs)
|
|
@state[:trace][:hops] = []
|
|
end
|
|
|
|
def record_host_uptime(attrs)
|
|
return unless in_tag("host")
|
|
@state[:uptime] = attr_hash(attrs)
|
|
end
|
|
|
|
def record_host_osmatch(attrs)
|
|
return unless in_tag("host")
|
|
return unless in_tag("os")
|
|
temp_hash = attr_hash(attrs)
|
|
if temp_hash["accuracy"].to_i == 100
|
|
@state[:os] ||= {}
|
|
@state[:os]["osmatch"] = temp_hash["name"]
|
|
end
|
|
end
|
|
|
|
def record_host_osclass(attrs)
|
|
return unless in_tag("host")
|
|
return unless in_tag("os")
|
|
@state[:os] ||= {}
|
|
temp_hash = attr_hash(attrs)
|
|
if better_os_match(@state[:os],temp_hash)
|
|
@state[:os] = temp_hash
|
|
end
|
|
end
|
|
|
|
def record_hostname(attrs)
|
|
return unless in_tag("host")
|
|
if attr_hash(attrs)["type"] == "PTR"
|
|
@state[:hostname] = attr_hash(attrs)["name"]
|
|
end
|
|
end
|
|
|
|
def record_host_script(attrs)
|
|
return unless in_tag("host")
|
|
return if in_tag("port")
|
|
temp_hash = attr_hash(attrs)
|
|
|
|
if temp_hash["id"] and temp_hash["output"]
|
|
@state[:scripts] ||= []
|
|
@state[:scripts] << { temp_hash["id"] => temp_hash["output"] }
|
|
end
|
|
end
|
|
|
|
def record_port_script(attrs)
|
|
return unless in_tag("host")
|
|
return unless in_tag("port")
|
|
temp_hash = attr_hash(attrs)
|
|
if temp_hash["id"] and temp_hash["output"]
|
|
@state[:port][:scripts] ||= []
|
|
@state[:port][:scripts] << { temp_hash["id"] => temp_hash["output"] }
|
|
end
|
|
end
|
|
|
|
def record_port_service(attrs)
|
|
return unless in_tag("host")
|
|
return unless in_tag("port")
|
|
svc = attr_hash(attrs)
|
|
if svc["name"] && @args[:fix_services]
|
|
svc["name"] = db.nmap_msf_service_map(svc["name"])
|
|
end
|
|
@state[:port] = @state[:port].merge(svc)
|
|
end
|
|
|
|
def record_port_state(attrs)
|
|
return unless in_tag("host")
|
|
return unless in_tag("port")
|
|
temp_hash = attr_hash(attrs)
|
|
@state[:port] = @state[:port].merge(temp_hash)
|
|
end
|
|
|
|
def record_port(attrs)
|
|
return unless in_tag("host")
|
|
@state[:port] ||= {}
|
|
svc = attr_hash(attrs)
|
|
@state[:port] = @state[:port].merge(svc)
|
|
end
|
|
|
|
def record_host_status(attrs)
|
|
return unless in_tag("host")
|
|
attrs.each do |k,v|
|
|
next unless k == "state"
|
|
if v == 'up'
|
|
@state[:host_alive] = true
|
|
else
|
|
@state[:host_alive] = false
|
|
end
|
|
end
|
|
end
|
|
|
|
def record_address(attrs)
|
|
return unless in_tag("host")
|
|
@state[:addresses] ||= {}
|
|
address = nil
|
|
type = nil
|
|
attrs.each do |k,v|
|
|
if k == "addr"
|
|
address = v
|
|
elsif k == "addrtype"
|
|
type = v
|
|
end
|
|
end
|
|
@state[:addresses][type] = address
|
|
end
|
|
|
|
def collect_os_data
|
|
return unless in_tag("host")
|
|
if @state[:os]
|
|
@report_data[:os_fingerprint] = {
|
|
:type => "host.os.nmap_fingerprint",
|
|
:data => {
|
|
:os_vendor => @state[:os]["vendor"],
|
|
:os_family => @state[:os]["osfamily"],
|
|
:os_version => @state[:os]["osgen"],
|
|
:os_accuracy => @state[:os]["accuracy"].to_i
|
|
}
|
|
}
|
|
if @state[:os].has_key? "osmatch"
|
|
@report_data[:os_fingerprint][:data][:os_match] = @state[:os]["osmatch"]
|
|
end
|
|
end
|
|
end
|
|
|
|
def collect_host_data
|
|
if @state[:host_alive] == true
|
|
@report_data[:state] = Msf::HostState::Alive
|
|
elsif @state[:host_alive] == false
|
|
@report_data[:state] = Msf::HostState::Dead
|
|
# Default to alive if no host state available (masscan)
|
|
else
|
|
@report_data[:state] = Msf::HostState::Alive
|
|
end
|
|
if @state[:addresses]
|
|
if @state[:addresses].has_key? "ipv4"
|
|
@report_data[:host] = @state[:addresses]["ipv4"]
|
|
elsif @state[:addresses].has_key? "ipv6"
|
|
@report_data[:host] = @state[:addresses]["ipv6"]
|
|
end
|
|
end
|
|
if @state[:addresses] and @state[:addresses].has_key?("mac")
|
|
@report_data[:mac] = @state[:addresses]["mac"]
|
|
end
|
|
if @state[:hostname]
|
|
@report_data[:name] = @state[:hostname]
|
|
end
|
|
if @state[:uptime]
|
|
@report_data[:last_boot] = @state[:uptime]["lastboot"]
|
|
end
|
|
if @state[:trace] and @state[:trace].has_key?(:hops)
|
|
@report_data[:traceroute] = @state[:trace]
|
|
end
|
|
if @state[:scripts]
|
|
@report_data[:scripts] = @state[:scripts]
|
|
end
|
|
end
|
|
|
|
def collect_port_data
|
|
return unless in_tag("host")
|
|
if @args[:fix_services]
|
|
if @state[:port]["state"] == "filtered"
|
|
return
|
|
end
|
|
end
|
|
@report_data[:ports] ||= []
|
|
port_hash = {}
|
|
extra = []
|
|
@state[:port].each do |k,v|
|
|
case k
|
|
when "protocol"
|
|
port_hash[:proto] = v
|
|
when "portid"
|
|
port_hash[:port] = v
|
|
when "state"
|
|
port_hash[:state] = determine_port_state(v)
|
|
when "name"
|
|
port_hash[:name] = v
|
|
when "tunnel"
|
|
port_hash[:name] = "#{v}/#{port_hash[:name] || 'unknown'}"
|
|
when "reason"
|
|
port_hash[:reason] = v
|
|
when "product"
|
|
extra[0] = v
|
|
when "version"
|
|
extra[1] = v
|
|
when "extrainfo"
|
|
extra[2] = v
|
|
when :scripts
|
|
port_hash[:scripts] = v
|
|
end
|
|
end
|
|
port_hash[:info] = extra.compact.join(" ") unless extra.empty?
|
|
# Skip localhost port results when they're unknown
|
|
if( port_hash[:reason] == "localhost-response" &&
|
|
port_hash[:state] == Msf::ServiceState::Unknown )
|
|
@report_data[:ports]
|
|
else
|
|
@report_data[:ports] << port_hash
|
|
end
|
|
end
|
|
|
|
def report_traceroute(host_object)
|
|
return unless host_object.kind_of? ::Mdm::Host
|
|
return unless @report_data[:traceroute]
|
|
tr_note = {
|
|
:workspace => host_object.workspace,
|
|
:host => host_object,
|
|
:type => "host.nmap.traceroute",
|
|
:data => { 'port' => @report_data[:traceroute]["port"].to_i,
|
|
'proto' => @report_data[:traceroute]["proto"].to_s,
|
|
'hops' => @report_data[:traceroute][:hops] }
|
|
}
|
|
db_report(:note, tr_note)
|
|
end
|
|
|
|
def report_uptime(host_object)
|
|
return unless host_object.kind_of? ::Mdm::Host
|
|
return unless @report_data[:last_boot]
|
|
up_note = {
|
|
:workspace => host_object.workspace,
|
|
:host => host_object,
|
|
:type => "host.last_boot",
|
|
:data => { :time => @report_data[:last_boot] }
|
|
}
|
|
db_report(:note, up_note)
|
|
end
|
|
|
|
def report_fingerprint(host_object)
|
|
return unless host_object.kind_of? ::Mdm::Host
|
|
return unless @report_data[:os_fingerprint]
|
|
fp_note = @report_data[:os_fingerprint].merge(
|
|
{
|
|
:workspace => host_object.workspace,
|
|
:host => host_object
|
|
})
|
|
db_report(:note, fp_note)
|
|
end
|
|
|
|
def report_host(&block)
|
|
if host_is_okay
|
|
scripts = @report_data.delete(:scripts) || []
|
|
host_object = db_report(:host, @report_data.merge( :workspace => @args[:wspace] ) )
|
|
db.emit(:address,@report_data[:host],&block) if block
|
|
|
|
scripts.each do |script|
|
|
script.each_pair do |k,v|
|
|
ntype =
|
|
nse_note = {
|
|
:workspace => host_object.workspace,
|
|
:host => host_object,
|
|
:type => "nmap.nse.#{k}.host",
|
|
:data => { 'output' => v },
|
|
:update => :unique_data
|
|
}
|
|
db_report(:note, nse_note)
|
|
end
|
|
end
|
|
|
|
host_object
|
|
end
|
|
end
|
|
|
|
def report_services(host_object,&block)
|
|
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|
|
|
scripts = svc.delete(:scripts) || []
|
|
wspace = db.workspaces({:id => host_object.workspace.id}).first
|
|
svc_obj = db_report(:service, svc.merge(:host => host_object, :workspace => wspace.name))
|
|
scripts.each do |script|
|
|
script.each_pair do |k,v|
|
|
ntype =
|
|
nse_note = {
|
|
:workspace => wspace,
|
|
:host => host_object,
|
|
:service => svc_obj,
|
|
:type => "nmap.nse.#{k}." + (svc[:proto] || "tcp") +".#{svc[:port]}",
|
|
:data => { 'output' => v },
|
|
:update => :unique_data
|
|
}
|
|
db_report(:note, nse_note)
|
|
end
|
|
end
|
|
reported << svc_obj
|
|
end
|
|
reported
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
end
|
|
|