Stage 2: http:BL with Apache2 mod_perl
Note: This article was originally published at Planet PHP on 2 December 2010.Referrer and Comment spammers are a PITA I came up with two mod_perl plugins to Apache and an "apache level" firewall.
The reason for the apache-level firewall is two-fold.A There is no direct way for the Apache user to manipulate an iptables chain (as it doesn't run as root), and second; I was not happy with suid root access or other forms of message passing to a daemon which would manipulate the firewall for me.
Architecture is thus, in httpd.conf place the following two lines:
PerlPreConnectionHandler PGREGG::httpBLBlockThe first tells apache to run the handler in my httpBLBlock.pm module when a connection is received (before the request has been sent by the client).A In this handler, I am simply looking for a filename matching that IP in a directory that is writable by the apache user.A The contents of the file are a SCORE:httpBL_answer:[LIST].A Based on this, the module checks the mtime of the filename is in the last SCORE days, then the firewall is in effect. If so, we simply tell apache to drop the connection.A If the file has expired, we delete the file.
The second line is more interesting, and what creates the firewall filenames. In order to not impede the general speed of request handling, processing is performed in the Logging section of the Apache process. Our module is called by apache after the response has been sent, but before the access_log entry has been written.A In our module we perform the http:BL API call and compute the above SCORE based upon the Threat* level and Age* of the API response. (* both Threat and Age are octets in the DNS lookup).A We merely discount the Threat down to zero based on the Age (0-255) where an entry 255 days old reduces the SCORE to zero.
If the SCORE is larger than our trigger level (3) then we create the firewall filename, log the entry in our own httpbl.log and return Apache2::Const::FORBIDDEN.A This causes Apache to not log the entry in the normal access_log.A Otherwise, if all is ok, we return Apache2::Const::OK and Apache logs the hit as normal.
I have a bit of code tidy up, restructure the config/firewall directory and pull some common code out to a shared module before I can release to the world.
An interesting side effect to publishing the last story out through Planet PHP and other news sources along with the Project Honey Pot image is that when browsers viewed those sources, they all asked for the image off my server. In several cases, these were known spammer, Comment spammer, and other abusers. My server then created the firewall entry blocking them before they were able to follow the links back to my server.
I have been reading up more on Apache Bucket Brigades in an attempt to allow the firewall filter to be placed immediately after the request has been received and allow a custom response to the browser. This may help an otherwise unsuspecting user if their machine had been trojaned. I don't mind admitting I'm thoroughly confused right now :)