On Saturday 28 January 2006 12:17 pm, Mike Hicks wrote:
> Hi,
>
> I finally got around to making an automatic blackhole setup to stop the
> bots that try to SSH into my box all the time.  I'm sure people have
> done this before, but I couldn't find many decent references googling
> around.  So, I figured I'd explain what I did so others can waste less
> time.
>
> First off, I switched from running the standard syslogd/klogd setup to
> using syslog-ng.  The advantage of syslog-ng is that you can have it
> pipe some or all of the logs that are received into another program,
> which can handle events as they happen.
>
> I pondered making a utility of my own to filter the logs for SSH login
> failures, but I finally came across a reasonable utility online.  SEC,
> the Simple Event Correlator [http://kodu.neti.ee/~risto/sec/], uses Perl
> and allows for Perl's regular expression syntax, which I'm most
> comfortable with (and when I forget things, I can just do "man perlre"
> to remember).
>
> Finally, I decided to make myself a simple blackhole script to give
> myself a little more flexibility (for example, I'll expand it in the
> future to have a "whitelist" of IP addresses, so if I'm feeling
> forgetful one day, I hopefully won't block myself).
>
> I added these entries to my /etc/syslog-ng/syslog-ng.conf file, which
> filters my logs and only sends messages from sshd to SEC (my firewall is
> just a Pentium 133, so I didn't want to send everything there):
>
>         # set up the destination of sec, which uses STDIN ('-') as input
>         destination d_sec { program("sec -input=- -conf=/etc/sec.conf"); };
>
>         # set up a filter to only pick up logs from the SSH daemon
>         filter f_sshd { program(sshd.*); };
>
>         # connect log source, filter, and destination
>         log {
>                 source(s_all);
>                 filter(f_sshd);
>                 destination(d_sec);
>         };
>
> I also created a rule for SEC that will run my blackhole script if three
> failed logins appeared within a 30-second window.  After an hour (3600
> seconds), I run my script again to unblock the IP address (otherwise my
> blackhole chain would probably become very long after a while).
>
>         type=SingleWith2Thresholds
>         ptype=RegExp
>         pattern=Failed password for (?:root|illegal user \S+) from
> ::ffff:([0-9.]+) desc=Repeated login failures from $1
>         window=30
>         thresh=3
>         action=shellcmd /usr/local/bin/blackhole.pl add $1
>         desc2=Blackholed $1 for one hour, removing
>         window2=3600
>         thresh2=0
>         action2=shellcmd /usr/local/bin/blackhole.pl remove $1
>
> I created a blackhole script that would add the IP to some rule chains
> that are already set up on my firewall.  I use the shorewall script to
> define rules, which has a pre-defined "dynamic" chain for blackholing
> sites, plus a "reject" target chain that will sanely react to a variety
> of different incoming traffic.  (I suppose calling it a "blackhole"
> isn't really accurate in this case, since my firewall will generally
> send TCP reset, port unreachable, or other responses when traffic is
> being rejected).
>
>         #!/usr/bin/perl
>         my ($action, $ip) = @ARGV;
>         if ($action eq 'add') {
>                 system ("iptables -A dynamic -s $ip -j reject");
>                 system ("logger -t blackhole.pl[$$] added $ip to blackhole
> list"); }
>         elsif ($action eq 'remove') {
>                 system ("iptables -D dynamic -s $ip -j reject");
>                 system ("logger -t blackhole.pl[$$] removed $ip from
> blackhole list"); }
>
> I finished it up late last night, and wasn't quite sure if it was
> working.  I'd attempted to fail SSH logins a few times from a remote
> shell account I have, but it didn't seem to work (maybe I just didn't
> type fast enough ;-)  However, when I woke up today, I was happy to see
> that this setup had added and then removed blackhole rules for two
> sites.


I have finished up a script about a month ago to do this very thing. 
Coincidentally I went from syslog-ng (yuck) to sysklog/klog. I do not want my 
logger program to be doing anything but writing to my log files and syslog-ng 
is way overly complex for even just writing to log files.

Now my perl script runs in cron every 5 minutes and checks the auth.log file 
for X failed login attempts from a single IP. If the IP procudes more than X 
failed login attempts a rule is added to iptables to block this host from 
talking to me on port 22. After Y seconds, the host is removed. I have Y set 
to 1 day for myself. It stores the blocked IPs in a local hash database so in 
case someone flushes iptables outside of the script it can repopulate 
iptables automatically on its next run. For every block and removal action it 
sends an email out to me.

It runs very well but it is still a work in progress and unfortunately I was 
not able to get the iptables perl module to install on my amd64 so I have 
resorted to calling iptables thru perl's "system" function.

My biggest gripe about my script is the log parsing code, it is currently not 
very flexible yet - it is still a work in progress.