A Denial of Service (DoS) attack is a cyber-attack in which someone tries to make a website or a service unavailable for its intended users/customers by flooding a server with a huge number of requests. It’s comparable to a Distributed Denial of Service (DDoS) attack, but that last one is run from many different sources. This makes the attacks much more difficult to counter.
While I was working on an AWS infrastructure for a client, I remembered that we had some issues in the past because of multiple crawlers ran in parallel by different SEO people who wanted to check the website. The configuration that was used was quite aggressive. Even our hosting provider at that time thought it was an attack…
To prevent the story from repeating itself, I did some research about DoS mitigation with Apache. And I eventually found Jonathan Zdziarski’s work on mod_evasive. It was exactly what I needed: an Apache module to add with only several lines of configuration. But then, I realized that the module was not compatible with Apache 2.4. That’s why I took the decision to fork his work to make it compatible and improve a few things.
How it works
A web hit request comes in. The following steps take place:
- The IP address of the requestor is looked up on the temporary blacklist.
- The IP address of the requestor and the URI are both hashed into a “key”. A lookup is performed in the listener’s internal hash table to determine if the same host has requested this page more than once within the past 1 second.
- The IP address of the requestor is hashed into a “key”. A lookup is performed in the listener’s internal hash table to determine if the same host has requested more than 50 objects within the past second (from the same child).
If any of the above are true, a 429 response is sent. This conserves bandwidth and system resources in the event of a DoS attack. Additionally, a system command and/or an email notification can also be triggered to block all the originating addresses of a DDoS attack.
Once a single 429 incident occurs, mod_evasive now blocks the entire IP address for a period of 10 seconds (configurable). If the host requests a page within this period, it is forced to wait even longer. Since this is triggered from requesting the same URL multiple times per second, this again does not affect legitimate users.
How to install
- Extract this archive
$APACHE_ROOT/bin/apxs -cia mod_evasive.c
- The module will be built and installed into
$APACHE_ROOT/modules, and loaded into your
- Restart Apache
How to configure
mod_evasive has default options configured, but you may also add the following block to your
<IfModule evasive_module> DOSHashTableSize 3097 DOSPageCount 2 DOSSiteCount 50 DOSPageInterval 1 DOSSiteInterval 1 DOSBlockingPeriod 10 </IfModule>
Optionally you can also add the following directives:
DOSEmailNotify firstname.lastname@example.org DOSSystemCommand "su - someuser -c '/sbin/... %s ...'" DOSLogDir "/var/lock/mod_evasive"
You will find all details related to the configuration in the readme.
How to test
You can verify your setup by running a test with ApacheBench: you should see a lot of non-2XX responses within your results as in the example below.
$ ab -kl -n 5000 -c 200 -H "Accept-Encoding: gzip, deflate" -bi https://www.XXXXX.XXX/ ... Benchmarking www.XXXXX.XXX (be patient) Completed 500 requests Completed 1000 requests Completed 1500 requests Completed 2000 requests Completed 2500 requests Completed 3000 requests Completed 3500 requests Completed 4000 requests Completed 4500 requests Completed 5000 requests Finished 5000 requests ... Concurrency Level: 200 Time taken for tests: 6.860 seconds Complete requests: 5000 Failed requests: 0 Non-2xx responses: 4671 Keep-Alive requests: 5000 Total transferred: 14302148 bytes HTML transferred: 12490903 bytes Requests per second: 728.89 [#/sec] (mean) Time per request: 274.391 [ms] (mean) Time per request: 1.372 [ms] (mean, across all concurrent requests) Transfer rate: 2036.06 [Kbytes/sec] received ...
To be honest, I’ve only added changes on an already awesome module. There are three major differences between my fork and the official repository:
- Add support for Apache 2.4
- Add the possibility to use XFF HTTP request header
- Replace HTTP_FORBIDDEN by HTTP_TOO_MANY_REQUESTS
If you are not interested in this, you can continue to use the legacy module without any issue.
Thanks for reading!