pavement

Firewall, Monitoring

From FreeBSDwiki
(Difference between revisions)
Jump to: navigation, search
(moving caveat about log permissions to main article)
(prettification, moved logfile permissions gotcha caveat from discussion page to main article)
Line 1: Line 1:
 
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.)
 
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.
+
You can specify alternate logfiles for it to read from the HTTP address, in the format '''<nowiki>http://youraddress/ipfwparser.cgi?logfile=/var/log/security.0.gz</nowiki>''' 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 [[chmod]]ding /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!
 
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 [[chmod]]ding /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!

Revision as of 04:40, 18 July 2005

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