?
Solved

Brute-force script blocking conversion

Posted on 2006-05-17
16
Medium Priority
?
342 Views
Last Modified: 2012-06-21
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
0
Comment
Question by:Michael Worsham
  • 7
  • 5
  • 4
16 Comments
 
LVL 17

Expert Comment

by:mjcoyne
ID: 16704434
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.
0
 
LVL 8

Expert Comment

by:Perl_Diver
ID: 16705131
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
}

0
 
LVL 29

Author Comment

by:Michael Worsham
ID: 16705304
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
0
Technology Partners: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
LVL 29

Author Comment

by:Michael Worsham
ID: 16705337
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
0
 
LVL 8

Expert Comment

by:Perl_Diver
ID: 16705454
/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+)\]\)/)
0
 
LVL 17

Expert Comment

by:mjcoyne
ID: 16705552
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...
0
 
LVL 29

Author Comment

by:Michael Worsham
ID: 16705772
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
0
 
LVL 17

Expert Comment

by:mjcoyne
ID: 16705849
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...
0
 
LVL 29

Author Comment

by:Michael Worsham
ID: 16705874
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
0
 
LVL 17

Expert Comment

by:mjcoyne
ID: 16705891
Ah, Zork -- I first played it on a Commodore VIC-20...:).
0
 
LVL 29

Author Comment

by:Michael Worsham
ID: 16705935
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
0
 
LVL 8

Expert Comment

by:Perl_Diver
ID: 16706109
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.
0
 
LVL 29

Author Comment

by:Michael Worsham
ID: 16715435
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
0
 
LVL 8

Assisted Solution

by:Perl_Diver
Perl_Diver earned 80 total points
ID: 16715919
looks like there is a problem in the final regexp mycoyne posted that you used, the actual parenthesis around the IP addresses are missing in the regexp:

-->(192.168.1.15[192.168.1.15])<--

change this:

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/)) {

to:

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/ ) {

I put parenthesis around (.*?) just so you will still have three capturing groups and the IP will be in $3.
0
 
LVL 17

Accepted Solution

by:
mjcoyne earned 620 total points
ID: 16717162
There will always be text between proftpd\[\d+\] and the beginning of the IP address -- why make it optional?  Also, It seemed more valuable to me to capture both IP addresses -- I don't know they'll always be the same.

But, Perl_Diver's other diagnosis is correct -- it's the parenthesis around the IP addesses that screwed things up; in particular the last one (because the line doesn't end with ...nn.nnn.nn.nnn] - no such user... but rather ends with ...nn.nnn.nn.nnn]) - no such user... so the final parenthesis is a must.  The opening parenthesis in the line is covered by the .* (which means any number of any characters), but there is a subtle flaw here -- as written, the regex will return:

$1 contains: proftpd[32673], $2 contains: 2.168.1.15, $3 contains: 192.168.1.15

when run against "May 17 00:59:37 servername proftpd[32673]: server.domain.com (192.168.1.15[192.168.1.15]) - no such user 'blah'".  Notice that $2 is incorrect -- it took the least number of digits that matched "any number of any characters followed by any number of digits followed by a period", considering the '1' and '9' of $2 to be part of the .* part, rather than part of the [0-9]+\. part.  The fix is to use the opening parenthesis as a delimiter, so it then becomes "any number of any characters followed by a parenthesis followed by any number of digits followed by a period...".  Thus, for two different reasons, we want to explicitly require the parenthesis:

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/)) {

Sorry -- I've been writing these from a machine without Perl installed, and thus had no opportunity to test them...  I'm back at my machine now, so I can be a bit more accurate...

BTW, now that I see a bit more of what your log looks like, you might want to use some of the other info in the proftpd line to further identify it, and make a better log entry.  If you want to do this, change the regex line to:

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 \'(.*)\'/)) {

and change the print DEBUG line to:

    if ($4) {
        my $time = localtime;
        print  DEBUG "$1: Unknown user $4 from $2\[$3\] detected on $time\n";     # add this line for debug
    }

This way, we're taking advantage of $4 (which only exists in the proftpd matches) to limit the entries written to the debug log to only those involving proftpd, rather than sshd and proftpd.  Additionally, since we now have a $4, we might as well use it -- I made it capture the attempted user name.  I also added a date stamp (which will be the time the script writes to the log, not necessarily the time the user attcked, but it'll be close, I imagine) to add a bit more info to the log.

Given "May 19 01:58:13 games proftpd[4153]: games.murpe.com (61.134.40.163[61.134.40.163]) - no such user 'Administrator'" as an example, it would enter "proftpd[4153]: Unknown user Administrator from 61.134.40.163[61.134.40.163] detected at Fri May 19 07:55:40 2006" in the log.
0
 
LVL 29

Author Comment

by:Michael Worsham
ID: 16730725
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
0

Featured Post

Vote for the Most Valuable Expert

It’s time to recognize experts that go above and beyond with helpful solutions and engagement on site. Choose from the top experts in the Hall of Fame or on the right rail of your favorite topic page. Look for the blue “Nominate” button on their profile to vote.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

I have been pestered over the years to produce and distribute regular data extracts, and often the request have explicitly requested the data be emailed as an Excel attachement; specifically Excel, as it appears: CSV files confuse (no Red or Green h…
Checking the Alert Log in AWS RDS Oracle can be a pain through their user interface.  I made a script to download the Alert Log, look for errors, and email me the trace files.  In this article I'll describe what I did and share my script.
Explain concepts important to validation of email addresses with regular expressions. Applies to most languages/tools that uses regular expressions. Consider email address RFCs: Look at HTML5 form input element (with type=email) regex pattern: T…
Six Sigma Control Plans
Suggested Courses

807 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question