Saturday, March 17, 2012

Nmap Open Ports Comparison Script

Since I was writing my other Nmap script, I thought that it would be beneficial to have a script that would compare two Nmap scans and report the differences in open ports. This would be handy for a few reasons, compliance, baseline scan comparisons, change control, etc.

This script takes the XML output file from your baseline Nmap scan and will compare it with the XML output from your current scan. For each host in the scan, the script will show you the open ports in the baseline and current scans, any new open ports that it found in the current scan, and any open ports in the baseline scan that are no longer open. It supports TCP and UDP ports.

This Perl script will use the same Nmap Parser module that I mentioned in my previous post. (http://search.cpan.org/~apersaud/Nmap-Parser/).

We need to ensure we take care of a couple of dependencies first:
  1. The first step is obviously to install Perl 5+ if you haven't already. 
  2. Then you need to install the Nmap Parser module. You can do this with the following command: perl -MCPAN -e 'install Nmap::Parser'
  3. Done!
Here is the actual perl script. Just copy/paste it and run. It takes two arguments. The first is the name of your baseline Nmap XML file and the second is the name of your current Nmap XML file.

#!/usr/bin/perl

# Author: Travis Lee
# Date Modified: 3-16-12
# Description: Script to compare results from two Nmap XML output files
#              and display the differences in open ports.
#
# Usage: NmapScanCompare.pl <baseline nmap scan.xml> <current nmap scan.xml>
#

use Nmap::Parser;
$base = new Nmap::Parser;
$curr = new Nmap::Parser;

if (!$ARGV[0] || !$ARGV[1])
{
print "Usage: NmapScanCompare.pl <baseline nmap scan.xml> <current nmap scan.xml>\n\n";
exit;
}

$base_file = $ARGV[0]; #baseline scan filename from command line
$curr_file = $ARGV[1]; #current scan filename from command line

$base->parsefile($base_file); #load baseline scan file
$curr->parsefile($curr_file); #load current scan file

$base_session = $base->get_session;
$curr_session = $curr->get_session;

print "\n\nBaseline Scan Parameters: ".$base_session->scan_args." -- Scan finished on: ".$base_session->time_str."\n";
print "Current Scan Parameters: ".$curr_session->scan_args." -- Scan finished on: ".$curr_session->time_str."\n";

#loop through all the IPs in the current scan file
for my $ip ($curr->get_ips) 
{
        #assume that IPs in current scan == IPs in base scan
        my $ip_base = $base->get_host($ip);
        my $ip_curr = $curr->get_host($ip);

#populate arrays with baseline and current tcp/udp open ports
my @base_tcpports = $ip_base->tcp_open_ports;
my @base_udpports = $ip_base->udp_open_ports;
my @curr_tcpports = $ip_curr->tcp_open_ports;
my @curr_udpports = $ip_curr->udp_open_ports;

#find tcp ports that exist in the current scan and not in the baseline scan
@curr_tcpdiff = &portdiff(\@base_tcpports, \@curr_tcpports);

#find tcp ports that existed in the baseline scan that do NOT exist in the current scan
@base_tcpdiff = &portdiff(\@curr_tcpports, \@base_tcpports);

#find udp ports that exist in the current scan and not in the baseline scan
@curr_udpdiff = &portdiff(\@base_udpports, \@curr_udpports);

#find tcp ports that existed in the baseline scan that do NOT exist in the current scan
@base_udpdiff = &portdiff(\@curr_udpports, \@base_udpports);

#give the open port arrays a value of "none" if no open ports are found
if (!@base_tcpports) { @base_tcpports = ("none"); }
if (!@curr_tcpports) { @curr_tcpports = ("none"); }
if (!@base_udpports) { @base_udpports = ("none"); }
if (!@curr_udpports) { @curr_udpports = ("none"); }

#give the diff arrays a value of "none" if no port diffs are found
if (!@base_tcpdiff) { @base_tcpdiff = ("none"); }
if (!@curr_tcpdiff) { @curr_tcpdiff = ("none"); }
if (!@base_udpdiff) { @base_udpdiff = ("none"); }
if (!@curr_udpdiff) { @curr_udpdiff = ("none"); }

print "\nReport for $ip:\n\n";
#print "Baseline scan had these TCP ports open: ".join(',',@base_tcpports)."\n";
#print "Current scan has these TCP ports open: ".join(',',@curr_tcpports)."\n\n";
        print "  New TCP ports open: ".join(',',@curr_tcpdiff)."\n";
        print "  TCP ports that are not open anymore: ".join(',',@base_tcpdiff)."\n\n";

#print "Baseline scan had these UDP ports open: ".join(',',@base_udpports)."\n";
#print "Current scan has these UDP ports open: ".join(',',@curr_udpports)."\n\n";
        print "  New UDP ports open: ".join(',',@curr_udpdiff)."\n";
        print "  UDP ports that are not open anymore: ".join(',',@base_udpdiff)."\n\n";
        
} #end for loop

#find ports that exists in $_[0], but not in $_[1]
sub portdiff
{
        my %port = ();
        my @result = ();
my @a = @{$_[0]}; #first parameter from sub call
my @b = @{$_[1]}; #second parameter from sub call

#build the lookup table
foreach $item (@a) { $port{$item} = 1; }

#find only elements in @b and not in @a
foreach $item (@b)
{
unless ($port{$item}) 
{
push(@result, $item); #it's not seen so add to @result
}
}

return @result;

} #end sub

Again, I am not a Perl ninja so it may not be the most efficient, but it works. If you have any suggestions on improvements to the script, please let me know! I would love to make this better and more helpful for anyone that needs it.

And of course there is also the obligatory disclaimer to use this at your own risk. :) Enjoy!

