11d65 Script - Detect brute-force password guessing on a mail server - The FreeBSD Forums
The FreeBSD Forums  

Go Back   The FreeBSD Forums > Miscellaneous > Howtos & FAQs (Moderated)

Howtos & FAQs (Moderated) Would you like to share some of your solutions for certain problems? Tips or tricks? Post here. All new topics are automatically moderated.

Reply
 
Thread Tools Display Modes
  #1  
Old January 7th, 2010, 23:55
Ruler2112's Avatar
Ruler2112 Ruler2112 is offline
Member
 
Join Date: Sep 2009
Location: Michigan, USA
Posts: 408
Thanks: 11
Thanked 23 Times in 21 Posts
Smile Script - Detect brute-force password guessing on a mail server

I've had a few people from around the world try to break into my mail server using a brute-force approach, trying various username-password combinations, sometimes several thousand of them, slowing my system down, filling my mail log with crap, and generally just annoying me. I figure there must be a kiddie-script out there to do this that these people are running, as some attacks send the exact same usernames in the exact same order... I've tried to ignore them, but they have been annoying.

After the latest one yesterday originating somewhere in China, I decided to do something about it. I wrote a script to detect when this happens and generate a report, including it in the daily periodic cron job so that when I go through the system report each morning, I can blacklist any IPs at my firewall. I figured I'd share it here in case somebody else finds it useful.


/usr/local/sbin/updateotherblocks:
Code:
/usr/bin/vi /usr/local/etc/IPBlocks/Others
/sbin/pfctl -t other-blocked -Tr -f /usr/local/etc/IPBlocks/Others
In /usr/local/etc/IPBlocks/Others, I have a list of IP addresses, one per line, that I want blacklisted. In my pf rule table, I have the following included:

Code:
# Block IPs that have tried to hack me
table <other-blocked> persist file "/usr/local/etc/IPBlocks/Others"
block in log quick on $ext_if from <other-blocked> to any

This effectively blocks any IP in the /usr/local/etc/IPBlocks/Others file at the firewall. While strictly a reactive measure, it does easily detect them and make it plain when they occur, allowing me to block the originating IP in a matter of a few seconds and ensuring that they won't be able to do so again from the same IP.


Log analysis script in the next post...
Reply With Quote
The Following User Says Thank You to Ruler2112 For This Useful Post:
gkontos (January 8th, 2010)
  #2  
Old January 8th, 2010, 00:02
Ruler2112's Avatar
Ruler2112 Ruler2112 is offline
Member
 
Join Date: Sep 2009
Location: Michigan, USA
Posts: 408
Thanks: 11
Thanked 23 Times in 21 Posts
Default

/usr/local/sbin/detectmailhack.pl:
Code:
#!/usr/bin/perl

###############################################################################
#                                                                             #
# Perl script to analyze a mail log file for failed logins in order to detect #
# brute-force attempts to guess passwords.  Outputs a table with the IP of    #
# the failed login, username attempted, and a count of each.                  #
#                                                                             #
# Parameter 1:                                                                #
#   Pass in the full path and name of a log file to use.                      #
#   If none is provided, the default is /var/log/maillog                      #
#                                                                             #
###############################################################################
#                                                                             #
# Ruler's Common-Sense License:                                               #
#                                                                             #
#   You may use this script however you want to, but I don't warrant it to    #
#   be good for anything in particular, though it happens to work well for    #
#   me.  (I hate putting BS like this in, but I hate more being sued.)  If    #
#   you use this script, you must keep this license and credit to me in it    #
#   in the form of this block, even if you modify it for your own use.  If    #
#   you want to send me money for it, fantastic!  Send me a private message   #
#   on the freebsd.org forums and I'll give you my PayPal address. :-)  Even  #
#   just a simple 'thank you' would be nice.  If not, that's fine too.  All   #
#   hate mail/spam is sent directly to /dev/null                              #
#                                                       - Jim, AKA Ruler2112  #
#                                                                             #
###############################################################################
#                                                                             #
# History:                                                                    #
#                                                                             #
#   2010-01-06 by Ruler2112       Wrote initial version.                      #
#   2010-01-07 by Ruler2112       Made it work better and support compressed  #
#                                 mail logs.                                  #
#   2010-01-07 by Ruler2112       Released on freebsd.org forums.             #
#                                                                             #
###############################################################################

