Finding big fish in your Labrea Tarpit

bigfish -- Identify Big Fish in Your Tarpit Automatically



Shortly after I got my first Linux machine working, I got my first tarpit working. It was satisfying to see the traffic light blinking on the NIC card but I had no idea who was stuck in the tarpit or how long they had been there. I also wanted information on what port number the captives were using to get stuck.

I quickly started playing with Perl and found Michael Robinton's Labrea::Tarpit module. Not only did it show me exactly who was there but it showed me exactly how long they had been there and what (they thought) they were doing.

After a few months of watching the pit work, I noticed that certain viruses, worms and attacks produced large quantities of persistent sockets from the same source. Occasionally I would LART these high-volume attackers or their ISPs. Sometimes these attackers would immediately disappear from the tarpit. Other times I would actually receive a reply from the user or ISP. Of course sometimes I would hear nothing and the attacker would stay in the pit.

I thought it would be a good idea if I could somehow automate the LARTing process. Simply finding the high-volume attackers and doing the lookup took more time than it was worth. If I could have something to simplify the process, I could chip away at the millions of infected/trojaned Windows machines out there. I would hope that there are still some people out there who are willing to fix their problems.

Mostly, it was an exercise, just to see if I could do it.

What does it do?

  1. In order to make sure we are working with fresh data, we make a cache dump of the pit. This is stored as /var/tmp/bigfish.cache
  2. Michael's code produces (among many other things) a nice CGI summary page of the attacking IP addresses and the total number of persistent sockets they have in the tarpit. We run this report and then look at the results.
  3. The code below parses the report and finds any IP addresses that have more than $isbigfish sockets stuck in the pit. Currently I have this set to 200 sockets. After it finds the first one, it proceeds on to the next step.
  4. It does a whois lookup on the offending IP address and attempts to find abuse and admin mail addresses.
  5. We prepend this address list to a short LART paragraph. (see options)
  6. It prints out all the text to STDOUT for logging and debug purposes.
  7. A simple list style database is kept in /var/tmp/fishfile.cache which allows us to see if we have LARTed this IP address before. We now check this database. If the address of the bigfish is not in the database, we add it.
  8. If the address was already in the database, we are done with this fish. We cut him lose and return to checking the results of the report in step "C" above.
  9. If the address was not in the database we send the LART to us so we can analyze it and forward it on if necessary. (see options)
  10. We continue checking the report (step "C") for other big fish.

Options

Currently the LART message is sent to a single address: You. Why do that? Well, auto-LARTing is almost as bad as virus auto-responders.

I prefer to know what is going on with my tarpit. I find the best way to do that is to have the LARTs sent to me first. Then, if I choose to, I can forward them on with a simple cut-and-paste from the message sent by bigfish.

However, if you would like to auto-LART, simply have it send the LART paragraph to the list of abuse addresses instead of yourself. Delete this line so that the abuse addresses aren't added to the LART E-mail:

######## 

                    $mails .= "$lline\; ";
########

Additionally, you'll need to modify the To: line of the Email being sent. Change this from your address to the list of abuse addresses. This list is stored in $lartaddr.

####### Change
##      To        =>$lartaddr,
##      instead of the current:
##      To        =>'admin@mydomain.org',
#######


How do I use it?

Its probably not as handy and complete as it could be, but I find it works fine if I do the following:

  1. Set up a cron job (or add a shell file to /etc/cron.hourly) to run the bigfish program hourly. I have mine run at 10 after the hour.
  2. 	[root@stinky root]# crontab -l
    	# DO NOT EDIT THIS FILE - edit the master and reinstall.
    	# (/tmp/crontab.22 installed on Tue Jan 19 10:47:47 2001)
    	# (Cron version -- $Id: crontab.c,v 3.10 1999/01/17 07:22:02 vixie Exp $)
    
    	10 * * * * /home/bubba/bigfish.pl > /dev/null 2>&1
            
  3. Set up another cron job (or add a shell file to /etc/cron.daily) to clear out the cache file. This will limit LARTing to once a day for a given address. You can modify this time period as required. Or, eliminate this step altogether and you will never receive a LART for the same address twice.
  4. 	[root@stinky cron.daily]# cat bigfish-clear
    	#!/bin/sh
    
    	/bin/rm -f /var/tmp/fishfile.cache > /dev/null 2>&1
    	/bin/touch /var/tmp/fishfile.cache > /dev/null 2>&1
    	[root@stinky cron.daily]#
            


The Code [Click to download text version or copy/paste from below]


