class Nmap::Parser
What Is This Library For?¶ ↑
This library provides a Ruby interface to the Nmap Security Scanner and its XML formatted scan data. It can run Nmap and parse its XML output directly from the scan, parse a file or string of XML scan data, or parse XML scan data from an object via its read() method. This information is presented in an easy-to-use and intuitive fashion for further storage and manipulation.
Note that while Anthony Persaud's Perl Nmap::Parser was certainly an inspiration when designing this library, there are a number of distinguishing characteristics. Very briefly, this library contains more classes, many more methods, and has blocks extensively available.
The Nmap Security Scanner is an awesome utility written and maintained by Fyodor (fyodor(a)insecure.org). Its main function is port scanning, but it also has service and operating system detection, its own scripting engine and a whole lot more. One of its many available output formats is XML, which allows machines to handle all of the information instead of us slowly sifting through tons of output.
Conventions¶ ↑
Depending on the data type, unavailable information is presented differently:
-
Arrays are empty
-
Non-arrays are nil, unless it's a method that returns the size of one of the previously mentioned empty arrays. In this case they still return the size (which would be 0).
All information available as arrays are presented via methods. These methods not only return the array, but they also yield each element to a block if one is given.
Module Hierarchy¶ ↑
Nmap::Parser | + Session <- Scan session information | + Host <- General host information | + ExtraPorts <- Ports consolidated in an "ignored" state | + Port <- General port information | | | + Service <- Port Service information | + Script <- NSE Script information (both host and port) | + Times <- Timimg information (round-trip time, etc) | + Traceroute <- General Traceroute information | | | + Hop <- Individual Hop information | + OS <- OS Detection information | + OSClass <- OS Class information | + OSMatch <- OS Match information
Examples¶ ↑
There are two ways to go about getting a new Parser object and actually parsing Nmap's XML output:
-
Call one of the Nmap::Parser class methods to parse the XML and return a new Parser object all in one step.
-
Call ::new to get a new object and then call one of the instance parsing methods (e.g. parsefile()). The main reason to go this route is that new() takes a hash of options; for example, this is how the callback feature is implemented.
Parsing XML Data Already Available as a String¶ ↑
This method is not limited to only String objects, but rather any object which responsds to to_str().
require 'nmap/parser' parser = Nmap::Parser.new parser.parsestring(xml)
or
parser = Nmap::Parser.parsestring(xml)
Reading and Parsing From a File¶ ↑
require 'nmap/parser' parser = Nmap::Parser.parsefile("log.xml")
Reading and Parsing From an Object¶ ↑
This method can read from any object responding to a read() method that returns a String (or something else responding to to_str())
require 'nmap/parser' parser = Nmap::Parser.parseread($stdin)
Scanning and Parsing¶ ↑
This is the only Parser method that requires Nmap to be available.
require 'nmap/parser' parser = Nmap::Parser.parsescan("sudo nmap", "-sVC 192.168.1.0/24")
Registering a Callback¶ ↑
To use a callback you create a new Parser object and register a proc or method to call each time a new host is parsed, as soon as it's parsed. The callback is then run in a new thread and is passed the newly created Nmap::Parser::Host object.
require 'nmap/parser' callback = proc do |host| return if host.status != "up" puts "Found #{host.addr}" end parser = Nmap::Parser.new(:callback => callback) parser.parsefile("nmaplog.xml") # Found 192.168.10.1 # Found 192.168.10.2 # Found 192.168.10.7 # [...]
Doing a Bit More¶ ↑
After printing a little session information, this example will cycle through all of the up hosts, printing state and service information on the open TCP and UDP ports. See the examples directory that comes with this library for more examples.
puts "Nmap args: #{parser.session.scan_args}" puts "Runtime: #{parser.session.scan_time} seconds" puts parser.hosts("up") do |host| puts "#{host.addr} is up:" puts [:tcp, :udp].each do |type| host.getports(type, "open") do |port| srv = port.service puts "Port ##{port.num}/#{port.proto} is open (#{port.reason})" puts "\tService: #{srv.name}" if srv.name puts "\tProduct: #{srv.product}" if srv.product puts "\tVersion: #{srv.version}" if srv.version puts end end puts end
Credits¶ ↑
Author & Maintainer:
-
Kris Katterjohn (katterjohn(a)gmail.com)
Contributors (in chronological order of first contribution):
-
Stefan Friedli (stfr(a)scip.ch)
-
Daniel Roethlisberger (daniel(a)roe.ch)
-
Dustin Webber (dustinw(a)aos5.com)
-
Tom Sellers (nmap(a)fadedcode.net)
-
Rory McCune (rorym(a)nmrconsult.net)
-
Russell Fulton (r.fulton(a)auckland.ac.nz)
Thanks a lot for taking the time and helping out, everybody!
For information on what each contributor actually did, please take a look at the project's ChangeLog and Subversion logs.
Constants
Attributes
Raw XML output from the scan
Session object for the scan
Public Class Methods
Creates a fresh Parser object, taking a hash of options as an optional argument. Use the instance parsing methods to read in the XML and parse the data into the available classes.
Returns the new Nmap::Parser object and yields it to a block if one is given
# File lib/nmap/parser.rb, line 538 def initialize(opts = {}) # :yields: parser @hosts = [] @fresh = true opts.keys.each do |key| begin send("option_#{key}", opts[key]) rescue NoMethodError end end yield self if block_given? end
Public Instance Methods
Returns a new Parser object with the following characteristics:
* rawxml = nil * session = nil * contains hosts from both operands. If any of the hosts in the first operand are also in the second (as determined by comparing host.addr information), the duplicate hosts from the second one are not available.
# File lib/nmap/parser.rb, line 446 def +(pa) return nil unless self.class == pa.class n = Nmap::Parser.new n.rawxml = nil n.session = nil [ self.hosts, pa.hosts ].each do |h| n.addhosts(h) end n end
This operator simply compares the rawxml members
# File lib/nmap/parser.rb, line 435 def ==(pa) @rawxml == pa.rawxml end
Returns a boolean value depending on whether this object is just a combination of others (e.g. using +)
# File lib/nmap/parser.rb, line 459 def combination? rawxml.nil? and session.nil? and not @fresh end
Deletes host with the specified IP address or hostname hostip
Calling this method from inside of a block given to a method like hosts() or #get_ips() may lead to adverse effects.
# File lib/nmap/parser.rb, line 409 def del_host(hostip) @hosts.delete_if do |host| host.addr == hostip or host.hostname == hostip end end
Returns an array of IPs scanned and yields them each to a block if one is given
If an argument is given, only hosts matching status
are given
NOTE: Calling parser.get_ips(status).size can be very different than running parser.session.numhosts(status) because the information there and here are coming from different places in the XML. Nmap will not typically list individual hosts which it doesn't know or assume are “up”.
# File lib/nmap/parser.rb, line 427 def get_ips(status = "") # :yields: host.addr hosts(status).map do |host| yield host.addr if block_given? host.addr end end
Returns a Host object for the host with the
specified IP address or hostname hostip
# File lib/nmap/parser.rb, line 397 def host(hostip) @hosts.find do |host| host.addr == hostip or host.hostname == hostip end end
Returns an array of Host objects and yields them each to a block if one is given
If an argument is given, only hosts matching status
are given
NOTE: Calling parser.hosts(status).size can be very different than running parser.session.numhosts(status) because the information there and here are coming from different places in the XML. Nmap will not typically list individual hosts which it doesn't know or assume are “up”.
# File lib/nmap/parser.rb, line 386 def hosts(status = "") # :yields: host @hosts.map { |host| if status.empty? or host.status == status yield host if block_given? host end }.compact end
Read and parse the contents of the Nmap XML file
filename
# File lib/nmap/parser.rb, line 320 def parsefile(filename) File.open(filename) { |f| parseread(f) } rescue raise $!.class, "Error parsing \"#{filename}\": #{$!}" end
Read and parse XML from obj
. obj
can be any
object responding to a read() method that returns a String (or something
else responding to to_str()). IO and File are just a couple of examples.
# File lib/nmap/parser.rb, line 311 def parseread(obj) if not obj.respond_to?(:read) raise TypeError, "Passed object must respond to read()" end parsestring(obj.read) end
Essentially runs “nmap
-d args
targets
”
nmap
is here to allow you to do things like:
parser.parsescan(“sudo ./nmap”, arguments, targets)
and still make it easy for me to inject the options for XML output and debugging.
args
can't contain arguments like -oA, -oX, etc. as these
could interfere with Parser's processing. If you need that other
output, you could run Nmap yourself and just
pass the -oX output to Parser. Or you could use
rawxml to grab the XML from the scan and write it to a file, for example.
targets
is an optional array of target specifications.
It's here only for convenience because you can also put any targets you
want scanned in args
(which is what I tend to do unless I
happen to already have a collection of targets as an array).
# File lib/nmap/parser.rb, line 355 def parsescan(nmap, args, targets = []) if args =~ /\s-o|^-o/ raise ArgumentError, "Output option (-o*) passed to parsescan()" end # Enable debugging and XML; pass args and targets command = "#{nmap} -d -oX - #{args} #{targets.join(" ")}" begin # First try popen3() if it loaded successfully.. Open3.popen3(command) do |sin, sout, serr| parseread(sout) end rescue NameError # ..but fall back to popen() if not IO.popen(command) do |io| parseread(io) end end end
Read and parse a String (or something else responding to to_str()) of XML
# File lib/nmap/parser.rb, line 328 def parsestring(str) if not str.respond_to?(:to_str) raise TypeError, "XML data should be a String, or must respond to to_str()" end parse(str.to_str) end
Wrapper around the instance method's functionality
Returns a new Nmap::Parser object and yields it to a block if one is given
# File lib/nmap/parser.rb, line 277
Wrapper around the instance method's functionality
Returns a new Nmap::Parser object and yields it to a block if one is given
# File lib/nmap/parser.rb, line 283
Wrapper around the instance method's functionality
Returns a new Nmap::Parser object and yields it to a block if one is given
# File lib/nmap/parser.rb, line 289
Wrapper around the instance method's functionality
Returns a new Nmap::Parser object and yields it to a block if one is given
# File lib/nmap/parser.rb, line 295