A simple ruby script that takes a single file or a directory with several NMAP xml files and creates a csv or a pdf with a list mapping open ports to a list of IP addresses. If you want to use the PDF feature it needs a gem , so you need to install prawn, otherwise it should work with a default ruby installation.
sudo gem install prawn
HELP:
./nmaports.rb --help
HELP for Nmaport
---------------------
--help, -h
Well I guess you know what this is for (To obtain this Help).
--dir, -d [directory_name]
The root path for the nmap files.
--pattern, -p
The pattern to use to detect file (i.e *client*).
--file, -f [file_name]
If we only want one file.
--output, -o
The output file name.
SAMPLE:
> ./nmapports -d nmaps -o openports.pdf
CODE:
#!/usr/bin/env ruby -wKU
# Author : FreedomCoder ( Matias Pablo Brutti )
# Email: matiasbrutti ---- gmail ---- com
# Created on : February 9, 2009
# License: MIT
# Description : NMAP output parser that creates a table of Ports --> IPs.
# This is helpful to create a list of open ports and their corresponding IP addresses.
require 'rubygems'
require 'getoptlong'
require 'rexml/document'
@nmap_dir, @nmap_file, @pattern, @output, @type = nil
opts = GetoptLong.new(
[ '--help', '-h', GetoptLong::NO_ARGUMENT ],
['--dir','-d', GetoptLong::REQUIRED_ARGUMENT ],
['--file','-f', GetoptLong::REQUIRED_ARGUMENT ],
['--pattern','-p', GetoptLong::REQUIRED_ARGUMENT ],
['--output','-o', GetoptLong::REQUIRED_ARGUMENT ],
['--type','-t', GetoptLong::REQUIRED_ARGUMENT ]
)
opts.each do |opt, arg|
case opt
when '--help':
# BEGIN OF HELP
puts "\nHELP for Nmaport\n---------------------\n
--help, -h
\tWell I guess you know what this is for (To obtain this Help).\n
--dir, -d [directory_name]
\t The root path for the nmap files.\n
--pattern, -p
\t The pattern to use to detect file (i.e *client*).\n
--file, -f [file_name]
\tIf we only want one file.\n
--output, -o
\tThe output file name.
Copyright 2009 - FreedomCoder\n"
#END OF HELP
exit(0)
when '--dir':
if File.exists?(arg)
@nmap_dir = arg
else
puts "Directory not found"
end
when '--file':
if File.exists?(arg)
@nmap_file = arg
else
puts "File not found"
end
when '--pattern':
@pattern = arg
when '--output':
@output = arg
@type = @output.split(".")[1].upcase unless @type
when '--type':
if arg =~ /PDF|pdf|CSV|csv/
@type = arg.upcase
else
puts "unrecognized file type. bye!"
exit(0)
end
else
puts "Unknown command. Please try again"
exit(0)
end
end
# method to read files from directoy ---------------------------------------------------------------
def get_files(dir,name)
files = Dir["#{dir}/**/#{name || "*"}.xml"]
end
def dir_open_ports(dir)
arr = []
dir.each do |doc|
arr += open_ports(read_xml(doc))
end
arr.uniq.sort
end
def dir_get_ips(dir, ports)
final_list = {}
dir.each do |doc|
puts "Working on #{doc}\n"
final_list.merge!(get_ips(read_xml(doc),ports)) { |k,o,n| final_list[k] = (o + n).sort.uniq }
end
final_list
end
# methods to parse XML Nmap output -----------------------------------------------------------------
def read_xml(xml)
doc = REXML::Document.new(File.read(xml)).root
end
def open_ports(doc)
out = []
doc.elements.each('host') do |h|
h.elements.each('ports/port') do |p|
if p.elements['state'].attributes['state'] == "open"
out << "#{p.attributes['portid']}/#{p.attributes['protocol']}"
end
end
end
out.uniq.sort
end
def open?(host,port)
host.elements.each('ports/port') do |p|
if p.attributes['portid'] == port.split("/")[0] && p.elements['state'].attributes['state'] == "open"
return true
end
end
false
end
def get_ips(doc,ports)
open_list = {}
ports.each do |port|
a = []
doc.elements.each('host') do |h|
a << h.elements['address'].attributes['addr'] if open?(h,port)
end
open_list[port] = a
end
open_list
end
#methods to output the data ------------------------------------------------------------------------
def create_output(list,name,type)
case type
when "PDF":
create_pdf(list,name)
puts "Data written to file #{@output || "output.pdf"}"
when "CSV":
create_csv(list,name)
puts "Data written to file #{@output || "output.csv"}"
else
puts "You did not specified an output type using CSV"
create_csv(list,name)
puts "Data written to file #{@output || "output.csv"}"
end
end
def create_csv(list,name=nil)
out = File.new(name || "output.csv", "w")
out << "PORT,IP Adressess\n"
list.each do |k,v|
out << "#{k},\"#{(v.map { |k| k + "\n" }.to_s).strip}\" \n"
end
end
def create_pdf(list,name=nil)
require 'prawn'
require 'prawn/layout'
Prawn::Document.generate(name || "output.pdf") do
data = []
list.each do |k,v|
data << [k,(v.map { |k| k + "\n" }.to_s).strip]
end
table data,
:position => :center,
:headers => ["Port", "IP Adresses"],
:header_color => "0046f9",
:row_colors => :pdf_writer, #["ffffff","ffff00"],
:font_size => 10,
:vertical_padding => 2,
:horizontal_padding => 5
end
end
def show_data(list)
puts "PORT\t IP Adressess\n"
list.each do |k,v|
puts "#{k}\t#{(v.map { |k| " " + k + "\n" }.to_s).strip} \n"
puts "--------------------------"
end
end
# Script -------------------------------------------------------------------------------------------
puts "Let's work ..."
lista = {}
if @nmap_dir
dir_list = get_files(@nmap_dir,@patern)
ports = dir_open_ports(dir_list)
lista = dir_get_ips(dir_list, ports)
end
if @nmap_file
xml_file = read_xml(@nmap_file)
ports = open_ports(xml_file)
if lista.empty?
lista = get_ips(xml_file,ports)
else
lista.merge!(get_ips(xml_file,ports)) do |k,o,n|
lista[k] = (o + n).sort.uniq
end
end
end
show_data(lista)
create_output(lista,@output,@type)
puts "Bye"
Enjoy.