#!/usr/bin/perl
#
# bigfish.pl
# A Program to look for big fish in your LaBrea Tarpit and send you notification
# Adapted from LaBrea::Tarpit::Report which is
# Copyright 2002, 2003, Michael Robinton & BizSystems 
# This has been modified under the terms of GNU GPL license on 25SEP04
# No rights reserved under this modification
# See additional documentation for this program at:
# http://www.pettingers.org/code/bigfish.html
#
# This program is useless unless you are running
# Michael Robinton's LaBrea::Tarpit program
# (available from CPAN)
#
#
my $version = '1.0';    #

#
use strict;
use LaBrea::Tarpit qw(
        prep_report
        restore_tarpit
);
use LaBrea::Tarpit::Report qw(
        guests_by_IP
);
use LaBrea::Tarpit::Util qw(
        daemon2_cache
        page_is_current
);

use MIME::Lite;

#########################################################
######## CONFIRM THE CONFIGURATION BELOW  ###############
#########################################################


use lib qw( ./ );

# SET for your system
#
my $config = {
  'd_host'      => 'localhost',         # defaults to 'localhost'
  'cache'       => '/var/tmp/labrea.cache',
  'scanners'    => 100,                 # keep this many dead threads
  'port_intvls' => 30,                  # keep #nintvls of port stats
  };

# SET THESE for your system

my $look_n_feel = {     # defaults shown
    'face'      => 'VERDANA,ARIAL,HELVETICA,SANS-SERIF',
    'color'     => '#ffffcc',
    'bakgnd'    => '#000000',
  # below are all for port_intervals
    'images'    => '/icons/',           # REQUIRED, path to images
    'threshold' => 10,  # ignore below this count
    'html_cache_file'   => '/var/tmp/bigfish.cache',    # required
    'html_expire'       => 60,                          # cache expiration, secs
  # optional other_sites stats cache location
};

  my (@thsip,@thnum,@orgdata);

####### Set the threshold for LARTing here. Minimum number of sustained sockets before LART is generated.

  my $isbigfish = 200;

#######

  my ($fish, $lart, $lline, $mails, $msg, $lartaddr) = '';

####### Set keys to look for in whois data. The items below generally do fairly well but could be fine-tuned.

  my $abuseinfo = "e-mail|abuseemail|OrgAbuseEmail|OrgTechEmail|TechEmail|abuse\@";

#######
####### This is the file that will store a list of fish so you don't get repeated alerts on the same one.
##      You can clear this out daily or weekly with a cron job.

  my $fishfile = "/var/tmp/fishfile.cache";

#######
#
##   END OF USER CONFIGURATION  ##############################################
#
#######

############ -- Generate a fresh cache of the tarpit before we go fishing -- ############

my ($image_cache,$use_cache,$error,$rpt,$sht,$html,$report,$short,$out,%tarpit);


my $page = 'attackers';

my ($mtime,$upd);
# first thing, check the cache age
  ($mtime,$upd) = daemon2_cache(
        $look_n_feel->{html_cache_file}.'.mem',
        $config,
        $look_n_feel->{html_expire},
 );

  $error = $@;
  if ($error) {         # was there a timeout error
    $page = 'error';    # falls through elsif's
# note that $upd & $use_cache will be false
  }
#  else {
    restore_tarpit(\%tarpit,$look_n_feel->{html_cache_file}.'.mem')
        unless ($use_cache = page_is_current($mtime,                     # always set $use_cache
        $look_n_feel->{html_cache_file}.'.'.$page)) || $page eq 'other'; # but skip restore if tarpit not needed
#  }


############## -- START of fishing -- ################

  $out = {
# threads per tarpitted host
        'guests_by_IP'  => undef,
        'th_srcIP'      => \@thsip,
        'th_numTH'      => \@thnum,     # number threads this IP
  };
  prep_report(\%tarpit,$out);
  guests_by_IP($out,$look_n_feel,$out);
  $rpt = \$out->{guests_by_IP};

####### Dig through generated report looking for BigFish

