Hardening WordPress against the ongoing brute-force attack

There’s an ongoing brute-force attack against WordPress and Joomla sites. The attack tries to brute-force the admin password. (Reddit)

I had to harden my WordPress some time ago. Here are the guides I followed when hardening my installation:

Additional steps I’ve taken today:

Migrating from Google Reader to Fever

The perfidious vandals at Google will kill Google Reader on July 1, 2013. Accordingly, it is time to wean ourselves off Google dependence and find an alternative. Perhaps this will prove to be a good thing, as Google Reader has strangled RSS innovation through its monopolist, good-enough position much like IE6 once strangled the web.

NewsBlur and The Old Reader are two services I’ve seen mentioned. Unfortunately, both are currently buckling under the load of my fellow reader-heads fleeing the sinking Google ship. (Edit: More alternatives are listed in the roundups at Kikolani and LifeHacker.)

Accordingly, I’ve just installed Fever on my shared hosting. I’m not going to recommend my hosting provider as my account is based on a grandfathered plan, but Dreamhost is popular. The more technically inclined may want to spin up an Amazon EC2 instance.

Fever is a PHP/MySQL web application. It’s very easy to install, assuming you have access to a web server. It costs a one-time $30, which is likely why it is very easy to install. It also comes with lots of really neat features that innovate beyond what Google Reader ever did, none of which I care about.

Migrate from Google Reader to Fever

  1. Log into Google Takeout.
  2. Download your Google Reader data.
  3. Unzip it. The subscriptions.xml file contains your feeds and folders in standard OPML format.
  4. Download the Fever Server Compatibility Suite
  5. Upload it to your server and let it verify compatibility.
  6. Is it compatible? Great! Paypal over the $30.
  7. Copy the activation code from the email in your inbox into the wizard.
  8. Let the wizard install Fever for you, importing your precious subscriptions.xml.
  9. Fever will display a brisk progress bar as it quickly processes your myriad feeds.
  10. Oh, you may want to enable the unread messages count:
Enable unread messages count


My fever feeds

Userscript for faster deletion of MediaWiki spam

A couple weeks ago I posted a userscript that makes banning MediaWiki spammers easier by setting good defaults for the user ban form. Since then, I’ve had to ban a lot of spammers, so I thought I should remove another point of friction.

For some reason, MediaWiki chooses to not provide direct deletion links on the User Contributions page, so after banning a spammer you have to click through to each piece of spam before deleting it. This may have been an acceptable user experience in Web 1.0 days, but it’s a ridiculous set of hoops to jump through today.

My goal is to eventually make banning a spammer and deleting all the spam they’ve posted a one-click process. If there’s an existing solution for this, I’d love to hear about it.

Since I needed to do some DOM manipulation, I chose to use jQuery in the userscript. This was also a lot easier than I might have expected. Userscripts really are a very solid technology.

// ==UserScript==
// @name           Mediawiki - Fast delete on user contributions view
// @namespace      https://userscripts.org/users/457667
// @description    Adds a delete link for every page on the user contributions view
// @include        http://example.com/index.php/Special:Contributions/*
// @require        https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js
// ==/UserScript==

