How to set up ModSecurity with nginx

ModSecurity

ModSecurity is a web application firewall with a long history, originally designed for Apache (the project was started before nginx was even around).

What's an application firewall? ModSecurity looks at every request that comes through nginx. If it meets certain parameters, (defined by the OWASP core rule set), the request is immediately denied with a 403 error.

ModSecurity 3, released a few years ago, has been adapting itself from an apache module to a server-independent library - libmodsecurity. I'm setting this up for an Ubuntu 18.04 server, but the steps will be similar for any Unix system. Ubuntu 20.04 provides a libmodsecurity3 package. So, I can take advantage of that when I'm on Ubuntu 20, but even then I'll still have to compile my own nginx module to connect this to nginx.

Here are the pieces you need to get ModSecurity working with nginx:

libmodsecurity

libmodsecurity itself. Compiled from https://github.com/SpiderLabs/ModSecurity/, synonymous with "ModSecurity".

ModSecurity-nginx

The nginx module. Compiled from https://github.com/SpiderLabs/ModSecurity-nginx/. In order to compile this, you need to download the nginx source code at the right version (in our case, 1.14.0). The configure flags passed during the configure step must also match those that our version of nginx was compiled with, by Ubuntu (You get these with nginx -V), or use the --with-compat flag.

From the nginx source directory:

./auto/configure --add-module=/path/to/ModSecurity-nginx --with-compat
                

OWASP coreruleset

Some of these rules require libmodsecurity to be compiled with certain library support. For example, the geolocation rules require a libmodsecurity with either GeoIP or MaxMind enabled. These are IP location databases. We're currently using GeoIP. I had an issue with MaxMind - the version that Ubuntu 18 provides is too old for ModSecurity.

Connecting everything together

All these pieces are connected with these configuration files:

# From https://github.com/SpiderLabs/ModSecurity/blob/master/
# modsecurity.conf-recommended
#
# Edit to set SecRuleEngine On
Include "/etc/nginx/modsecurity/modsecurity.conf"

Include "/etc/nginx/modsecurity/coreruleset/crs-setup.conf"
Include "/etc/nginx/modsecurity/coreruleset/rules/*.conf"

# Basic test rule
SecRule ARGS:testparam "@contains test" "id:1234,deny,status:403"
                

After restarting nginx, if everything's working as expected, you should see the following line in /var/log/nginx/error.log after nginx starts up:

2020/05/21 12:08:51 [notice] 6362#6362: ModSecurity-nginx v1.0.1 \
  (rules loaded inline/local/remote: 0/907/0)
                

Note that 907 local rules were loaded here, that's from the OWASP ruleset. Because of the test rule in main.conf, you can also test this by making a request to your server with the GET param ?testparam=test. That request should be denied with a 403 error.

Now, take a look in /var/log/modsec_audit.log and /var/log/nginx/error.log. There will be lots of interesting info in these files as requests are blocked by ModSecurity. You might need to tune your new firewall if you're seeing false positives — I'm still learning about that process, and I'll have another post documenting that.