Patch to mod_evasive to enhance reporting

This morning I took the opportunity to install mod_evasive on my Apache Web Server after being hammered by zombies last night. Quote from [www.nuclearelephant.com]:

mod_evasive is an evasive maneuvers module for Apache to provide evasive action in the event of an HTTP DoS or DDoS attack or brute force attack. It is also designed to be a detection and network management tool, and can be easily configured to talk to ipchains, firewalls, routers, and etcetera. mod_evasive presently reports abuses via email and syslog facilities.

It appears to work well, I tested it out by loading it up with small scale DoS attacks. It blocked the offending addresses as expected and produced the relevant syslog entires & triggered my external reporting script. I was however a little disappointed with its script execution functionality, it basically did a "system" call allowing you to pass only one argument - the offending IP address.

I already have mod_security installed which also executes an external reporting script. However mod_security has a neat little feature which I took for granted, it passes all the 'environment' variables from the request to the script allowing you to see the request itself & any headers passed.

For example, a typical mod_security email alert for me would contain:

DOCUMENT_ROOT=/usr/local/apache/vhosts/www.domain.com
GATEWAY_INTERFACE=CGI/1.1
HTTP_ACCEPT=*/*
HTTP_ACCEPT_ENCODING=gzip, x-gzip
HTTP_CONNECTION=close
HTTP_HOST=www.domain.com
HTTP_MOD_SECURITY_ACTION=500
HTTP_MOD_SECURITY_EXECUTED=/usr/local/scripts/modsec_alert.pl
HTTP_MOD_SECURITY_MESSAGE=Access denied with code 500. Error normalizing REQUEST_URI: Invalid URL encoding detected: not enough characters
HTTP_USER_AGENT=Mozilla/4.0
PATH=/bin:/sbin...
PATH_INFO=/search.cgi
PATH_TRANSLATED=/usr/local/scripts/modsec_alert.pl
QUERY_STRING=q='object+levels%
REDIRECT_STATUS=302
REMOTE_ADDR=XXX.XXX.XXX.XXX
REMOTE_PORT=45852
REQUEST_METHOD=GET
REQUEST_URI=/cgi-bin/search.cgi?q='object+levels%
SCRIPT_FILENAME=/usr/local/apache/vhosts/www.domain.com/cgi-bin
SCRIPT_NAME=/cgi-bin
SERVER_ADDR=XXX.XXX.XXX.XXX
SERVER_ADMIN=foo@bar
SERVER_NAME=www.domain.com
SERVER_PORT=80
SERVER_PROTOCOL=HTTP/1.1
SERVER_SIGNATURE=
SERVER_SOFTWARE=Apache

This shows me detailed information about the request which was declined and why. I wanted to get similar functionality out of mod_evasive and I achieved this with the following additional code (butchered from mod_security).

C++:
  1. if (sys_command != NULL) {
  2.   char **env = NULL;
  3.   const char *args[5];
  4.  
  5.   ap_add_cgi_vars(r);
  6.   ap_add_common_vars(r);
  7.  
  8.   env = (char **)ap_create_environment(r->pool, r->subprocess_env);
  9.  
  10.   ap_cleanup_for_exec();
  11.  
  12.   args[0] = filename;
  13.   args[1] = text_add;
  14.   args[2] = NULL;
  15.   execve(sys_command, (char ** const)&args, env);
  16. }

The original mod_evasive code is expecting a sprintf format string as the parameter 'sys_command' allowing you to define a position with '%s' where the IP address should be inserted. My code above does not to this, it expects 'sys_command' to be the path to the executable which takes a single argument of the IP address.

This change can be applied automagically - to the Apache 1.3.x version of mod_evasive.c only - with the following patch: mod_evasive_execve.patch

Assuming mod_evasive_1.10.1.tar.gz & mod_evasive_execve.patch have already been downloaded to the same directory:

[foo@bar ~]$ tar zxf mod_evasive_1.10.1.tar.gz
[foo@bar ~]$ cd mod_evasive
[foo@bar mod_evasive]$ patch -p1 < ../mod_evasive_execve.patch
patching file mod_evasive.c
[foo@bar mod_evasive]$ $APACHE_ROOT/bin/apxs -iac mod_evasive.c
gcc -DLINUX=22 -DEAPI -I/usr/include/gdbm -DUSE_HSREGEX -fpic -DEAPI -DSHARED_MODULE -I/usr/local/apache/include -c mod_evasive.c
gcc -shared -o mod_evasive.so mod_evasive.o
[activating module `evasive' in /usr/local/apache/conf/httpd.conf]
cp mod_evasive.so /usr/local/apache/libexec/mod_evasive.so
chmod 755 /usr/local/apache/libexec/mod_evasive.so
cp /usr/local/apache/conf/httpd.conf /usr/local/apache/conf/httpd.conf.bak
cp /usr/local/apache/conf/httpd.conf.new /usr/local/apache/conf/httpd.conf
rm /usr/local/apache/conf/httpd.conf.new
[foo@bar mod_evasive]$

Now create a simple shell/perl/something script to use this info. My example emails myself and the address listed as the SERVER_ADMIN, because each VirtualHost on my server has a 'ServerAdmin' entry with the owners email address, my customers get a copy of the email too.

PERL:
  1. #!/usr/bin/perl
  2. # /usr/local/scripts/mod_evasive_alert.pl
  3. $IP=$ARGV[0];
  4. $MSG="mod_evasive has blacklisted the IP $IP.\n\n";
  5.  
  6. foreach $key ( sort keys %ENV ) {
  7.    $MSG .= $key . "=" . $ENV{$key} . "\n";
  8. }
  9.  
  10. open(SENDMAIL, "|/usr/sbin/sendmail -t") or die "Cannot open sendmail: $!";
  11. print SENDMAIL "Reply-To: foo\@bar\n";
  12. print SENDMAIL "Subject: [lobstertechnology.com] mod_evasive alert $IP\n";
  13. print SENDMAIL "To: " . $ENV{'SERVER_ADMIN'} . "\n";
  14. print SENDMAIL "Cc: foo\@bar\n";
  15. print SENDMAIL "Content-type: text/plain\n\n";
  16. print SENDMAIL $MSG;
  17. close(SENDMAIL);

Now configure mod_evasive to execute your script when it is triggered, add the following to your $APACHE_ROOT/conf/httpd.conf:

CODE:
  1. <ifmodule mod_evasive.c>
  2.     DOSSystemCommand    "/usr/local/scripts/mod_evasive_alert.pl"
  3. </ifmodule>

Now restart Apache:

[foo@bar mod_evasive]$ $APACHE_ROOT/bin/apachectl restart
/usr/local/apache/bin/apachectl restart: httpd restarted

Tada! You're done. Use the 'test.pl' script provided by mod_evasive to trigger a blocking of your IP and see the email generated.

2 Comments so far
Leave a comment

I get a compile error with the ap_create_environment line (I also get an error when I try to post the whole message!!) cast to pointer from integer of different size.

Is it possible to get HTTP variable(or Env variable) of mod_evasive if DOS occurs? I am using Mod_perl to grab DOSs record in specific log.



Leave a comment
Line and paragraph breaks automatic, e-mail address never displayed, HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

(required)

(required, but not displayed publically)