Compiling Nginx with ModSecurity on Ubuntu 14.04 LTS

We wanted to test ModSecurity with the OWASP ModSecurity ruleset on an Nginx reverse proxy in front of a node.js server. This means compiling both Nginx and ModSecurity from source which is somewhat challenging until you have tried it a couple of times, and since we had to document the process for our own use anyway we decided to share it here. Use at your own risk.

Using ModSecurity with Nginx requires

  1. compiling ModSecurity from source as a standalone module, and then
  2. compiling Nginx from source with the ModSecurity module

Our starting point is a freshly provisioned DigitalOcean droplet built with the Ubuntu 14.04 LTS image. (We use Linode for production, but DigitalOcean droplets are great for testing purposes because resetting a droplet to the freshly installed state or to a snapshot is quick and easy).

The steps below describe how to get a simple virtual host up and running with ModSecurity and the OWASP ruleset on Nginx. We assume that the reader has a basic understanding of Linux and some experience managing a http server from the command lind.

Dependencies

With the Nginx configuration options that we use below we will need to install the following dependencies on the system:

# apt-get install build-essential libpcre3 libpcre3-dev libssl-dev libtool autoconf apache2-prefork-dev libxml2-dev libcurl4-openssl-dev

You may have to install additional dependencies depending on your specific configuration.

Compiling ModSecurity

Choose a folder somewhere to store the source code. (We use the /usr/src folder)

At this time the most recent ModSecurity version is v2.8.0 so we download that version:

# cd /usr/src
# wget https://github.com/SpiderLabs/ModSecurity/archive/v2.8.0.tar.gz
# tar -zxvf v2.8.0.tar.gz

Now compile ModSecurity as a standalone module that we can include when we build Nginx:

# cd /usr/src/ModSecurity-2.8.0/
# ./autogen.sh
# ./configure --enable-standalone-module
# make

Compiling Nginx

We are using the mainline version of Nginx per the recommendations. At the time of writing the most recent mainline version of Nginx is v1.7.9.

Download and extract the Nginx source code:

# cd /usr/src
# wget http://nginx.org/download/nginx-1.7.9.tar.gz
# tar -zxvf nginx-1.7.9.tar.gz

We want Nginx worker processes to run as the www-data user instead of the default nobody user. We are also enabling a couple of Nginx core features that are off by default, and in addition to ModSecurity we are building Nginx with SSL support. We specify the configuration that we require using the configure command:

# cd /usr/src/nginx-1.7.9/
# ./configure \
  --user=www-data \
  --group=www-data \
  --with-pcre-jit \
  --with-debug \
  --with-ipv6 \
  --with-http_ssl_module \
  --add-module=/usr/src/ModSecurity-2.8.0/nginx/modsecurity

On your own system you should modify the argument list according to your needs. Check the Nginx wiki to see what modules are available.

The output from configure shows that we are using the nginx defaults:

nginx path prefix: "/usr/local/nginx"
nginx binary file: "/usr/local/nginx/sbin/nginx"
nginx configuration prefix: "/usr/local/nginx/conf"
nginx configuration file: "/usr/local/nginx/conf/nginx.conf"
nginx pid file: "/usr/local/nginx/logs/nginx.pid"
nginx error log file: "/usr/local/nginx/logs/error.log"
nginx http access log file: "/usr/local/nginx/logs/access.log"
nginx http client request body temporary files: "client_body_temp"
nginx http proxy temporary files: "proxy_temp"
nginx http fastcgi temporary files: "fastcgi_temp"
nginx http uwsgi temporary files: "uwsgi_temp"
nginx http scgi temporary files: "scgi_temp"

If you require a different setup you can specify your preferred locations as arguments to configure. The arguments are described on the InstallOptions page of the Nginx wiki.

We can now compile and build Nginx:

# make
# make install

Init Script

We are using Jason Giedymin's Nginx init script to manage the nginx service on our system:

# wget https://raw.github.com/JasonGiedymin/nginx-init-ubuntu/master/nginx -O /etc/init.d/nginx
# chmod +x /etc/init.d/nginx
# update-rc.d nginx defaults

This script provides the following options for managing the Nginx service:

# service nginx start|stop|restart|force-reload|reload|
  status|configtest|quietupgrade|terminate|destroy

Nginx Configuration

We are running nginx under the non-default "www-data" user so update /usr/local/nginx/conf/nginx.conf to reflect this. Replace

#user  nobody;

with

user www-data;

