Using ModPerl to Keep Zencart and phpBB2 Hackers Away

I’ve seen a bunch of attempts to hack at my zencart admin area and an old phpBB2 install that is no longer there. Both of these don’t work, and just return a 404 error, but it’s kind of annoying to keep spending compute cycles on this sort of behavior. Besides, now that I have installed the statsd and graphite packages, I am set up to provide a graph of whatever I want. So why not track the hackers, lock them out, and provide a graph of activity at the same time? So here goes.

2016-12-28 edit: see my github repo at https://github.com/dminear/apache_hack_check

I also run mod_perl in the Apache2 server, so I wanted to hook in to the PerlAccessHandler and do the work. The following is the Apache2 config lines:

        PerlModule ModPerl::DanHandler
        <Location />
                #SetHandler perl-script
                PerlAccessHandler ModPerl::DanHandler
        </Location>

And the following is the Perl module that is in the /usr/local/lib/site_perl/ModPerl directory on my Ubuntu machine:

package ModPerl::DanHandler;
use strict;
use warnings;
use FileHandle;
use IO::Socket::INET;

use Apache2::Log;
use Apache2::RequestRec ();
use Apache2::Connection ();

use Apache2::Const -compile => qw(FORBIDDEN OK :log);

BEGIN {
mkdir "/tmp/bad_ips";
chmod 0777, "/tmp/bad_ips"
}

my $sock = IO::Socket::INET->new(PeerPort => 8125,
				PeerAddr => '127.0.0.1',
				Proto => 'udp');

sub handler {
	my $r = shift;

	my $str = $r->connection->remote_ip();

	# if there is an attempt to access "zencart/admin", then put the ip on the block list
	if ($r->unparsed_uri() =~ /zencart\/admin$/ ||
		$r->unparsed_uri() =~ /zencart\/+admin\/+/ ||
		$r->unparsed_uri() =~ /phpbb2/i ) {
		#$r->log_error("BAD IP: $str");
		$sock->send( "hacker.unparsed_uri." . $r->unparsed_uri() . ":1|c\n" ) if defined $sock;
		my $fh = FileHandle->new( "> /tmp/bad_ips/$str");
		if (defined $fh) {
			print $fh "dummy";
			$fh->close;
		}
	}

	# check the block list
	if (-e "/tmp/bad_ips/$str") {
		$sock->send( "request.blocked:1|c\n" ) if defined $sock;
		return Apache2::Const::FORBIDDEN;
	} else {
		$sock->send( "request.allowed:1|c\n" ) if defined $sock;
		$sock->send( "request.hostname." . $r->hostname() . ":1|c\n" ) if defined $sock;
		return Apache2::Const::OK;
	}
}
1;

So now when someone comes in and tries to access /zencart/admin (or some gyrations thereof), the IP address gets stored in the tmp directory as a filename. On every request, the remote IP address is checked, and if found returns a 403 Forbidden response. The nice thing is that this happens for any request thereafter.  Because it’s early in the request stage, there’s not too much overhead. Plus I get the satisfaction of watching the banned IP addresses grow.

Then there’s some logic to update the statsd server based on good or bad requests. Here’s a screen capture of it in action (click on the image to enlarge):

Monitoring Graph