use strict;

my $LogFile = shift;

###############################################################################
#                      Variable Declaration Section                           #
# Set these variables to customize this script's behavior.                    #
###############################################################################

# UserDelim
#   This specifies what the user in the mail log is immediately preceeded by.
#   I don't know if this differs for MTAs other than Postfix, but figured I'd
#   add it for ease of customizability.
my $UserDelim = "user=";

# IpDelim
#   What the IP address of the connecting client is immediately preceeded by
#   in the mail log.  Again, I'm not certain that this would ever be useful to
#   change, but it's easy enough to make customizable.
my $IpDelim = "ip=[";

# UserEnd
#   What designates the end of the user field in the mail log.  Some attempts
#   may have spaces in the name of the user, so a plain space probably isn't
#   a good choice.
my $UserEnd = ", ";

# IpEnd
#   What comes immediately after the IP address in the mail log.
my $IpEnd = "]";

# CompressedCat
#   This is a cat utility that reads compressed files.  Typically known as 
#   'zcat' under FreeBSD.  This should be the full absolute path to and name 
#   of the file.
my $CompressedCat = "/usr/bin/zcat";

# RegularCat
#   This is the normal cat utiliy and again should contain the full absolute
#   path to and name of the file.
my $RegularCat = "/bin/cat";

###############################################################################
# !!! WARNING !!! WARNING !!! WARNING !!! WARNING !!! WARNING !!! WARNING !!! #
###############################################################################
#                    This is the Beginning of the Script                      #
#                                                                             #
# Do not change anything below this line unless you know what you're doing!   #
###############################################################################

my ($CatUtil);
my (@FailedIps, @FailedCount, $NumberIps, $TotalCount);
my (@FailedUsers, @FailedUserCount, $NumberUsers);

if($LogFile eq "")
  {
  $LogFile = "/var/log/maillog";
  }
if((-e $LogFile) and (-r $LogFile))
  {
  SetCatUtility();
  AnalyzeIps();
  AnalyzeUsers();
  }
else
  {
  print "$LogFile either does not exist or cannot be read!  Aborting...\n";
  }
exit;

sub SetCatUtility()
  {
  if((substr($LogFile, length($LogFile) - 3, 3) eq "bz2") or
     (substr($LogFile, length($LogFile) - 2, 2) eq "gz"))
    {
    $CatUtil = $CompressedCat;
    }
  else
    {
    $CatUtil = $RegularCat;
    }
  return;
  }

sub AnalyzeIps()
  {
  my ($counter, $workstr);
  BuildIpTable();
  $workstr = $NumberIps + 1;
  print "$TotalCount failed mail login attempts from $workstr sources\n\n";
  return;
  }

sub BuildIpTable()
  {
  my (@workary, @sortedarray);
  @FailedIps = ();
  @FailedCount = ();
  $NumberIps = -1;
  $TotalCount = 0;
  open(INFILE, "$CatUtil $LogFile |");
  while(<INFILE>)
    {
    if(index($_, "LOGIN FAILED") > 0)
      {
      ProcessIpRecord($_);
      }
    }
  close(INFILE);
  push @workary, [$FailedIps[$_], $FailedCount[$_]] foreach (0..$NumberIps);
  @sortedarray = sort { $a->[1] <=> $b->[1] } @workary;
  @sortedarray = reverse @sortedarray;
  @FailedIps = map { $$_[0] } @sortedarray;
  @FailedCount = map { $$_[1] } @sortedarray;
  return;
  }

sub ProcessIpRecord()
  {
  my $logline = shift;
  my ($workstr, $cutpos, $endpos, $leftover);
  my ($attempteduser, $attemptedip);
  chomp($logline);
  $cutpos = index($logline, $UserDelim);
  $endpos = index($logline, $UserEnd, $cutpos + 1);
  $attempteduser = substr($logline, $cutpos + length($UserDelim), $endpos - $cutpos - length($UserDelim));
  $cutpos = index($logline, $IpDelim);
  $endpos = index($logline, $IpEnd, $cutpos + 1);
  $attemptedip = substr($logline, $cutpos + length($IpDelim), $endpos - $cutpos - length($IpDelim));
  $workstr = FindIp($attemptedip);
  @FailedCount[$workstr]++;
  $TotalCount++;
  return;
  }

