pavement

Firewall, Monitoring

From FreeBSDwiki
Revision as of 17:09, 25 August 2012 by Jimbo (Talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

I wrote myself a handy little CGI application in Perl to let me monitor my ipfw firewall from a web browser. It uses (optional) reverse DNS host lookups for the source IPs of the things you're logging, (optional) service lookups from /etc/services for the destination port numbers, and (optional) service override lookups for things that you want to look different in the firewall than in /etc/services. (I personally like to put attack types and such in the overrides file, WITHOUT necessarily winding up obliterating legitimate services that may also use that particular port in my /etc/services file.)

You can specify alternate logfiles for it to read from the HTTP address, in the format http://youraddress/ipfwparser.cgi?logfile=/var/log/security.0.gz here, if you like. Don't sweat GZIPped or BZIP2ed logs; as long as you make sure that the locations of gzcat and bzcat specified in the config section are correct (and that you are using .gz and .bz2 extensions on any compressed logfiles), it'll handle the compressed logs transparently.

One common "gotcha" to remember: if you want this to work from a web browser, you'll need to make sure that your firewall log is readable from the user context of your webserver (in most cases, the user 'www'). Usually you'll want to do this by chmodding /var/log/security to 644 - and don't forget to change the value in /etc/newsyslog.conf as well, or it'll be overwritten the first time your logs rotate!

#! /usr/bin/perl
 
 ##
 ## ipfwparser.cgi - (c) 2004 Jim Salter
 ## Version 1.0, 2004 Nov 13
 ##
 ## this script is open source software.  you may use it under the terms
 ## of the GNU GPL - you may use it, alter it, redistribute it, and 
 ## redistribute any alterations you make as well.  Any changes you make
 ## must also be made available to the public under the same terms.  No 
 ## warranties express or implied are made, and you use this software at 
 ## your own risk.
 ##
 ## See http://www.opensource.org/licenses/gpl-license.php for a complete
 ## copy of the GNU GPL.
 ##
 
 #########
 #########  Config Section
 #########
 
 # gzcat utility is used to read GZIP compressed logfiles
 $gzcat = "/usr/bin/gzcat";
 
 # bzcat utility is used to read BZIP2 compressed logfiles
 $bzcat = "/usr/bin/bzcat";
 
 # location of default logfile - may be overridden with HTTP GET or POST arguments 
 $in{'logfile'} = "/var/log/security";
 
 # dig command and options are used for reverse DNS lookup
 $dig_cmd = "/usr/bin/dig -x";
 $dig_opts = "+short +time=1 +tries=1";
 $host_lookups = 1;
 
 # use service lookups from /etc/services and optional overrides from elsewhere
 $service_lookups = 1;
 $service_overrides = '/usr/local/etc/service_overrides';
 
 # get HTTP GET and POST arguments
 &ReadParse;
 
 # munge $in{'logfile'} if we see extensions indicating compressed logs 
 if ($in{'logfile'} =~ m/\.gz$/) {$in{'logfile'} = "$gzcat $in{'logfile'} |";}
 if ($in{'logfile'} =~ m/\.bz2$/) {$in{'logfile'} = "$bzcat $in{'logfile'} |";}
  
 #########
 #########
 #########
 
 
 #### read logfile into an array
 
 my $counter = 0;                # for use iterating through the array
 open FH, "$in{'logfile'}";
 
 foreach (<FH>) {
         chomp();
         @templine = split (/\s+/, $_);
 
         # datestamp
         $log[$counter][1] = $templine[0] . ' ' . $templine[1] . ' ' . $templine[2];
 
         # hostname
         $log[$counter][2] = $templine[3];
 
         # check for "last message repeated"
         if ($templine[4] ne "last") {
                 # count of specific action
                         $log[$counter][0] = 1;
                 # rule number
                         $log[$counter][3] = $templine[6];
                 # action
                         $log[$counter][4] = $templine[7];
                 # protocol
                         $log[$counter][5] = $templine[8];
                 # source address and port
                         my @source = split (/:/, $templine[9]);
                         $log[$counter][6] = $source[0];
                         $log[$counter][7] = $source[1];
                 # destination address and port
                         my @destination = split (/:/, $templine[10]);
                         $log[$counter][8] = $destination[0];
                         $log[$counter][9] = $destination[1];
 
                 # direction
                         $log[$counter][10] = $templine[11];
                 # interface
                         $log[$counter][11] = $templine[13];
                 # check for ICMP in [5] and split out protocol as ports if so
                         my @ICMP = split (/:/,$log[$counter][5]);
                         if ($ICMP[0] eq 'ICMP') {
                                 $log[$counter][5] = 'ICMP';
                                 $log[$counter][7] = $ICMP[1];
                                 $log[$counter][9] = $ICMP[1];
                         }
         } else {
                 # repeat message, parse accordingly
                 # first, clone the last entry
                         for ($loop=0; $loop<12; $loop++) {
                                 $log[$counter][$loop] = $log[($counter-1)][$loop];
                         }
                 # then replace the count and timestamp portions with current
                         $log[$counter][0] = $templine[7];
                         $log[$counter][1] = "$templine[0] $templine[1] $templine[2]";
         }
         $counter ++;
 }
 close FH;
 
 ###### Now let's print the results
 
 &printresults;
 
 ###### Okay, we're done
 exit;
 
 ##############################################################################
 #######################                                                     ##
 ####################### Subroutines follow                                  ##
 #######################                                                     ##
 ##############################################################################
 
 sub printresults {
 
         # set HTML formatting variables
         my $headercellbegin = '<td align="center"><font color="#FFFFFF"><b>';
         my $headercellend = "</b></font></td>\n";
         my $bodycellbegin = '<td><font face="Arial, Helvetica" size="1">';
         my $bodycellend = "</font></td>\n";
 
 
         # open page for printing as HTML
         print "Content-type: text/html\n\n";
         print "<html>\n";
         print "<head></head>\n";
         print "<body>\n";
 
 
         # if spec'ed, read /etc/services into a hash for fast lookups
         if ($service_lookups) {
                 open FH, "/etc/services";
                 foreach (<FH>) {
                         my @servtemp = split (/\s+/, $_);
                         my @portproto = split ('/', $servtemp[1]);
                         if ($portproto[1] eq 'tcp') {$portproto[1] = 'TCP';}
                         if ($portproto[1] eq 'udp') {$portproto[1] = 'UDP';}
                         # assign text to hash index {port}{protocol}
                         $service{$portproto[0]}{$portproto[1]} = $servtemp[0];
                 }
                 close FH;
 
                 # if spec'ed, patch results from /etc/services with local overrides
                 if ($service_overrides) {
                         open FH, "$service_overrides";
                         foreach (<FH>) {
                                 my @servtemp = split (/\s+/, $_);
                                 my @portproto = split ('/', $servtemp[1]);
                                 if ($portproto[1] eq 'tcp') {$portproto[1] = 'TCP';}
                                 if ($portproto[1] eq 'udp') {$portproto[1] = 'UDP';}
                                 # assign text to hash index {port}{protocol}
                                 $service{$portproto[0]}{$portproto[1]} = $servtemp[0];
                         }
                         close FH;
                 }
         }
 
         print "<table border=1 cellpadding=5 cellspacing=0>\n";
         print "<tr bgcolor='#000000'>\n";
         print $headercellbegin . '#' . $headercellend;
         print $headercellbegin . 'datestamp' . $headercellend;
 #       print $headercellbegin . 'fwhost' . $headercellend;
         print $headercellbegin . 'rule' . $headercellend;
         print $headercellbegin . 'action' . $headercellend;
         print $headercellbegin . 'proto' . $headercellend;
         print $headercellbegin . 'shost' . $headercellend;
           if ($host_lookups) {print $headercellbegin . 'shostname' . $headercellend;}
         print $headercellbegin . 'sport' . $headercellend;
         print $headercellbegin . 'dhost' . $headercellend;
         print $headercellbegin . 'dport' . $headercellend;
         print $headercellbegin . 'dir' . $headercellend;
         print $headercellbegin . 'iface' . $headercellend;
           if ($host_lookups) {print $headercellbegin . 'servicename' . $headercellend;}
 
         for ($loop = $counter - 1; $loop > -1; $loop--) {
                 unless ($log[$loop][6] eq '') {
                         print "<tr>\n";
 
                         for ($element=0; $element<12; $element++) {
                                 unless ($element eq 2) {print $bodycellbegin . $log[$loop][$element] . $bodycellend;}
                                 if ($host_lookups * ($element eq 6)) {
                                         my $hostname = `$dig_cmd $log[$loop][6] $dig_opts`;
                                         if (($hostname =~ m/\<\<\>\>/) + ($hostname eq '')) {$hostname = ' ';}
                                         print $bodycellbegin . $hostname . $bodycellend;
                                 }
                         }
                         if ($service_lookups) {print $bodycellbegin . $service{$log[$loop][9]}{$log[$loop][5]} . ' ' . $bodycellend;}
                         print "</tr>\n";
                 }
         }
         print "</table>\n";
         print "</body>\n";
         print "</html>\n";
 }
 
 
 ################################################################
 ################################################################
 
 sub ReadParse {
   local (*in) = @_ if @_;
   local ($i, $key, $val);
 
   # Read in text
   if ($ENV{'REQUEST_METHOD'} eq "GET") {
     $in = $ENV{'QUERY_STRING'};
   } elsif ($ENV{'REQUEST_METHOD'} eq "POST") {
     read(STDIN,$in,$ENV{'CONTENT_LENGTH'});
   }
 
   @in = split(/[&;]/,$in);
 
   foreach $i (0 .. $#in) {
     # Convert plus's to spaces
     $in[$i] =~ s/\+/ /g;
 
     # Split into key and value.
     ($key, $val) = split(/=/,$in[$i],2); # splits on the first =.
 
     # Convert %XX from hex numbers to alphanumeric
     $key =~ s/%(..)/pack("c",hex($1))/ge;
     $val =~ s/%(..)/pack("c",hex($1))/ge;
 
     # Associate key and value
     $in{$key} .= "\0" if (defined($in{$key})); # \0 is the multiple separator
     $in{$key} .= $val;
 
   }
 
   return scalar(@in);
 }
Personal tools