Compiling Nginx with Naxsi on Ubuntu 14.04 LTS

We are running Node.js with Nginx as a frontend reverse proxy, and we wanted to add a Web Application Firewall to the mix - just in case we make coding mistakes with security implications on Node.js.

We are happy with ModSecurity on our LiteSpeed servers, but the ModSecurity implementation for Nginx appears to be less mature than we would like, so we are trying Naxsi instead. Naxsi is a much simpler WAF than ModSecurity, but the protection it provides should be sufficient for our purpose, and we expect it to be a much better performer due to its simpler architecture.

Naxsi is available for Ubuntu as a precompiled package, but the repositories are usually behind so we have chosen to compile it with Nginx from source instead.

Our starting point is a freshly provisioned DigitalOcean droplet built with the Ubuntu 14.04 LTS image.

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-dev libxslt1-dev libgd2-xpm-dev libgeoip-dev libssl-dev

You will most likely have to install additional dependencies depending on your specific configuration.

Source code

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

The latest release of Naxsi is version 0.53-2 from December 5, 2013:

# cd /usr/src
# wget https://github.com/nbs-system/naxsi/archive/0.53-2.tar.gz
# tar -zxvf 0.53-2.tar.gz

We also need the Nginx source code (version 1.7.9 is the most recent at time of writing):

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

Compiling Nginx

We are configuring Nginx with a number of non-default options (similar to those used in compiling the version of nginx that can be downloaded from the Ubuntu repositories). We specify the configuration that we require using the configure command:

# cd /usr/src/nginx-1.7.9/
# ./configure --add-module=/usr/src/naxsi-0.53-2/naxsi_src --with-cc-opt='-g -O2 -fstack-protector --param=ssp-buffer-size=4 -Wformat -Werror=format-security -D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-Bsymbolic-functions -Wl,-z,relro' --http-log-path=/var/log/nginx/access.log --error-log-path=/var/log/nginx/error.log --with-debug --with-pcre-jit --with-ipv6 --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_addition_module --with-http_dav_module --with-http_geoip_module --with-http_gzip_static_module --with-http_image_filter_module --with-http_spdy_module --with-http_sub_module --with-http_xslt_module --with-mail --with-mail_ssl_module

The Naxsi Wiki strongly recommends adding Naxsi as the first argument to the .configure command.

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

If you require a different setup you can give other 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

With the above configuration the nginx executable is stored in the default location /usr/local/nginx/sbin/nginx

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

If you are changing the default locations in your configure arguments you can edit the /etc/default/nginx file with your locations. See the Github page for details.

Nginx Configuration

We will be running nginx worker processes under the "www-data" user so we will 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 will 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 the Nginx package 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;
  }
}

At this point you should start nginx and open the server IP in your browser to verify that Nginx is working correctly.

Configure Naxsi

Configuring Naxsi is straightforward. We need to do two things:

  1. Include the Naxsi core rules in the http section of nginx.conv, and
  2. Include a set of Naxsi rules in our virtual host definition

The Naxsi core rules are found in the source code we downloaded, in the naxsi_config/naxsi_core.rules file. We copy those to the same folder where our nginx.conf is located:

# cp /usr/src/naxsi-0.53-2/naxsi_config/naxsi_core.rules \
  /usr/local/nginx/conf/

Next we add the include statement to nginx.conf:

