#!/usr/bin/perl -w
## fwreport
## pod at tail
$|++;
use strict;
my $version = '0.09.27';
use File::Temp qw/tempfile/;
use Socket;
use Net::Whois::IANA;
use Locale::Country;
use File::Slurp;
use Text::Autoformat;
# use Module::Versions::Report;
## Config params:
my $infile = shift || 'fwreport.in'; ## logfile from Netfilters
+ firewall
my $foutfile = shift || 'fwreport.out'; ## formatted output report
my $routfile = shift || 'fwreport.raw'; ## unformatted output repo
+rt
my $tempdir = '/tmp/';
my $template = 'file_XXXXXXXXXX';
my $fqdn_level = 2;
## Eliminate duplicates from input file:
my $attacks = 1;
my ( $fh, $filename1 ) = tempfile($template, DIR=>$tempdir, UNLINK=>1)
+;
{
open( IN, $infile ) or die "Error opening $infile: $!";
while(<IN>){
my %seen;
for (<IN>){
++$attacks;
++$seen{$_};
}
print $fh sort keys %seen;
}
close IN or die "Error closing $infile: $!";
}
## Ready intermediate file from write to read:
seek $fh,0,0;
## Parse intermediate file:
my (@countries, @domains, @s_svcs, @d_svcs, );
while(<$fh>){
## Populate hash with select elements from entry:
my @elements = split( /\s+/ );
my %entry;
for my $element (@elements) {
my ($key, $value) = split(/=/, $element, 2);
next unless ($key =~ m/^(IN|OUT|SRC|DST|PROTO|SPT|DPT)$/);
$entry{$key} = (defined($value) ? $value : '');
}
## Determine what country attack is from:
my $iana = new Net::Whois::IANA;
$iana->whois_query(-ip=>$entry{SRC});
## leading 2 chars only, avoid CACA, USUS, EU # whole world msgs:
my $cc = substr($iana->country(), 0, 2);
## decode to name:
my $cn = code2country($cc);
push(@countries, $cn) if $cn;
## Reverse DNS lookup of attacking host:
my $domain = gethostbyaddr(inet_aton($entry{SRC}),AF_INET);
## truncate FQDN down to n-level:
push @domains,($domain ? $domain =~ m/((?:[^.]+\.?){1,$fqdn_level})$
+/ : $entry{SRC});
## Determine name of attacking service:
my $s_svc = getservbyport( $entry{SPT}, lc($entry{PROTO}) );
push @s_svcs,($s_svc ? $s_svc : $entry{SPT});
## Determine name of attacked service:
my $d_svc = getservbyport( $entry{DPT}, lc($entry{PROTO}) );
push @d_svcs,( $d_svc ? $d_svc : $entry{DPT});
}
## Wrap it up:
{
undef my %saw; @saw{@countries} = (); my @countries_u = sort keys %s
+aw;
undef %saw; @saw{@domains} = (); my @domains_u = sort keys %s
+aw;
undef %saw; @saw{@s_svcs} = (); my @s_svcs_u = sort keys %s
+aw;
undef %saw; @saw{@d_svcs} = (); my @d_svcs_u = sort keys %s
+aw;
open( OUT, "+> $routfile" ) or die "Error opening $routfile: $!";
my ($sec,$min,$hour,$mday,$mon,$year,) = localtime(time);
my $stamp = sprintf(
"%02d-%02d-%02d %02d:%02d", $year+1900, $mon+1, $mday, $hour, $m
+in
);
print OUT $stamp . "\n"x2;
print OUT "\n"x2;
print OUT 'BLOCKED ATTACKS = ' . $attacks . "\n"x2;
print OUT "\n"x1;
print OUT 'ATTACKING COUNTRIES = ';
print OUT my $countries_u = @countries_u . "\n"x2;
print OUT $_ . ' ' for (@countries_u);
print OUT "\n"x3;
my @domains_us = SortAlphaThenNum( @domains_u );
print OUT 'ATTACKING DOMAINS = ';
print OUT my $domains_us = @domains_us . "\n"x2;
print OUT $_ . ', ' for (@domains_us);
print OUT "\n"x3;
my @s_svcs_us = SortAlphaThenNum( @s_svcs_u );
print OUT 'ATTACKING SERVICES = ';
print OUT my $s_svcs_us = @s_svcs_us . "\n"x2;
print OUT $_ . ', ' for (@s_svcs_us);
print OUT "\n"x3;
my @d_svcs_us = SortAlphaThenNum( @d_svcs_u );
print OUT 'ATTACKED SERVICES = ';
print OUT my $d_svcs_us = @d_svcs_us . "\n"x2;
print OUT $_ . ', ' for (@d_svcs_us);
print OUT "\n"x3;
close OUT or die "Error closing $routfile: $!";
}
## Spruce it up a bit:
{
my $rawtext = read_file($routfile);
my $formatted = autoformat $rawtext,{left=>1,right=>72,all=>1,squeez
+e=>0};
write_file($foutfile, $formatted);
}
######################################################################
+####
## Elements with any alpha before elements with all numeric
sub SortAlphaThenNum {
my @unsorted = @_;
return my @sorted =
map $_->[0],
sort {
$a->[1] <=> $b->[1] || $a->[2] cmp $b->[2] ||
$a->[3] <=> $b->[3] || $a->[0] cmp $b->[0]
}
map [ $_, scalar(/^\d/), split /(\d+)/, $_, 2],
@unsorted
;
}
END {
sleep 1 && print "\a" for( 1..3 );
}
######################################################################
+####
=head1 TITLE
fwreport
=head1 SYNOPSIS
fwreport infile outfile
=head1 DESCRIPTION
A lightweight parser for Netfilter logs.
Produces highly-simplified summary for PHB consumption.
=head1 TODO
?combine redundant 'wrap it up' code into sub?
Sanity-check IN
Strip leading whitespace from IN:
s/^\s+//g
Datetimestamp for filename
Datestamp of input file to output report
Getopt::Long and Pod::Usage
--infile, --outfile, --versions, --help, --man
Module::Versions::Report conditionally with END{...}
Test on Win32
File::Temp instead of $routfile
File::Slurp may not allow this
=head1 UPDATE
2003-11-10 14:45 CST
Post to PerlMonks
Reduce number and scope of global vars
PC speaker beeps on completion
Datestamp of program run to output report
Sample input and report to pod
2003-11-08 22:00 CST
File::Slurp, Text::Autoformat to prettify output report
Output sort any-alpha first, all-numeric last
Revise parsing:
number of (attacks, attackers, ISPs)
list of (countries, domains, attack(ing|ed) services)
Parse country codes to names
Add Module::Versions::Report
Eliminate duplicates entries from intermediate file
Print to OUT instead of STDOUT
Strip all but rightmost 2 or 3 elements of FQDN
Strip bogus country code info:
CACA => CA, USUS => US, EU # Country is really world wide => EU
Only print protocol num if no service name
Eliminate duplicate entries from IN
Use File::Temp for intermediate file
Parse src/dst ports to service names:
Debug TCP is uc while /etc/services is lc
Read data from IN
Rename from "whobe"
Parse input from syslog contents
Read from file (one IPaddr/line) instead of @ARGV
Reverse name lookup of source address
Variable-ize output delimiter
2003-10-31 17:10 CST
Initial working code
=head1 TESTED
Debian 3.0r1
Perl 5.8.0
strict 1.02
warnings 1.00
File::Temp 0.14
Socket 1.75
Net::Whois::IANA 0.03
Locale::Country 2.06
File::Slurp 2004.0904
Text::Autoformat 1.12
Modules::Versions::Report 1.02
=head1 BUGS
Unclear on root-cause, but IP addresses registered to LACNIC
occasionally cause errors and lookup failure:
Use of uninitialized value in scalar chomp at Net/Whois/IANA.pm lin
+e 117.
=head1 CREDITS
Props ta menolly for gracious reminder of rudimentary perling,
atcroft for hash-populating aid,
tye and jmcnamara for es'plaining seek() re: File::Temp write then re
+ad,
wufnik and Enlil for FQDN truncating idears,
bart, Enlil, and Limbic~Region for unnatural list sorting with ST,
dru145 for www.ginini.com.au/tools/fwlogsum/,
Lincoln Stein for NPwP,
And to some guy named vroom.
=head1 AUTHOR
ybiC
=head1 SAMPLE INPUT
(extraneous fields stripped, to reduce lateral scrolling at PM)
IN=eth0 OUT= SRC=172.141.50.1 DST=1.2.3.4 SPT=3155 DPT=17300 PROTO=UD
+P
IN=eth0 OUT= SRC=200.226.91.1 DST=1.2.3.4 SPT=1369 DPT=17300 PROTO=UD
+P
ACTION=drop-input IN=eth0 OUT= SRC=202.123.79.1 DST=1.2.3.4 SPT=49634
+ DPT=21 PROTO=TCP
IN=eth0 OUT= SRC=211.162.110.1 DST=1.2.3.4 SPT=30112 DPT=1026 PROTO=T
+CP
IN=eth0 OUT= SRC=213.25.170.1 DST=1.2.3.4 SPT=4674 DPT=2282 PROTO=TCP
IN=eth0 OUT= SRC=221.141.148.1 DST=1.2.3.4 SPT=4124 DPT=25 PROTO=TCP
IN=eth0 OUT= SRC=24.128.81.1 DST=1.2.3.4 SPT=3789 DPT=17300 PROTO=UDP
IN=eth0 OUT= SRC=24.150.221.1 DST=1.2.3.4 SPT=1900 DPT=17300 PROTO=TC
+P
IN=eth0 OUT= SRC=24.187.84.1 DST=1.2.3.4 SPT=4789 DPT=593 PROTO=UDP
IN=eth0 OUT= SRC=24.194.92.1 DST=1.2.3.4 SPT=3540 DPT=17300 PROTO=TCP
IN=eth0 OUT= SRC=24.214.27.1 DST=1.2.3.4 SPT=4076 DPT=17300 PROTO=TCP
IN=eth0 OUT= SRC=24.60.217.1 DST=1.2.3.4 SPT=2268 DPT=17300 PROTO=TCP
IN=eth0 OUT= SRC=61.143.182.1 DST=1.2.3.4 SPT=30110 DPT=1026 PROTO=TC
+P
IN=eth0 OUT= SRC=61.172.3.1 DST=1.2.3.4 SPT=32997 DPT=1026 PROTO=TCP
IN=eth0 OUT= SRC=61.250.91.1 DST=1.2.3.4 SPT=58926 DPT=443 PROTO=TCP
IN=eth0 OUT= SRC=62.59.148.1 DST=1.2.3.4 SPT=666 DPT=1026 PROTO=TCP
IN=eth0 OUT= SRC=63.83.69.1 DST=1.2.3.4 SPT=4823 DPT=57 PROTO=TCP
IN=eth0 OUT= SRC=65.130.116.1 DST=1.2.3.4 SPT=2782 DPT=524 PROTO=TCP
IN=eth0 OUT= SRC=65.50.156.1 DST=1.2.3.4 SPT=2394 DPT=17300 PROTO=TCP
IN=eth0 OUT= SRC=68.1.179.1 DST=1.2.3.4 SPT=2878 DPT=515 PROTO=TCP
IN=eth0 OUT= SRC=68.13.16.1 DST=1.2.3.4 SPT=53 DPT=1026 52 PROTO=TCP
IN=eth0 OUT= SRC=68.16.97.1 DST=1.2.3.4 SPT=65322 DPT=554 PROTO=TCP
IN=eth0 OUT= SRC=68.16.97.1 DST=1.2.3.4 SPT=65323 DPT=7070 PROTO=TCP
IN=eth0 OUT= SRC=68.21.37.1 DST=1.2.3.4 SPT=2991 DPT=20168 PROTO=TCP
IN=eth0 OUT= SRC=68.22.76.1 DST=1.2.3.4 SPT=1379 DPT=20168 PROTO=UDP
IN=eth0 OUT= SRC=68.3.106.1 DST=1.2.3.4 SPT=3577 DPT=20168 PROTO=UDP
IN=eth0 OUT= SRC=68.50.191.1 DST=1.2.3.4 SPT=4565 DPT=20168 PROTO=TCP
IN=eth0 OUT= SRC=68.51.50.1 DST=1.2.3.4 SPT=3425 DPT=20168 PROTO=TCP
IN=eth0 OUT= SRC=68.83.144.1 DST=1.2.3.4 SPT=1298 DPT=20168 PROTO=TCP
IN=eth0 OUT= SRC=68.99.193.1 DST=1.2.3.4 SPT=4070 DPT=1026 PROTO=TCP
IN=eth0 OUT= SRC=68.99.193.1 DST=1.2.3.4 SPT=4070 DPT=1027 PROTO=UDP
IN=eth0 OUT= SRC=81.166.217.1 DST=1.2.3.4 SPT=3963 DPT=21 PROTO=TCP
IN=eth0 OUT= SRC=193.108.95.1 DST=1.2.3.4 SPT=80 DPT=1042 PROTO=TCP
IN=eth0 OUT= SRC=193.108.95.1 DST=1.2.3.4 SPT=80 DPT=1043 PROTO=TCP
IN=eth0 OUT= SRC=202.12.29.1 DST=1.2.3.4 SPT=43 DPT=38423 PROTO=TCP
IN=eth0 OUT= SRC=216.239.59.1 DST=1.2.3.4 SPT=80 DPT=1031 PROTO=TCP
=head1 SAMPLE OUTPUT
2003-11-09 14:55
BLOCKED ATTACKS = 36
ATTACKING COUNTRIES = 10
Australia Brazil Canada China France Hong Kong Kore
+a,
Republic of Netherlands Poland United States
ATTACKING DOMAINS = 25
ameritech.net, apnic.net, attbi.com, bellsouth.net, cgocable.net,
com.br, comcast.net, cox.net, knology.net, optonline.net, qwest.net,
rogers.com, rr.com, tiscali.fr, zonnet.nl, 61.143.182.1, 61.172.3.1,
61.250.91.1, 63.83.69.1, 193.108.95.1, 202.123.79.1,
211.162.110.1, 213.25.170.1, 216.239.59.1, 221.141.148.1,
ATTACKING SERVICES = 32
domain, whois, www, 666, 1298, 1369, 1379, 1900, 2268, 2394, 2782, 28
+78,
2991, 3425, 3540, 3577, 3789, 3963, 4070, 4076, 4124, 4565, 4674, 478
+9,
4823, 30110, 30112, 32997, 49634, 58926, 65322, 65323,
ATTACKED SERVICES = 18
ftp, https, mtp, printer, smtp, 524, 554, 593, 1026, 1027, 1031, 1042
+,
1043, 2282, 7070, 17300, 20168, 38423,
=cut
|