Secure WordPress Advanced

This is a more advanced guide to securing your WordPress site. Unlike our basic guidelines and Security 101 guide this stuff can and will break your site if implemented wrong. Many of these rules assume you have WordPress running on Apache with mod_rewrite and mod_alias installed.

It is highly advisable to use this as a reference to further your knowledge and test some of these options on a dev site. Do not copy and paste any of this on a live site without testing!

1. Limit Admin access by IP

You can add another layer of protection by limiting the WP admin section by IP address in your .htaccess file. If you have a dynamic IP address or your traveling you can sign up to a static IP service for a small monthly fee. This .htaccess must go in your admin folder, not the root path.

order deny,allow
deny from all
allow from xx.xx.xx.xx  //( your static IP)
2. Limit Login access by IP

The login file, wp-login.php, does not reside in the admin folder, it is in the root so you can deny access to just the login by IP as well. This must be in the root .htaccess file/folder.


Order Deny,Allow
Deny from All
Allow from x.x.x.x //( your static IP)
3. Disable Trace and Track

HTTP Trace attack (XST) can be used to return header requests and grab cookies and other information and is used along with a cross site scripting attacks (XSS) . This can be turned off in your apache configuration file or through .htaccess with the following.

RewriteEngine On
RewriteCond %{REQUEST_METHOD} ^TRACE
RewriteRule .* - [F]
4. Forbid Proxy comment posting

This will deny any requests that use a proxy server when posting to comments eliminating some spam and proxy requests, script courtesy of perishablepress.com.

RewriteCond %{REQUEST_METHOD} =POST
RewriteCond %{HTTP:VIA}%{HTTP:FORWARDED}%{HTTP:USERAGENT_VIA}%{HTTP:X_FORWARDED_FOR}%{HTTP:PROXY_CONNECTION} !^$ [OR]
RewriteCond %{HTTP:XPROXY_CONNECTION}%{HTTP:HTTP_PC_REMOTE_ADDR}%{HTTP:HTTP_CLIENT_IP} !^$
RewriteCond %{REQUEST_URI} !^/(wp-login.php|wp-admin/|wp-content/plugins/|wp-includes/).* [NC]
RewriteRule .* - [F,NS,L]
5. Disable PHP settings

Please be aware some these settings will take away some functionality, these edits go in your php.ini file ( some shared hosting does not allow for editing the php.ini).

display_errors = Off       //safe to disable on live site
register_globals = Off    //off by default but a good reminder to check
expose_php = Off         //safe to disable
allow_url_fopen = Off    //might break something
allow_url_include = Off  //might break something
log_errors = On           //logging errors is always a good idea if you check them
error_log = /var/log/phperror.log
enable_dl = Off           //might break something
disable_functions="popen,exec,system,passthru,proc_open,shell_exec,show_source,php
file_uploads = Off //will most likely break something

Highly advisable, at the very least, to turn display errors to off, many WP files ( some depreciated but still included) will return the path/location of your web root since they return an error on page load.

6. Enable Suhosin PHP

Suhosin is a PHP security hardening system the that protects against a variety of exploits and works with any normal PHP installation. Some PHP installs already bundle Suhosin, if you want to check if you have it installed create an empty php file and add

and then check it in your browser ( remember to delete this file when your done!). You php info file should show something like the following:
To install Suhosin you can follow the guidelines here: Install Suhosin <—

7. Remove WordPress header outputs

WordPress can add a lot of stuff in your header for various services, this will remove everything, but take care, it also removes some functionality ( for instance if someone is looking for your RSS feed). If you want to keep some just comment the line out.

// remove junk from head
remove_action('wp_head', 'feed_links', 2);
remove_action('wp_head', 'feed_links_extra', 3);
remove_action('wp_head', 'rsd_link');
remove_action('wp_head', 'wlwmanifest_link');
remove_action('wp_head', 'index_rel_link');
remove_action('wp_head', 'parent_post_rel_link', 10, 0);
remove_action('wp_head', 'start_post_rel_link', 10, 0);
remove_action('wp_head', 'adjacent_posts_rel_link_wp_head', 10, 0);
remove_action('wp_head', 'wp_generator');
remove_action('wp_head', 'wp_shortlink_wp_head', 10, 0);
remove_action('wp_head', 'noindex', 1);
8. Remove l10n script (added in WP 3.1)

