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!

No comments:

Post a Comment