Linux: Nginx as a WAF and reverse proxy (for WordPress running with Apache on cubieboard)

In my cubieboard saga, I continued installing Nginx to both accelerate and securise some Apache hosted sites.
Nginx is not always well supported by common  opensource web applications such as WordPress. However, it performs really well as a (cache) reverse proxy. Using Naxsi module you can also build a free and efficient WAF, making Nginx a nice opensource enhencer for Apache hosting.

The following describes how to install Nginx with the aforementioned functionalities on a cubieboard (1) running debian with a root filesystem on an sdcard.

Install base packages

plain and simple : install nginx + naxsi and start nginx at system startup

apt-get install nginx-naxsi
update-rc.d nginx defaults

 

Reverse proxy configuration

Set main proxy settings…

…by editing the file /etc/nginx/proxy_params (it is possible to add settings in main nginx.conf, but its less clean IMHO):

# default nginx header when proxying
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 
# disable as it is not useful when web server is on another machine
proxy_redirect off;
 
# limit max body size. If bigger, throw 413
client_max_body_size 5M;
 
# max buffer size : if content is bigger, it will be written on disk
client_body_buffer_size 1M;
# bufferize or not response before passing it
proxy_buffering on;
 
# set temp path to a tmpfs storage 
# here on a tmpfs directory for speed 
# and less sdcard write usage
proxy_temp_path /var/tmp/nginx/temp;
 
# activate cache (again, on a tmpfs directory)
# levels=1:2 => set cache structure to [dir]/[file]
# keys_zone=static:3m => set the cache key name and the size for key index
# inactive=7d => delete file older than x days
# max_size=200m => set cache size 
proxy_cache_key "$scheme://$host$request_uri";
proxy_cache_path /var/tmp/nginx/static levels=1:2 keys_zone=static:3m inactive=7d max_size=200m;

 

Make some adjustments in the main nginx configuration file (/etc/nginx/nginx.conf).

  1. Define the number of worker process depending on how many cpu (core) should be used by nginx
    worker_processes 1; # usualy 1 per exposed cpu

     

  2. enable gzip to offload apache server
     gzip on;
     gzip_disable "msie6";
     gzip_vary on;
     gzip_proxied any;
     gzip_comp_level 6;
     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 application/javascript text/x-js;

     

  3. Uncomment naxsi core rules (explained later)
    include /etc/nginx/naxsi_core.rules;

     

  4. Include the proxy parameters file
    include /etc/nginx/proxy_params;

     

Configure virtual host(s)

The following example is based on a WordPress site with an https encrypted backoffice. As a reverse proxy, Nginx will be used to offload Apache for ssl encryption.

Generate a self signed certificate

nothing special here :

cd /etc/ssl
openssl genrsa -out private/<domain_name>.key 2048
openssl req -new -key private/<domain_name>.key -out certs/<domain_name>.csr
openssl x509 -req -days 365 -in certs/<domain_name>.csr -signkey private/<domain_name>.key -out certs/<domain_name>.crt

 

Create the virtual host file in /etc/nginx/sites-available/<domain_name>

Every re-usable generic part is put in external files. This allow to use same configurations for multiple sites. A variable is used to store Ip adress  for some external config files.

server {
  listen 80;
  server_name www.&lt;domain_name&gt; &lt;domain_name&gt;;
 
  # custom variable used in conf file
  set $backend "http://x.x.x.x:80";
 
  include /etc/nginx/custom_conf/base_reverse.conf;
  include /etc/nginx/custom_conf/reverse_wp.conf;
  include /etc/nginx/custom_conf/static_files.conf;
  include /etc/nginx/custom_conf/hardening.conf;
  include /etc/nginx/custom_conf/blacklist.conf;
  include /etc/nginx/custom_conf/naxsi_location.conf;
 
  # force admin in HTTPS
    location ~ /wp-(admin|login) {
    return 301 https://$host$request_uri;
  }
}
 
server {
  listen 443 ssl;
  server_name www.&lt;domain_name&gt; &lt;domain_name&gt;;
 
  # custom variable used in conf file
  set $backend "http://x.x.x.x:80";
 
  # configure ssl for admin part
  ssl_certificate /etc/ssl/certs/www.&lt;domain_name&gt;.crt;
  ssl_certificate_key /etc/ssl/private/www.&lt;domain_name&gt;.key;
  keepalive_timeout 70;
 
  include /etc/nginx/custom_conf/base_reverse.conf;
  include /etc/nginx/custom_conf/reverse_nocache_wp.conf;
  include /etc/nginx/custom_conf/static_files.conf;
  include /etc/nginx/custom_conf/hardening.conf;
  include /etc/nginx/custom_conf/blacklist.conf;
  include /etc/nginx/custom_conf/naxsi_location.conf;
}

 

