DreamHost & Let’s Encrypt & WordPress

Mid-last year, Electronic Freedom Frontier announced Let’s Encrypt: free, automated, Open Source HTTPS Certificate Authority for everyone. That’s the padlock in the address bar. Supernaut and other sites of mine have either gone the parasitic paid route, or the “whole day lost and still not working” free route.

DreamHost, who has been my webhost for close to a decade (thanks Emile), announced in December they would be providing One-Click Let’s Encrypt setup for everyone, no matter what their hosting plan. Yesterday it arrived; It really is one-Click! Ok, two clicks, one checkbox, one select. Four things you have to do in DreamHost panel for what used to cost tens to hundreds of dollars/euros a year and hours of pain if you didn’t want to pay.

Awesome. Best thing that’s happened to the internet since ages.

Anyway, this isn’t about all that, it’s about “I have a WordPress site and how do I make the green padlock appear?” Cos that’s a couple more steps. Call it 15 minutes if you’re paying attention; an hour if you’re drinking.

So, you’ve already added the certificate in DreamHost Panel. No? To the DreamHost Wiki! Done that, wait for the confirmation email (might take a couple of hours), and on to your webserver.

I start with getting wp-admin all https-ified. Open your site in whatever FTP client you’re using, open wp-config.php and add:

define( 'FORCE_SSL_ADMIN', true );

Re-login to wp-admin, and check for the padlock in the address bar. Open Web Inspector (command-alt-i), select the Console tag and check for Mixed Content errors. Unless you’re doing weird things in wp-admin, that’s that side of things sorted.

In your site root directory, you’ll see a directory called “.well-known”. That’s added by Let’s Encrypt. Probably don’t want to delete that.

Open your root .htaccess and add two chunks of code:

# force redirect http to https
# ---------------------------------------------------------------

<IfModule mod_rewrite.c>
 RewriteEngine On
 RewriteCond %{HTTPS} off
 RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</IfModule>

# https security headers
# http strict transport security (hsts)
# X-Xss-Protection
# X-Frame-Options
# X-Content-Type-Options
# ---------------------------------------------------------------

<IfModule mod_headers.c>
 Header always set Strict-Transport-Security "max-age=16070400; includeSubDomains"
 Header always set X-Xss-Protection "1; mode=block"
 Header always set X-Frame-Options "SAMEORIGIN"
 Header always set X-Content-Type-Options "nosniff"
 #Header always set Content-Security-Policy "default-src https://website.tld:443;"
</IfModule>

The first force redirects any requests for http to https. The second does some fairly obtuse header security stuff. You can read about that on securityheaders.io. (The Content Security Policy stuff takes a lot of back-and-forth to not cause chaos. Even wp-admin requires specific rules as it uses Google Fonts. Expect to lose at least an hour on that if you decide to set that up.)

Other ways of doing this are possible. It’s kinda unclear what’s canonical and what’s depricated, but WordPress Codex and elsewhere have variations on these (definite rabbithole, this stuff):

SSLOptions +StrictRequire
SSLRequireSSL
SSLRequire %{HTTP_HOST} eq "yourwebsite.tld"
ErrorDocument 403 https://yourwebsite.tld

<IfModule mod_rewrite.c>
 RewriteEngine On
 RewriteCond %{HTTPS} off
 RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

 RewriteBase /
 RewriteRule ^index\.php$ - [L]
 RewriteCond %{REQUEST_FILENAME} !-f
 RewriteCond %{REQUEST_FILENAME} !-d
 RewriteRule . /index.php [L]
</IfModule>

Then go through the remainder of your .htaccess and change any specific instances of your old http://yourwebsite.tld to https://yourwebsite.tld.

Open robots.txt and do the same, http changed to https.

Now’s a good time to empty your cache and check your live site to see how it’s going. Everytime I’ve done this so far it’s been all green padlock and magic S. But there’s still a couple of things to do.

Off to your Theme directory, open functions.php, and add these chunks of code:

/* content over ssl
------------------------------------------------------ */
 
function themename_ssl_content( $content ) {
 if ( is_ssl() )
 $content = str_ireplace( 'http://' . $_SERVER[ 'SERVER_NAME' ], 'https://' . $_SERVER[ 'SERVER_NAME' ], $content );
 return $content;
}

add_filter( 'the_content', 'themename_ssl_content' );

/* advanced custom fields over ssl
------------------------------------------------------ */

add_filter( 'wp_get_attachment_url', 'set_url_scheme' );

The first one makes sure your old http links in Posts and Pages are spat out as https. (Can probably extend that for excerpts and other things if you’re that way inclined.) The second was for me specifically dealing with Advanced Custom Fields plugin, and is part of a larger issue that’s been bashed around on Make WordPress Core. There’s a few other bits of code floating around for issues like this and the Media Library not behaving properly over https, but the next step I think deals with that, if you want to go that far.

Before that though, go through your theme for irksome hardcoded http strings. If you really can’t use proper WordPress functions (like get_template_directory_uri() or whatever), then change them to https.

Addendum:

Turns out WordPress’ responsive images support using src-set needs its own attention:

/* responsive images src-set
------------------------------------------------------ */

function ssl_srcset( $sources ) {
 foreach ( $sources as &$source ) {
 $source['url'] = set_url_scheme( $source['url'], 'https' );
 }

 return $sources;
}

add_filter( 'wp_calculate_image_srcset', 'ssl_srcset' );

If you fully commit to the next step though, these functions aren’t required.

(End of addendum.)

Database funtime! Backup your database, it’s time to search and replace. I’ve been using interconnect/it’s brilliant Search Replace DB for ages when I need to shift localhost websites to remote. Dump it in your WordPress folder, open it in a browser, and search-replace http://yourwebsite.tld to https://yourwebsite.tld. I do a dry run first, just to get an idea of what’s going to be changed. This step isn’t really necessary, and if you end up going back to http (dunno why), you’d need to reverse this process; it’s probably just that I like everything to be all orderly.

Another browser to make sure crap hasn’t fallen everywhere, and it’s cleanup time.

In wp-admin, check all the settings are showing https where they should (can even resave Permalinks if clicking buttons feels good). If you’re using a plugin like Yoast SEO, then checking the sitemaps are correct is good.

Caching plugins also need to be checked, and caches emptied. If you’re using W3 Total Cache and are manually minifying, check the file urls are all https, for some reason this was’t changed even with the database search replace. Also under Page Cache check “Cache SSL (https) requests”.

Then it’s checking in a couple of browsers with and without caching, particularly any pages that use plugins which embed or iframe content, or otherwise interact with stuff outside your website. Most sites like Vimeo, Twitter, YouTube etc that WordPress provides embeds for are already over https, but that doesn’t mean code in a plugin is up to date.

If you’re using Google Analytics or Webmasters or similar, you’ll need to set things up there for the new https version of your site as well.

Buncha caveats: Do some of this wrong and you will break things. At least backup your database before doing this. Some/all of this might be depreciated/incorrect/incomplete/not the best way to do it. Finding definitive, single ways to do things isn’t really how code works, so try and understand what the code does so you can ask if it’s doing what you want. For me to be able to do this in 15 minutes is because I’ve spent years scruffing around in stuff and breaking things horribly, and the best I can say is, this seems to work and cover the most common issues.

DreamHost. Let’s Encrypt. Excellent!

(I swear this is much quicker than it took to read.)

https:// + supernaut (& WordPress)

Every month, I get a newsletter from my webhost, DreamHost. And I read it! That’s how I found out that supernaut could be SSL, have https:// in its URL and have that fancy lock in the address bar. Up there, ^.

Why would I want to do something like that? Well, because I can. Because it’s useful for a lot of reasons, especially now. Since the Snowdon NSA whistleblowing, which gets worse and more damning with each document release, it became obvious to me I should take the implementation of privacy and security as seriously as I do reading about it. Recently this has meant beginning the move off Google, which I use for so much; installing PiWik instead of using Google’s Analytics, installing GPGTools for email encryption (and badgering my friends to do the same); and obviously, if easy website encryption was possible, I’d give it a spin.

The first things I tried it out on have been my private server (running Pydio, formerly AjaXplorer) – my self-running DropBox, and for PiWik, then on a couple of small, low-traffic sites, to test how SSL would play with my standard-ish WordPress setup, which led to some rewriting of htaccess rules, and quick/easy code cleanup. So then I thought to try it on supernaut, which gets enough traffic and is complex enough to really show the horror.

WordPress make it reasonably simple – provided you have STFP/shell access – to make the switch. First the admin and login side of things can be SSL’d simply by adding:

define('FORCE_SSL_ADMIN', true);

to wp-config. Then in the Admin General Settings changing WordPress Address and Site Address to https. Then in the root .htaccess, editing the WordPress with two additional lines:

RewriteEngine On
RewriteCond %{SERVER_PORT} !^443$
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R,L]
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]

This did the majority of the change; the remainder turned out to be annoying. Images in the content seemed very reluctant to make the jump. This is partly because they are effectively hardcoded via the process of inserting the using the Media Library. I tried several methods with varying degrees of success, and finally adding this to functions.php seemed to do the job, updated a bit to use WordPress’ inbuilt is_ssl() function, which is a wrapper for both isset($_SERVER[“HTTPS”]) and a check for server port 443, and uses str_ireplace() instead of the depreciated ereg_replace:

function supernaut_ssl_content($content) {
if ( is_ssl() )
$content = str_ireplace('http://' . $_SERVER['SERVER_NAME'], 'https://' . $_SERVER['SERVER_NAME'], $content);
return $content;
}
add_filter('the_content', 'supernaut_ssl_content');

Which just left data in custom fields, which I use primarily for video. These look like they are best edited by hand in supernaut, though it’s possible I could use the WordPress HTTPS (SSL) plugin … I’d rather bash around in the code so I understand what’s going on. (edit) well obviously I can just wrap any code in the template with the above function, and just replace $content with $variable that pulls the post meta, no? (edit 2) And also for situations where I’m using Advanced Custom Fields (sadly not here), adding this to functions.php takes care of the rest:

add_filter( 'wp_get_attachment_url', 'set_url_scheme' );

This just leaves changing the WordPress Address and Site URLs in General Settings to https://, and that’s it (I think).

The last thing then is my own certificate. While Chrome is reasonably calm about the fact my SSL is an unsigned certificate (i.e. I haven’t spent up to $1500 on one from a trusted vendor), and Safari drops down a fairly innocuous warning – which admittedly is enough to make most people anxious, FireFox turns on all the alarms and does a mad freakout that’s impossible to simply bypass. Horrible, no? I figured that $15 a year for a secure certificate was probably worth it, for the experiment alone.

supernaut on SSL then! Most Excellent!

And for those reading for whom this was all WTFBBQ?, here’s what DreamHost said:

SNI – SSL Without a Unique IP!

“Server Name Indication,” or SNI, is a biiiig deal in the world of web hosting.

Every site on the web is tied to a specific IP (v4) address, but a single address can be shared across several different domains. In fact that’s one feature that’s helped to keep the Internet from bursting apart at the seems up ’till now.

IPv4 addresses are the Brettcoins of the shared hosting world in that they are both EXTREMELY valuable and that there are only a finite number of them gifted to humanity by the Gods.

While IPs can be shared among websites, they cannot be shared among SSL-enabled (secure) websites. If you want to handle secure web transactions on your own without the use of a specialized third party payment platform you’ll need to lease (and pay for,) a unique IP address for your own personal use.

Or at least…that’s how things USED to work.

SNI extends the protocols used to process secure web transactions to allow for the usage of a single IP address across several different SECURE websites. And, as of not too long ago, we support it!

You can still obtain a unique IP address and tie your secure hosting to it if you’d prefer – but it’ll cost ya ($3.95/month.)

To add or modify the secure hosting settings on any of your domains, visit the “Domains/Secure Hosting” section of your control panel, and click to “Add” or “Edit” services on your domains.

https://panel.dreamhost.com/index.cgi?tree=domain.secure

For a little background on setting up secure hosting in general, including some caveats of SNI, check out our wiki!

http://wiki.dreamhost.com/Secure_Hosting

wordpress security (in many small steps)

Yeah, I’d really suggest searching for newer ways of doing all this, it’s from 2010 (edited on 2015-10-08)

(This is for people who like reading code, cross-posted at thingswithbits.info)

Earlier this year supernaut got hacked. Many other of my WordPress installs did also, perhaps because they occupy the same shared hosting space. I learnt a lot about website and WordPress security very quickly – even to the point of inadvertently vanishing all but my index page for quite some time. Nothing if not clever, I am.

Because I am doing all my projects in WordPress at the moment, and also seem to have turned quite a few people over to using it also, I thought to document my approach and methods. The first thing I do then, is read. A lot.

I have a subjective and not-too carefully analysed approach to learning, especially when it comes to finding out information on a topic I know nothing about and need to know much quickly. It applies to everything, not simply limited to web design or computer stuff. I search and read and search and read and keep repeating until the same stuff starts to come up over and over again. Then I start to think I might be on the right path. So I might try a few things then. The key here is easiness. Anything requiring more than a few clicks, a few lines of text or modifications is not a reasonable solution.

Things that break this early get thrown away. A plug-in that asks for stupid things, or doesn’t perform without me rewriting some line in php.ini is not going to stay installed long. I wondered often if this was the wrong approach, but really, basic, effective security should be as simple to understand as a household door key. You shouldn’t have to build a lathe in order to cut the key yourself.

So, having done some research and playing, I slowly put together something useful. This is a mix of things I’ve been using for a while, and new things I’m adding at the moment, in response to pissy annoying php exploits, sql injections and other clever irritations.

Installing WordPress.

The first thing to change during an install is the database table prefix wp_. If you’ve already installed WordPress, it’s possible to also change this either using a plugin, or by editing wp-config.php and changing the table prefixes in phpMyAdmin.

Once logged in, make a new user with administrator privileges and suitably complex password (OSX Keychain Access has a very good password generator), log in with the new user and delete the user, ‘Admin’.

Now is also a good time to delete the default theme (after uploading your new one of course). As with the user named ‘Admin’, the wp_ table prefix and other defaults, botnet code injection methods look for these defaults as an easy place to start.

To avoid messiness, I think it’s better to leave installing plugins till last, though because information is sent in the clear unless using SSL or SSH, it’s probably a good idea to change the password again when it’s all finished.

Get rid of install.php

After your installation is finished, you don’t need this file, located in wp-admin. Delete it, or change the name, or even better log attempts to access it with this (just change the email address to receive notifications):

// install.php replacement page: http://perishablepress.com/press/2009/05/05/important-security-fix-for-wordpress
header("HTTP/1.1 503 Service Temporarily Unavailable");
header("Status 503 Service Temporarily Unavailable");
header("Retry-After 3600"); // 60 minutes
mail("email@domain.tld", "Database Error", "There is a problem with teh database!");

Error Establishing Database Connection
Error Establishing Database Connection
We are currently experiencing database issues. Please check back shortly. Thank you.

Dealing with wp-config

Every time I open this file and see the database name, user, password and host all in plain text, I get a little queasy. There are several ways to make this less painful, firstly using htaccess, which I’ll cover later. A quite elegant solution is to put all the sensitive information in a separate php file outside the root web directory, and make a call to that in the wp-config file.

First make a new file, config.php stick it (on Dreamhost) in the /home directory, chmod to 644, and cut-paste the following from the original wp-config.php:

// ** MySQL settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define('DB_NAME', 'database-name');
/** MySQL database username */
define('DB_USER', 'username');
/** MySQL database password */
define('DB_PASSWORD', 'p@s5w0rD');
/** MySQL hostname */
define('DB_HOST', 'sqlhost.domainname.tld');
/** * WordPress Database Table prefix. * * You can have multiple installations in one database if you give each a unique * prefix. Only numbers, letters, and underscores please! */
$table_prefix = 'prefix_';
/** force ssl login and admin - might slow things down */
/** on dreamhost must pay for ssl cert, hence not used */
/** define('FORCE_SSL_ADMIN', true); */

Then in the original file, just put:

include('/home/path/to/config.php');

For those lucky enough to have SSL on their server, using FORCE_SSL_ADMIN is an excellent idea. Changing permissions to 640 also is a good idea.

Adding Unique Authentication Keys takes about 30 seconds, and gives four separate keys to be used with your password. Copy-paste from the Secret Key online generator, it will look like this:

define('AUTH_KEY', ' ;+ Xk*Kf:y3e1L?.,r[Hx<m;rV57d>2WL#<#3[ d]!#+$79/pSAF(HrGEAfS`a4');
define('SECURE_AUTH_KEY', '.k0zMi[@f&)E>~y=ZqO6~IfHS$S SP8d>C]S@:zhxh?H]VtXEpqV?p-OJV*O~3?v');
define('LOGGED_IN_KEY', '~:b*7/m+Lx|-irCxYAHQn1t2$sYA+2}+*2c@!_,9/D2-H5cJ_:wJ8X7|-p%W&xGh');
define('NONCE_KEY', '%#T+Y*|N>cq/2m3CRqR}SCM BodKio`<x+?nMAe6,qgU:YiyKgEu,%>qS$V');

Functions.php

Most themes have a functions.php file which does all sorts of exciting things, writing bits to the theme templates, interacting with WordPress admin interface… A couple of extra lines provide a little obscurity. WordPress puts its version number in the header in wp_generator, and also a link to xml-rpc.php, which for most people is unnecessary – unless they are using a blogging client like Marsedit – and a risk. This quickly removes both, as well as hiding information about failed login attempts through the browser:

//security stuff
add_filter('login_errors',create_function('$a', "return null;"));
function removeHeadLinks() {
remove_action('wp_head', 'rsd_link');
remove_action('wp_head', 'wlwmanifest_link');
}
add_action('init', 'removeHeadLinks');
function no_generator() {
return'';
}
add_filter('the_generator', 'no_generator');

htaccess

.htaccess is a joyous little world unto itself, like finding a hole in your backyard that leads into a vast cave system. mmm spelunking.

Much of my learning about security has revolved around what can be done with htaccess, and in particular Perishable Press and their 4G Blacklist. And much of what I do for security takes place here.

Starting with denying access to all to read the htaccess file itself. Then there is the WordPress hook that allows the install to exist in a different directory location to the site url. For those again who have SSL on their server, forcing SSL can be done here for admin and login. Then there are a bunch of protections to stop access to certain important files, install.php, wpconfig.php, and the WordPress readme.html.

Using gzip compression to deliver files and adding content expires information doesn’t strictly have much to do with security, but really, the difference in load times the former can make to a site, and the general usefulness of expires tags make this one to automatically add.

For those on Dreamhost, the DH-PHP handlers is automatically added when using the site-specific php.ini installer, something I’ll cover a bit further down.

Hotlinking prevents leechers sucking images and other content off your site, one of the first things I ever learnt how to prevent, when supernaut suddenly had massive bandwidth use as my images turned up in all manner of places.

The no-referrer section is specifically to thwart spammers circumventing your site altogether and trying to inject comment spam directly into the comments php. It’s also possible to block access to xml-rpc here, and use login passwords via httpasswd for extra security on the login page, both not included here.

Then comes the Perishable Press 4G Blacklist, a cornucopia of amazingness, which I left out for sake of brevity (haha). I have included two lines that need to be commented out in order for the browser-based file manager AjaXplorer to function ok.

# === DENY HTACCESS ===
order allow,deny
deny from all

# === BEGIN WORDPRESS ===
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]

# === FORCE SSL ===
#RewriteRule !^/wp-(admin|login|register)(.*) - [C]

# === PROTECT install.php ===
Order Allow,Deny
Deny from all
Satisfy all

# === PROTECT readme.html
Order deny,allow
deny from all

# === PROTECT wpconfig.php ===
order allow,deny
deny from all

# === DH-PHP handlers ===
AddHandler fastcgi-script fcg fcgi fpl
AddHandler php-fastcgi .php
Action php-fastcgi /cgi-bin/dispatch.fcgi

# === BEGIN GZIP FILE TYPES BY EXTENSION ===
SetOutputFilter DEFLATE
SetOutputFilter DEFLATE
SetOutputFilter DEFLATE
SetOutputFilter DEFLATE

# === BEGIN CONTENT EXPIRES ===
#set expire dates
ExpiresActive on
# 60 seconds * 60 minutes * 24 hours * 7 days
ExpiresDefault A604800
# 60 seconds * 60 minutes * 24 hours
ExpiresByType text/html A86400

<FilesMatch "\.(ico|pdf|flv|f4v|m4v|jpg|jpeg|png|gif|swf|js|css|ttf)$">
# configure ETag
FileETag none
# max-age set to one week as above
Header set Cache-Control "max-age=604800, public, must-revalidate"
# if you use ETags, you should unset Last-Modified
# Header unset Last-Modified

# === DISABLE HOTLINKING ===
RewriteEngine on
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{REQUEST_FILENAME} -f
RewriteCond %{REQUEST_FILENAME} \.(gif|jpe?g?|png|ico)$ [NC]
RewriteCond %{HTTP_REFERER} !^https?://([^.]+\.)?domainname\. [NC]
RewriteRule \.(gif|jpe?g?|png|ico)$ - [F,NC,L]

# === DENY ACCESS TO NO-REFERRER REQUESTS ===
RewriteCond %{REQUEST_METHOD} POST
RewriteCond %{REQUEST_URI} .wp-comments-post\. [NC]
RewriteCond %{HTTP_REFERER} !.*domainname\. [OR,NC]
RewriteCond %{HTTP_USER_AGENT} ^$
RewriteRule (.*) - [F,L]

# === PERISHABLE PRESS 4G BLACKLIST ===
(snip…)

# QUERY STRING EXPLOITS
# this line stops ajaxplorer working
RewriteRule ^(.*)$ - [F,L]

# CHARACTER STRINGS
# BASIC CHARACTERS
# RedirectMatch 403 \/\/ ajaxplorer again

robots.txt

Equally effective, and probably overkill, using robots.txt can grant or forbid access to a slew of places, particularly directories that you don’t want spidered, as well as any and all WordPress directories, using Disallow: /wp*.

User-agent: *
Disallow: /cgi-bin
Disallow: /lurking
Disallow: /phpsecinfo
Disallow: /wp-*
Disallow: /tag
Disallow: /author
Disallow: /wget/
Disallow: /httpd/
Disallow: /i/
Disallow: /f/
Disallow: /t/
Disallow: /c/
Disallow: /j/

User-agent: Mediapartners-Google
Allow: /
User-agent: Adsbot-Google
Allow: /
User-agent: Googlebot-Image
Allow: /
User-agent: Googlebot-Mobile
Allow: /
User-agent: ia_archiver-web.archive.org
Disallow: /
Sitemap: http://www.domainname.tld/sitemap.xml

php.ini and phpsecinfo

Getting deeper into the system still and further yet from WordPress, modifying php.ini, the file that sets up what php can do is another essential. Dreamhost doesn’t make it easy to edit the php.ini, but fortunately there’s a script which installs it locally. More excitement ahead.

As with htaccess, much can be done in php.ini to prevent messiness. The following seem to work rather well. I’ll leave this uncommented upon except to say AjaXplorer needs fopen to be on, and shall devote a future post to elaborating on php.ini security.

open_basedir = /home/site/folder:/home/site/tmp/folder
disable_functions = exec,passthru,system,proc_open,popen,curl_multi_exec,
parse_ni_file,show_source
expose_php = Off
error_reporting = E_ALL & ~E_NOTICE
register_globals = Off

; Whether to allow HTTP file uploads.
file_uploads = On
upload_tmp_dir = /home/site/folder/tmp/php
; Maximum allowed size for uploaded files.
upload_max_filesize = 200M

; Whether to allow the treatment of URLs (like http:// or ftp://) as files.
allow_url_fopen = On

; Whether to allow include/require to open URLs (like http:// or ftp://) as files.
allow_url_include = Off

In addition to editing php.ini, and making sure there isn’t a file lying around called info.php with phpinfo() inside, phpSecInfo is an invaluable tool for assaying the security of your website, the results from which can be directly used to edit php.ini.

FTP, or rather SFTP.

As with passwords being sent in the clear, so too is FTP on its own not so great. Dreamhost allows for shell plus SFTP access with FTP disabled, which is both sensible for using desktop FTP clients (such as the amazing Transmit), and for searching out code injections. Time to open Terminal.

Commandline access is essential for a number of reasons, and instead of using the username/password combination, create passwordless login using private keys.

//Generate a RSA private key
ssh-keygen -t rsa

// copy the key to your website
scp ~/.ssh/id_rsa.pub user@domainname.tld:~/

//ssh into your website
ssh user@domainname.tld

//Make a new folder, .ssh and copy the key to the authorized_keys file, then delete the key
mkdir .ssh
cat id_rsa.pub >> .ssh/authorized_keys
rm id_rsa.pub

//Set all permissions
chmod go-w ~
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

Why might this all be useful? Going back to when I was hacked earlier this year, I could have gone though all the files on all my sites looking for base64 code, instead I opened Terminal, SSH’ed in and sent this command:

find . -name "*.php" -exec grep "base64" '{}' \; -print

Which searches through all files with the extension .php for the string base64 and dumps the results on screen. I found every instance of the hack in a matter of seconds.

Plugins for security.

Leaving aside all that code now…

WordPress is amazing because of its plugins and the community around its development. The problem though, for any plugin is twofold. Which one does the task you want the best (while integrating with the rest of your setup), and is updated frequently enough to not become a liability?

After the initial hack, I had many installed, which I then uninstalled because of small irritations and annoyances. After changing all my passwords to 16+ characters, including as many of type !@£$%^&_ as allowable, WordPress File Monitor has become an installation standard.

Rather than provide security, it lets you know when any modifications to files and folders have occurred, and in which. Notification via email and/or Dashboard alert, alterable scan intervals and directory path exclusions for me make this indispensable. When a new exploit emerges, instead of panicking and manually scanning all my installs for changes (which I do anyway out of nervous boredom), I can be fairly secure they will show up here. Of course, the idea is not to get hacked in the first place.

I’ve read a lot of good things about AskApache Password Protect, but I’ve never got it working, despite adding all the .htaccess files and even chmod up to 666. I would at least play with it otherwise, but for now don’t want to spend the time on it.

In general though (and said with a caveat that I don’t really know WordPress very well), much or all security that can be done with plugins can be done in other ways – .htaccess, robots.txt. php.ini, wp-config, sql changes and so on. Also, so many of the plugins haven’t been updated recently, which for me is worse than no protection due to the false sense of security.

During the course of the last two days, while I went through all the security stuff I could find, websites, pdfs, my own archives, I came across a couple of other plugins which I think are useful.

Semi Secure Login Reimagined provides about as good public and secret key encryption for passwords as possible if you don’t have access to SSL.

WP Security Scan I found useful for a post-install check to make sure all the settings were as minimally tight as could be. In the interests of not having hundreds of plugins, I uninstalled it after.

404 Notifier does just that, though I suspect getting off my ass and reading the logs (or ssh and then grepping them for 404s) would be a better idea.

Sources

Much of my information for this comes from a few places.

The WordPress Codex itself is a good place to start, and the Plugin directory also worth spending time in.
Perishable Press is invaluable, and not just for security.
Digging into WordPress, both the website and the book are the fundamental step-by-step guide for all things security and WordPress.
The WordPress community, across many blogs, forums, books, comments and bits and pieces.

Oh, and while this applies also to WordPress 2.9.x, I’m currently running the 3.0 beta on thingswithbits.info where I tested all this. (hopefully this all doesn’t add to confusion.)