sub FindIp()
  {
  my $address = shift;
  my ($counter, $retval);
  $retval = -1;
  $counter = 0;
  while($counter <= $NumberIps)
    {
    if($address eq @FailedIps[$counter])
      {
      $retval = $counter;
      $counter = $NumberIps;
      }
    $counter++;
    }
  if($retval < 0)
    {
    $NumberIps++;
    $retval = $NumberIps;
    @FailedIps[$retval] = $address;
    @FailedCount[$retval] = 0;
    }
  return $retval;
  }

sub AnalyzeUsers()
  {
  my ($counter, $counter2, $spacing, $workstr);
  for($counter = 0; $counter <= $NumberIps; $counter++)
    {
    BuildUserTable(@FailedIps[$counter]);
    $workstr = @FailedIps[$counter] . ":";
    $spacing = 40 - length($workstr) - length(@FailedCount[$counter]);
    $workstr = $workstr . (" " x $spacing);
    $workstr = $workstr . @FailedCount[$counter];
    print "$workstr\n";
    for($counter2 = 0; $counter2 <= $NumberUsers; $counter2++)
      {
      $workstr = (" " x 10) . @FailedUsers[$counter2];
      $spacing = 30 - length($workstr) - length(@FailedUserCount[$counter2]);
      $workstr = $workstr . (" " x $spacing) . @FailedUserCount[$counter2];
      print "$workstr\n";
      }
    }
  return;
  }

sub BuildUserTable()
  {
  my $address = shift;
  my (@workary, @sortedarray);
  $NumberUsers = -1;
  @FailedUsers = ();
  @FailedUserCount = ();
  open(INFILE, "$CatUtil $LogFile |");
  while(<INFILE>)
    {
    if((index($_, "LOGIN FAILED") > 0) and (index($_, "$address") > 0))
      {
      ProcessUserRecord($_);
      }
    }
  close(INFILE);
  push @workary, [$FailedUsers[$_], $FailedUserCount[$_]] foreach (0..$NumberUsers);
  @sortedarray = sort { $a->[0] cmp $b->[0] } @workary;
  @sortedarray = sort { $a->[1] <=> $b->[1] } @workary;
  @sortedarray = reverse @sortedarray;
  @FailedUsers = map { $$_[0] } @sortedarray;
  @FailedUserCount = map { $$_[1] } @sortedarray;
  return;
  }

sub ProcessUserRecord()
  {
  my $logline = shift;
  my ($workstr, $cutpos, $endpos, $leftover);
  my ($attempteduser, $attemptedip);
  chomp($logline);
  $cutpos = index($logline, $UserDelim);
  $endpos = index($logline, $UserEnd, $cutpos + 1);
  $attempteduser = substr($logline, $cutpos + length($UserDelim), $endpos - $cutpos - length($UserDelim));
  $cutpos = index($logline, $IpDelim);
  $endpos = index($logline, $IpEnd, $cutpos + 1);
  $attemptedip = substr($logline, $cutpos + length($IpDelim), $endpos - $cutpos - length($IpDelim));
  $workstr = FindUser($attempteduser);
  @FailedUserCount[$workstr]++;
  return;
  }

sub FindUser()
  {
  my $user = shift;
  my ($counter, $retval);
  $retval = -1;
  $counter = 0;
  while($counter <= $NumberUsers)
    {
    if($user eq @FailedUsers[$counter])
      {
      $retval = $counter;
      $counter = $NumberUsers;
      }
    $counter++;
    }
  if($retval < 0)
    {
    $NumberUsers++;
    $retval = $NumberUsers;
    @FailedUsers[$retval] = $user;
    @FailedUserCount[$retval] = 0;
    }
  return $retval;
  }
Reply With Quote
  #3  