user www-data;
worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include            naxsi_core.rules;
    include            mime.types;
    default_type       application/octet-stream;
    sendfile           on;
    keepalive_timeout  65;
    include /usr/local/nginx/conf/sites-enabled/*;
}

The Naxsi rules that we must include in our virtual host definition are not distributed with the Naxsi source, but we can use this example from the Naxsi wiki's setup page:

LearningMode; #Enables learning mode
SecRulesEnabled;
DeniedUrl "/RequestDenied";
## check rules
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$EVADE >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;

The idea is that the core rules assign scores to various variables depending on the content of an incoming http request, and the rules included in the virtual host configuration designates actions to be taken when a score exceeds a certain value. We can save the above in e.g. /usr/local/nginx/conf/naxsi.rules and then include that file in our virtual host:

server {
  listen       80;
  server_name  example.com;

  location / {
    include /usr/local/nginx/conf/naxsi.rules;
    root   html;
    index  index.html index.htm;
  }

  location /RequestDenied {
    return 403;
  }
}

In naxsi.rules we specify that requests that are denied should redirect to the /RequestDenied URL. In the virtual host we tell Nginx to respond with a 403 - forbidden to any requests that attempt hit that location.

Testing the setup

We can verify that Naxsi is working by attempting an SQL injection attack on our virtual host, for example 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.

If you try this URL in your browser you will get a 404 Not Found (since there is no index.php). If you look in the Nginx error log (/var/log/nginx/error.log) you should see two entries similar to these:

2015/01/12 09:42:46 [error] 10410#0: *1 NAXSI_FMT: ip=xx.xx.xx.xx&server=example.com&uri=/index.php&learning=1&vers=0.53-1&total_processed=3&total_blocked=1&block=1&cscore0=$SQL&score0=36&cscore1=$XSS&score1=64&zone0=ARGS&id0=1009&var_name0=username&zone1=ARGS&id1=1013&var_name1=username&zone2=ARGS&id2=1009&var_name2=password&zone3=ARGS&id3=1013&var_name3=password, client: 83.47.21.29, server: example.com, request: "GET /index.php?username=1%27%20or%20%271%27%20=%20%271&password=1%27%20or%20%271%27%20=%20%271 HTTP/1.1", host: "example.com"
2015/01/12 09:42:46 [error] 10410#0: *1 open() "/usr/local/nginx/html/index.php" failed (2: No such file or directory), client: xx.xx.xx.xx, server: example.com, request: "GET /index.php?username=1%27%20or%20%271%27%20=%20%271&password=1%27%20or%20%271%27%20=%20%271 HTTP/1.1", host: "example.com"

The first entry shows that Naxsi would have blocked the request, but since Naxsi is operating in "learning" mode the request is allowed to continue. The second entry is an error message - Nginx is complaining about the missing index.php.

Remove the "LearningMode" line from naxsi.rules and restart Nginx, then retry the request. You now get a 403 forbidden response, and the Nginx error.log file contains the following:

2015/01/12 09:56:03 [error] 10464#0: *2 NAXSI_FMT: ip=83.47.21.29&server=example.com&uri=/index.php&learning=0&vers=0.53-1&total_processed=1&total_blocked=1&block=1&cscore0=$SQL&score0=18&cscore1=$XSS&score1=32&zone0=ARGS&id0=1009&var_name0=username&zone1=ARGS&id1=1013&var_name1=username, client: xx.xx.xx.xx, server: example.com, request: "GET /index.php?username=1%27%20or%20%271%27%20=%20%271&password=1%27%20or%20%271%27%20=%20%271 HTTP/1.1", host: "example.com"

This time there is only a single entry - with learning mode disabled Naxsi will intercept the request and prevent it from continuing (as we would expect from a WAF).

Whitelisting

The recommended way to use Naxsi is to let it run in learning mode for a while and then examine the log files to see which requests Naxsi would have blocked in normal operation, with learning mode disabled. If there are legitimate requests being blocked you can then proceed to whitelist specific rules so those requests are allowed. See the wiki for additional information

Previous versions of Naxsi have included a simple tool called nx_util to assist with whitelist generation from log entries. This tool is shown as "deprecated" in the Naxsi wiki, but it is still included with the source code (in the /usr/src/naxsi-0.53-2/nx_util folder). See the wiki for additional information if you want to use this tool.

The replacement for nx_util appears to be something called nxapi. Whatever this is, it is currently undocumented - there is an entry in the wiki, but it simply redirects to the wiki front page.