Naxsi - The Web Application Firewall for Nginx


In this tutorial we'll present naxsi nginx module, which provides a WAF (Web Application Firewall) to any application running behind Nginx web server. It works by inspecting HTTP requests and matching the malicious pattern rules in naxsi_core.rules. If a match is found, the malicious request is blocked and never reaches an application running behind the web server. An example of malicious query is the one that contains a special character '<' in the URI, which is not normally allowed. There could be an application that specifically uses such an URI, but in such case the administrator must manually add specific entries to the whitelist. Therefore, Naxsi drops requests by default, which makes it a whitelist firewall instead of a blacklist firewall, which is more powerful, because it doesn't allow unknown requests to pass through.


If we're running Debian distribution of Linux, we can simply run the command below to install Naxsi:

# apt-get install nginx-naxsi

After the installation, we must edit the /etc/nginx/nginx.conf configuration file and add the following line into the “http { }” code block.

include /etc/nginx/naxsi\_core.rules;

We also need to add the Naxsi configuration options available under /etc/nginx/naxsi.rules to the default web web site specified under /etc/nginx/sites-enabled/default. We should add additional include directive under the location root as seen below.

access\_log /var/log/nginx/site.access.log;
error\_log /var/log/nginx/site.error.log;

location / {
  include /etc/nginx/naxsi.rules;
  try\_files $uri $uri/ /index.php?q=$uri&$args;

This will effectively use the Naxsi rules when connecting to our application through web server. The /etc/nginx/naxsi.rules file contains the rules that will be applied to the default application to guide Naxsi and are the following.

# Sample rules file for default vhost.

DeniedUrl "/RequestDenied";

## check rules
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$EVADE >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;

Naxsi can operate in two modes, which is identified by the LearningMode above.

  • Normal Mode: malicious requests are redirected to specific location. Because we're redirecting the requests rather than blocking them, we can return a static web page presenting the error message.
  • Learning Mode: malicious requests are copied to the specific location, but still processed by the application.

Then we only need to restart Nginx:

# /etc/init.d/nginx restart

Create the Whitelist

The very first thing we must do is creating the whitelist, which can be done by one of the following method:

  • Manually: we can generate the whitelist manually, which can be rather time consuming, but should be used in case of problems with tool.
  • this tool uses the generated nginx logs, which are then turned into the whitelist. When using this option we must first generating legitimate traffic by browsing the website. We must specifically request each GET and POST request and input some valid text into every input filed, which will be saved into the log files and later parsed by the tool.

At first we must install the tool, which can be done by the commands below.

# git clone
# cd naxsi/nx\_util/
# python build
# python install

After the installation, the tool will be available on our system. Below we can see it's help page describing every command-line parameter.

# nx\
Usage: nx\ [-l /var/log/\*error.log] [-o] [-H file] [-d bname] [-c config]
nginx/naxsi log parser, whitelist and report generator.

 -h, --help show this help message and exit
 -v VERB, --verbosity=VERB set verbosity level, from 0 (quiet), to 4 (verbose).  Defaults to 2
 -d DB, --dbname=DB db (sqlite3) name
 -i, --incremental Append to database, rather than creating a new one
 -H DST\_FILE, --html-out=DST\_FILE Generate HTML report to file
 -o, --out Generate whitelists, outputs on stdout
 -r WL\_RLIMIT, --rules-limit=WL\_RLIMIT Control the number of rules to be match in a whitelist before suggesting a wl:0
 -p WL\_PLIMIT, --pages-limit=WL\_PLIMIT Number of pages an exception must happen on before suggesting a location-wide whitelist
 -l, --log Parse logfile(s) matching regex, ie.  /var/log/nginx/\*myproj\*error.log
 -c CONF\_PATH, --config=CONF\_PATH Path to configuration (defaults to /usr/local/etc/nx\_util.conf)
 -f USR\_FILTER, --filters=USR\_FILTER Filter imported data

After that we must generate a legitimate traffic that will be used by and turned into a whitelist, which we can use to tell Naxsi about legitimate traffic that should not be blocked. While the Naxsi is in learning mode, the invalid requests will be logged, but won't be blocked. We can test whether Naxsi is detecting the malicious requests by inputting a string “<script>alert(0);</script>” into an input field on our web page where we're implementing Naxsi; I've entered that string into the search input form, but it doesn't really matter where we enter it. The returned page says that no results were found when looking for that input string as presented on the picture below.


Prior to entering that input string, we must also issue the following command on the server to grep for any events triggered by Naxsi/Nginx.

# tail -f /var/log/nginx/\*.log

Once we input the command, the following event will be triggered.

==> /var/log/nginx/site.error.log <==
2013/12/01 18:30:09 [error] 20421#0: \*3 NAXSI\_FMT: ip=\_processed=2&total\_blocked=2&zone0=ARGS&id0=1008&var\_name0=s&zone1=ARGS&id1=1010&var\_name1=s&zone2=ARGS&id2=1011&var\_name2=s&zone3=ARGS&id3=1302&var\_name3=s&zone4=ARGS&id4=1303&var\_name4=s, client:, server:, request: "GET /?s=%3Cscript%3Ealert%280%29%3B%3C%2Fscript%3E HTTP/1.1", host: ""

Notice that the event was triggered in the /var/log/nginx/site.error.log file as specified by the error_log directive in the /etc/nginx/sites-enabled/default. The parameters of the above log entry are exrawed on the picture below [2]:


After that we can run the “ -l /var/log/nginx/* -o” command, which parses all log files in the /var/log/nginx/ directory and generates whitelists, which are outputted to stdout as seen below.

# nx\ -l /var/log/nginx/\* -o
12/01/2013 06:38:01 Deleting old database :naxsi\_sig
12/01/2013 06:38:01 List of files :['/var/log/nginx/site.access.log', /var/log/nginx/site.error.log']
########### Optimized Rules Suggestion ##################
# total\_count:1 (20.0%), peer\_count:1 (100.0%) \| html close tag
BasicRule wl:1303 "mz:$URL:/\|$ARGS\_VAR:s";
# total\_count:1 (20.0%), peer\_count:1 (100.0%) \| html open tag
BasicRule wl:1302 "mz:$URL:/\|$ARGS\_VAR:s";
# total\_count:1 (20.0%), peer\_count:1 (100.0%) \| parenthesis, robable sql/xss
BasicRule wl:1011 "mz:$URL:/\|$ARGS\_VAR:s";
# total\_count:1 (20.0%), peer\_count:1 (100.0%) \| parenthesis, robable sql/xss
BasicRule wl:1010 "mz:$URL:/\|$ARGS\_VAR:s";
# total\_count:1 (20.0%), peer\_count:1 (100.0%) \| ; in stuff
BasicRule wl:1008 "mz:$URL:/\|$ARGS\_VAR:s";

The inputted string was detected by the rule 1010 (notice the id1 parameter), which is presented below. Notice that we're matching the character '(' in url before ? as well as in GET and POST variables. Since we inputted the string “<script>alert(0);</script>” that contains the '(' character, the rule is matched. Once the rule has been matched, it will increase the SQL counter by 4 and XSS counter by 8.

MainRule "str:(" "msg:parenthesis, probable sql/xss" "mz:ARGS\|URL\|BODY\|$HEADERS\_VAR:Cookie" "s:$SQL:4,$XSS:8" id:1010;

The naxsi.rules contains the following declarations for SQL and XSS counters; it says that the request should be blocked when the SQL and XSS counter is at least 8. Therefore if we disable the learning mode, the above query would have been blocked by the naxsi.

CheckRule "$SQL >= 8" BLOCK;
CheckRule "$XSS >= 8" BLOCK;

If we comment-out the learning mode and restart the Nginx and reenter the malicious input string “<script>alert(0);</script>”, the request will be blocked as seen below. Previously the web application responded that there was no matched found in the backed database, but now we're getting an error as seen below.


We've just seen an example where Naxsi has blocked the incoming request because it contained the character '(' in GET/POST parameter. This is a very big limitation on the web application, because we can broke the application very easily where the user won't be able to do some action anymore, because it contains an invalid character. But this has one great advantage: very fast processing, because Naxsi uses only very basic operations, but also has a downside: we must create a whitelist, which identifies the problematic input strings that should be allowed to pass through Nginx/Naxsi to the application.

Since we had learning mode enabled when inputting the malicious “<script>alert(0);</script>” string, the BasicRules outputted by the should enable that input string to be entered without being blocked (once the learning mode is disabled). Let's take a look at the basic rules again:

BasicRule wl:1303 "mz:$URL:/\|$ARGS\_VAR:s";
BasicRule wl:1302 "mz:$URL:/\|$ARGS\_VAR:s";
BasicRule wl:1011 "mz:$URL:/\|$ARGS\_VAR:s";
BasicRule wl:1010 "mz:$URL:/\|$ARGS\_VAR:s";
BasicRule wl:1008 "mz:$URL:/\|$ARGS\_VAR:s";

We can clearly see that we're disabling the rules 1008, 1010, 1011, 1302 and 1303 in GET parameters on / url. Let's display each of those rules below, so we can see what we're actually disabling by applying the BasicRules above.

# cat naxsi\_core.rules \| egrep "1008\|1010\|1011\|1302\|1303"
MainRule "str:;" "msg:; in stuff" "mz:BODY\|URL\|ARGS" s:$SQL:4,$XSS:8" id:1008;
MainRule "str:(" "msg:parenthesis, probable sql/xss" mz:ARGS\|URL\|BODY\|$HEADERS\_VAR:Cookie" "s:$SQL:4,$XSS:8" id:1010;
MainRule "str:)" "msg:parenthesis, probable sql/xss" mz:ARGS\|URL\|BODY\|$HEADERS\_VAR:Cookie" "s:$SQL:4,$XSS:8" id:1011;
MainRule "str:<" "msg:html open tag" mz:ARGS\|URL\|BODY\|$HEADERS\_VAR:Cookie" "s:$XSS:8" id:1302;
MainRule "str:>" "msg:html close tag" mz:ARGS\|URL\|BODY\|$HEADERS\_VAR:Cookie" "s:$XSS:8" id:1303;

Notice that we're effectively whitelisting/disabling the rules that match the special characters [:, (, ), <, >] in URL as well as GET and POST parameters. This is most certainly not what we want to achieve, which is why we don't want to apply the rules above. Remember that we were runnin the Naxsi in learning mode, which means we should have inputted valid input strings into the input fields as well as visit only the valid URLs and interact with the application in a correct way without introducing any malicious requests. Since we've inputted the input string “<script>alert(0);</script>”, we've effectively disabled a few of the very important rules that shouldn't be disabled.

For the sake of the argument, let's say that we want to allow only the ')' and '(' characters to be entered into the input field. In order to allow that we can take the corresponding BasicRules from above and put them into our configuration file. Since the Nginx was compiled with Naxsi enabled, we can include the BasicRule lines anywhere in the configuration file. We can add the two lines into the naxsi.rules as follows; we needed to whitelist the rule IDs 1010 and 1011, since those two are the rules matching our special characters ')' and '('.

# Sample rules file for default vhost.
DeniedUrl "/RequestDenied";

## check rules
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$EVADE >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;

BasicRule wl:1011 "mz:$URL:/\|$ARGS\_VAR:s";
BasicRule wl:1010 "mz:$URL:/\|$ARGS\_VAR:s";

After restarting Nginx, we will be able to include the brackets in our search string without being blocked as we can see below:


If we remove those two lines and reenter the '(' character into the input box, the request will be blocked as we've already seen.


The Syntax of the Rules

It's not required to know the syntax of the rules the Naxsi uses in /etc/nginx/naxsi_core.rules, but it's a nice thing to know to really understand what Naxsi is doing. Each rule has the following syntax [1]:

BasicRule wl:ID [negative] [mz:[$URL:target\_url]\|[match\_zone]\|[$ARGS\_VAR:varname]\|[$BODY\_VARS:varname]\|[$HEADERS\_VAR:varname]\|[NAME]]

Each rule starts with one of the following directives:

  • wl : specifies the rule ID, which will be disabled for specific match zone, which can be used when whitelisting certain input arguments (wl stands for whitelist, which effectively disables selected rule under certain conditions).
  • mz : match zone, which specifies the part of request that will be inspected; it can contain several parameters separated by character '|'
  • rx : matches multiple strings separated by '|' character
  • str : matches exactly the inputted string where '|' doesn't have special significance
  • msg : a description string
  • s : represents the increase number of counter when certain rule matches
  • id : the id of the rule
  • negative : used to apply the score when the rule doesn't match; a negative rule

Let's take a look at the first rule that tries to identify SQL injection, which can be seen below and was taken directly from the naxsi_core.rules file.

## SQL Injections IDs:1000-1099 ##
MainRule rx:select\|union\|update\|delete\|insert\|table\|from\|ascii\|hex\|unhex\|drop" msg:sql keywords" "mz:BODY\|URL\|ARGS\|$HEADERS\_VAR:Cookie" "s:$SQL:4" d:1000;

The id identifies this rule as rule 1000 and the s specifies that the SQL counter will increase by a a value of 4 when rule matches. The rx specifies a list of keywords select, union, update, delete, insert,table,from,ascii,hex,unhex,drop which will be searched for in the request; more specifically in the part of request specified by mz, which is BODY (the content and name of each body variable), URL (the url before ?), ARGS (content and name of each variable) and HTTP header Cookie. The mz supports the following zones [1]:



In this tutorial we've seen how we can configure Naxsi as a WAF to protect the application against most common web attacks. Since the Naxsi is a whitelist instead of a blacklist WAF, it offers greater security, but we also have to check whether we need to whitelist certain rules to not block valid requests towards application.

We can also run to get graph results based on identified malicious input requests over a period of time. We can do that with the command below, which writes a HTML report directly to the document root, so we can directly request the nxutil.html web page in our browser.

# nx\ -l /var/log/nginx/\*error.log -H /var/www/nxutil.html

An example of one graph from the generated report can be seen below, where we can see the percentage of identified threats.