Link to home
Start Free TrialLog in
Avatar of Michael Worsham
Michael WorshamFlag for United States of America

asked on

Brute-force script blocking conversion

I am in the process of locking down one of my Linux servers through dynamic iptables usage. Currently I have my sshd covered from brute-force attacking via a special block.pl script (link available below), but now I found that script kiddies are now attempting to get through via my ftp port (currently running ProFTPD).

Block.pl script (the one for sshd) can be found here: http://shellscripts.org/projects/s/sshblock/version_1.2/block.pl

Though I am using I am using ProFTPD and I know I could modify the proftpd.conf to give a more subtle log file via LogFormat, I would rather like to have a converted script (like the one I use for the sshd) checking the /var/log/messages file as well.

I have absolutely no Perl scripting/programming knowledge, thus the question is being asked here...

Need to convert the following string:

May 17 00:59:37 servername proftpd[32673]: server.domain.com (192.168.1.15[192.168.1.15]) - no such user 'blah'

The perl script already has a way to look at sshd attack/attempts in the following manner:

if (/sshd.*(Failed password for|Invalid user) (\w+) from (?:::ffff:)?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/) {


Anyone have a guess on what I need to have to read the /var/log/messages file then look for the "no such user" string given, then extract the IP address from the [ ] field and place it into a variable that can be added to the iptables rule in the rest of the script?

Thanks.

-- Michael
Avatar of mjcoyne
mjcoyne

Maybe something like:

if ((/sshd.*(Failed password for|Invalid user) (\w+) from (?:::ffff:)?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/) || (/(proftpd[\d+]).*([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)[([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)] - no such user/)) {

The original statement has three capturing parenthesis: Failed password for or Invalid user (captured in $1), the username (shown by \w+, and contained within $2), and the IP address (defined by [0-9]+\.[0-9]+\.[0-9]+\.[0-9]+ and captured by $3).

The new line will still look for that, but also react to ...proftpd[32673]: server.domain.com (192.168.1.15[192.168.1.15]) - no such user...  capturing "proftpd['series of numbers']" as $1, and the IP address twice, in $2 and $3.  The script relies on the IP address to be in $3 (see the line my $ip=$3;), so I figured that's the major requirement in the addition -- that the IP address to be blocked resides in $3.

Your log won't be perfect, but you'll be able to tell the entry was generated from proftpd, and the IP address that was blocked.
my @no_users = ();
open(LOG,'</var/logs/messages') or die "can't open var/logs/messages: $!";
while (my $line = <LOG>) {
   next unless index($line,'no such user') > -1;
   push (@no_users,$1) if ($line =~ /\(\d+\.\d+\.\d+\.\d+\[(\d+\.\d+\.\d+\.\d+)\]\)/);
}
close(LOG);
foreach my $lines (@no_users) {
   do something useful
}

Avatar of Michael Worsham

ASKER

mjcoyne: I tried your version first in place of the code already in the block.pl file. When I tried to execute it, I was given this message...

Unmatched ) in regex; marked by <-- HERE in m/(proftpd[\d+]).*([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)[([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+) <-- HERE ] - no such user/ at ./block.pl line 12.

Any ideas?

-- Michael
Perl_Diver: I see the idea behind the script, but the extraction of the ip address inbetween the '[ ]' in the log file is crucial as this is what will be added to the iptables rule. As I said originally, I have no Perl experience so I will be pretty much need to be told exactly what goes where.

The iptables layout: iptables -I INPUT -s <ipaddress> -j DROP

Thanks for the snippet, though. It does give me some insight on how Perl works.

-- Michael
/quote/
the extraction of the ip address in between the '[ ]' is crucial
/endquote/

thats what is being done here:

push (@no_users,$1) if ($line =~ /\(\d+\.\d+\.\d+\.\d+\[(\d+\.\d+\.\d+\.\d+)\]\)/);

but how that gets incorporated into your existing script is hard to say. Your existing script has two conditions

if (/sshd.*(Failed password for|Invalid user) (\w+) from (?:::ffff:)?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/) {

and:

elsif (/sshd.*Accepted (password|publickey) for (\w+) from (::ffff:)?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/) {

do those still have to be checked? Are you adding a third condition to the file parsing?:

if (index($_,'no such user') > -1 && /\(\d+\.\d+\.\d+\.\d+\[(\d+\.\d+\.\d+\.\d+)\]\)/)
Oops -- I was working without an editor...

It should be:

if ((/sshd.*(Failed password for|Invalid user) (\w+) from (?:::ffff:)?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/) || (/(proftpd\[\d+\]).*([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\[([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\] - no such user/)) {

There were the correct number of parenthesis, it's just that the square brackets (from, for example, [32673], or [192.168.1.15]) were throwing the regular expression off -- it interprets these brackets as defining a character class.  Escape them (by adding a slash in front of them, like \[) and it'll interpret them as literal characters...

Sorry...
Perl Diver: Modifying the block.pl would be plus to handle both sshd and proftpd for invalid users, but its not a necessity. The original sshd block.pl works just fine as is. I was just hoping to add in the proftpd check as script kiddies have found ftp exploits can do just as much damage as a sshd one and having one script always running in the background makes it a little easier on the system (and on me) for checking the logs, etc.

mjcoyne: Testing the new code now. Two Quick questions: is there a way to add in a sort of mock debug statement to see what $1, $2 and $3 are showing? If I were to copy the block.pl to say ftp-block.pl, would the proftpd part you have replace the sshd check or would something else need to be modified to allow it to run (i.e. less a '()', etc)?

-- M
Sure -- how about something like:

use Data::Dumper;
open (DEBUG, ">>debug.log") || die "Cannot open debug.log: $!\n";     # add this line for debug

...etc...

print STDOUT "block.pl: $1 $2 from $3 \n";
print DEBUG "\$1 contains: $1, \$2 contains: $2, \$3 contains: $3\n";     # add this line for debug

You can just comment them out when you want them inactive.

As to splitting the script in two, it would have to be re-worked a bit because it's actually an if-else loop, and if you change the "if" part to include only the FTP entries, you'll need to also change the "else" part, but it wouldn't be too hard.

I have my SSH server blocked in a similar manner, but I use sshblack (see http://www.pettingers.org/code/sshblack.html).  I run an SSH server exclusively, so I have my FTP port blocked at the router (and by IPtables, 'cause all my unbused ports are blocked here anyway).  Why do you need both an SSH and an FTP server?

Have you considered a door-knocking approach (see http://www.soloport.com/iptables.html)?  A pretty clever solution for a low traffic, somewhat exclusive server, but it'd be difficult to make workable with hundreds of users...
I run an online game hosting server for MUDs (text-based games, sort of like Zork but multiplayer based). I have a Linksys RV082 business-class firewall/router (not one of those basic NAT translators). Then I have a SuSe Linux server (running VMWare Server) in the background running the hosting environment under another SuSe Linux.

I have http, ssh/sftp, ftp, telnet and each of the game accounts ports open routed through the hardware firewall to the ports on the virtual SuSe image as it does virtual hosting for each of the game sites. The virtual image is running iptables along with portsentry (for port scan attacks) and the block.pl for sshd brute-force attacks. Just lately I noticed that the script kiddies are trying to brute-force the ftp port. I have considered just keeping the ssh/sftp and disabling basic ftp, but it would be nice just to foil the script kiddies instead.

-- M
Ah, Zork -- I first played it on a Commodore VIC-20...:).
I have the script w/ modifications in place. I am going to allow it to run overnight. So far, most of the ftp attacks have originated from China, Russia and other 3rd world countries. Did have one early this morning from Australia, but it didn't last very long (might have been my e-mail to the originating ISPs abuse site ;-) ).

As for my site, you're welcome to visit it: www.murpe.com (MultiUser RolePlay Entertainment). Primarly I am a solutions provider for SMBs in the Southeast Georgia region, but I do game development in addition to the game hosting side. In my spare time, I am working on designing a Java-based game engine that allows you to actively modify java files on the fly while inside the virtual game world and recompile them in a real-time fashion w/o restarting the game.

-- M
personally I think you should write an entirely seperate regexp to check for the proftpd lines, the regexp that mjcoyne wrote looks like it  will work but it's getting long and complicated and probably should have comments added to it. At least you would know what bits are doing what with the comments in case you ever needed to modify it again.
Update on the script:

I let the modified block.pl script w/ the lines added to it run for overnight:

if ((/sshd.*(Failed password for|Invalid user) (\w+) from (?:::ffff:)?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/) ||
(/(proftpd\[\d+\]).*([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\[([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\] - no such user/)) {
print STDOUT "block.pl: $1 $2 from $3 \n";
print DEBUG "\$1 contains: $1, \$2 contains: $2, \$3 contains: $3\n";     # add this line for debug

The debug.log file did see and reposnd corrected to a ssh attack, revealing the details below:

$1 contains: Invalid user, $2 contains: fluffy, $3 contains: 218.57.13.166
$1 contains: Invalid user, $2 contains: admin, $3 contains: 218.57.13.166
$1 contains: Invalid user, $2 contains: carol, $3 contains: 62.245.114.254

As of a few minutes ago, the server got attacked again, but its seems the proftpd part in the script isn't picking up the offender's IP address as the debug.log didn't record it -- so I had to manually enter it in.

The /var/log/messages shows the following:

May 19 01:58:13 games proftpd[4153]: games.murpe.com (61.134.40.163[61.134.40.163]) - FTP session opened.
May 19 01:58:13 games proftpd[4153]: games.murpe.com (61.134.40.163[61.134.40.163]) - no such user 'Administrator'
May 19 01:58:13 games proftpd[4153]: games.murpe.com (61.134.40.163[61.134.40.163]) - USER Administrator: no such user found from 61.134.40.163 [61.134.40.163] to 192.168.1.16:21
May 19 01:58:15 games proftpd[4153]: games.murpe.com (61.134.40.163[61.134.40.163]) - Maximum login attempts (3) exceeded

Any more ideas?

-- M
SOLUTION
Avatar of Perl_Diver
Perl_Diver

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
ASKER CERTIFIED SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Modifications worked perfectly to the block.pl code. Added to the system and already had 10 sshd and 15 proftpd brute force attempts -- IP address added successfully to the iptables rules and dropped from connection. Can't ask for anything better. :-)

Thanks all for the help.

-- Michael