autoblocking many ssh failed logins from the same IP....

Hornet hornetmadness at gmail.com
Fri Jul 1 14:10:13 GMT 2005


Below (and atached) is a script I wrote do exactly what you are talking about.
It's commented, so edit to your taste. I have been using to for about 4 months.

Since I am using PF as my firewall, it is customized for that. If you
are using something other then PF, again... edit to your taste.

-Erik-

#!/usr/bin/perl
# created by hornetmadness at gmail.com 03/05

my $time=localtime();

use strict;
use Time::localtime;
use Mail::Send;

my $hostname="domain.orIP.com";

#The white list that contains either the account or host.
my $whilelist="/home/user/scripts/sshwhitelist";

#LOG to search on
my $logfile="/var/log/auth.log";

#Where to read the current list of blackhole address.
my $blacklist="/etc/pf.blackholes";

#Name of the table in your pf.conf
my $tablename="blackhole";

#Where to store the cache file. This is removed and updated daily
my $cache="/root/.sshprotect.cache";

#Where to log actions taken.
my $log="/root/sshprotect.log";

#Command you want to run in response of a potential attack.
my $command="whois";
my $useip=1;  #useful in conjunction with $command which will do
something with the IP.
            #comment out if not needed.

#Max attempts a host can have until blocked.
my $attempts=5;

# Set this to "run" the $command or "print" a report or "email" the report,
# also "update" will update the $blacklist and reload the blackholes table.
# "abuse" will try to find and email the offending network about the attack
# These can be combind to run all actions:
#my $action="run print";
#my $action="print";
my $action="email run update abuse";
#my $action="print email update";
#my $action="print email";

#Email setup;
my $to="to\@email.com";
my $from="from\@email.com";
my $cc="";
my $subject="Excesssive login attempts";


my $debug=0;

my $host;
my @logs;
my @whtlst;
my %track;
my @blacklist;
my $block=1;
my @abuse;
my @cache;
my $currentcache;
my @runoutput;

my $version="1.2.1beta";
print "Version: $version\n" if $debug;

#find todays datemask
use vars qw($yr $mon $day $today $mday);
$yr=localtime->year() + 1900;
$mon=localtime->mon() + 1;
$mday=localtime->mday();
if ($mon != /\d\d/) {$mon="0$mon";}
if ($mday < 10) {$mday="0$mday";}
$today="$yr$mon$mday";
print "$today\n" if $debug;
#no Time::localtime;

open (WRITELOG, ">> $log") || die "$log $!\n";

open (BLACK,$blacklist) || die "$blacklist $!\n";
while (<BLACK>) {
  chomp;
  push (@blacklist, $_);
}
close BLACK;

open (WHITE, $whilelist) || die "$whilelist $!\n";
while (<WHITE>) {
  chomp;
  push (@whtlst,$_);
}
close WHITE;

open (READCACHE, $cache) || print "$cache $!\n";
while (<READCACHE>) {
  chomp;
  push (@cache, $_);
}
close READCACHE;

open (WRITECACHE, ">> $cache") || print "$cache $!\n";

if (@cache[0] < $today) {
  close WRITECACHE;
  system ("rm -f $cache");
  open (WRITECACHE, ">> $cache") || print "$cache $!\n";
  print "Cache file is out of date @cache[0] < $today\n" if $debug;
  @cache=();
  print WRITECACHE "$today\n"
}

open (LOG, $logfile) || die "logfile $!";
while (<LOG>) {
  chomp;

  if (
    /Failed password for illegal user (.*) from (.*) port/
    || /Failed password for (.*) from (.*) port/
    || /Illegal user (.*) from (.*)/
    || /Did not (receive) identification string from (.*)/
     ) {

    my $account=$1;
    my $host=$2;

    ckwhtlst($account, $host);
    if ($block == 0 ) { next; }
    ckcache($host);
    if ($block == 0 ) { next; }
    ckblklst($host);
    if ($block == 0 ) { next; }
    $block=1;
    if ($track{$host}) {
      $track{$host}=$track{$host}+1;
      print "$host is now $track{$host} user=$account\n" if $debug;
    } else { $track{$host}=1; }
  }
}
close LOG;

