pavement

Firewall, Monitoring

From FreeBSDwiki
Revision as of 20:30, 14 November 2004 by Jimbo (Talk | contribs)
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.

#! /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