Sunday, August 15, 2010

PF Block Report (Perl Script)

I wanted to come up with an easy solution to help me review the PF logs on my OpenBSD firewalls. A quick search came up with Hatchet; however, it required SQLite modules and I really wanted to write something myself. So I borrowed a few regexes from Hatchet and wrote my own little Perl script which reports on IPs with over a certain number of blocked entries in the PF log.

How the script works:

  1. Copies over the current pflog file to the working directory

  2. Runs tcpdump on the copied pflog file and creates a tcpdump text file

  3. Creates an array of the file and then goes through it looking for all blocked in traffic

  4. Creates a hash table of the unique blocked IPs and then determines how many blocked entries for each IP

  5. For those blocked IPs with over a certain number of blocked entries it goes back through the log array and finds how many passed in, blocked out, and passed out entries

  6. Reports that information via an HTML formatted email to an address of your choice

  7. Deletes the tcpdump output and copied over pflog files


This script is designed to be ran as a cron job, right before the log rotates.


----------pfblockreport.pl------------------------

#!/usr/bin/perl -w

use strict;

use File::Copy;
use Net::SMTP;
use Sys::Hostname;

my @blockTemp = ();
my @log = ();
my %blockedIPs;
my $pktLimit = 100;
my $crdt = localtime;
my $to = "dean\@company.com";
#additional recipient (if needed)
#my $to2 = "deptaccount\@company.com";
my $from = "pfblocked\@company.com";
my $hstname = hostname();
my $subject = "PF Block Report for $hstname";

#start of html report
my $message = "<p>PF Block Report for $hstname on $crdt</p> \n" .
"<table border\=\"2\" width\=\"650\"><tr><td>Blocked IP</td><td align\=\"center\">Blocked In</td>" .
"<td align\=\"center\">Passed In</td><td align\=\"center\">Blocked Out</td><td align\=\"center\">Passed Out</td></tr> \n";

#copy over current pf log file
#run tcpdump on output file
#then load array with data

copy("/var/log/pflog","pflogfile");
system("tcpdump -neltttr pflogfile > tcpdumpfile.txt");
open(TDF,"tcpdumpfile.txt");
@log = <TDF>;
close TDF;

#parse each entry with regex looking for IPv4 blocked in only
#load those source IPs into array

foreach my $line(@log)
{

my ($action, $srcIP);

if ($line =~ /(\w+ \d+ \d+:.\d:.\d+)\.(\d+) rule (\d+)\/\(match\) (\w+ \w+) \w+ (\w+)\: (\d+\.\d+\.\d+\.\d+)(.*)/)
{
($action, $srcIP) = ($4, $6);

if ($action eq "block in")
{
push(@blockTemp,$srcIP);
}
}

}

#load hash to get unique blocked IPs
#IP address is the hash key and 1 is value for all

%blockedIPs = map { $_ => 1 } @blockTemp;

#find how many blocked entries per blocked IP
#add that to the value of the hash

foreach my $key (keys(%blockedIPs))
{

my $pkt = 0;
my $host = $key;

foreach my $tmpip(@blockTemp)
{
if ($host eq $tmpip)
{
$pkt++;
}
}

$blockedIPs{$key} = $pkt;
}

#look for blocked IPs that are over the bad entries limit
#then go back through logfile and check to see how many entries
#have those IPs as source with pass in or destination with pass or out


foreach my $key (sort (keys(%blockedIPs)))
{

my $blkIP = $key;
my $x = length ($blkIP);
my $pktNum = $blockedIPs{$key};
my $inCnt = 0;
my $outCnt = 0;
my $boCnt = 0;

if ($pktNum > $pktLimit)
{

foreach my $line(@log)
{

my ($action, $srcHost, $dstHost);

if ($line =~ /(\w+ \d+ \d+:.\d:.\d+)\.(\d+) rule (\d+)\/\(match\) (\w+ \w+) \w+ (\w+)\: (\d+\.\d+\.\d+\.\d+)\.(\d+) > (\d+\.\d+\.\d+\.\d+)\.(\d+)\:(.*)/)
{

($action, $srcHost, $dstHost) = ($4, $6, $8);

if ($blkIP eq $srcHost and $action eq "pass in")
{
$inCnt++;
}


if ($blkIP eq $dstHost and $action eq "pass out")
{
$outCnt++;
}

if ($blkIP eq $dstHost and $action eq "block out")
{
$boCnt++;
}


}


if ($line =~ /(\w+ \d+ \d+:.\d:.\d+)\.(\d+) rule (\d+)\/\(match\) (\w+ \w+) \w+ (\w+)\: (\d+\.\d+\.\d+\.\d+) > (\d+\.\d+\.\d+\.\d+)\:(.*)/)
{

($action, $srcHost, $dstHost) = ($4, $6, $7);

if ($blkIP eq $srcHost and $action eq "pass in")
{
$inCnt++;
}


if ($blkIP eq $dstHost and $action eq "pass out")
{
$outCnt++;

}

if ($blkIP eq $dstHost and $action eq "block out")
{
$boCnt++;
}

}


}


$message = $message . "<tr><td>$key</td><td align\=\"center\">$blockedIPs{$key}</td>" .
"<td align\=\"center\">$inCnt</td><td align\=\"center\">$boCnt</td>" .
"<td align\=\"center\">$outCnt</td></tr> \n";

}

}

$message = $message . "</table>";

#open SMTP connection and mail information

my $smtp = Net::SMTP->new("smtp.company.com");
$smtp->mail( $from );
#for single recipient
$smtp->to( $to );
#for two or more recipients
#$smtp->to( $to,$to2 );
$smtp->data();
$smtp->datasend("MIME-Version: 1.0\n");
$smtp->datasend("Content-Type: text/html; charset=us-ascii\n");
$smtp->datasend("From: " . $from . "\n");
$smtp->datasend("To: " . $to . "\n");
$smtp->datasend("Subject: " . $subject . "\n");
$smtp->datasend("\n");
$smtp->datasend("\n");
$smtp->datasend( $message );
$smtp->datasend("\n");
$smtp->dataend();
$smtp->quit();

#delete tcpdump output and copied over pflog files
unlink("tcpdumpfile.txt");
unlink("pflogfile");