Click here to Skip to main content
Click here to Skip to main content

Use Perl to Summarize the Secure Log File on Linux

, 22 Dec 2011 Ms-PL
Rate this:
Please Sign up or sign in to vote.
Quickly summarize the secure log file on Linux systems to easily see who is trying to ssh to the server
Article.gif

Introduction

Any server admins out there know that one of the most important things about their job is making sure that their servers are kept secure. Linux is a popular server OS that's scalable, extendable, and widely used for hosting as well as other uses. Almost every Linux box uses something known as ssh for remote administration. ssh stands for Secure shell, which allows for completely secure communications between a client and the server to administer the server from the command line.
Note: You can also do something called Port Forwarding (which I won't cover in this article, maybe a later one...) which allows you to forward unsecure protocols (such as FTP, or my favorite: Remote VNC) through the ssh "tunnel" effectively turning any unsecure protocol into a secure one.

Anyway, ssh almost always uses port 22. Some people change this to a non-standard port so that the "bad guys" might not think they have ssh running on their servers. However, most leave it to the default. It's a VERY common thing for people to write scripts that will look for servers with port 22 open, then start trying to guess usernames and passwords to try to break into the server. Every failed and successful log in attempt is logged in a file under "/var/log/secure". This is a relatively simple Perl script that parses this log file (which can get VERY long) and summarizes the information in it and displays it to you.

Background

I initially wrote this for a Perl programming course I took last semester. However, I do a lot of server administration in my work-study job, and I thought it would be a good idea to take a look at the failed log in attempts on my servers. If you think I'm joking about how serious of a problem this is, here is the very end of the output of my script which I just ran:

SUCCESS - klschaefer attempted 10 times
Results:
          (Failed due to bad password: 714)
               Failed due to bad user: 8560
                                     +______________
      Number of Failed Login Attempts: 9274

  Number of Successful Login Attempts: 10

As you can see, I logged in to the server 10 times (my username is "klschaefer"), and 8560 failed log in attempts were made. Now, it's true that having even a decent password is probably enough to not have to worry about any of these attempts. But if you're a "password", "i_love_cheese", "1234", or "(uses same password as username)" kind of person, you might want to rethink what you use as a password.

Another important thing to note: every Linux server has an administrator "super user" account called "root". That is the only guaranteed user account on every Linux system. So, it's safe to say that about half of the attacks are made trying to guess the password for root. The other half are made for random user accounts. The reason they don't all target root is that, in general, a server administrator is going to have a fairly good password, whereas a standard user might not. Take for example one of the servers in our department at school. All of the students who take a certain few programming courses get accounts on this server where they can do whatever development they need. I know for a fact that a couple of those people haven't changed from their default password (a 7 digit student id), and several others use common dictionary words. So, it wouldn't be too difficult for somebody to possibly break into one of these students' accounts on the server.

Using the Code

One note before we really dive into things, if you don't know much about Regular Expressions, you might be a little confused in this article. I would suggest reading up on them, or looking at a quick reference guide when you see one. Regular Expressions in Perl (and Javascript, among others.) denote Regular Expressions between forward slashes: "/". So, this is a regular expression: "/[Ii]nvalid\suser\s(\w+)\sfrom\s(\d+\.\d+\.\d+\.\d+)/". Don't be afraid, but they were the hardest thing for me to learn about Perl, but once you learn them, the following code really isn't that complicated.

There are three basic files: "/var/log/secure", "secure.pl", and then a "secure_out.log" file (which is generated by the script). This isn't a tutorial on Perl, so I'm assuming you can figure out how the code works, but I'll explain some of it. First, we'll open up our files and define the variables we'll need to keep track of everything:

open LOG, "</var/log/secure";
open OUT, ">secure_out.log";
my $failedCount = 0;            # number of bad logins from any user
my $successCount = 0;           # number of successful logins from any user
my $badUserCount = 0;           # number of bad users
my $badPasswordCount = 0;       # number of bad passwords, excludes bad users

my %ips;
my %users;
my %successful;
my %failed;

The first group of variables are simple counters. The second group (notice the "%") are hashes that we use to associate certain keys with corresponding values (ie. 123.456.789.012 has 7 failed log in attempts, or "klschaefer" logged in successfully 4 times, etc.). Next, we open up the log file, reading in 1 line per iteration. The following code is INSIDE the loop. First, we ignore comment lines (anything beginning with a pound sign ("#"), and start running checks to see what each line means:

  #first, ignore comment lines
  next if ($line =~ /\s*#/);

  if($line =~ /[Ii]nvalid\suser\s(\w+)\sfrom\s(\d+\.\d+\.\d+\.\d+)/)
  {
    print OUT "Invalid user: $1 from IP: $2\n";
    if(exists $ips{$2})
    {
      $ips{$2}++;
    }
    else
    {
      $ips{$2} = 1;
    }
    if(exists $users{$1})
    {
      $users{$1}++;
    }
    else
    {
      $users{$1} = 1;
    }
    if(exists $failed{$1})
    {
      $failed{$1}++;
    }
    else
    {
      $failed{$1} = 1;
    }
    $failedCount++;
    $badUserCount++;
  }

This above code segment checks to see if the line represents an invalid user (e.g., somebody who doesn't exist on the server at all). Since we're using hashes, we have to first check to see if a key already exists, and if it does, then we can increment it, or else we create a new item and set it equal to 1.

Next, here are the rest of the checks:

  elsif($line =~ /[Ff]ailed\spassword\sfor\s(\w+)\sfrom\s(\d+\.\d+\.\d+\.\d+)/)
  {
    print OUT "Bad password for user: $1 from IP: $2\n";
    if(exists $ips{$2})
    {
      $ips{$2}++;
    }
    else
    {
      $ips{$2} = 1;
    }
    if(exists $users{$1})
    {
      $users{$1}++;
    }
    else
    {
      $users{$1} = 1;
    }
    if(exists $failed{$1})
    {
      $failed{$1}++;
    }
    else
    {
      $failed{$1} = 1;
    }
    $failedCount++;
    $badPasswordCount++;
  }
  elsif($line =~ /[Aa]ccepted\spassword\sfor\s(\w+)\sfrom\s(\d*\.\d*\.\d*\.\d*)/
    || $line =~ /[Aa]ccepted\spublickey\sfor\s(\w+)\sfrom\s(\d*\.\d*\.\d*\.\d*)/)
  {
    print OUT "Successful Login for User: $1 from IP: $2\n";
    if(exists $ips{$2})
    {
      $ips{$2}++;
    }
    else
    {
      $ips{$2} = 1;
    }
    if(exists $users{$1})
    {
      $users{$1}++;
    }
    else
    {
      $users{$1} = 1;
    }
    if(exists $successful{$1})
    {
      $successful{$1}++;
    }
    else
    {
      $successful{$1} = 1;
    }
    $successCount++;
  }

So, we just check each line of the log file like this, increment the appropriate counters, and now we need to display the results.

while(($ip, $count) = each %ips)
{
  print "LOGIN - $ip attempted $count times\n";
}
while(($user, $count) = each %users)
{
  print "LOGIN - $user attempted $count times\n";
}
while(($user, $count) = each %failed)
{
  print "FAILED - $user attempted $count times\n";
}
while(($user, $count) = each %successful)
{
  print "SUCCESS - $user attempted $count times\n";
}

print <<END_OF_MESSAGE
Results:
          (Failed due to bad password: $badPasswordCount)
               Failed due to bad user: $badUserCount
                                     +______________
      Number of Failed Login Attempts: $failedCount

  Number of Successful Login Attempts: $successCount

END_OF_MESSAGE

Additional Notes

You'll need to be logged in as root in order to be able to read the /var/log/secure file.

It's always a good idea to restrict logging in for root to the local machine. In other words, you can disallow everybody from being able to log in as root remotely. You can still gain root access by first logging in on your personal account, then "su"ing into root mode.

If you want to see a more detailed list of failed and successful log ins, just open up the "secure_out.log" file

And finally, the script may generate too much output for you to see all of it, so you might need to pipe it to "less" (eg: "./secure.pl | less").

History

  • 12/20/2011: Initial draft

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)

Share

About the Author

Kevin Schaefer
Student
United States United States
No Biography provided

Comments and Discussions

 
QuestionRegular Expressions PinmemberKevin Schaefer22-Dec-11 21:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.1411023.1 | Last Updated 22 Dec 2011
Article Copyright 2011 by Kevin Schaefer
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid