WordPress

[Ref: Installation]

[Ref: Hardening WordPress]

There's a range of official, unofficial, brilliant, and downright stupid recommendations out there for securing your WordPress installation. You need to be vigilant, and ...

  1. Follow the official documentation, and
  2. Augment your understanding using your nearest Search Engine

Do not read any further, these notes are review points for securing my wordpress installations, but require identifying the false points from valueable points which leads back to reading the official documentation and using your insight.

Directory/File Permissions

The basic installation will have the following recommended path access configurations, where the ${WPUSER} is the user-account that will need to read/write (make changes) to file content and ${WWWUSER} is the web server user account.

$ export WPUSER=wpuser
$ export WWWUSER=WWWUSER
$ export DST=/path-to-wordpress/
$ sudo chown -R ${DST} ${WPUSER}:${WWWUSER}
$ sudo find ${DST} -type d -exec chmod 755 {} \;
$ sudo find ${DST}/wp-admin -type f -exec chmod 755 {} \;
$ sudo find ${DST}/wp-includes -type f -exec chmod 755 {} \;
$ sudo find ${DST}/wp-content -type f -exec chmod 766 {} \;
$ sudo find ${DST}/wp-content/plugins -type f -exec chmod 755 {} \;

In our production environment, we provide a pre-deployment site where the development can verify their changes are working, and then require operations involvement to transfer those changes/files to the production environment.

The deployed environment replaces the ${WPUSER} with and administrator account ${ADMINUSER}:

$ export ADMINUSER=adminuser
$ sudo chown -R ${DST} ${ADMINUSER}:${WWWUSER}
$ sudo chown -R ${DST}/wp-content/themes wpuser:${WWWUSER}
$ sudo find ${DST}/wp-content/themes -type f -exec chmod 755 {} \;

Apache Configurations

SQL Injection

[Ref SQL Injection, How secure is your WordPress, WordPress Security Hacks ]

RewriteCond %{REQUEST_METHOD} ^(HEAD|TRACE|DELETE|TRACK) [NC]
RewriteRule ^(.*)$ - [F,L]

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} (\<|%3C).*script.*(\>|%3E) [NC,OR]
RewriteCond %{QUERY_STRING} mosConfig_[a-zA-Z_]{1,21}(=|%3D) [NC,OR]
RewriteCond %{QUERY_STRING} base64_encode.*\(.*\) [NC,OR]
RewriteCond %{QUERY_STRING} ^.*(\[|\]|\(|\)|\<|\>|ê|\"|;|\?|\*|=$).* [NC,OR]
RewriteCond %{QUERY_STRING} ^.*(\"|\'|\<|\>|\\|\{|\|).* [NC,OR]
RewriteCond %{QUERY_STRING} ^.*(%24&x).* [NC,OR]
RewriteCond %{QUERY_STRING} ^.*(%0|%A|%B|%C|%D|%E|%F|127\.0).* [NC,OR]
RewriteCond %{QUERY_STRING} ^.*(globals|encode|localhost|loopback).* [NC,OR]
RewriteCond %{QUERY_STRING} ^.*(request|select|insert|union|declare).* [NC]
RewriteCond %{HTTP_COOKIE} !^.*wordpress_logged_in_.*$
RewriteRule ^(.*)$ - [F,L]

RewriteCond %{QUERY_STRING} [^a-z](declare¦char¦set¦cast¦convert¦delete¦drop¦exec¦insert¦meta¦script¦select¦truncate¦update)[^a-z] [NC]
RewriteRule (.*) - [F,L]

RewriteCond %{QUERY_STRING} (\<|%3C).*script.*(\>|%3E) [NC,OR]
RewriteCond %{QUERY_STRING} GLOBALS(=|\[|%[0-9A-Z]{0,2}) [OR]
RewriteCond %{QUERY_STRING} _REQUEST(=|\[|%[0-9A-Z]{0,2})
RewriteRule ^(.*)$ index.php [F,L]

Securing wp-includes

    # Block the include-only files.
    RewriteRule ^wp-admin/includes/ - [F,L]
    RewriteRule !^wp-includes/ - [S=3]
    RewriteRule ^wp-includes/[^/]+\.php$ - [F,L]
    RewriteRule ^wp-includes/js/tinymce/langs/.+\.php - [F,L]
    RewriteRule ^wp-includes/theme-compat/ - [F,L]

File Access

Use Apache's FilesMatch to add some restrictions to some of the key files.

<files wp-config.php>
    order allow,deny
    deny from all
</files>
<FilesMatch "^(install.php|readme.html|license.txt|wp-config.php|error_log)">
    Deny from all
    # Allow from my-ip-address

    # SSL must be used to access this location
    SSLRequireSSL
    # Do not allow SSLRequireSSL to be overriden
    # by some other authorization directive
    SSLOptions +StrictRequire

    AuthName "REALM"
    AuthType Digest
    AuthDigestFile /var/www/conf/auth/siteX.htdigest
    require valid-user  

</FilesMatch>

Directory Access

Lock down administration to require SSL (so our passwords aren't trivially replayed) and further lock it down to IP Range where appropriate

Require SSL for administration

# For a site running on port 80 (http)
RewriteCond %{SERVER_PORT}  ^80$
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^wp-(admin|login|register)(.*) https://%{SERVER_NAME}/wp-$1$2 [L]
<Directory /path-to-wordpress-root/wp-admin>
    # SSL must be used to access this location
    SSLRequireSSL
    # Do not allow SSLRequireSSL to be overriden
    # by some other authorization directive
    SSLOptions +StrictRequire

    AuthName "REALM"
    AuthType Digest
    AuthDigestFile /var/www/conf/auth/siteX.htdigest
    require valid-user  
</Directory>

Require SSL for specific paths

RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^/secure(.*) https://%{SERVER_NAME}/secure$1 [R,L]
RewriteRule !^/wp-admin/(.*) - [C]
RewriteRule ^/(.*) http://www.mysite.com/$1 [QSA,L]

Wordpress Configuration

Require SSL

Although you can specify SSL requirements at the Apache, base, level, you can also get wordpress going in the right direction by configuring administration to be through SSL.

File extract: wp-config.php

define('FORCE_SSL_LOGIN', true);
#define('FORCE_SSL_ADMIN', true);

/* That's all, stop editing! Happy blogging. */

We could require SSL for Administration, but that is a cost, advantage that can be weighed separately. There should not be significant load on the existing server.

Disable File Editing

File extract: wp-config.php

define('DISALLOW_FILE_EDIT', true);

/* That's all, stop editing! Happy blogging. */