. .

Perl: w00tw00t-Einträge im Apache-Log automatisch für iptables-Firewallregeln verwenden


Im Apache-Errorlog finden sich bei mir immer wieder einige Einträge dieser Art:
„[Wed Apr 07 23:17:01 2010] [error] [client 88.191.61.110] client sent HTTP/1.1 request without hostname (see RFC2616 section 14.23): /w00tw00t.at.ISC.SANS.DFind:“.

Diese Einträge entstehen wohl, wenn Apache von einem Tool namens `dfind‘ gescannt wird. `dfind‘ sucht nach Schwachstellen in der verwendeten Server-Software.
Da solche Log-Einträge ziemlich überhand nahmen, schrieb ich ein kleines Perl-Script, dass ähnlich wie `tail‘ das Apache-Logfile beobachtet und bei einem Eintrag nach entsprechendem Muster („.*request without hostname.*w00tw00t.*“) iptables anweist, die Client-IP zu sperren – falls diese IP ohnehin nicht schon gesperrt ist. Das Script setzt sich selbst in den Hintergrund.

UPDATE: Ich habe das Script angepasst und erweitert. Um eigene Logformat-Einträge mit diesem Script durchsuchen zu lassen, kann über die Variablen $columnnumber und $ipdelimiter die Spalte identifiziert werden, in der sich die Client-IP befindet. Außerdem können jetzt mehrere Suchmuster nach verdächtigen Log-Einträgen angegeben werden.

Das Perl-Script verwendet die Pakete File::Tail, Net::Whois::IANA und Log::Log4perl, die unter Umständen nicht installiert sind.
Diese können eventuell vom Paketverwaltungssystem des Betriebssystems installiert werden, alternativ aber natürlich auch über `cpan':

cpan[1]> install File::Tail
cpan[2]> install Net::Whois::IANA
cpan[3]> install Log::Log4perl

Sollten die Paketinstallation über cpan fehlschlagen, kann versucht werden, mit `force install File::Tail‘ die Installation zu erzwingen.


Hier ist der Code des kleinen Scripts, ausgeführt mit Perl v5.16.1 (gentoo Linux):

#!/bin/env perl

#########################################################################

use strict;
use warnings;

use File::Tail;
use Net::Whois::IANA;
use Log::Log4perl qw(:easy);
use POSIX qw(setsid);

#########################################################################
# Konfiguration

my $bin_iptables =  '/sbin/iptables';
my $bin_grep =      '/bin/grep';
my $bin_gawk =      '/bin/gawk';
my $working_dir =   '/tmp';
my $logfile =       '/chroot/apache/var/log/apache2/error_log';
my $columnnumber =  10;                   # apache2.2: 7
my @ipdelimiter =   ( '^', ':\d*\]$' );   # apache2.2: ( '^', ':\]$' )
my @regexp_searches = (
    '.*request without hostname.*w00tw00t.*',
    '.*Invalid method in request.*'
);
my @ignore_ips = ( 
    '64.4.11.37'
);
my $log_conf =      q(
    log4perl.rootLogger              = DEBUG, LOG1
    log4perl.appender.LOG1           = Log::Log4perl::Appender::File
    log4perl.appender.LOG1.filename  = /var/log/w00tw00t.log
    log4perl.appender.LOG1.mode      = append
    log4perl.appender.LOG1.layout    = Log::Log4perl::Layout::PatternLayout
    log4perl.appender.LOG1.layout.ConversionPattern = %d %p %m %n
);

#########################################################################

my ($line, $ip);
my (@parts, @ipparts, @already_blocked);
my ($iana) = new Net::Whois::IANA;
Log::Log4perl->init(\$log_conf);

#########################################################################

sub block_ip {
    my $ip = shift;
    my $regexp = shift;
    my $logger = get_logger();

    if (system($bin_iptables." -I INPUT 1 -s ".$ip." -j DROP") != 0) {
        $logger->error("Failed executing command `".$bin_iptables." -I INPUT 1 -s ".$ip." -j DROP`");
    } else {
        $iana->whois_query(-ip=>$ip);
        $logger->warn($ip." from ".$iana->country()." (".$iana->descr().") blocked (matched: '".$regexp."')");
        push(@already_blocked, $ip);
    }
}
sub check_line {
    # Example line (apache 2.2):
    # [Wed Apr 07 23:17:01 2010] [error] [client 88.191.61.110] client sent HTTP/1.1 request \
    #   without hostname (see RFC2616 section 14.23): /w00tw00t.at.ISC.SANS.DFind:)

    # Example line (apache 2.4):
    # [Mon Nov 11 10:23:03.940219 2013] [core:error] [pid 19468:tid 139718641649424] \
    #   [client 80.86.84.72:36116] AH00135: Invalid method in request  /horde/util/barcode.php\
    #   ?type=../../../../../../../../../../../var/log/psa-horde/psa-horde.log%00 HTTP/1.1, referer: 1

    my $line = shift @_;
    my $logger = get_logger();

    foreach my $regexp_search (@regexp_searches) {
        if ($line =~ m/$regexp_search/) {
            @parts = split(/\s+/, $line);

            if (defined($parts[$columnnumber])) {
                if ($parts[$columnnumber] =~ m/\d*\.\d*\.\d*\.\d*.*/) {
                    ($ip) = $parts[$columnnumber] =~ m/$ipdelimiter[0](\d*\.\d*\.\d*\.\d*)$ipdelimiter[1]/;

                    if ($ip) {
                        if (!(grep $_ eq $ip, @ignore_ips)) {
                            if (!(grep $_ eq $ip, @already_blocked)) {
                                block_ip($ip, $regexp_search);
                            } else {
                                $logger->info($ip." already blocked (matched: '".$regexp_search."')");
                            }
                        } else {
                                $logger->info($ip." is in ignore list (matched: '".$regexp_search."')");
    }   }   }   }   }   }
}
sub get_already_blocked_ips {
    my @output = `$bin_iptables -vnL INPUT | $bin_grep DROP | $bin_gawk '{ print \$8 }'`;

    foreach my $line (@output) {
        if ($line =~ m/^\d*\.\d*\.\d*\.\d*$/) {
            push(@already_blocked, substr($line, 0, -1));
    }   }
}
sub daemonize {
    chdir($working_dir) or die("Can't chdir to $working_dir: $!");
    open(STDIN, '/dev/null') or die("Can't read /dev/null: $!");
    open(STDOUT, '>>/dev/null') or die("Can't write to /dev/null: $!");
    open(STDERR, '>>/dev/null') or die("Can't write to /dev/null: $!");
    defined(my $pid = fork()) or die("Can't fork: $!");
    exit() if $pid;
    setsid() or die("Can't start a new session: $!");
    umask(0);
}

#########################################################################

$| = 1;
&daemonize();

while (1) {
    my $tailed_logfile = File::Tail->new(name => $logfile, tail => -1, maxinterval => 20);
    get_already_blocked_ips();

    while(defined($line = $tailed_logfile->read)) {
        check_line($line);
    }

    sleep(30);
}



Im Logfile kann man nun sehen, wann Einträge erfasst worden sind. In iptables sollten die entsprechenden IP-Adressen dann geblockt werden:


Man sollte allerdings hin und wieder die Firewall-Regeln resetten, da ansonsten auch Besucher ausgesperrt werden, die durch Zuweisung einer geblockten dynamischen IP-Adresse „fälschlicherweise“ betroffen sind.


Hinterlasse eine Antwort

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *

Du kannst folgende HTML-Tags benutzen: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>