class Rex::Parser::NmapXMLStreamParser
Stream parser for nmap -oX xml output
Yields a hash representing each host found in the xml stream. Each host will look something like the following:
{ "status" => "up", "addrs" => { "ipv4" => "192.168.0.1", "mac" => "00:0d:87:a1:df:72" }, "ports" => [ { "portid" => "22", "state" => "closed", ... }, { "portid" => "80", "state" => "open", ... }, ... ] }
Usage:
parser = NmapXMLStreamParser.new { |host| # do stuff with the host } REXML::Document.parse_stream(File.new(nmap_xml), parser)
– or –
parser = NmapXMLStreamParser.new parser.on_found_host = Proc.new { |host| # do stuff with the host } REXML::Document.parse_stream(File.new(nmap_xml), parser)
This parser does not maintain state as well as a tree parser, so malformed xml will trip it up. Nmap shouldn't ever output malformed xml, so it's not a big deal.
Attributes
on_found_host[RW]
Callback for processing each found host
Public Class Methods
new(&block)
click to toggle source
Create a new stream parser for NMAP XML output
If given a block, it will be stored in on_found_host
, otherwise you need to set it explicitly, e.g.:
parser = NmapXMLStreamParser.new parser.on_found_host = Proc.new { |host| # do stuff with the host } REXML::Document.parse_stream(File.new(nmap_xml), parser)
# File lib/rex/parser/nmap_xml.rb, line 56 def initialize(&block) reset_state on_found_host = block if block end
Public Instance Methods
reset_state()
click to toggle source
# File lib/rex/parser/nmap_xml.rb, line 61 def reset_state @host = { "status" => nil, "addrs" => {}, "ports" => [], "scripts" => {} } @state = nil end
tag_end(name)
click to toggle source
# File lib/rex/parser/nmap_xml.rb, line 138 def tag_end(name) case name when "port" @state = nil when "host" on_found_host.call(@host) if on_found_host reset_state end end
tag_start(name, attributes)
click to toggle source
# File lib/rex/parser/nmap_xml.rb, line 66 def tag_start(name, attributes) begin case name when "address" @host["addrs"][attributes["addrtype"]] = attributes["addr"] if (attributes["addrtype"] =~ /ipv[46]/) @host["addr"] = attributes["addr"] end when "osclass" # If there is more than one, take the highest accuracy. In case of # a tie, this will have the effect of taking the last one in the # list. Last is really no better than first but nmap appears to # put OSes in chronological order, at least for Windows. # Accordingly, this will report XP instead of 2000, 7 instead of # Vista, etc, when each has the same accuracy. if (@host["os_accuracy"].to_i <= attributes["accuracy"].to_i) @host["os_vendor"] = attributes["vendor"] @host["os_family"] = attributes["osfamily"] @host["os_version"] = attributes["osgen"] @host["os_accuracy"] = attributes["accuracy"] end when "osmatch" if(attributes["accuracy"].to_i == 100) @host["os_match"] = attributes["name"] end when "uptime" @host["last_boot"] = attributes["lastboot"] when "hostname" if(attributes["type"] == "PTR") @host["reverse_dns"] = attributes["name"] end when "status" # <status> refers to the liveness of the host; values are "up" or "down" @host["status"] = attributes["state"] @host["status_reason"] = attributes["reason"] when "port" @host["ports"].push(attributes) when "state" # <state> refers to the state of a port; values are "open", "closed", or "filtered" @host["ports"].last["state"] = attributes["state"] when "service" # Store any service and script info with the associated port. There shouldn't # be any collisions on attribute names here, so just merge them. @host["ports"].last.merge!(attributes) when "script" # Associate scripts under a port tag with the appropriate port. # Other scripts from <hostscript> tags can only be associated with # the host and scripts from <postscript> tags don't really belong # to anything, so ignore them if @state == :in_port_tag @host["ports"].last["scripts"] ||= {} @host["ports"].last["scripts"][attributes["id"]] = attributes["output"] elsif @host @host["scripts"] ||= {} @host["scripts"][attributes["id"]] = attributes["output"] else # post scripts are used for things like comparing all the found # ssh keys to see if multiple hosts have the same key # fingerprint. Ignore them. end when "trace" @host["trace"] = {"port" => attributes["port"], "proto" => attributes["proto"], "hops" => [] } when "hop" if @host["trace"] @host["trace"]["hops"].push(attributes) end end rescue NoMethodError => err raise err unless err.message =~ /NilClass/ end end