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