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
1 2 |
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):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
# 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).
- Define the number of worker process depending on how many cpu (core) should be used by nginx
1worker_processes 1; # usualy 1 per exposed cpu
- enable gzip to offload apache server
12345678gzip 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;
- Uncomment naxsi core rules (explained later)
1include /etc/nginx/naxsi_core.rules;
- Include the proxy parameters file
1include /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 :
1 2 3 4 |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
server { listen 80; server_name www.<domain_name> <domain_name>; # 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.<domain_name> <domain_name>; # 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.<domain_name>.crt; ssl_certificate_key /etc/ssl/private/www.<domain_name>.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.
123456# disable access log as apache already store them...# ...and we want minimum write on the sdcardaccess_log off;#Allow gzip of text based ressourcesgzip_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)123456789# reverse proxy everything + little cachelocation / {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)
12345# reverse proxy everythinglocation / {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…)
12345678910111213141516171819202122# specific cache for static fileslocation ~*^.+(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 workproxy_set_header Host $host;# set reverse proxy cache to offload Apache for static filesproxy_cache static;proxy_cache_valid 10d;# Allow browser caching for 7 daysexpires 7d;# authorize cache for browser proxiesadd_header Pragma public;add_header Cache-Control "public, must-revalidate, proxy-revalidate";# remove cookies as their are useless for static filesfastcgi_hide_header Set-Cookie;# remove php cache control header to control them from nginxfastcgi_hide_header Cache-Control;fastcgi_hide_header Pragma;access_log off;# enable etag for proxy caching optimization on clusteretag on;}
- hardening.conf : basic security and spam protection hardening
123456789# Only allow common method : GET, POST and HEAD (for browser caching)if ($request_method !~ ^(GET|HEAD|POST)$ ) {return 444;}# spam protectionif ( $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)
123456if ($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:
1 2 3 4 5 6 7 8 9 10 |
#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 >= 8" BLOCK; CheckRule "$RFI >= 8" BLOCK; CheckRule "$TRAVERSAL >= 4" BLOCK; CheckRule "$EVADE >= 4" BLOCK; CheckRule "$XSS >= 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.
1 2 3 4 5 |
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 :
1 2 3 |
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:
1 2 3 |
cd /etc/nginx/sites-enabled ln -s ../sites-available/www.<domain_name> /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)