There are a number of other configuration options in nginx.conf that you may want to modify on a production system.

We like to keep virtual hosts in their own directories, similar to the way it works when you install Nginx using apt-get. We create two directories to hold the virtual host files:

# mkdir /usr/local/nginx/conf/sites-available
# mkdir /usr/local/nginx/conf/sites-enabled

Remove the sample virtual hosts from nginx.conf (make a backup copy first), and then add the following line to the end of nginx.conf, before the closing curly brace:

include /usr/local/nginx/conf/sites-enabled/*;

We can now create the virtual hosts in the sites-available directory and then link them in the sites-enabled directory as required. We are using a simple virtual host similar to the example in nginx.conf for testing purposes:

server {
  listen       80;
  server_name  example.com;

  location / {
    root   html;
    index  index.html index.htm;
  }
}

Configure ModSecurity

Download and extract the current version of the OWASP ruleset:

# cd /usr/src
# wget https://github.com/SpiderLabs/owasp-modsecurity-crs/tarball/master -O owasp.tar.gz
# tar -zxvf owasp.tar.gz

The OWASP rules are extracted to the /usr/src/SpiderLabs-owasp-modsecurity-crs-ebe8790/ folder. (The last part of the folder name depends on the ruleset version and will most likely be different on your system).

Edit your virtual hosts file to enable ModSecurity:

server {
  listen       80;
  server_name  example.com;

  location / {
    ModSecurityEnabled on;
    ModSecurityConfig modsecurity.conf;
    root   html;
    index  index.html index.htm;
  }
}

The ModSecurity source code that we downloaded earlier includes a sample modsecurity.conf file with some recommended settings. Copy this file to the folder with the Nginx configuration files (/usr/local/nginx/conf in our case):

# cp /usr/src/ModSecurity-2.8.0/modsecurity.conf-recommended \
  /usr/local/nginx/conf/modsecurity.conf

We also need a unicode mapping file that is being referenced in the recommended modsecurity.conf file:

# cp /usr/src/ModSecurity-2.8.0/unicode.mapping /usr/local/nginx/conf/

The OWASP rules we downloaded above contains three types of files that we need:

  • A base configuration example file, modsecuritycrs10_setup.conf.example
  • Several .conf rule files
  • Several supporting .data files

With Apache you would normally include the .conf files into a master configuration file, but ModSecurity for Nginx does not allow includes so the only option is to append the individual .conf files to our modsecurity.conf file. We should then copy all the .data files to the directory where modsecurity.conf is located:

# cd /usr/src/SpiderLabs-owasp-modsecurity-crs-ebe8790/
# cat modsecurity_crs_10_setup.conf.example >> \
  /usr/local/nginx/conf/modsecurity.conf
# cd /usr/src/SpiderLabs-owasp-modsecurity-crs-ebe8790/base_rules
# cat *.conf >> /usr/local/nginx/conf/modsecurity.conf
# cp *.data /usr/local/nginx/conf/

Restart Nginx to enable ModSecurity on the virtual host

Testing the setup

We can verify that ModSecurity is working by attempting an SQL injection attack on our virtual host, e.g. by opening the following URL in the browser (replace example.com with the server IP or the domain name you are using):

http://example.com/index.php?username=1'%20or%20'1'%20=%20'1&password=1'%20or%20'1'%20=%20'1 

The decoded version looks like this:

http://example.com/index.php?username=1' or '1' = '1&password=1' or '1' = '1 

This is simple attempt to bypass a naive user authentication system by placing specifically crafted values for 'username' and 'password' in the query string.

By default ModSecurity is running in "detection only" mode so the request is allowed (giving a 404 response in our case since we do not have an index.php file), but you should see a number of entries in the nginx error log similar to the following:

2015/01/08 04:44:43 [error] 30933#0: [client xx.xx.xx.xx] ModSecurity: Warning. Pattern match "\\W{4,}" at ARGS:username. [file "/usr/local/nginx/conf/modsecurity.conf"] [line "1527"] [id "960024"] [rev "2"] [msg "Meta-Character Anomaly Detection Alert - Repetative Non-Word Characters"] [data "Matched Data: ' = ' found within ARGS:username: 1' or '1' = '1"] [ver "OWASP_CRS/2.2.9"] [maturity "9"] [accuracy "8"] [hostname ""] [uri "/index.php"] [unique_id "AcAcAqAcgcAcA6Acuc4cAcAc"]
2015/01/08 04:44:43 [error] 30933#0: [client xx.xx.xx.xx] ModSecurity: Warning. Pattern match "\\W{4,}" at ARGS:password. [file "/usr/local/nginx/conf/modsecurity.conf"] [line "1527"] [id "960024"] [rev "2"] [msg "Meta-Character Anomaly Detection Alert - Repetative Non-Word Characters"] [data "Matched Data: ' = ' found within ARGS:password: 1' or '1' = '1"] [ver "OWASP_CRS/2.2.9"] [maturity "9"] [accuracy "8"] [hostname ""] [uri "/index.php"] [unique_id "AcAcAqAcgcAcA6Acuc4cAcAc"]
2015/01/08 04:44:43 [error] 30933#0: [client xx.xx.xx.xx] ModSecurity: Warning. Pattern match "(?i:([\\s'\"`\xc2\xb4\xe2\x80\x99\xe2\x80\x98\\(\\)]*?)\\b([\\d\\w]++)([\\s'\"`\xc2\xb4\xe2\x80\x99\xe2\x80\x98\\(\\)]*?)(?:(?:=|<=>|r?like|sounds\\s+like|regexp)([\\s'\"`\xc2\xb4\xe2\x80\x99\xe2\x80\x98\\(\\)]*?)\\2\\b|(?:!=|<=|>=|<>|<|>|\\^|is\\s+not ..." at ARGS:username. [file "/usr/local/nginx/conf/modsecurity.conf"] [line "1805"] [id "950901"] [rev "2"] [msg "SQL Injection Attack: SQL Tautology Detected."] [data "Matched Data:  '1' = '1 found within ARGS:username: 1' or '1' = '1"] [severity "CRITICAL"] [ver "OWASP_CRS/2.2.9"] [maturity "9"] [accuracy "8"] [tag "OWASP_CRS/WEB_ATTACK/SQL_INJECTION"] [tag "WASCTC/WASC-19"] [tag "OWASP_TOP_10/A1"] [tag "OWASP_AppSensor/CIE1"] [tag "PCI/6.5.2"] [hostname ""] [uri "/index.php"] [unique_id "AcAcAqAcgcAcA6Acuc4cAcAc"]
2015/01/08 04:44:43 [error] 30933#0: [client xx.xx.xx.xx] ModSecurity: Warning. Pattern match "(?i:([\\s'\"`\xc2\xb4\xe2\x80\x99\xe2\x80\x98\\(\\)]*?)\\b([\\d\\w]++)([\\s'\"`\xc2\xb4\xe2\x80\x99\xe2\x80\x98\\(\\)]*?)(?:(?:=|<=>|r?like|sounds\\s+like|regexp)([\\s'\"`\xc2\xb4\xe2\x80\x99\xe2\x80\x98\\(\\)]*?)\\2\\b|(?:!=|<=|>=|<>|<|>|\\^|is\\s+not ..." at ARGS:password. [file "/usr/local/nginx/conf/modsecurity.conf"] [line "1805"] [id "950901"] [rev "2"] [msg "SQL Injection Attack: SQL Tautology Detected."] [data "Matched Data:  '1' = '1 found within ARGS:password: 1' or '1' = '1"] [severity "CRITICAL"] [ver "OWASP_CRS/2.2.9"] [maturity "9"] [accuracy "8"] [tag "OWASP_CRS/WEB_ATTACK/SQL_INJECTION"] [tag "WASCTC/WASC-19"] [tag "OWASP_TOP_10/A1"] [tag "OWASP_AppSensor/CIE1"] [tag "PCI/6.5.2"] [hostname ""] [uri "/index.php"] [unique_id "AcAcAqAcgcAcA6Acuc4cAcAc"]
2015/01/08 04:44:43 [error] 30933#0: [client xx.xx.xx.xx] ModSecurity: Warning. Pattern match "(?i:\\bor\\b ?(?:\\d{1,10}|[\\'\"][^=]{1,10}[\\'\"]) ?[=<>]+|(?i:'\\s+x?or\\s+.{1,20}[+\\-!<>=])|\\b(?i:x?or)\\b\\s+(\\d{1,10}|'[^=]{1,10}')|\\b(?i:x?or)\\b\\s+(\\d{1,10}|'[^=]{1,10}')\\s*?[=<>])" at ARGS:username. [file "/usr/local/nginx/conf/modsecurity.conf"] [line "1861"] [id "959071"] [rev "2"] [msg "SQL Injection Attack"] [data "Matched Data: ' or '1' = found within ARGS:username: 1' or '1' = '1"] [severity "CRITICAL"] [ver "OWASP_CRS/2.2.9"] [maturity "9"] [accuracy "8"] [tag "OWASP_CRS/WEB_ATTACK/SQL_INJECTION"] [tag "WASCTC/WASC-19"] [tag "OWASP_TOP_10/A1"] [tag "OWASP_AppSensor/CIE1"] [tag "PCI/6.5.2"] [hostname ""] [uri "/index.php"] [unique_id "AcAcAqAcgcAcA6Acuc4cAcAc"]
2015/01/08 04:44:43 [error] 30933#0: [client xx.xx.xx.xx] ModSecurity: Warning. Pattern match "(?i:\\bor\\b ?(?:\\d{1,10}|[\\'\"][^=]{1,10}[\\'\"]) ?[=<>]+|(?i:'\\s+x?or\\s+.{1,20}[+\\-!<>=])|\\b(?i:x?or)\\b\\s+(\\d{1,10}|'[^=]{1,10}')|\\b(?i:x?or)\\b\\s+(\\d{1,10}|'[^=]{1,10}')\\s*?[=<>])" at ARGS:password. [file "/usr/local/nginx/conf/modsecurity.conf"] [line "1861"] [id "959071"] [rev "2"] [msg "SQL Injection Attack"] [data "Matched Data: ' or '1' = found within ARGS:password: 1' or '1' = '1"] [severity "CRITICAL"] [ver "OWASP_CRS/2.2.9"] [maturity "9"] [accuracy "8"] [tag "OWASP_CRS/WEB_ATTACK/SQL_INJECTION"] [tag "WASCTC/WASC-19"] [tag "OWASP_TOP_10/A1"] [tag "OWASP_AppSensor/CIE1"] [tag "PCI/6.5.2"] [hostname ""] [uri "/index.php"] [unique_id "AcAcAqAcgcAcA6Acuc4cAcAc"]
2015/01/08 04:44:43 [error] 30933#0: [client xx.xx.xx.xx] ModSecurity: Warning. Pattern match "([\~\\!\\@\\#\\$\\%\\^\\&\\*\\(\\)\\-\\+\\=\\{\\}\\[\\]\\|\\:\\;\"\\'\\\xc2\xb4\\\xe2\x80\x99\\\xe2\x80\x98\\`\\<\\>].*?){4,}" at ARGS:username. [file "/usr/local/nginx/conf/modsecurity.conf"] [line "1887"] [id "981173"] [rev "2"] [msg "Restricted SQL Character Anomaly Detection Alert - Total # of special characters exceeded"] [data "Matched Data: = found within ARGS:username: 1' or '1' = '1"] [ver "OWASP_CRS/2.2.9"] [maturity "9"] [accuracy "8"] [tag "OWASP_CRS/WEB_ATTACK/SQL_INJECTION"] [hostname ""] [uri "/index.php"] [unique_id "AcAcAqAcgcAcA6Acuc4cAcAc"]
2015/01/08 04:44:43 [error] 30933#0: [client xx.xx.xx.xx] ModSecurity: Warning. Pattern match "([\~\\!\\@\\#\\$\\%\\^\\&\\*\\(\\)\\-\\+\\=\\{\\}\\[\\]\\|\\:\\;\"\\'\\\xc2\xb4\\\xe2\x80\x99\\\xe2\x80\x98\\`\\<\\>].*?){4,}" at ARGS:password. [file "/usr/local/nginx/conf/modsecurity.conf"] [line "1887"] [id "981173"] [rev "2"] [msg "Restricted SQL Character Anomaly Detection Alert - Total # of special characters exceeded"] [data "Matched Data: = found within ARGS:password: 1' or '1' = '1"] [ver "OWASP_CRS/2.2.9"] [maturity "9"] [accuracy "8"] [tag "OWASP_CRS/WEB_ATTACK/SQL_INJECTION"] [hostname ""] [uri "/index.php"] [unique_id "AcAcAqAcgcAcA6Acuc4cAcAc"]
2015/01/08 04:44:43 [error] 30933#0: [client xx.xx.xx.xx] ModSecurity: Warning. Pattern match "(?i:(?i:\\d[\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98]\\s+[\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98]\\s+\\d)|(?:^admin\\s*?[\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98]|(\\/\\*)+[\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98]+\\s?(?:--|#|\\/\\*|{)?)|(?:[\"'`\xc2\xb4\xe2\x80\x9 ..." at ARGS:username. [file "/usr/local/nginx/conf/modsecurity.conf"] [line "1933"] [id "981244"] [msg "Detects basic SQL authentication bypass attempts 1/3"] [data "Matched Data: ' = ' found within ARGS:username: 1' or '1' = '1"] [severity "CRITICAL"] [tag "OWASP_CRS/WEB_ATTACK/SQL_INJECTION"] [hostname ""] [uri "/index.php"] [unique_id "AcAcAqAcgcAcA6Acuc4cAcAc"]
2015/01/08 04:44:43 [error] 30933#0: [client xx.xx.xx.xx] ModSecurity: Warning. Pattern match "(?i:(?i:\\d[\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98]\\s+[\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98]\\s+\\d)|(?:^admin\\s*?[\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98]|(\\/\\*)+[\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98]+\\s?(?:--|#|\\/\\*|{)?)|(?:[\"'`\xc2\xb4\xe2\x80\x9 ..." at ARGS:password. [file "/usr/local/nginx/conf/modsecurity.conf"] [line "1933"] [id "981244"] [msg "Detects basic SQL authentication bypass attempts 1/3"] [data "Matched Data: ' = ' found within ARGS:password: 1' or '1' = '1"] [severity "CRITICAL"] [tag "OWASP_CRS/WEB_ATTACK/SQL_INJECTION"] [hostname ""] [uri "/index.php"] [unique_id "AcAcAqAcgcAcA6Acuc4cAcAc"]
2015/01/08 04:44:43 [error] 30933#0: [client xx.xx.xx.xx] ModSecurity: Warning. Pattern match "(?i:(?:[\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98]\\s*?(x?or|div|like|between|and)\\s*?[\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98]?\\d)|(?:\\\\x(?:23|27|3d))|(?:^.?[\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98]$)|(?:(?:^[\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98\\\\]*?(?:[\\ ..." at ARGS:username. [file "/usr/local/nginx/conf/modsecurity.conf"] [line "1965"] [id "981242"] [msg "Detects classic SQL injection probings 1/2"] [data "Matched Data: ' or '1 found within ARGS:username: 1' or '1' = '1"] [severity "CRITICAL"] [tag "OWASP_CRS/WEB_ATTACK/SQL_INJECTION"] [hostname ""] [uri "/index.php"] [unique_id "AcAcAqAcgcAcA6Acuc4cAcAc"]
2015/01/08 04:44:43 [error] 30933#0: [client xx.xx.xx.xx] ModSecurity: Warning. Pattern match "(?i:(?:[\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98]\\s*?(x?or|div|like|between|and)\\s*?[\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98]?\\d)|(?:\\\\x(?:23|27|3d))|(?:^.?[\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98]$)|(?:(?:^[\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98\\\\]*?(?:[\\ ..." at ARGS:password. [file "/usr/local/nginx/conf/modsecurity.conf"] [line "1965"] [id "981242"] [msg "Detects classic SQL injection probings 1/2"] [data "Matched Data: ' or '1 found within ARGS:password: 1' or '1' = '1"] [severity "CRITICAL"] [tag "OWASP_CRS/WEB_ATTACK/SQL_INJECTION"] [hostname ""] [uri "/index.php"] [unique_id "AcAcAqAcgcAcA6Acuc4cAcAc"]
2015/01/08 04:44:43 [error] 30933#0: [client xx.xx.xx.xx] ModSecurity: Warning. Pattern match "(?i:(?:[\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98]\\s*?\\*.+(?:x?or|div|like|between|and|id)\\W*?[\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98]\\d)|(?:\\^[\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98])|(?:^[\\w\\s\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98-]+(?<=and\\s)(?<=or|xor ..." at ARGS:username. [file "/usr/local/nginx/conf/modsecurity.conf"] [line "1973"] [id "981243"] [msg "Detects classic SQL injection probings 2/2"] [data "Matched Data: ' or '1' = '1 found within ARGS:username: 1' or '1' = '1"] [severity "CRITICAL"] [tag "OWASP_CRS/WEB_ATTACK/SQL_INJECTION"] [hostname ""] [uri "/index.php"] [unique_id "AcAcAqAcgcAcA6Acuc4cAcAc"]
2015/01/08 04:44:43 [error] 30933#0: [client xx.xx.xx.xx] ModSecurity: Warning. Pattern match "(?i:(?:[\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98]\\s*?\\*.+(?:x?or|div|like|between|and|id)\\W*?[\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98]\\d)|(?:\\^[\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98])|(?:^[\\w\\s\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98-]+(?<=and\\s)(?<=or|xor ..." at ARGS:password. [file "/usr/local/nginx/conf/modsecurity.conf"] [line "1973"] [id "981243"] [msg "Detects classic SQL injection probings 2/2"] [data "Matched Data: ' or '1' = '1 found within ARGS:password: 1' or '1' = '1"] [severity "CRITICAL"] [tag "OWASP_CRS/WEB_ATTACK/SQL_INJECTION"] [hostname ""] [uri "/index.php"] [unique_id "AcAcAqAcgcAcA6Acuc4cAcAc"]
2015/01/08 04:44:43 [error] 30933#0: [client xx.xx.xx.xx] ModSecurity: Warning. Operator GE matched 5 at TX:inbound_anomaly_score. [file "/usr/local/nginx/conf/modsecurity.conf"] [line "2834"] [id "981204"] [msg "Inbound Anomaly Score Exceeded (Total Inbound Score: 62, SQLi=28, XSS=0): 981243-Detects classic SQL injection probings 2/2"] [hostname ""] [uri "/index.php"] [unique_id "AcAcAqAcgcAcA6Acuc4cAcAc"]

As you can see a number of the OWASP rules are triggered by this request. We can block it completely by replacing this line in modsecurity.conf:

SecRuleEngine DetectionOnly

with this:

SecRuleEngine On

If you restart nginx and refresh the URL in the browser you should now get a 403 forbidden response from nginx. If you look again in the error log you will see something similar to this:

2015/01/08 04:53:25 [error] 32340#0: [client xx.xx.xx.xx] ModSecurity: Access denied with code 403 (phase 2). Pattern match "\\W{4,}" at ARGS:username. [file "/usr/local/nginx/conf/modsecurity.conf"] [line "1527"] [id "960024"] [rev "2"] [msg "Meta-Character Anomaly Detection Alert - Repetative Non-Word Characters"] [data "Matched Data: ' = ' found within ARGS:username: 1' or '1' = '1"] [ver "OWASP_CRS/2.2.9"] [maturity "9"] [accuracy "8"] [hostname ""] [uri "/index.php"] [unique_id "AcAcAcAJAcAcA1AcAcAcAcAc"]

Note that only one rule was triggered this time after which the request was terminated.

Issues

We have not done any extensive testing of ModSecurity on Nginx or the OWASP ruleset in production, but there is one issue that quickly pops up. If you look in the nginx error log you may see lines similar to:

2015/01/08 04:53:25 [error] 32340#0: [client xx.xx.xx.xx] ModSecurity: Audit log: Failed to unlock global mutex: Permission denied [hostname ""] [uri "/index.php"] [unique_id "AcAcAcAJAcAcA1AcAcAcAcAc"]

The root cause appears to be related to the use of serial logging in modsecurity.conf:

SecAuditLogType Serial
SecAuditLog /var/log/modsec_audit.log

Since the audit log file is in fact being written to it is unclear if this is simply a cosmetic issue or something more serious.

A possible "fix" is running Nginx under the "root" user instead of "www-data", but this is undesirable for a number of reasons. Another option is to switch to concurrent logging, but this means that the audit log becomes very difficult to read without some kind of tool support since individual log entries will be written across a number of files.

While workarounds do exist for this particular issue, it has been around for several years and this does give the impression that the Nginx version of ModSecurity is not well maintained. It has a "stable" designation on the download page which led us to consider it in the first place, but it may be better to think of it as beta quality code.

The OWASP ruleset is free to use but you may need to modify or remove some rules to avoid false positives (you may want to use the "DetectionOnly" setting until you are confident that legitimate requests are not being blocked). You may also need to add additional rules yourself to protect your particular environment since the basic rules are very generic.

More extensive ModSecurity rulesets with frequent updats are available from Atomicorp and from Trustwave. The Atomicorp rules cost around $100/year per server while the Trustwave rules are considerably more expensive.

We have been using the Atomicorp rules with LiteSpeed web servers with good results, but the Nginx version of the rules are marked "experimental" so we haven't bothered with them (since we wouldn't be able to use them in production anyway if the vendor thinks they can break at any time). Atomicorp seems much more interested in pushing their ASL product which contains a proprietary WAF built around ModSecurity, so it is unlikely that they will spend much effort on getting the rules to work with Nginx going forward.

We have no experience with the Trustwave rules (they are too expensive for our purposes).