for ($fish=0; $fish < (scalar(@thsip)); $fish++) {
        if ($thnum[$fish] >= $isbigfish){
####### Got one!  Do an abuse desk lookup

                $lart = getwhois($thsip[$fish]);
                @orgdata = split(/:/, $lart);
                $lart = '';
                $mails = "\n\n\n";
                $lline = shift(@orgdata);
                while ($lline) {
                  if ($lline =~ m/$abuseinfo/i){
                    $lline = shift(@orgdata)
                        unless ($lline =~ m/abuse\@/i);
                    $lline = substr($lline, 0, (index($lline, "\n")));
                    $lline =~ s/\s//g;
#                   print "abuse info is: $lline\n";

####### Delete the following line if you are going to LART directly. What this does is embed the abuse
##      E-mail addresses into the body of the message which will be sent to you.  You can then cut-and-paste
##      these addresses into the TO: line of a LART from another account if you wish.  If you want to LART
##      directly, delete this and look below for other comments about what to change in the MIME::Lite 
##      generated message.
######## 

                    $mails .= "$lline\; ";
########
		            $lartaddr .= "$lline\; ";
                  }
                $lline = shift(@orgdata);
                }
#               print "Big Fish! Address = $thsip[$fish] with $thnum[$fish] sockets at position $fish \n";
                $mails = substr($mails, 0 , -2);

####### This is the actual text of the message that will be generated. Could be coded a bit more elegantly....

                $mails .= "\n\n\n\n\nYour host with IP address $thsip[$fish] is attacking our site.\n\n
This attack may simply be the result of an infected Microsoft Windows host attempting to propagate a virus/worm
or may be a directed, intentional attack.  There are currently $thnum[$fish] malicious sockets connected to our server.
Please disconnect this host from the network IMMEDIATELY.  
Your netblock has been BLACKLISTED until this issue is resolved.\n
If you would like to speak to our technical support department regarding this matter, 
please call\: 1-123-555-1234 x-1234\n\n
Please note: This message is sent from a filtered account.  However, it is monitored for replies.
Thank you for your prompt action in resolving this issue.";


####### Note that the message below will print out to STDOUT even if an E-mail is not sent.  
##      That is, the message will print for debugging purposes even though the host
##      may be in the fishfile.cache (i.e. already LARTed) 
#######

        print "\n++++++++++++++++++++++++\n Message is--\n\n $mails \n+++++++++++++++++++++++++\n";


####### Check the list of previous fish to see if we have already sent a LART for them.

        $lart = '';
        open (PTR, $fishfile) || die ("Could not open file. \n $!");
        $lline = <PTR>;
        while ($lline){
          if ($lline =~ m/$thsip[$fish]/) {
            $lart = 1;
            }
          $lline = <PTR>;
        }
        close (PTR);

####### Unless we already sent something, send a message out using MIME::Lite
##      NOTE: As it stands now, it will send this message to you as (presumably) system administrator.
##      Obviously you could have it send it to whomever you like, but if you want to have
##      it sent directly to the offending ISP/organization abuse desk, you'll need to make
##      the following modification:
##      To        =>$lartaddr,
##      instead of the current:
##      To        =>'admin@mydomain.org',
#######

        unless ($lart) {
          #  Format the message to send with MIME::Lite package
          $msg = MIME::Lite->new(
          From    =>'Security@mydomain.org',
          To      =>'admin@mydomain.org',
          Bcc     =>'abuse@mydomain.org',
          Subject =>"Malicious Activity From Your Network -- $thsip[$fish]",
          Data    =>$mails
          );
          #   Send it best way possible (usually sendmail)
          $msg->send;

####### They've been LARTed. Put them in the fish file

          open (PTR, ">>$fishfile")|| die ("Could not open file. \n $!");
          print PTR "$thsip[$fish]\n";
          close (PTR);
        }



        $mails = '';
        }    # end if > big fish

}  #  end for
##########################################################################################
sub getwhois
{


BEGIN {
  use vars qw($old_d_q $lastresp);
  use Net::Whois::IP qw(whoisip_query);
  $old_d_q = \&Net::Whois::IP::_do_query;
}

sub Net::Whois::IP::_do_query {
  my @rv = &$old_d_q(@_);
  $lastresp = $rv[0];
  return @rv;
}

#########################################################

$ENV{SCRIPT_NAME} =~ /whois\.([a-zA-Z_-]+)/;
my $action = 'whois.'. $1;
my $IP = "@_";

#my $IP = ($ENV{QUERY_STRING} =~ /query=(\d+\.\d+\.\d+\.\d+)/)
#       ? $1 : '';

my $html = '';
if ($IP) {
    $lastresp = '';
    eval {whoisip_query($IP)};

    if ($lastresp && ! $@) {
      $html .= join('',@$lastresp);
    } else {
     $html .= 'could not connect to whois server, try again later';
    }
}
# print "your info--- $html\n---\n";

return $html;

} # end of subroutine


exit;

##############


Prerequisites




Copyright 2004 Pettingers.org

Vectors at

pettingers.org