Web Application Firewall Setup for Apache Server

Including additional configuration for intrusion prevention and DDoS protection

We’re now at a good stage to beef up our cybersecurity posture for our website. In my previous two posts, we installed WordPress, locked down administrative access (hopefully you set strong passwords), obfuscated the server and OS versions, enforced HTTP strict transport security, forced only later versions of SSL/TLS and HTTP, and enabled some modules to help prevent cross-site scripting and click-jacking.

Remember, cybersecurity is not a silver bullet: given enough time and resources, even the best defenses can be defeated. What we are doing, however, is making ourselves a hard target, especially for drive-by (opportunistic) attacks. These types of attacks tend to be less sophisticated and are usually probes that take advantage of known vulnerabilities – they’re the equivalent of checking car handles in a parking lot for unlocked doors. And who’s responsible for lost or stolen items? You are.

So we’re going to deploy three packages: mod_evasive (DDoS protection), Fail2ban (an intrusion prevention package that enables us to ban IP addresses), and ModSecurity (a web-application firewall).

Installing mod_evasive and Fail2ban are straightforward, so we’ll start there first. To install mod_evasive:

sudo apt install libapache2-mod-evasive -y

Next we’ll configure the thresholds for DoS protection. This configuration is one that I use, and it’s pretty restrictive, so you may want to adjust the DOSPageCount, DOSSiteCount, DOSPageInterval, and DOSSiteInterval. There are also other options you can enable, like system command execution, but for now we’ll just focus on thresholds:

sudo cat << EOF | sudo tee -a /etc/apache2/mods-available/evasive.conf
<IfModule mod_evasive20.c>

    # DOSHashTableSize: The size of the hash table used to store client IP addresses.
    DOSHashTableSize 3097

    # DOSPageCount: The threshold for the number of requests for a specific page or script within the interval period before it is considered malicious.
    DOSPageCount 1

    # DOSSiteCount: The threshold for the total number of requests for all pages/scripts within the interval period before it is considered malicious.
    DOSSiteCount 10

    # DOSPageInterval: The interval (in seconds) in which the page count is checked for the specified page/script.
    DOSPageInterval 1

    # DOSSiteInterval: The interval (in seconds) in which the site count is checked for all pages/scripts.
    DOSSiteInterval 1

    # DOSBlockingPeriod: The time (in seconds) that a client will be blocked if they exceed the thresholds.
    DOSBlockingPeriod 60

    # DOSEmailNotify: An email address to which a notification will be sent when an IP is blocked. Set to Off to disable notifications.
    #DOSEmailNotify admin@example.com

    # DOSLogDir: The directory where mod_evasive will log suspicious requests and actions.
    DOSLogDir "/var/log/apache2/mod_evasive"

    # DOSSystemCommand: A command to execute when an IP is blocked. For example, you can use iptables to block the IP.
    #DOSSystemCommand "/sbin/iptables -I INPUT -p tcp --dport 80 -s %s -j DROP"

    # DOSPageWhitelist: A list of whitelisted pages/scripts that will not be subjected to the DOSPageCount and DOSPageInterval checks.
    # DOSPageWhitelist "/allowed-page1" "/allowed-page2"

    # DOSSiteWhitelist: A list of whitelisted IP addresses that will not be subjected to the DOSSiteCount and DOSSiteInterval checks.
    #DOSSiteWhitelist OtherIPToWhitelist, etc.

    # DOSBlockingMethod: The method to use for blocking clients. The value can be "system", "dbm", or "fpn".
    #DOSBlockingMethod system

If you missed it, don’t forget to whitelist your IP address in the DOSPageWhitelist directive. Now we’ll install Fail2ban and configure the regex rules for recognizing malicious requests and banning those IP addresses.

sudo apt install fail2ban -y

Once installed, we need to create a jail and configure rules – essentially, the jail tells Fail2ban where to locate the rules and then how to proceed; the rules are configured separately. Let’s call our rules (formally: filters) apache-malicious and bad-requests (aside: you can configure as many rules to suit your needs. I often see consecutive bad requests from IPs trying to fuzz my server. Since it’s a simple blog, there shouldn’t be any reason to have this many bad requests, so I just ban those IPs).

Open up a local jail:

sudo nano /etc/fail2ban/jail.local

And enter the following configuration:

