I have been pondering how to make wordpress more secure. This is when i stepped on NAXSI. This is a WAF developed specifically for nginx. As it happens, i am providing an nginx debian package for squeeze that I plan to update. So here is the package for nginx 1.2.6 (amd64) built against naxsi 0.48. I am using Debian Squeeze as a server.
First, credits where they are due, I based my blog entry on the blog entries of 2 friends: Guigui and iMil.
You will need to edit /etc/nginx/nginx.conf and add:
http {
include /etc/nginx/naxsi_core.rules;
}
server {
listen 80;
listen [::]:80; #only if you are using ipv6
server_name weblog.frlinux.net;
root /where/your/awesome/blog/is;
proxy_set_header Proxy-Connection "";
location /RequestDenied {
return 403;
}
location / {
index index.html index.php;
include /etc/nginx/naxsi.rules;
}
Then add the following file: /etc/nginx/naxsi.rules with this:
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;
# WordPress naxsi rules
### HEADERS
BasicRule wl:1000,1001,1005,1007,1010,1011,1013,1200,1308,1309,1315 "mz:$HEADERS_VAR:cookie";
# xmlrpc
BasicRule wl:1402 "mz:$HEADERS_VAR:content-type";
### simple BODY (POST)
# comments
BasicRule wl:1000,1010,1011,1013,1015,1200 "mz:$BODY_VAR:post_title";
BasicRule wl:1000 "mz:$BODY_VAR:original_publish";
BasicRule wl:1000 "mz:$BODY_VAR:save";
BasicRule wl:1008,1010,1011,1015 "mz:$BODY_VAR:sk2_my_js_payload";
BasicRule wl:1009,1005,1100,1310 "mz:$BODY_VAR:url";
BasicRule wl:1009,1100 "mz:$BODY_VAR:referredby";
BasicRule wl:1100 "mz:$BODY_VAR:_wp_original_http_referer";
BasicRule wl:1000,1001,1008,1009,1010,1011,1013,1015,1016,1100,1200,1302,1303,1310,1311,1315,1400 "mz:$BODY_VAR:comment";
BasicRule wl:1100 "mz:$BODY_VAR:redirect_to";
BasicRule wl:1000,1009,1315 "mz:$BODY_VAR:_wp_http_referer";
BasicRule wl:1000 "mz:$BODY_VAR:action";
BasicRule wl:1001,1013 "mz:$BODY_VAR:blogname";
BasicRule wl:1015,1013 "mz:$BODY_VAR:blogdescription";
BasicRule wl:1015 "mz:$BODY_VAR:date_format_custom";
BasicRule wl:1015 "mz:$BODY_VAR:date_format";
BasicRule wl:1015 "mz:$BODY_VAR:tax_input%5bpost_tag%5d";
BasicRule wl:1100 "mz:$BODY_VAR:siteurl";
BasicRule wl:1100 "mz:$BODY_VAR:home";
BasicRule wl:1000 "mz:$BODY_VAR:submit";
# news content matches pretty much everything
BasicRule wl:0 "mz:$BODY_VAR:content";
BasicRule wl:1000 "mz:$BODY_VAR:delete_option";
BasicRule wl:1000 "mz:$BODY_VAR:prowl-msg-message";
BasicRule wl:1100 "mz:$BODY_VAR:_url";
BasicRule wl:1001 "mz:$BODY_VAR:c2c_text_replace%5btext_to_replace%5d";
BasicRule wl:1200 "mz:$BODY_VAR:ppn_post_note";
BasicRule wl:1100 "mz:$BODY_VAR:author";
### BODY|NAME
BasicRule wl:1000 "mz:$BODY_VAR:delete_option|NAME";
### Simple ARGS (GET)
# WP login screen
BasicRule wl:1100 "mz:$ARGS_VAR:redirect_to";
BasicRule wl:1000,1009 "mz:$ARGS_VAR:_wp_http_referer";
BasicRule wl:1000 "mz:$ARGS_VAR:wp_http_referer";
BasicRule wl:1000 "mz:$ARGS_VAR:action";
BasicRule wl:1000 "mz:$ARGS_VAR:action2";
# load and load[] GET variable
BasicRule wl:1015 "mz:$ARGS_VAR:load";
BasicRule wl:1015 "mz:$ARGS_VAR:load[]";
BasicRule wl:1015 "mz:$ARGS_VAR:q";
### URL
BasicRule wl:1000 "mz:URL|$URL:/wp/wp-admin/update-core.php";
BasicRule wl:1000 "mz:URL|$URL:/wp/wp-admin/update.php";
# URL|BODY
BasicRule wl:1009,1100 "mz:$URL:/wp/wp-admin/post.php|$BODY_VAR:_wp_http_referer";
BasicRule wl:1016 "mz:$URL:/wp/wp-admin/post.php|$BODY_VAR:metakeyselect";
BasicRule wl:11 "mz:$URL:/wp/xmlrpc.php|BODY";
BasicRule wl:11 "mz:$URL:/wp/wp-cron.php|BODY";
# URL|BODY|NAME
BasicRule wl:1100 "mz:$URL:/wp/wp-admin/post.php|$BODY_VAR:_wp_original_http_referer|NAME";
BasicRule wl:1000 "mz:$URL:/wp/wp-admin/post.php|$BODY_VAR:metakeyselect|NAME";
BasicRule wl:1000 "mz:$URL:/wp/wp-admin/user-edit.php|$BODY_VAR:from|NAME";
# URL|ARGS|NAME
BasicRule wl:1310,1311 "mz:$URL:/wp/wp-admin/load-scripts.php|$ARGS_VAR:load[]|NAME";
BasicRule wl:1000 "mz:$URL:/wp/wp-admin/users.php|$ARGS_VAR:delete_count|NAME";
There is a learning mode that you can enable to train your application, I would suggest you download the following script then take a look at Guigui’s article. I just focused on a ready to go configuration.
This configuration works on the latest Debian Squeeze using the latest packaged wordpress, of course YMMV. I have deployed this about 2 weeks ago and is proving quite nice in terms of security. It protects 2 blogs on my main server.