External generic configurations files

  • base_reverse.conf : base configuration.
    # disable access log as apache already store them...
    # ...and we want minimum write on the sdcard
    access_log off;
     
    #Allow gzip of text based ressources
    gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript text/x-js application/javascript;

     

  • reverse_wp.conf : reverse proxy setting for every files (including dynamic php). Files are cached for 1h on the already defined cache entry “static”.
    Note the usage of the “$backend” variable that is set in the virtualhost configuration.
    The “naxsi_wordpress.rules” files contains the WAF rules to use (see next chapter)

    # reverse proxy everything + little cache
    location / {
      proxy_pass $backend;
      proxy_cache static;
      proxy_cache_valid 1h;
      proxy_cache_use_stale error timeout invalid_header updating;
     
      include naxsi_wordpress.rules;
    }

     

  • reverse_nocache_wp : reverse proxy setting with no cache (for backoffice)
    # reverse proxy everything
    location / {
    proxy_pass $backend;
    proxy_set_header Host $host; # make apache named virtual host to wor
    }

     

  • static_files.conf : reverse proxy optimised for static files (better cache, client rendering optimization…)
    # specific cache for static files
    location ~*^.+(swf|jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|doc|xls|exe|pdf|ppt|txt|tar|mid|midi|wav|bmp|rtf|js) {
      proxy_pass $backend;
      # make apache named virtual host to work
      proxy_set_header Host $host;
      # set reverse proxy cache to offload Apache for static files
      proxy_cache static;
      proxy_cache_valid 10d;
      # Allow browser caching for 7 days
      expires 7d;
      # authorize cache for browser proxies
      add_header Pragma public;
      add_header Cache-Control "public, must-revalidate, proxy-revalidate";
      # remove cookies as their are useless for static files
      fastcgi_hide_header Set-Cookie;
      # remove php cache control header to control them from nginx
      fastcgi_hide_header Cache-Control;
      fastcgi_hide_header Pragma;
      access_log off;
      # enable etag for proxy caching optimization on cluster
      etag on;
    }

     

  • hardening.conf : basic security and spam protection hardening
    # Only allow common method : GET, POST and HEAD (for browser caching)
    if ($request_method !~ ^(GET|HEAD|POST)$ ) {
      return 444;
    }
     
    # spam protection
    if ( $http_referer ~* (babes|forsale|girl|jewelry|love|nudit|organic|poker|porn|sex|teen) ) {
      return 403;
    }

     

  • blacklist.conf : black list bad user agent (from HackRepair.com). Lines below are two examples (the real file is way larger)
    if ($http_user_agent ~* "^BlackWidow"){
      return 403;
    }
    if ($http_user_agent ~ "^Bolt"){
      return 403;
    }

     

  • naxsi_location.conf : this file contains the location “/RequestDenied” that is called by naxsi on every forbidden request.

Waf configuration (naxsi plugin)

Default rules are stored in the file “naxsi_core.rules” which is included in the main config file “nginx.conf”.

Any other rule file should be included in virtualhost as rules are usualy specialized by host (different modules, or CMS software).

To build a rule file, Naxsi’s wiki gives a good base to start with:

#LearningMode;    # uncomment to start learning mode and create a while list
SecRulesEnabled;  # comment to disabled naxsi (for the location the file is included in) 
DeniedUrl "/RequestDenied"; # location to go on rejected query
 
## check rules
CheckRule "$SQL &gt;= 8" BLOCK;
CheckRule "$RFI &gt;= 8" BLOCK;
CheckRule "$TRAVERSAL &gt;= 4" BLOCK;
CheckRule "$EVADE &gt;= 4" BLOCK;
CheckRule "$XSS &gt;= 8" BLOCK;

With only theses rules, there is few chance to get a site working. To customize the rules file, the line “LearningMode” must be uncomment. This will let pass all queries but log every one which should be blocked.

So you can start browsing public and backoffice pages. When its done, a tool named “nx_util.py” is provided to parse error log file and generate a white list rules.

apt-get install python
wget https://naxsi.googlecode.com/files/nx_util-1.0.tgz
tar -zxf nx_util-1.0.tgz
cd nx_util-1.0/nx_util
python nx_util.py -c ./nx_util.conf -l /var/log/nginx/error.log -o

 

Finalize and launch

Create cache directories :

mkdir /var/tmp/nginx/temp
mkdir /var/tmp/nginx/static
chown -R www-data:www-data /var/tmp/nginx

If cache is stored on a tmpfs filesystem (like on example files previsouly presented), theses lines can be added in  “/etc/rc.local” to recreate them on boot.

Enable site and restart nginx:

cd /etc/nginx/sites-enabled
ln -s ../sites-available/www.&lt;domain_name&gt;
/etc/init.d/nginx start

 

Note : if you want apache to log the real IP address of the visitor and not the nginx one, use mod_rpaf (package libapache2-mod-rpaf)

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.