Old January 8th, 2010, 01:07
danger@'s Avatar
danger@ danger@ is offline
FreeBSD Developer
 
Join Date: Oct 2007
Location: Bratislava, Slovakia
Posts: 774
Thanks: 4
Thanked 165 Times in 84 Posts
Default

You might want to take a look at security/bruteforceblocker.
__________________
Looking for administrator? http://www.syscare.sk
Reply With Quote
  #4  
Old January 8th, 2010, 01:35
Ruler2112's Avatar
Ruler2112 Ruler2112 is offline
Member
 
Join Date: Sep 2009
Location: Michigan, USA
Posts: 408
Thanks: 11
Thanked 23 Times in 21 Posts
Default

LOL - I saw your post and thought there was already something built to do this, which bummed me out. Then saw it only worked with SSH and was happy again. Doesn't look like bruteforceblocker works with mail, only SSH, which I don't have on the box. (I only administer the server from a PC in my office hard-wired to a dedicated NIC.) I also don't want to automatically block an IP based on the number of failed attempts (I thought of it early on, but then discarded the idea) for the simple reason that the vast majority of my co-workers are rather stupid and routinely forget their passwords, so it'd block everybody in the store from connecting to retrieve/send e-mail.
Reply With Quote
  #5  
Old January 8th, 2010, 02:32
danger@'s Avatar
danger@ danger@ is offline
FreeBSD Developer
 
Join Date: Oct 2007
Location: Bratislava, Slovakia
Posts: 774
Thanks: 4
Thanked 165 Times in 84 Posts
Default

bruteforceblocker works with anything, you only need to slightly modify its source (i.e. add the corresponding regexps to the appropriate place) and configure syslog correctly...
__________________
Looking for administrator? http://www.syscare.sk
Reply With Quote
  #6  
Old January 8th, 2010, 06:29
johnblue johnblue is offline
Member
 
Join Date: Jan 2009
Location: O-o-o-o-o-o-o-klahoma
Posts: 176
Thanks: 11
Thanked 17 Times in 15 Posts
Default

In another thread SirDice posted that he uses sshguard and it looks pretty slick.

http://www.sshguard.net

http://www.freebsd.org/cgi/ports.cgi...uard&stype=all
__________________
Advise on initial contact you have information Bravo Sierra Delta.
Reply With Quote
  #7  
Old January 8th, 2010, 19:07
Ruler2112's Avatar
Ruler2112 Ruler2112 is offline
Member
 
Join Date: Sep 2009
Location: Michigan, USA
Posts: 408
Thanks: 11
Thanked 23 Times in 21 Posts
Unhappy

Quote:
Originally Posted by danger@ View Post
bruteforceblocker works with anything, you only need to slightly modify its source (i.e. add the corresponding regexps to the appropriate place) and configure syslog correctly...
Damn... never mind this script then.

I'm still going to use it for what I wrote it for, namely to run from the periodic daily cron job to add the report to the daily system e-mail. It's done and does exactly what I want, plus I really don't want the IPs blocked automatically. It's here if anybody wants to use it.
Reply With Quote
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump

Similar Threads
Thread Thread Starter Forum Replies Last Post
Change password via bash script tmw Userland Programming & Scripting 6 December 29th, 2009 02:21
[Solved] Detect crashing application in a script octix Userland Programming & Scripting 2 December 18th, 2009 05:06
Which mail server, POP server, IMAP server are good for install on FreeBSD 7.2 Detective Web & Network Services 7 December 4th, 2009 10:05
Check connection with timeout, script telnet web server in script bsddaemon Web & Network Services 5 December 7th, 2008 14:03
ss.h.i.t alternatives || other brute force blockers? dave Installation and Maintenance of FreeBSD Ports or Packages 12 November 30th, 2008 00:28


All times are GMT +1. The time now is 07:26.


Powered by vBulletin® Version 3.8.7
Copyright ©2000 - 2013, vBulletin Solutions, Inc.
The mark FreeBSD is a registered trademark of The FreeBSD Foundation and is used by The FreeBSD Project with the permission of The FreeBSD Foundation.
Web protection and acceleration provided by CloudFlare
0