Acting on logfiles with sec.pl

Block Brute Force SSH Attacks

The Simple Event Correlator (SEC), or sec.pl, can follow logfiles (written by syslog-ng or other log sources), and trigger actions depending on user defined rules.

swatch is another tool for monitoring logfiles.

Block Brute Force SSH Attacks

Rules for sec.pl | badhost: block specified addresses | badhosts rules for pf.conf

Using sec.pl, brute force SSH attacks can be automatically detected and blocked. This section outlines how to detect the brute force attacks, and update a Packet Filter (PF) firewall. Ensure good hosts cannot be blocked, or that blocks timeout, to prevent legitimate users from being locked out. Use expiretable to expire table entries by age.

Other options to stop brute force attacks include pam_tally. Traffic from undesirable sources can also be sent to a slow queue instead of being blocked.

Rules for sec.pl

Rule to match failed SSH logins, and trigger a block. The thresholds are set very low, as the rules are for a personal system that does not see failed logins:

type=singlewiththreshold
ptype=regexp
pattern=sshd\[\d+\]: Failed password for (?:illegal user )?(.+?) from (\S+)
desc=ssh brute force from $2
action=shellcmd /usr/local/sbin/badhost $2
thresh=3
window=600

To run sec.pl, read the above data via a block.conf configuration file:

$ sec.pl --detach --syslog=local0 \
--conf=/etc/sec/block.conf --input=/var/log/messages

badhost: block specified addresses

The badhost script adds Internet addresses to a PF firewall table, and drops any state for the blocked address, to prevent continued access by an already open connection.

#!/usr/bin/perl
#
# $Id: badhost,v 1.3 2005/10/03 00:15:47 jmates Exp $
#
# Adds the specified IP addresses to the <badhosts> table in a Packet
# Filter (pf) firewall, then finds and removes state entries involving
# the addresses. Requires a table defined in pf.conf:
#
# table <badhosts> persist
#
# And blocking rules involving the <badhosts> table, along the lines of:
#
# block in quick on $ext_if from <badhosts>
# block return out quick on $ext_if to <badhosts>
#
# Warning: either ensure that blocked hosts are remove automatically
# after some period of time, or that good hosts cannot be blocked.

die "usage: $0 [-t table] [ [ip] [ip2 ..] | - ]\n" unless @ARGV;

use Getopt::Std;
my %opts;
getopts 't:', \%opts;

my $table_name = $opts{t} || 'badhosts';

# TODO steal regex from Regexp::Common or something
my $ip_re = qr/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/;

my %badhost;

# read from STDIN if - only argument
chomp( @ARGV = <STDIN> ) if $ARGV[-1] eq '-' and @ARGV == 1;

# add each entry to hash, add to blocking table
for my $entry (@ARGV) {
next unless $entry =~ m/$ip_re/;
next if $badhost{$entry}++;

system qw{/sbin/pfctl -t}, $table_name, qw{-T add}, $entry;
}

# parse state list for badhosts, kill of any existing states
open STATELIST, '-|'
or exec qw{/sbin/pfctl -s state}
or die "error: pfctl -s state failed: $!\n";

while ( my $line = <STATELIST> ) {
chomp $line;

if ( $line =~ m/^
\w+ \s+ \w+ \s+ # leading 'self tcp' or similar
($ip_re):\d+ \s+ (<-|->) \s+ ($ip_re):\d+ # source to dest
\s+ (.+) # state (established, etc.)
/x ) {

my ( $one, $direction, $two, $state ) = ( $1, $2, $3, $4 );

# if state, kill it off, and remove host from additional state
# matches (which should now be dead)
if ( exists $badhost{$one} and $direction eq '->' ) {

system qw{/sbin/pfctl -k}, $one;
delete $badhost{$one};

} elsif ( exists $badhost{$two} ) {

system qw{/sbin/pfctl -k}, $one, '-k', $two;
delete $badhost{$two};
}

} else {
die "error: unparseable pfctl -s state output: line=$line\n";
}
}

badhosts rules for pf.conf

The firewall must define a badhosts table, and block all traffic from (or even to) the addresses in the table. If using quick, place the following block statements near the top of the pf.conf configuration file. I strongly recommend the use of a goodhosts table, so that spoofed queries do not lock out allowed systems.

table <badhosts> persist

block in quick on $ext_if from <badhosts>
block return out quick on $ext_if to <badhosts>