Monitoring your network with nmap
December 15, 2008 at 10:56 pmThis all came about while I was pondering the best way to dump an overview of what was sat on my network in the datacenter; the main reason being a lack of useful documentation telling me what IPs were in use to narrow it down at all. The idea was that it’d dump in CSV format something along the lines of:
IP,TCP Ports
10.0.0.1,22 143 5000 12345 31337
Of course, once I had this data, my mind started down the path of ‘wouldn’t it be nice to have an automated system that would e-mail me and tell me if any ports change (read: “if any developers set something up without my knowledge”), and also tell me if any hosts are added to our network. I present to you an automated network monitoring script, this will scan your network and send you a detailed report (with port probing), via a direct SMTP connection to your remote mail server - which is useful in the event that your network gets compromised and you can no longer trust any binaries. All that you need to do is put it somewhere, make it executable, and add it to a cronjob - mine runs hourly, but I’m paranoid.
0 */1 * * * /root/bin/nmap.pl auto >/dev/null 2>&1
The e-mails look something similar to this:
Nmap Scan Report. Hosts/Ports changed!
Generated on: 2008-12-08 18:39:44——–BEGIN——–
10.0.0.1 is a new host with 3 ports open/filtered.10.0.0.1 has these ports open: 22,111,5000
22 might be ssh
111 might be rpcbind
5000 might be vtun
——–END——–
All code that I’ve written is licensed under the Creative Commons Attribution-Non-Commercial-Share Alike 2.0 UK: England & Wales Licence. Where non-commercial means don’t sell it, not ‘you can’t use it in your company’. =]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | #!/usr/bin/perl use File::Copy; use Nmap::Parser; use Net::DNS::Sendmail; use warnings; use strict; #config begin my $nmap_path = "/usr/bin/nmap"; #path to nmap binary my $nmap_args = "-T5 -F -sV"; #nmap arguments (accepts multiple), do not specify any output arguments! my $ips = ""; #networks to scan, accepts standard nmap input my $save_as = "/root/parser-cache.xml"; #path to storage file - this file is used for comparative scans, so it's best not to place it in /tmp. my $email_from_address = ''; #from address to use when sending mail - should probably exist to avoid sender callouts my $email_to_address = ''; #address you want results delivered to my $email_sender_domain = ''; #domain to masquerade as - useful in cases where remote mta checks helo. my $email_subject = "Nmap Network Scan Results"; #subject for email. my $debug = "false"; #enables some output if you need to test auto on the cli - i.e. why mail isn't being sent. #config end my $np = new Nmap::Parser; my $old = new Nmap::Parser; my $curr = new Nmap::Parser; my $tempstore = "/tmp/parser-cache.tmp"; my $emailfile = "/tmp/nmap-notify"; my $newmachines = 0; my $auto = (lc($ARGV[0]) eq "auto"); my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time); sub debug { print "@_\n" if($debug eq "true"); } sub print_twice { print "@_"; print EMAIL "@_"; } sub create_files { foreach (@_) { if(!-e $_) { print "$_ does not exist... creating\n" if(!$auto); open(TEMP,">$_") or die "Can't create $_: $!"; close(TEMP) or die "Couldn't close $_: $!"; } } } create_files($save_as,$tempstore); if($auto) { open(EMAIL,">$emailfile") or die "Can't create $emailfile: $!"; print EMAIL "Nmap Scan Report. Hosts/Ports changed!\nGenerated on: "; printf EMAIL "%4d-%02d-%02d %02d:%02d:%02d\n\n",$year+1900,$mon+1,$mday,$hour,$min,$sec; print EMAIL "--------BEGIN--------\n"; } if($auto) { debug("Running auto mode"); if(-s $save_as) { $old->parsefile($save_as); #load previous state $curr->cache_scan($tempstore); #set temporary file to store xml after we're done $curr->parsescan($nmap_path, $nmap_args, $ips); #scan current hosts for my $ip ($curr->get_ips('up')) { my $ip_old = $old->get_host($ip); my $ip_curr = $curr->get_host($ip); my @alltcp = $ip_curr->tcp_open_ports(); my $numtcp = $#alltcp + 1; if(!$ip_old) { print_twice("$ip is a new host with".$numtcp."ports open/filtered.\n"); ++$newmachines; } } print_twice("\n") if($newmachines != 0); for my $ip ($curr->get_ips('up')) { my $ip_old = $old->get_host($ip); my $ip_curr = $curr->get_host($ip); my %port = (); if($ip_old) { my @tcpdiff = grep { $port{$_} < 2} (map {$port{$_}++; $_} ( $ip_curr->tcp_open_ports , $ip_old->tcp_open_ports)); if(scalar @tcpdiff) { print_twice("$ip has these new ports open: ".join(',',@tcpdiff)."\n"); for (@tcpdiff) { print_twice("\t"."$_ might be ",$ip_curr->tcp_service($_)->name,"\n"); } print_twice("\n"); } }else{ my @tcp = (map {$port{$_}++; $_} ( $ip_curr->tcp_ports('open') )); if(scalar @tcp) { print_twice("$ip has these ports open: ".join(',',@tcp)."\n"); for (@tcp) { print_twice("\t"."$_ might be ",$ip_curr->tcp_service($_)->name,"\n"); } print_twice("\n"); } } } }else{ $curr->cache_scan($tempstore); #set temporary file to store xml after we're done $curr->parsescan($nmap_path, $nmap_args, $ips); #scan current hosts for my $ip ($curr->get_ips('up')) { my $ip_curr = $curr->get_host($ip); my @alltcp = $ip_curr->tcp_open_ports(); my $numtcp = $#alltcp + 1; print_twice("$ip is a new host with ".$numtcp." ports open/filtered.\n"); ++$newmachines; } print_twice("\n") if($newmachines != 0); for my $ip ($curr->get_ips('up')) { my $ip_curr = $curr->get_host($ip); my %port = (); my @tcp = (map {$port{$_}++; $_} ( $ip_curr->tcp_ports('open') )); if(scalar @tcp) { print_twice("$ip has these ports open: ".join(',',@tcp)."\n"); for (@tcp) { print_twice("\t"."$_ might be ",$ip_curr->tcp_service($_)->name,"\n"); } print_twice("\n"); } } } move($tempstore, $save_as); print EMAIL "--------END--------"; close EMAIL; open(EMAIL,"<$emailfile") or die "Can't read $emailfile: $!"; my $mail = Net::DNS::Sendmail->new(); $mail->verbose() if($debug eq "true"); $mail->senderdomain( "$email_sender_domain" ); $mail->to( "$email_to_address" ); $mail->from( "$email_from_address" ); $mail->subject( "$email_subject" ); while () { $mail->data($_); } $mail->sendmail() if($newmachines != 0); debug("mail sent"); close EMAIL; } else { #Unsure whether or not to move the output of this into $save_as... it may #be that someone runs this, and doesn't take care of the output, in which #case, we still want to be notified that night. $np->cache_scan($save_as); $np->parsescan($nmap_path, $nmap_args, $ips); print "IP,OPEN TCP PORTS\n"; for my $ip ($np->get_ips('up')) { my $ip_check = $np->get_host($ip); my %port = (); my @tcp = (map {$port{$_}++; $_} ( $ip_check->tcp_ports('open') )); print "$ip,".join(' ',@tcp)."\n" if(scalar @tcp); } } |