// Add page delete links to the user contributions page
function enhanceHistory() {
  "use strict";

  // Find the history link for each revision
  $('#bodyContent ul li')
    .each(function(i) {
      // Append a page delete link after each history link
      var url = this.toString().replace('action=history', 'action=delete');
      $('<span> | </span><a href="' + url + '">del</a>').insertAfter($(this));


I passed the above through JSHint to make sure there’s nothing silly in it, but I haven’t consulted the jQuery style guide so it may not conform to the usual formatting.

The page delete links still lead to a standard confirmation form, so this doesn’t violate the RESTful practice of only using links for reading content rather than changing it.

Here’s a screenshot of what it adds to the user interface:

Userscript to make banning MediaWiki spammers easier

Somehow, I’ve come to be responsible for administering two MediaWiki-powered wikis. The main burden is having to ban spammers, which sometimes sign up in batches of 20 at a time.

To help with process, I’ve put together the following browser userscript. On Firefox, you can easily set it up using the Greasemonkey extension. Opera and Chrome have their own facilities.

The script basically makes the default values on the user ban form sane, so I can just click through without fiddling with dropdown and checkboxes. Obviously, the ban has to be permanent. Obviously, I don’t want spammers emailing anyone.

// ==UserScript==
// @name           Blocker
// @namespace      https://userscripts.org/users/457667
// @include        http://example.com/index.php/Special:Block/*
// ==/UserScript==

// Set default expiry to 'infinite' or 'indefinite', depending on MediaWiki version
function makeExpiryInfinite () { "use strict";
  // Get the element
  var elExpiry = document.getElementById('wpBlockExpiry');
  if (!elExpiry) { elExpiry = document.getElementById('mw-input-wpExpiry'); }

  // Abort if element not found
  if (!elExpiry || !elExpiry.children) { return; }

  // Find the infinite option
  var expiryNodes = elExpiry.children;
  var index = 0;
  for (var i in expiryNodes) {
    if (expiryNodes[i].label && expiryNodes[i].label in {infinite:1, indefinite:1}) {
      index = i;

  // Set dropdown to the infinite option
  elExpiry.selectedIndex = index;

// Automatically prevent user from sending e-mail
function preventEmail() { "use strict";
  // Find check box
  var elEmailBan = document.getElementById('wpEmailBan');
  if (!elEmailBan) { elEmailBan = document.getElementById('mw-input-wpDisableEmail'); }

  // Abort if it's not there
  if (!elEmailBan) { return; }

  // Check the box
  elEmailBan.checked = true;

// Automatically prevent user from sending e-mail
function reallyBanThatIP() { "use strict";
  // Find check box
  var elHardBlock = document.getElementById('mw-input-wpHardBlock');
  if (!elHardBlock) { elHardBlock = document.getElementById('mw-input-wpHardBlock'); }

  // Abort if it's not there
  if (!elHardBlock) { return; }

  // Check the box
  elHardBlock.checked = true;


I’ve been hearing wonderful things about userscripts for years, but this is the first one I’ve put together for myself. It’s actually very easy to write these, assuming you know Javascript and have tools like Firefox’s Web Console and the Web Developer extension handy.

I’m planning to enhance it a little so that it handles the slightly different form for banning anonymous users, but I’m not sure if it makes sense to submit to any official repository. It helps with running small wikis that have open memberships, so there isn’t any one site I can identify it with. Obviously, it’s not suited for Wikipedia, as they have a very different set of problems.

Edit: Updated the script with better handling for banning anonymous users by IP address.

Looking for help with DB2 support in MediaWiki

MediaWiki is the PHP application underlying Wikipedia and other sites. Over the past couple years, I’ve spent some of my spare time to add DB2 support to it. Here’s where things stand now:

  • Working: Installing on DB2 using the old installer
  • Broken: Creating and editing articles
  • Not implemented: Search
  • Not implemented: Installing on DB2 using the new 1.17 installer

Implementing the installer was half the battle, as it required a fully functional DB2 database abstraction layer. I think the main issue with article editing right now are some data inconsistencies in the initial database.

However, MediaWiki is getting a new installer in 1.17. I’ll try to spend some time soon to establish the scope of adding DB2 support to it, and then start implementing.

My MediaWiki time is pretty limited, though. If you or someone you know can pitch in on debugging, maintaining, and expanding the DB2 support in it, I’d be very happy to bring you up to speed and review your initial patches.

Keep in mind that MediaWiki is GPL software.

Some links:

  • http://www.mediawiki.org/wiki/MediaWiki
  • http://www.mediawiki.org/wiki/How_to_become_a_MediaWiki_hacker
  • http://www.mediawiki.org/wiki/Commit_access
  • http://www.mediawiki.org/wiki/Manual:Coding_conventions
  • http://www.mediawiki.org/wiki/Manual:IBM_DB2

MediaWiki with DB2 support 1.16alpha

MediaWiki 1.15 was released with partial DB2 support, but unfortunately there were some changes to the database schema that I missed and it didn’t work out of the box. Since then, I’ve committed several fixes to the main development trunk. The trunk is currently destined to become 1.16alpha, though theoretically I could backport these to the 1.15 branch.

My Apache is currently crashing willy-nilly on startup. Hopefully, there are no silly development bugs left in.:-)

Continuing MediaWiki development

No fresh download quite yet, but I just made a large commit to the MediaWiki source of the last week’s work. This is going in the trunk — aka MediaWiki 1.16alpha. There are a few bugs I hope to catch this weekend, at which point I’ll put up a fresh archive of working code.

* Made installation on IBM DB2 more robust
* Replaced E_ALL error reporting mode with E_ALL | E_STRICT

* Enabled DB2_CASE_LOWER option for all connections and statements
* Enabled DB2_DEFERRED_PREPARE_ON for all statements — delays statement preparation until execution to reduce database load
* Enabled DB2_ROWCOUNT_PREFETCH_ON for all statements — makes db2_num_rows() work correctly
* Cleaned up error handling
* Cleaned up method signatures
* Rewrote insertion to use prepared statements — required for inserting more than 32k of text
* Insertion will never try to insert a NULL value into a primary key
* Now relying on implicit casting in DB2 9.7 — no longer sniffing to see if column is integer or string before adding quotes
* Implemented actual prepared statement handling — required for correct INSERT, UPDATE behaviour
* In install mode, the class will print additional messages to the install bullet scroll
* Added bitwise operation abstraction (BITNOT, BITAND, BITOR)

* Added skeleton DB2 syntax to the database-specific switch statement

* Made limit clause database-agnostic

* Contents replaced with link to http://www.mediawiki.org/wiki/Manual:IBM_DB2

* Revised types to better match the main schema
* All tables names now the same as MySQL — was using Postgres schema’s names before
* Added some additional indices
* Added the change_tag, tag_summary, valid_tag, user_properties, log_search, and l10n_cache tables
* Added several new columns

* Made limit clause database-agnostic

IBM DB2 patch for MediaWIki

MediaWiki is the software that powers sites like Wikipedia, Ubuntu Help Wiki, and many others.

In my spare time, I’ve written a patch to add IBM DB2 support to the development trunk. Hopefully, it will be added to the official source soon.

In the meantime:

There is a very good reason for the filenames.

This is all under GPL, so don’t look at it if you work on closed-source wikis.;-)

Make tickets by milestone useful in trac

Trac is a great bug tracking, work ticket, action item management environment. It integrates well with SVN and other source control repositories.

Its Roadmap view conveniently order milestones by due date. However, the custom report queries it comes with order milestones by their name. Frankly, that’s much less useful. I understand that they are replacing the SQL-driven custom reports with a customizable GUI interface, but in the meantime here’s how you can fix the issue:

  1. Go to Edit Report
  2. Insert this clause after any FROMs and before any ORDER BYs:
    LEFT JOIN milestone m ON m.name = t.milestone
  3. Prefix any ambiguous column names with a t.
  4. Insert due as the first column in the ORDER BY list

The report will now sort by milestone due date while allowing for empty milestones.

See Also