for my $host (%track) {
  if (!$host) {print "Nothing Found\n"; exit;}
  if ($track{$host} >= $attempts) {
    push (@abuse,$host);
    ckcache($host);
    print WRITECACHE "$host\n" if !$block == 0;
    if ($action =~ /print/) { print "Host $host, past $attempts
attempted logins\n"; }
    if ($action =~ /run/ && $useip) { (@runoutput=`$command $host`); }
    if ($action =~ /run/ && !$useip) { (@runoutput=`$command`); }
    if ($action =~ /update/) { update($host); }
  }
}


#Sends emails
if ($action !~/email/) {
  exit;
} elsif (@abuse) {
  send_email(@abuse);
}

if ($action !~/abuse/) {
  exit;
} elsif (@abuse) {
  abuse_email(@abuse);
}

sub ckwhtlst {
  (my $account, my $host)=@_;
  foreach (@whtlst) {
    if (!/$account|$host/) {
      $block=1;
      return;
    } else {
      print "$host or $account is on the while list.\n" if $debug;
      $block=0;
      return;
    }
  }
}

sub ckblklst {
  my $host=@_[0];
  foreach (@blacklist) {
    if (/$host/) {
     print "$host $_ is already blacklisted\n" if $debug;
      $block=0;
      return;
    } else { $block=1; } #print "$host is NOT blacklisted\n" if $debug; }
  }
}
sub ckcache {
  my $host=@_[0];
  if (!@cache) { $block=1; return;}
  foreach (@cache) {
    if (/$host/) {
      $block=0;
      print "$host is already cached\n" if $debug;
      return;
    } else { $block=1; } #print "$host is not found in cache\n" if $debug; }
  }
}

sub update {
  open (OUT, ">> $blacklist") || die "$blacklist $!\n";
  print OUT "@_[0]\n";
  system ("pfctl -t $tablename -f /etc/pf.conf");
  close OUT;
}

sub send_email {
  my $subject="$subject $today";
  my $msg = new Mail::Send;
  my $host;
  $msg->subject($subject);
  $msg->to($to);
  $msg->cc($cc) if $cc;
  $msg->add("From", "$from");
  $msg->add("Return-Path", "$from");
  $msg->add("Reply-To", "$from");

  my $fh=$msg->open;

  foreach (@_) {
    $host=$_;
    if (!$track{$_}) {return;}
    print $fh "\nThe host $_ has $track{$_} attempted logins.\n";
  }
  print $fh "\nThe threshold is set to $attempts attempts\n";
  print $fh "\nActions taken: $action\n\n";
  if (($action =~/run/ && $command) && !$useip) { print $fh "Ran:
$command\n\n Output:\n at runoutput"; }
  if (($action =~/run/ && $command) && $useip) { print $fh "Ran:
$command $host,\n\n Output:\n at runoutput"; }

  print $fh "\nVersion: $version\n";
  print "Sending email\n" if $debug;
  print WRITELOG "$time Sent email to $to about $_ \"$subject\"\n";
  $fh->close;
}

sub abuse_email {
  $subject="[ABUSE] $subject $today";
  my $msg;
  my $fh;
  my $acct;
  my $domain;

  foreach my $ip (@_) {
    my $syntax="$ip SOA";
    if (!$track{$ip}) {return;}
    if (~/\d{1,3\.\d{1,3}\.\d{1,3}\.\d{1,3}/) { $syntax="-x $ip SOA"; }

    print "Running dig $syntax\n" if $debug;
    my @dig=`/usr/bin/dig $syntax`;

    foreach my $dig (@dig) {
      if ($dig =~ /in-addr\.arpa\..*SOA\t.*\. (.*)\./) {
        my $addy=$1;
        if (!$addy) { next; }
        if ($addy =~ /(\w+)\.(.+)/) {
          $acct=$1;
          $domain=$2;
        }
        $to="$acct\@$domain";

        print "Found $to from Dig\n" if $debug;

        $msg = new Mail::Send;
        $msg->subject($subject);
        $msg->to($to);
        $msg->cc($cc) if $cc;
        $msg->add("From", "$from");
        $msg->add("Return-Path", "$from");
        $msg->add("Reply-To", "$from");
        $fh=$msg->open;
        print $fh "\nThis host $ip has attempted $track{$ip} erroneous
ssh logins to my server $hostname on $today.\n";
        print $fh "Please report this to your customer and advise them
on any AUP violations you enforce.\n";
        print $fh "\n\nThank you\nErik\n";

        print $fh "\nVersion: $version\n" if $debug;
        print "Sending abuse email to $to\n" if $debug;
        print WRITELOG "$time ABUSE:Sent email to $to about $ip\n";
        $fh->close;

      }

    }

  }

}

close WRITECACHE;
close LOG;

use Data::Dumper;
print Dumper %track if $debug;


On 7/1/05, John Cholewa <freebsd-questions at jc-news.com> wrote:
> Jun 30 10:36:05 phantom sshd[70478]: Failed password for news from 212.88.182.121 port 51218 ssh2
> Jun 30 10:36:16 phantom sshd[70500]: Failed password for sshd from 212.88.182.121 port 51608 ssh2
> Jun 30 10:36:39 phantom sshd[70569]: Failed password for root from 212.88.182.121 port 52297 ssh2
> 
> I get the above a lot in my logs (except more of it).  Each day, a couple hundred failed attempts to log in from one or sometimes two IP addresses shows up.  I don't have anything like ipf running, and since this machine is about fifteen hundred miles away from me, I don't want to experiment with software firewalling right now.
> 
> That known, is there any way to tell sshd (or some more powerful daemon) to stop accepting login attempts from a given IP if it tries and fails to log in too many times in a limited duration (like in the same minute)?
> 
> I suppose, now that I'm thinking about it, that it'd be best to actually just read the man pages and figure out how to get sshd to ignore any attempt to attach from ports other than 22.  I mean, why are other machines trying to ssh in at ports over fifty thousand anyway?
> 
> --
>   -JC
>   http://www.livejournal.com/users/jcholewa/
> 
> PS:  Oh, yeah ... "FreeBSD 4.8-RELEASE #0: Thu Apr  3 10:53:38 GMT 2003" ; openssh-3.6.1_5 ; openssl-0.9.7d_1
> 
> 
> 
> _______________________________________________
> freebsd-questions at freebsd.org mailing list
> http://lists.freebsd.org/mailman/listinfo/freebsd-questions
> To unsubscribe, send any mail to "freebsd-questions-unsubscribe at freebsd.org"
>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: sshprotect.pl
Type: application/octet-stream
Size: 7341 bytes
Desc: not available
Url : http://lists.freebsd.org/pipermail/freebsd-questions/attachments/20050701/8b509cf5/sshprotect.obj


More information about the freebsd-questions mailing list