This script help translate comments entities.

if (!is_admin()) {
  wp_deregister_script('l10n');
}
9. Hide hard urls, use relative ones

https://gist.github.com/2315279

10. Hide /wp-content/

https://gist.github.com/2315295

11. Deny bad query strings

This script goes in your .htaccess and will attempt to prevent malicious string attacks on your site (XSS). Please be aware some of these strings might be used for plugins or themes and doing so will disable the functionality. This script from perishablepress is fairly safe to use and should not break anything important. A more advanced one can be found on askapache.com.

# QUERY STRING EXPLOITS

 RewriteCond %{QUERY_STRING} ../    [NC,OR]
 RewriteCond %{QUERY_STRING} boot.ini [NC,OR]
 RewriteCond %{QUERY_STRING} tag=     [NC,OR]
 RewriteCond %{QUERY_STRING} ftp:     [NC,OR]
 RewriteCond %{QUERY_STRING} http:    [NC,OR]
 RewriteCond %{QUERY_STRING} https:   [NC,OR]
 RewriteCond %{QUERY_STRING} mosConfig [NC,OR]
 RewriteCond %{QUERY_STRING} ^.*([|]|(|)||'|"|;|?|*).* [NC,OR]
 RewriteCond %{QUERY_STRING} ^.*(%22|%27|%3C|%3E|%5C|%7B|%7C).* [NC,OR]
 RewriteCond %{QUERY_STRING} ^.*(%0|%A|%B|%C|%D|%E|%F|127.0).* [NC,OR]
 RewriteCond %{QUERY_STRING} ^.*(globals|encode|config|localhost|loopback).* [NC,OR]
 RewriteCond %{QUERY_STRING} ^.*(request|select|insert|union|declare|drop).* [NC]
 RewriteRule ^(.*)$ - [F,L]
12. Delete default MySQL “test” database

MySQL installs with a default database, it is very advisable that you delete this as it allows for any user role access, also delete any other unused databases and users.

mysql> DROP DATABASE test;
13. Fingerprint source code and header removal

Many plugins and themes write information into the source code, and some even write info directly to your HTTP headers. This can be used to fingerprint which version and plugins you are using. There is no easy way to get rid of this info, sometimes there is a action filter you can use but more often then not you have to dive in and directly edit the files. There are some caching and minify plugins that remove source code comments but they do not work 100% of the time. PHP 5.3 also has a new header function that can sometimes be effective, this example shows how to remove the x-powered-by header if it is set to “geuss”.

header_remove('x-powered-by', guess);
14. Disable non WordPress attack vectors

You can disable non WordpPress services running on your server. For instance some attack vectors go straight for your phpMyAdmin, which can compromise you entire server with full access. For the truly paranoid you should disable services like phpMyAdmin, Cpanel, Webmin, FTP, or anything else that might allow entry, and lock down all ports to only allow access to HTTP 80.

15. Use Apache authorization for access

You can restrict site usage, say for your admin section, based on apache authorization and password files and groups. This process is to detailed to go into here, so have a look at the official docs, http://httpd.apache.org/Auth. Another option is to check out askapache.com’s wordpress plugin that interfaces directly with this AskApache Password Protect.

16. PHP intrusion detection

PHPIDS is an intrusion detection layer for PHP run by http://phpids.org/ that blocks malicious code from doing nasty things to your site. It is especially useful if your site has any user submitted content forms such as comments and contacts. They even have a demo where you can test malicious scripts, http://demo.phpids.org/

The cool thing about PHPIDS is that it is constantly updated through an XML feed and there is a WordPress plugin that works directly with the updates, it even logs intrusion attempts. It is called Mute Screamer and can be found here http://wordpress.org/extend/plugins/mute-screamer/

17. Change the database prefix on a live site

As mentioned in the basic guide WordPress installs with a default database prefix (‘wp_”) which can be used by bots and attackers to gain access to your database by easily guessing your database names. Changing the prefix on a live site can be a scary task so proceed with caution. There are many methods to do this and even some plugins, it is advisable to do some research and testing before hand. Below I have outlined the steps for a manual change where you download the dump and change the text with a text editor as a “safe method”, alternatively you can do this directly in MySQL.

1. Make a complete database dump through MySQL ( not through the WordPress admin).
2. Copy the dump into 2 files, one back-up that you don’t touch, and 1 for editing.
3. Use a solid text editor to find/replace all instances of “wp_” with your new prefix on the dump you want to edit.
4. De-activate your plugins.
5. Turn on maintenance mode at an hour when traffic is low.
6. Drop your old database and import your edited one with the new prefix’s
7. Change your database settings in wp-config.php to the new prefix.
8. Re-activate your plugins
9. Refresh your permalink structure by hitting save ( even without changing structure)
10. Cross your fingers.

18. Additional resources and Links
  • Perishable Press maintains a great security section and a blacklist of referrers –>blacklist.txt
  • AskApache, everything you ever wanted to know and more about apache, file permissions, WordPress and .htaccess.
  • Check out our Server Guide for locking down your OS.
19. Character string filter for .htaccess

I left this to the end due to the length, this blocks bad character matches ( mostly XSS) from messing about with your site, this goes in your root .htaccess file, some strings might break functionality.

 
<fModule mod_alias.c>
RedirectMatch 403 ^
RedirectMatch 403 `
 RedirectMatch 403 {
 RedirectMatch 403 }
 RedirectMatch 403 ~
 RedirectMatch 403 &quot;
 RedirectMatch 403 $
 RedirectMatch 403 &lt;
 RedirectMatch 403 &gt;
 RedirectMatch 403 |
 RedirectMatch 403 ..
 RedirectMatch 403 //
 RedirectMatch 403 %0
 RedirectMatch 403 %A
 RedirectMatch 403 %B
 RedirectMatch 403 %C
 RedirectMatch 403 %D
 RedirectMatch 403 %E
 RedirectMatch 403 %F
 RedirectMatch 403 %22
 RedirectMatch 403 %27
 RedirectMatch 403 %28
 RedirectMatch 403 %29
 RedirectMatch 403 %3C
 RedirectMatch 403 %3E
 RedirectMatch 403 %3F
 RedirectMatch 403 %5B
 RedirectMatch 403 %5C
 RedirectMatch 403 %5D
 RedirectMatch 403 %7B
 RedirectMatch 403 %7C
 RedirectMatch 403 %7D
 # COMMON PATTERNS
 Redirectmatch 403 _vpi
 RedirectMatch 403 .inc
 Redirectmatch 403 xAou6
 Redirectmatch 403 db_name
 Redirectmatch 403 select(
 Redirectmatch 403 convert(
 Redirectmatch 403 /query/
 RedirectMatch 403 ImpEvData
 Redirectmatch 403 .XMLHTTP
 Redirectmatch 403 proxydeny
 RedirectMatch 403 function.
 Redirectmatch 403 remoteFile
 Redirectmatch 403 servername
 Redirectmatch 403 &amp;rptmode=
 Redirectmatch 403 sys_cpanel
 RedirectMatch 403 db_connect
 RedirectMatch 403 doeditconfig
 RedirectMatch 403 check_proxy
 Redirectmatch 403 system_user
 Redirectmatch 403 /(null)/
 Redirectmatch 403 clientrequest
 Redirectmatch 403 option_value
 RedirectMatch 403 ref.outcontrol
 # SPECIFIC EXPLOITS
 RedirectMatch 403 errors.
 RedirectMatch 403 config.
 RedirectMatch 403 include.
 RedirectMatch 403 display.
 RedirectMatch 403 register.
 Redirectmatch 403 password.
 RedirectMatch 403 maincore.
 RedirectMatch 403 authorize.
 Redirectmatch 403 macromates.
 RedirectMatch 403 head_auth.
 RedirectMatch 403 submit_links.
 RedirectMatch 403 change_action.
 Redirectmatch 403 com_facileforms/
 RedirectMatch 403 admin_db_utilities.
 RedirectMatch 403 admin.webring.docs.
 Redirectmatch 403 Table/Latest/index.
</IfModule> 

Not enough for you? Check out our server guide.