Monday, March 5, 2012

Converting Nmap XML output to CSV (Excel) Script

Ever ran a Nmap scan and wished that there was a way to easily get the data into Excel? You could want the data in Excel for a number of reasons... I personally like to have it in Excel sometimes so it is easier to track information about the host, OS, ports, services, vulnerabilities, etc.

The closest output that Nmap has is its built in XML format (-oX flag). Using this XML output I have created a perl script that easily converts the XML output into a CSV file for use in Excel. Luckily, a guy named Anthony Persaud created an Nmap Parser module for perl that we can use (http://search.cpan.org/~apersaud/Nmap-Parser/).

We need to ensure we take care of a couple of dependencies first:

  1. The first step is obviously to install Perl 5+ if you haven't already. 
  2. Then you need to install the Nmap Parser module. You can do this with the following command: perl -MCPAN -e 'install Nmap::Parser'
  3. That's it! Not too shabby.

Here is the actual perl script. Just copy/paste it and run. It takes two arguments. The first is the name of the Nmap XML file and the second is the name of your output file. 

#!/usr/bin/perl

# Author: Travis Lee
# Date Modified: 3-5-12
# Description: Script to convert a Nmap scan XML file to CSV
#
# Usage: NmapXMLtoCSV.pl <nmap xml file.xml> <output file.csv>
#

use Nmap::Parser;
$base = new Nmap::Parser;

if (!$ARGV[0] || !$ARGV[1])
{
print "Usage: NmapXMLtoCSV.pl <nmap xml file.xml> <output file.csv>\n\n";
exit;
}

$base_file = $ARGV[0]; #baseline scan filename from command line
$out_file = $ARGV[1]; #output filename from command line

$base->parsefile($base_file); #load baseline scan file

open (OUTFILE, ">$out_file");

$session = $base->get_session;
print OUTFILE $session->scan_args."  --  Scan finished on: ".$session->time_str;

print OUTFILE "\n\nIP,Hostname,MAC,OS,Port,Proto,State,Service,Version\n";

#loop through all the IPs in the current scan file
for my $ip ($base->get_ips) 
{
        #get host object for the current IP
        $ip_base = $base->get_host($ip);

#populate arrays with tcp/udp ports
my @tcpports = $ip_base->tcp_ports;
my @udpports = $ip_base->udp_ports;

print OUTFILE $ip_base->ipv4_addr.",";
print OUTFILE $ip_base->hostname.",";
print OUTFILE $ip_base->mac_addr.",";

#get os object for the current IP
my $os = $ip_base->os_sig;

#if smb-os-discrovery script was run, use this value for os. more accurate
if ($ip_base->hostscripts("smb-os-discovery"))
{
my @os_split1 = split(/\n/, $ip_base->hostscripts("smb-os-discovery"));
my @os_split2 = split("OS: ", $os_split1[1]);
print OUTFILE $os_split2[1].",";
}
else { print OUTFILE $os->name.","; } #else use what was discovered with os fingerprint

$first = 1;

&portout(\@tcpports, "tcp");
&portout(\@udpports, "udp");

print OUTFILE "\n";
        
} #end for loop

close (OUTFILE);
print "\n\nConversion complete!\n\n";

sub portout
{
my @ports = @{$_[0]};
my $proto = $_[1];

for my $port (@ports)
{
#get service object for the given port
if ($proto eq "tcp")
{
$svc = $ip_base->tcp_service($port);
}
elsif ($proto eq "udp")
{
$svc = $ip_base->udp_service($port);
}

if ($first == 1) { $first = 0; }
else { print OUTFILE ",,,,"; }

print OUTFILE $port.",";
print OUTFILE $proto.",";

if ($proto eq "tcp")
{
print OUTFILE $ip_base->tcp_port_state($port).",";
}
elsif ($proto eq "udp")
{
print OUTFILE $ip_base->udp_port_state($port).",";
}

print OUTFILE $svc->name.",";
print OUTFILE $svc->product."\n";

} #end for loop

} #end sub



I am not a professional perl ninja so it may not be the most efficient, but it works. If you have any suggestions on improvements to the script, please let me know! I would love to make this better and more helpful for anyone that needs it.

And of course there is also the obligatory disclaimer to use this at your own risk. :) Enjoy!