Tuesday, September 28, 2010

PF Block Report (Perl Script) V2

I’ve been running PF Blocked Report script for well over a month on my firewalls and have made a few improvements. Version 1.0 of the script didn’t take into account legitimate blocked traffic (e.g. FIN ACK web server traffic). So I modified the tcpdump command to only look for TCP with only SYN flag set, ICMP, and UDP traffic. Additionally, I added general reporting for outbound hosts. The script is designed to be ran as a cron job, right before the log rotates. The size of your report will depend of what numbers you choose for the blocked limits. I’m using 100 as the inbound threshold to limit the size of my reports and taking into consideration even a quick NMAP scan will create well over a thousand entries it should catch port scans.

----------pfblocked.pl-------------------------------


#!/usr/bin/perl -w

# pfblocked.pl version 3.0
# last modified 09/28/10

use strict;

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

my @log = ();
my @blockTemp = ();
my @obTemp = ();
my %blockedIPs;
my %obIPs;
my $pktLimit = 100;
my $obPktLimit = 50;
my ($year, $month, $day, $hour, $min) = (localtime)[5,4,3,2,1];
my $timestamp = sprintf ("%02d-%02d-%02d %02d:%02d", $month+1, $day, $year+1900, $hour, $min);
my $to = "userid\@mycompany.com";
#my $to2 = "deptaccount\@mycompany.com"; #additional recipient (if needed)
my $from = "pfblocked\@mycompany.com";
my $smtpserver = "smtp.mycompany.com";
my $hstname = hostname();
my $subject = "PF Block Report for $hstname";

#start of html report
my $message = "<p>PF Block Report for $hstname on $timestamp</p> \n" .
"Inbound Hosts<br /> \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 log file and create output file
#then load array with output file data

copy("/var/log/pflog","pflogfile");
system("tcpdump -neltttr pflogfile 'tcp[13] & 2 !=0' or icmp or udp > tcpdumpfile.txt");

open(TDF,"tcpdumpfile.txt");
@log = <TDF>;
close TDF;

#parse each entry with regex looking for IPv4 blocked traffic
#load those source IPs into arrays

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);
}

if ($action eq "block out")
{
push(@obTemp,$srcIP);
}

}

}

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

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

#find how many blocked entries per blocked in 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 out.
#then report formatting check based upon length of IP address

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);

#regex for tcp and udp traffic

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++;
}


}

#regex for icmp

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>";

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

%obIPs = map { $_ => 1 } @obTemp;

$message = $message . "<br /><br /> \n" .
"Outbound Hosts \n".
"<table border\=\"2\" width\=\"300\"><tr><td>Blocked IP</td><td align\=\"center\">Blocked Out</td></tr> \n";

#find how many entries per blocked out IP
#add that to the value of the hash
#if value is over outbound limit add to report data

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

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

foreach my $tmpip(@obTemp)
{

if ($host eq $tmpip)
{
$pkt++;
}
}

$obIPs{$key} = $pkt;

if($pkt > $obPktLimit)
{
$message = $message . "<tr><td>$key</td><td align\=\"center\">$obIPs{$key}</td></tr> \n";
}
}

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

#open SMTP connection and mail information

my $smtp = Net::SMTP->new($smtpserver);
$smtp->mail( $from );
$smtp->to( $to ); # for single recipient
#$smtp->to( $to,$to2 ); # for two or more recipients
$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");












-------------------------------------------------------------------