Nginx by examples: naxsi waf
Naxsi is an open source WAF module developed by NBS System and released under GPL v3.
In the past a nginx-naxsi
standard Ubuntu package was available from the official repositories. Unfortunately this package is no longer maintained so we must now rebuild Nginx from source to use Naxsi.
System setup
Let’s start with a fresh Ubuntu 14.04.x system
# this is needed to build nginx
sudo apt-get install libpcre3 libpcre3-dev libssl-dev unzip make -y
cd /tmp
# we download Nginx
wget http://nginx.org/download/nginx-1.8.1.tar.gz
# we download the latest Naxsi source code
wget https://github.com/nbs-system/naxsi/archive/master.zip
tar xvzf nginx-1.8.1.tar.gz
unzip master.zip
cd nginx-1.8.1/
Now we need to build Nginx with the Naxsi waf module we just downloaded
# a standard configure block where we disable
# some normally unused nginx modules (POP3 / IMAP / SMTP etc)
./configure --conf-path=/etc/nginx/nginx.conf --add-module=../naxsi-master/naxsi_src/ \
--error-log-path=/var/log/nginx/error.log --http-client-body-temp-path=/var/lib/nginx/body \
--http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-log-path=/var/log/nginx/access.log \
--http-proxy-temp-path=/var/lib/nginx/proxy --lock-path=/var/lock/nginx.lock \
--pid-path=/var/run/nginx.pid --with-http_ssl_module \
--without-mail_pop3_module --without-mail_smtp_module \
--without-mail_imap_module --without-http_uwsgi_module \
--without-http_scgi_module --with-ipv6 --prefix=/usr
# we compile
make
# we install in /etc/nginx by default
sudo make install
# we make sure the /var/lib/nginx folder exists
sudo mkdir /var/lib/nginx
In most cases you should get no warnings and everything should work just fine.
As we are building Nginx from scratch we need to setup a init script manually:
For this exercise we’ll download an use a stock standard sysv startup script borrowed by the official Nginx PPA which I made available on my S3 account.
# download
sudo wget -O /etc/init.d/nginx http://fratuz610.s3.amazonaws.com/bitsandpieces.it/nginx-by-examples-naxsi-waf/nginx
# fix the file permissions
sudo chmod a+x /etc/init.d/nginx
# setup on this server
sudo update-rc.d nginx defaults
Naxsi setup
Naxsi is a WAF) built around a security model which is very strict (almost unusable) by default and needs to be relaxed on a case by case basis. This approach makes the configuration more resilient to future or unknown type of security breaches.
Naxsi works based off a set of strict standard rules available on its Github repository
sudo wget -O /etc/nginx/naxsi_core.rules https://raw.githubusercontent.com/nbs-system/naxsi/master/naxsi_config/naxsi_core.rules
These rules change slowly over time as the development of Naxsi continues. Through experience, I’ve setup a list of common relaxation rules that I tend to reuse over and over.
They are just given here as a convenient starting point
# Allows most characters in Cookies
# Without these rules basically no site will ever work
# If your web app doesn't use cookies you can comment them out safely
BasicRule wl:1000 "mz:$HEADERS_VAR:Cookie";
BasicRule wl:1001 "mz:$HEADERS_VAR:Cookie";
BasicRule wl:1005 "mz:$HEADERS_VAR:Cookie";
BasicRule wl:1007 "mz:$HEADERS_VAR:Cookie";
BasicRule wl:1010 "mz:$HEADERS_VAR:Cookie";
BasicRule wl:1011 "mz:$HEADERS_VAR:Cookie";
BasicRule wl:1013 "mz:$HEADERS_VAR:Cookie";
BasicRule wl:1015 "mz:$HEADERS_VAR:Cookie";
BasicRule wl:1100 "mz:$HEADERS_VAR:Cookie";
BasicRule wl:1101 "mz:$HEADERS_VAR:Cookie";
BasicRule wl:1314 "mz:$HEADERS_VAR:Cookie";
BasicRule wl:1315 "mz:$HEADERS_VAR:Cookie";
BasicRule wl:1306 "mz:$HEADERS_VAR:Cookie";
BasicRule wl:1310 "mz:$HEADERS_VAR:Cookie";
BasicRule wl:1311 "mz:$HEADERS_VAR:Cookie";
BasicRule wl:1401 "mz:$HEADERS_VAR:Cookie";
# allows " in args
BasicRule wl:1001 "mz:ARGS";
# allows ' in args
BasicRule wl:1013 "mz:ARGS";
# Allows -- in a URL
BasicRule wl:1007 "mz:URL";
# Allows ; in a URL - not great but used by some CMSs
BasicRule wl:1008 "mz:URL";
# Allows () in a URL
BasicRule wl:1010 "mz:URL";
BasicRule wl:1011 "mz:URL";
# allows [ and ] in the URL arguments
BasicRule wl:1310 "mz:ARGS";
BasicRule wl:1311 "mz:ARGS";
Once again I’ve made them available online on my S3 account
# download the modified relaxation rules
sudo wget -O /etc/nginx/naxsi_relax.rules http://fratuz610.s3.amazonaws.com/bitsandpieces.it/nginx-by-examples-naxsi-waf/naxsi_relax.rules
Nginx base config
For the sake of testing Naxsi out let’s setup our server as a reverse proxy in front of bitsandpieces.it
user www-data;
worker_processes 1;
pid /run/nginx.pid;
events {
worker_connections 1024;
# multi_accept on;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
server_tokens off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logging Settings
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
# Gzip Settings
gzip off;
gzip_disable "msie6";
# gzip_vary on;
# gzip_proxied any;
gzip_comp_level 2;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
# we include the nginx-naxsi core rules
include /etc/nginx/naxsi_core.rules;
# we allow for uploads greater than 1mb (the default)
client_max_body_size 128m;
# vhosts
server {
server_name bitsandpieces.it;
access_log /var/log/nginx/bitsandpieces.it.access;
error_log /var/log/nginx/bitsandpieces.it.error error;
resolver 8.8.8.8 8.8.4.4;
gzip on;
location / {
proxy_set_header Host $server_name;
proxy_pass http://$server_name;
proxy_http_version 1.1;
## proxy buffer (up from 8k to 32k)
proxy_buffer_size 32k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 32k;
## Naxsi rules
#LearningMode;
SecRulesEnabled;
#SecRulesDisabled;
DeniedUrl "/RequestDenied";
## check rules
CheckRule "$SQL >= 10" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$EVADE >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
# nginx-naxsi relaxation rules
include /etc/nginx/naxsi_relax.rules;
}
location /RequestDenied {
internal;
return 403;
}
}
}
Naxsi setup
The following lines govern how Naxsi intercepts and stops potentially malicious requests
# We disable LearningMode
#LearningMode;
# We enable the rules
SecRulesEnabled;
#SecRulesDisabled;
# In case of a Denied request we'll redirect to this interal URL
# (matched by our Nginx config)
DeniedUrl "/RequestDenied";
# All rules come with a weight and are considered in a block
# Here we define the thresholds for each block
CheckRule "$SQL >= 10" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$EVADE >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
# we include the nginx-naxsi relaxation rules discussed before
include /etc/nginx/naxsi_relax.rules;
As we disabled LearningMode
(which would have just logged the blocked requests) the WAF is now fully operational
sudo service nginx start
Testing the WAF
The best way to test our WAF configuration is to verify that normal pages work as expected:
wget --server-response --spider http://127.0.0.1/
should yield HTTP code 200
Connecting to 127.0.0.1:80... connected.
HTTP request sent, awaiting response...
HTTP/1.1 200 OK
Server: nginx
Date: Fri, 11 Mar 2016 00:26:10 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Accept-Encoding
Status: 200 OK
...
and try to send malicious requests (with PHP code or SQL injection for example) and see them correctly blocked
wget --server-response --spider http://127.0.0.1/%3C?php%20echo%20%22hello%22;%20?%3E
should yield HTTP code 403
Connecting to 127.0.0.1:80... connected.
HTTP request sent, awaiting response...
HTTP/1.1 403 Forbidden
Server: nginx
Date: Fri, 11 Mar 2016 00:31:56 GMT
Content-Type: text/html
Content-Length: 162
Connection: keep-alive
...