enabled = true
port = http,https
filter = apache-malicious
logpath = /var/log/apache2/*error.log
maxretry = 1
bantime = -1
findtime = 3600
action = iptables-allports[name=apache-malicious, protocol=all]

enabled = true
port = http,https
filter = bad-requests
logpath = /var/log/apache2/*access.log
maxretry = 1
bantime = -1
findtime = 3600
action = iptables-allports[name=apache-malicious, protocol=all]

Notice the filter and logpath variables. The filter is the name of the file, or rules we’ll set up, and the logpath is the log that Fail2ban will monitor for those rules. So for apache-malicious, Fail2ban will apply the apache-malicious filter rules to the error.log file. Alternatively, for bad-requests, I’ve configured Fail2ban to monitor the access.log (since bad requests don’t usually make it to the error.log). A bantime of -1 is permanent, but you can adjust to your liking. Let’s create the filter for apache-malicious.

sudo nano /etc/fail2ban/filter.d/apache-malicious.conf

And add the regex rules:

failregex = \[evasive20:error\] \[pid \d+\] \[client <HOST>:\d+\] client denied by server configuration:
            \[security2:error\] \[pid \d+\] \[client <HOST>:\d+\] \[client .*?\] ModSecurity: Access denied
            \[access_compat:error\] \[pid \d+\] \[client <HOST>:\d+\] AH01797: client denied by server configuration

#Whitelist ip addresses or queries that match regex expressions below
#ignoreregex = ip_address, query match, etc.

The above regex rules will look for evasive20:error, security2:error, and access_compat:error(s) in our error log files and ban the IP addresses associated with those errors. We haven’t set up ModSecurity yet, but that’s okay. In the ignoreregex = directive, you can list your public IP address to whitelist it.

Similarly, let’s open up a filter for bad-requests:

sudo nano /etc/fail2ban/filter.d/bad-requests.conf

And add the following:

failregex = <HOST> .* 400
            <HOST> -.*"[A-Z]+ .*" 400
#Whitelist ip addresses or queries that match regex expressions below
#ignoreregex = ip_address, query match, etc.

This filter simply blocks the response code 400 for “bad request” in our access logs.

Finally, we’ll install and configure our web application firewall ModSecurity using the OWASP core ruleset. CAVEAT: ModSecurity is currently being maintained but is no longer being updated, with EOL support ending effective July 1, 2024, where it will be returned to the open-source community. The version for Apache is v2, but doesn’t support the most current ruleset, so for that we are going to use an unofficial repository for ModSecurity provided by Ervin Hegedüs over at DigitalWave.

First install the official ModSec package.

sudo apt install ibapache2-mod-security2 -y

Then we have to set up a remote repository.

sudo apt-get -y install apt-transport-https lsb-release ca-certificates curl

Pull and add the repo key.

sudo wget -qO - https://modsecurity.digitalwave.hu/archive.key | sudo apt-key add -

Add the releases to our sources.list file.

sudo sh -c 'echo "deb http://modsecurity.digitalwave.hu/ubuntu/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/dwmodsec.list'
sudo sh -c 'echo "deb http://modsecurity.digitalwave.hu/ubuntu/ $(lsb_release -sc)-backports main" >> /etc/apt/sources.list.d/dwmodsec.list'

Set a policy.

# Set up policy
cat << EOF | sudo tee -a /etc/apt/preferences.d/99modsecurity
Package: *libapache2-mod-security2*
Pin: origin modsecurity.digitalwave.hu
Pin-Priority: 900

Package: *modsecurity-crs*
Pin: origin modsecurity.digitalwave.hu
Pin-Priority: 900

Package: *libmodsecurity*
Pin: origin modsecurity.digitalwave.hu
Pin-Priority: 900

And then update our package repo to include the new releases.

sudo apt update && sudo apt upgrade -y

Awesome! We’re nearly there! Let’s create a configuration file by copying the recommended config:

sudo cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf

Open the file with nano and set the security rule engine directive to “on.”

SecRuleEngine On

Now we can download the OWASP coreruleset and configure ModSecurity to use it:

curl -O -L https://github.com/coreruleset/coreruleset/archive/refs/tags/v3.3.5.zip

If you don’t have the “unzip” command, you can install it with the apt package manager; otherwise, unzip the coreruleset and move the rules folder to /etc/modsecurity folder:

unzip v3.3.5

sudo mv coreruleset-3.3.5/rules/ /etc/modsecurity/

Let’s edit the ModSecurity configuration in the Apache directory to include the crs-setup.conf and ruleset we just downloaded.

sudo nano /etc/apache2/mods-available/security2.conf

And add the following inclusions:

IncludeOptional /etc/modsecurity/crs/*.conf
IncludeOptional /etc/modsecurity/rules/*.conf

In the /etc/modsecurity folder, create a whitelist configuration and add a rule for your IP address.

sudo nano /etc/modsecurity/whitelist.conf

Add the rule:

SecRule REMOTE_ADDR "@ipMatch public_ip_you_use_to_access_site" "id:92300,phase:1,allow,nolog"

Now enable the security module:

sudo a2enmod security2

Open your /etc/apache2/sites-available/wordpress-le-ssl.conf and add

SecRuleEngine On

Restart apache.

sudo systemctl restart apache2

Finally, start and enable Fail2ban

sudo systemctl enable fail2ban && sudo systemctl start fail2ban

And we’re done! Please remember this is a basic setup and configuration, but you should be able to adjust the settings and do more research into how to tweak functionality to what suits your needs (for example, something I’ve not covered in this blog post is I’ve also set up ModSecurity DoS/DDoS protection as an extra layer). Good luck!

I’m really excited about the next post, where we’ll be installing a VM with the ELK stack on it, so we can ingest and visualize logs from all our VMs, including the server logs from our website.